Compare commits

..

5 Commits

Author SHA1 Message Date
lebaudantoine d39d02d445 🔖(minor) bump release to 0.1.36 2025-09-10 22:05:39 +02:00
lebaudantoine e28e0024be ⬆️(backend) bump Django to 5.2.6 to fix severe security issue
Upgrade Django from previous version to 5.2.6 to address critical
security vulnerabilities:
https://www.djangoproject.com/weblog/2025/sep/03/security-releases/
2025-09-10 21:32:09 +02:00
lebaudantoine c492243ab1 🐛(frontend) fix controlbar mobile responsiveness after refactor
Restore proper controlbar layout and spacing on mobile screens that broke
during recent audio control component refactoring, ensuring consistent
user interface across all device sizes.
2025-09-10 21:32:09 +02:00
lebaudantoine 38e6adf811 ⬇️(frontend) downgrade livekit-js-sdk to troubleshoot production issues
Temporarily roll back LiveKit client SDK version to investigate and
resolve production stability problems that emerged after recent upgrade,
enabling system restoration while root cause analysis is performed.
2025-09-10 21:32:09 +02:00
lebaudantoine 88dbfae925 🔖(minor) bump release to 0.1.35
- fix crisp regression
2025-09-09 23:30:54 +02:00
11 changed files with 20 additions and 229 deletions
-211
View File
@@ -1,211 +0,0 @@
"""Visible room join agent (for connection/testing) + JSON speaker intervals per participant."""
import logging
import os
from datetime import datetime, timezone
from typing import Dict, List, Optional
import json
import pathlib
from io import BytesIO
from minio import Minio
from minio.error import S3Error
from dotenv import load_dotenv
from livekit import api, rtc
from livekit.agents import (
AutoSubscribe,
JobContext,
JobRequest,
WorkerOptions,
WorkerPermissions,
cli,
)
load_dotenv()
logger = logging.getLogger("visible-joiner")
logger.setLevel(logging.INFO)
logger.propagate = False
if not logger.handlers:
_h = logging.StreamHandler()
_h.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s %(name)s - %(message)s"))
logger.addHandler(_h)
VISIBLE_AGENT_NAME = os.getenv("VISIBLE_AGENT_NAME", "visible-joiner")
class SpeakerTracker:
def __init__(self, room_name: str, write_json: bool = True):
self.room_name = room_name
self.active_since: Dict[str, datetime] = {}
self.by_participant: Dict[str, List[dict]] = {}
self.write_json_flag = write_json
ts = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')
outdir = pathlib.Path("./speaker_logs")
outdir.mkdir(parents=True, exist_ok=True)
self.json_path: Optional[pathlib.Path] = outdir / f"speakers_{room_name}_{ts}.json" if write_json else None
def _now(self) -> datetime:
return datetime.now(timezone.utc)
def _emit_interval(self, identity: str, start: datetime, end: datetime):
dur = max(0.0, (end - start).total_seconds())
seg = {
"start_iso": start.isoformat(),
"end_iso": end.isoformat(),
"duration_sec": round(dur, 3),
}
self.by_participant.setdefault(identity, []).append(seg)
def update_active_speakers(self, current_identities: List[str]):
now = self._now()
current = set(current_identities)
before = set(self.active_since.keys())
for ident in current - before:
self.active_since[ident] = now
for ident in before - current:
start = self.active_since.pop(ident, None)
if start:
self._emit_interval(ident, start, now)
def on_participant_disconnected(self, identity: str):
now = self._now()
start = self.active_since.pop(identity, None)
if start:
self._emit_interval(identity, start, now)
def flush_all(self):
now = self._now()
for ident, start in list(self.active_since.items()):
self._emit_interval(ident, start, now)
self.active_since.clear()
def build_json(self) -> dict:
return {
"room": self.room_name,
"generated_at": self._now().isoformat(),
"by_participant": self.by_participant,
}
def write_json(self):
def _as_bool(v: str, default=False):
if v is None:
return default
return v.strip().lower() in ("1", "true", "yes", "y")
if not self.write_json_flag:
return
payload = self.build_json()
minio_client = Minio(
endpoint=os.getenv("AWS_S3_ENDPOINT_URL"),
access_key=os.getenv("AWS_S3_ACCESS_KEY_ID"),
secret_key=os.getenv("AWS_S3_SECRET_ACCESS_KEY"),
secure= _as_bool(os.getenv("AWS_S3_SECURE_ACCESS", "false"))
)
bucket = "meet-media-storage"
ts = self._now().strftime("%Y%m%dT%H%M%SZ")
object_name = f"speaker_logs/{self.room_name}/speakers_{self.room_name}_{ts}.json"
data = json.dumps(payload, indent=2).encode("utf-8")
stream = BytesIO(data)
try:
minio_client.put_object(
bucket,
object_name,
stream,
length=len(data),
content_type="application/json",
)
logger.info("Uploaded speaker intervals JSON to s3://%s/%s", bucket, object_name)
except S3Error:
logger.exception("Failed to upload JSON to bucket=%s object=%s", bucket, object_name)
async def entrypoint(ctx: JobContext):
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
lp = ctx.room.local_participant
logger.info(
"Connected to SFU; room=%s identity=%s sid=%s",
ctx.room.name,
getattr(lp, "identity", "<unknown>"),
getattr(lp, "sid", "<unknown>"),
)
tracker = SpeakerTracker(ctx.room.name, write_json=True)
def _on_participant_connected(p: rtc.RemoteParticipant):
logger.info("remote participant connected: %s (%s)", p.identity, p.sid)
def _on_active_speakers_changed(speakers: list[rtc.Participant]):
idents = [p.identity for p in speakers]
logger.info("active speakers changed: %s", idents)
tracker.update_active_speakers(idents)
def _on_participant_disconnected(p: rtc.RemoteParticipant):
logger.info("remote participant disconnected: %s", p.identity)
tracker.on_participant_disconnected(p.identity)
if not ctx.proc.userdata.get("events_registered"):
ctx.room.on("participant_connected", _on_participant_connected)
ctx.room.on("active_speakers_changed", _on_active_speakers_changed)
ctx.room.on("participant_disconnected", _on_participant_disconnected)
ctx.proc.userdata["events_registered"] = True
async def cleanup():
if ctx.proc.userdata.get("events_registered"):
ctx.room.off("participant_connected", _on_participant_connected)
ctx.room.off("active_speakers_changed", _on_active_speakers_changed)
ctx.room.off("participant_disconnected", _on_participant_disconnected)
ctx.proc.userdata["events_registered"] = False
tracker.flush_all()
tracker.write_json()
ctx.add_shutdown_callback(cleanup)
async def handle_job_request(job_req: JobRequest) -> None:
room_name = job_req.room.name
agent_identity = f"{VISIBLE_AGENT_NAME}-{room_name}"
async with api.LiveKitAPI() as lk:
try:
resp = await lk.room.list_participants(
list=api.ListParticipantsRequest(room=room_name)
)
already_present = any(
p.kind == rtc.ParticipantKind.PARTICIPANT_KIND_AGENT
and p.identity == agent_identity
for p in resp.participants
)
if already_present:
logger.info("Agent already in the room '%s' — reject", room_name)
await job_req.reject()
else:
logger.info("Accept job for '%s' — identity=%s", room_name, agent_identity)
await job_req.accept(identity=agent_identity)
except Exception:
logger.exception("Error treating the job for '%s'", room_name)
await job_req.reject()
if __name__ == "__main__":
cli.run_app(
WorkerOptions(
entrypoint_fnc=entrypoint,
request_fnc=handle_job_request,
agent_name=VISIBLE_AGENT_NAME,
permissions=WorkerPermissions(),
)
)
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "agents"
version = "0.1.23"
version = "0.1.36"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.2.6",
+2 -2
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "meet"
version = "0.1.34"
version = "0.1.36"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -38,7 +38,7 @@ dependencies = [
"django-redis==6.0.0",
"django-storages[s3]==1.14.6",
"django-timezone-field>=5.1",
"django==5.2.3",
"django==5.2.6",
"djangorestframework==3.16.0",
"drf_spectacular==0.28.0",
"dockerflow==2024.4.2",
+6 -6
View File
@@ -1,12 +1,12 @@
{
"name": "meet",
"version": "0.1.34",
"version": "0.1.36",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "0.1.34",
"version": "0.1.36",
"dependencies": {
"@livekit/components-react": "2.9.13",
"@livekit/components-styles": "1.1.6",
@@ -26,7 +26,7 @@
"i18next-parser": "9.3.0",
"i18next-resources-to-backend": "1.2.1",
"libphonenumber-js": "1.12.10",
"livekit-client": "2.15.5",
"livekit-client": "2.15.2",
"posthog-js": "1.256.2",
"react": "18.3.1",
"react-aria-components": "1.10.1",
@@ -7460,9 +7460,9 @@
}
},
"node_modules/livekit-client": {
"version": "2.15.5",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.15.5.tgz",
"integrity": "sha512-zn36akmDlqZxlrTOUgYXtxtj35HQ44aJ+mgKat9BTSPiZru4RjEHOtp8RJE6jGoN2miJlWiOeEKHB2+ae3YrSw==",
"version": "2.15.2",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.15.2.tgz",
"integrity": "sha512-hf0A0JFN7M0iVGZxMfTk6a3cW7TNTVdqxkykjKBweORlqhQX1ITVloh6aLvplLZOxpkUE5ZVLz1DeS3+ERglog==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/mutex": "1.1.1",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "0.1.34",
"version": "0.1.36",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
@@ -31,7 +31,7 @@
"i18next-parser": "9.3.0",
"i18next-resources-to-backend": "1.2.1",
"libphonenumber-js": "1.12.10",
"livekit-client": "2.15.5",
"livekit-client": "2.15.2",
"posthog-js": "1.256.2",
"react": "18.3.1",
"react-aria-components": "1.10.1",
@@ -63,11 +63,13 @@ export function MobileControlBar({
onDeviceError={(error) =>
onDeviceError?.({ source: Track.Source.Microphone, error })
}
hideMenu={true}
/>
<VideoDeviceControl
onDeviceError={(error) =>
onDeviceError?.({ source: Track.Source.Camera, error })
}
hideMenu={true}
/>
<HandToggle />
<Button
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mail_mjml",
"version": "0.1.34",
"version": "0.1.36",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mail_mjml",
"version": "0.1.34",
"version": "0.1.36",
"license": "MIT",
"dependencies": {
"@html-to/text-cli": "0.5.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "0.1.34",
"version": "0.1.36",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "sdk",
"version": "0.1.34",
"version": "0.1.36",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sdk",
"version": "0.1.34",
"version": "0.1.36",
"license": "ISC",
"workspaces": [
"./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "0.1.34",
"version": "0.1.36",
"author": "",
"license": "ISC",
"description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "0.1.34"
version = "0.1.36"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",