Compare commits

..

1 Commits

Author SHA1 Message Date
lebaudantoine 1ce80cb57e 🩹(doc) fix wrong endpoint path
Applications to application in the application/token endpoint.
Spotted by external contributor.
2025-10-22 05:01:41 +02:00
70 changed files with 414 additions and 2032 deletions
+7 -9
View File
@@ -14,8 +14,6 @@ on:
env:
DOCKER_USER: 1001:127
DOCKER_CONTAINER_REGISTRY_HOSTNAME: docker.io
DOCKER_CONTAINER_REGISTRY_NAMESPACE: lasuite
jobs:
build-and-push-backend:
@@ -29,7 +27,7 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: '${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-backend'
images: lasuite/meet-backend
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
@@ -42,7 +40,7 @@ jobs:
uses: numerique-gouv/action-trivy-cache@main
with:
docker-build-args: '--target backend-production -f Dockerfile'
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-backend:${{ github.sha }}'
docker-image-name: 'docker.io/lasuite/meet-backend:${{ github.sha }}'
-
name: Build and push
uses: docker/build-push-action@v6
@@ -65,7 +63,7 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: '${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend'
images: lasuite/meet-frontend
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
@@ -78,7 +76,7 @@ jobs:
uses: numerique-gouv/action-trivy-cache@main
with:
docker-build-args: '-f src/frontend/Dockerfile --target frontend-production'
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend:${{ github.sha }}'
docker-image-name: 'docker.io/lasuite/meet-frontend:${{ github.sha }}'
-
name: Build and push
uses: docker/build-push-action@v6
@@ -102,7 +100,7 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: '${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend-dinum'
images: lasuite/meet-frontend-dinum
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
@@ -115,7 +113,7 @@ jobs:
uses: numerique-gouv/action-trivy-cache@main
with:
docker-build-args: '-f docker/dinum-frontend/Dockerfile --target frontend-production'
docker-image-name: '${{ env.DOCKER_CONTAINER_REGISTRY_HOSTNAME }}/${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-frontend-dinum:${{ github.sha }}'
docker-image-name: 'docker.io/lasuite/meet-frontend-dinum:${{ github.sha }}'
-
name: Build and push
uses: docker/build-push-action@v6
@@ -139,7 +137,7 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: '${{ env.DOCKER_CONTAINER_REGISTRY_NAMESPACE }}/meet-summary'
images: lasuite/meet-summary
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
+1 -1
View File
@@ -24,7 +24,7 @@ RUN npm run build
# Inject PostHog sourcemap metadata into the built assets
# This metadata is essential for correctly mapping errors to source maps in production
RUN set -e && \
npx @posthog/cli@0.4.8 sourcemap inject --directory ./dist/assets
npx @posthog/cli sourcemap inject --directory ./dist/assets
COPY ./docker/dinum-frontend/dinum-styles.css \
./dist/assets/
@@ -1,50 +0,0 @@
upstream meet_backend {
server ${BACKEND_HOST}:8000 fail_timeout=0;
}
upstream meet_frontend {
server ${FRONTEND_HOST}:8080 fail_timeout=0;
}
server {
listen 8083;
server_name localhost;
charset utf-8;
# Disables server version feedback on pages and in headers
server_tokens off;
proxy_ssl_server_name on;
location @proxy_to_meet_backend {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_pass http://meet_backend;
}
location @proxy_to_meet_frontend {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_pass http://meet_frontend;
}
location / {
try_files $uri @proxy_to_meet_frontend;
}
location /api {
try_files $uri @proxy_to_meet_backend;
}
location /admin {
try_files $uri @proxy_to_meet_backend;
}
location /static {
try_files $uri @proxy_to_meet_backend;
}
}
-88
View File
@@ -1,88 +0,0 @@
services:
postgresql:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 1s
timeout: 2s
retries: 300
env_file:
- env.d/postgresql
- env.d/common
volumes:
- ./data/databases/backend:/var/lib/postgresql/data/pgdata
redis:
image: redis:5
backend:
image: lasuite/meet-backend:latest
user: ${DOCKER_USER:-1000}
restart: always
env_file:
- env.d/common
- env.d/backend
- env.d/postgresql
healthcheck:
test: ["CMD", "python", "manage.py", "check"]
interval: 15s
timeout: 30s
retries: 20
start_period: 10s
depends_on:
postgresql:
condition: service_healthy
restart: true
redis:
condition: service_started
livekit:
condition: service_started
frontend:
image: lasuite/meet-frontend:latest
user: "${DOCKER_USER:-1000}"
entrypoint:
- /docker-entrypoint.sh
command: ["nginx", "-g", "daemon off;"]
env_file:
- env.d/common
# Uncomment and set your values if using our nginx proxy example
# environment:
# - VIRTUAL_HOST=${MEET_HOST} # used by nginx proxy
# - VIRTUAL_PORT=8083 # used by nginx proxy
# - LETSENCRYPT_HOST=${MEET_HOST} # used by lets encrypt to generate TLS certificate
depends_on:
backend:
condition: service_healthy
volumes:
- ./default.conf.template:/etc/nginx/templates/docs.conf.template
# Uncomment if using our nginx proxy example
# networks:
# - proxy-tier
# - default
livekit:
image: livekit/livekit-server:latest
command: --config /config.yaml
ports:
- 7881:7881/tcp
- 7882:7882/udp
volumes:
- ./livekit-server.yaml:/config.yaml
# Uncomment and set your values if using our nginx proxy example
# environment:
# - VIRTUAL_HOST=${LIVEKIT_HOST} # used by nginx proxy
# - VIRTUAL_PORT=7880 # used by nginx proxy
# - LETSENCRYPT_HOST=${LIVEKIT_HOST} # used by lets encrypt to generate TLS certificate
depends_on:
redis:
condition: service_started
# Uncomment if using our nginx proxy example
# networks:
# - proxy-tier
# - default
# Uncomment if using our nginx proxy example
#networks:
# proxy-tier:
# external: true
-92
View File
@@ -1,92 +0,0 @@
# Deploy and Configure Keycloak for Meet
## Installation
> [!CAUTION]
> We provide those instructions as an example, for production environments, you should follow the [official documentation](https://www.keycloak.org/documentation).
### Step 1: Prepare your working environment:
```bash
mkdir keycloak/env.d && cd keycloak
curl -o compose.yaml https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/docs/examples/compose/keycloak/compose.yaml
curl -o env.d/kc_postgresql https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/env.d/production.dist/kc_postgresql
curl -o env.d/keycloak https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/env.d/production.dist/keycloak
```
### Step 2:. Update `env.d/` files
The following variables need to be updated with your own values, others can be left as is:
```env
POSTGRES_PASSWORD=<generate postgres password>
KC_HOSTNAME=https://id.yourdomain.tld # Change with your own URL
KC_BOOTSTRAP_ADMIN_PASSWORD=<generate your password>
```
### Step 3: Expose keycloak instance on https
> [!NOTE]
> You can skip this section if you already have your own setup.
To access your Keycloak instance on the public network, it needs to be exposed on a domain with SSL termination. You can use our [example with nginx proxy and Let's Encrypt companion](../nginx-proxy/README.md) for automated creation/renewal of certificates using [acme.sh](http://acme.sh).
If following our example, uncomment the environment and network sections in compose file and update it with your values.
```yaml
version: '3'
services:
keycloak:
...
# Uncomment and set your values if using our nginx proxy example
# environment:
# - VIRTUAL_HOST=id.yourdomain.tld # used by nginx proxy
# - VIRTUAL_PORT=8080 # used by nginx proxy
# - LETSENCRYPT_HOST=id.yourdomain.tld # used by lets encrypt to generate TLS certificate
...
# Uncomment if using our nginx proxy example
# networks:
# - proxy-tier
# - default
# Uncomment if using our nginx proxy example
#networks:
# proxy-tier:
# external: true
```
### Step 4: Start the service
```bash
`docker compose up -d`
```
Your keycloak instance is now available on https://doc.yourdomain.tld
> [!CAUTION]
> Version of the images are set to latest, you should pin it to the desired version to avoid unwanted upgrades when pulling latest image. You can find available versions on [Keycloak registry](https://quay.io/repository/keycloak/keycloak?tab=tags).
```
## Creating an OIDC Client for Meet Application
### Step 1: Create a New Realm
1. Log in to the Keycloak administration console.
2. Navigate to the realm tab and click on the "Create realm" button.
3. Enter the name of the realm - `meet`.
4. Click "Create".
#### Step 2: Create a New Client
1. Navigate to the "Clients" tab.
2. Click on the "Create client" button.
3. Enter the client ID - e.g. `meet`.
4. Enable "Client authentication" option.
6. Set the "Valid redirect URIs" to the URL of your meet application suffixed with `/*` - e.g., "https://meet.example.com/*".
1. Set the "Web Origins" to the URL of your meet application - e.g. `https://meet.example.com`.
1. Click "Save".
#### Step 3: Get Client Credentials
1. Go to the "Credentials" tab.
2. Copy the client ID (`meet` in this example) and the client secret.
@@ -1,36 +0,0 @@
services:
postgresql:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 1s
timeout: 2s
retries: 300
env_file:
- env.d/kc_postgresql
volumes:
- ./data/keycloak:/var/lib/postgresql/data/pgdata
keycloak:
image: quay.io/keycloak/keycloak:latest
command: ["start"]
env_file:
- env.d/kc_postgresql
- env.d/keycloak
# Uncomment and set your values if using our nginx proxy example
# environment:
# - VIRTUAL_HOST=id.yourdomain.tld # used by nginx proxy
# - VIRTUAL_PORT=8080 # used by nginx proxy
# - LETSENCRYPT_HOST=id.yourdomain.tld # used by lets encrypt to generate TLS certificate
depends_on:
postgresql:
condition: service_healthy
restart: true
# Uncomment if using our nginx proxy example
# networks:
# - proxy-tier
# - default
#
#networks:
# proxy-tier:
# external: true
@@ -1,39 +0,0 @@
# Nginx proxy with automatic SSL certificates
> [!CAUTION]
> We provide those instructions as an example, for extended development or production environments, you should follow the [official documentation](https://github.com/nginx-proxy/acme-companion/tree/main/docs).
Nginx-proxy sets up a container running nginx and docker-gen. docker-gen generates reverse proxy configs for nginx and reloads nginx when containers are started and stopped.
Acme-companion is a lightweight companion container for nginx-proxy. It handles the automated creation, renewal and use of SSL certificates for proxied Docker containers through the ACME protocol.
## Installation
### Step 1: Prepare your working environment:
```bash
mkdir nginx-proxy && cd nginx-proxy
curl -o compose.yaml https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/docs/examples/compose/nginx-proxy/compose.yaml
```
### Step 2: Edit `DEFAULT_EMAIL` in the compose file.
Albeit optional, it is recommended to provide a valid default email address through the `DEFAULT_EMAIL` environment variable, so that Let's Encrypt can warn you about expiring certificates and allow you to recover your account.
### Step 3: Create docker network
Containers need share the same network for auto-discovery.
```bash
docker network create proxy-tier
```
### Step 4: Start service
```bash
docker compose up -d
```
## Usage
Once both nginx-proxy and acme-companion containers are up and running, start any container you want proxied with environment variables `VIRTUAL_HOST` and `LETSENCRYPT_HOST` both set to the domain(s) your proxied container is going to use.
@@ -1,36 +0,0 @@
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- html:/usr/share/nginx/html
- certs:/etc/nginx/certs:ro
- /var/run/docker.sock:/tmp/docker.sock:ro
networks:
- proxy-tier
acme-companion:
image: nginxproxy/acme-companion
container_name: nginx-proxy-acme
environment:
- DEFAULT_EMAIL=mail@yourdomain.tld
volumes_from:
- nginx-proxy
volumes:
- certs:/etc/nginx/certs:rw
- acme:/etc/acme.sh
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- proxy-tier
networks:
proxy-tier:
external: true
volumes:
html:
certs:
acme:
-22
View File
@@ -1,22 +0,0 @@
port: 7880
redis:
address: redis:6379
keys:
meet: <your livekit secret key>
# WebRTC configuration
rtc:
# # when set, LiveKit will attempt to use a UDP mux so all UDP traffic goes through
# # listed port(s). To maximize system performance, we recommend using a range of ports
# # greater or equal to the number of vCPUs on the machine.
# # port_range_start & end must not be set for this config to take effect
udp_port: 7882
# when set, LiveKit enable WebRTC ICE over TCP when UDP isn't available
# this port *cannot* be behind load balancer or TLS, and must be exposed on the node
# WebRTC transports are encrypted and do not require additional encryption
# only 80/443 on public IP are allowed if less than 1024
tcp_port: 7881
# use_external_ip should be set to true for most cloud environments where
# the host has a public IP address, but is not exposed to the process.
# LiveKit will attempt to use STUN to discover the true IP, and advertise
# that IP with its clients
use_external_ip: true
+4 -1
View File
@@ -6,9 +6,12 @@ Here are a bunch of resources to help you install the project.
La Suite Meet maintainers use only the Kubernetes deployment method in production, so advanced support is available exclusively for this setup. Please follow the instructions provided [here](/docs/installation/kubernetes.md).
## Docker Compose
We understand that not everyone has a Kubernetes cluster available, please follow the instructions provided [here](/docs/installation/compose.md) to set up a docker compose instance.
We understand that not everyone has a Kubernetes cluster available.
We also provide [Docker images](https://hub.docker.com/u/lasuite?page=1&search=meet) that can be deployed using Compose.
> [!WARNING]
> Under construction: A PR is in progress to support deploying La Suite Meet via Docker Compose.
## Other ways to install La Suite Meet
Community members have contributed alternative ways to install La Suite Meet 🙏. While maintainers may not provide direct support, we help keep these instructions up to date, and you can reach out to contributors or the community for assistance.
-227
View File
@@ -1,227 +0,0 @@
# Installation with docker compose
We provide a sample configuration for running Meet using Docker Compose. Please note that this configuration is experimental, and the official way to deploy Meet in production is to use [k8s](../installation/k8s.md)
## Requirements
All services are required to run the minimalist instance of LaSuite Meet. Click the links for ready-to-use configuration examples:
| Service | Purpose | Example Config |
|-------------------|---------|----------------------------------------------------------|
| **PostgreSQL** | Main database | [compose.yaml](../examples/compose/compose.yaml) |
| **Redis** | Cache & sessions | [compose.yaml](../examples/compose/compose.yaml) |
| **Livekit** | Real-time communication | [compose.yaml](../examples/compose/compose.yaml) |
| **OIDC Provider** | User authentication | [Keycloak setup](../examples/compose/keycloak/README.md) |
| **SMTP Service** | Email notifications | - |
> [!NOTE] Some advanced features, as Recording and transcription, require additional services (MinIO, email). See `/features` folder for details.
## Software Requirements
Ensure you have Docker Compose(v2) installed on your host server. Follow the official guidelines for a reliable setup:
Docker Compose is included with Docker Engine:
- **Docker Engine:** We suggest adhering to the instructions provided by Docker
for [installing Docker Engine](https://docs.docker.com/engine/install/).
For older versions of Docker Engine that do not include Docker Compose:
- **Docker Compose:** Install it as per the [official documentation](https://docs.docker.com/compose/install/).
> [!NOTE]
> `docker-compose` may not be supported. You are advised to use `docker compose` instead.
## Step 1: Prepare your working environment:
```bash
mkdir -p meet/env.d && cd meet
curl -o compose.yaml https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/docs/examples/compose/compose.yaml
curl -o .env https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/env.d/production.dist/hosts
curl -o env.d/common https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/env.d/production.dist/common
curl -o env.d/postgresql https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/env.d/production.dist/postgresql
curl -o livekit-server.yaml https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/docs/examples/livekit/server.yaml
curl -o default.conf.template https://raw.githubusercontent.com/suitenumerique/meet/refs/heads/main/docker/files/production/default.conf.template
```
## Step 2: Configuration
Meet configuration is achieved through environment variables. We provide a [detailed description of all variables](../env.md).
In this example, we assume the following services:
- OIDC provider on https://id.yourdomain.tld
- Livekit server on https://livekit.yourdomain.tld
- Meet server on https://meet.yourdomain.tld
**Set your own values in `.env`**
### OIDC
Authentication in Meet is managed through Open ID Connect protocol. A functional Identity Provider implementing this protocol is required.
For guidance, refer to our [Keycloak deployment example](../examples/compose/keycloak/README.md).
If using Keycloak as your Identity Provider, in `env.d/common` set `OIDC_RP_CLIENT_ID` and `OIDC_RP_CLIENT_SECRET` variables with those of the OIDC client created for Meet. By default we have set `meet` as the realm name, if you have named your realm differently, update the value `REALM_NAME` in `.env`
For others OIDC providers, update the variables in `env.d/common`.
### Postgresql
Meet uses PostgreSQL as its database. Although an external PostgreSQL can be used, our example provides a deployment method.
If you are using the example provided, you need to generate a secure key for `DB_PASSWORD` and set it in `env.d/postgresql`.
If you are using an external service or not using our default values, you should update the variables in `env.d/postgresql`
### Redis
Meet uses Redis for caching and inter-service communication. While an external Redis can be used, our example provides a deployment method.
If you are using an external service, you need to set `REDIS_URL` environment variable in `env.d/common`.
### Livekit
[LiveKit](https://github.com/livekit/livekit) server is used as the WebRTC SFU (Selective Forwarding Unit) allowing multi-user conferencing. For more information, head to [livekit documentation](https://docs.livekit.io/home/self-hosting/).
Generate a secure key for `LIVEKIT_API_SECRET` in `env.d/common`.
We provide a minimal recommanded config for production environment in `livekit-server.yaml`. Set the previously generated API secret key in the config file.
To view other customization options, see [config-sample.yaml](https://github.com/livekit/livekit/blob/master/config-sample.yaml)
> [!NOTE]
> In this example, we configured multiplexing on a single UDP port. For better performances, you can configure a range of UDP ports.
### Meet
The Meet backend is built on the Django Framework.
Generate a [secure key](https://docs.djangoproject.com/en/5.2/ref/settings/#secret-key.) for `DJANGO_SECRET_KEY` in `env.d/common`.
### Mail
The following environment variables are required in `env.d/common` for the mail service to send invitations :
```env
DJANGO_EMAIL_HOST=<smtp host>
DJANGO_EMAIL_HOST_USER=<smtp user>
DJANGO_EMAIL_HOST_PASSWORD=<smtp password>
DJANGO_EMAIL_PORT=<smtp port>
DJANGO_EMAIL_FROM=<your email address>
#DJANGO_EMAIL_USE_TLS=true # A flag to enable or disable TLS for email sending.
#DJANGO_EMAIL_USE_SSL=true # A flag to enable or disable SSL for email sending.
DJANGO_EMAIL_BRAND_NAME=<brand name used in email templates> # e.g. "La Suite Numérique"
DJANGO_EMAIL_LOGO_IMG=<logo image to use in email templates.> # e.g. "https://meet.yourdomain.tld/assets/logo-suite-numerique.png"
```
## Step 3: Configure your firewall
If you are using a firewall as it is usually recommended in a production environment you will need to allow the webservice traffic on ports 80 and 443 but also to allow UDP traffic for the WebRTC service.
The following ports will need to be opened:
- 80/tcp - for TLS issuance
- 443/tcp - for listening on HTTPS and TURN/TLS packets
- 7881/tcp - WebRTC ICE over TCP
- 7882/udp - for WebRTC multiplexing over UDP
If you are using ufw, enter the follwoing:
```
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 443/udp
ufw allow 7881/tcp
ufw allow 7882/udp
ufw enable
```
## Step 4: Reverse proxy and SSL/TLS
> [!WARNING]
> In a production environment, configure SSL/TLS termination to run your instance on https.
If you have your own certificates and proxy setup, you can skip this part.
You can follow our [nginx proxy example](../examples/compose/nginx-proxy/README.md) with automatic generation and renewal of certificate with Let's Encrypt.
You will need to uncomment the environment and network sections in compose file and update it with your values.
```yaml
frontend:
...
# Uncomment and set your values if using our nginx proxy example
# environment:
# - VIRTUAL_HOST=${MEET_HOST} # used by nginx proxy
# - VIRTUAL_PORT=8083 # used by nginx proxy
# - LETSENCRYPT_HOST=${MEET_HOST} # used by lets encrypt to generate TLS certificate
...
# Uncomment if using our nginx proxy example
# networks:
# - proxy-tier
# - default
...
# environment:
# - VIRTUAL_HOST=${LIVEKIT_HOST} # used by nginx proxy
# - VIRTUAL_PORT=7880 # used by nginx proxy
# - LETSENCRYPT_HOST=${LIVEKIT_HOST} # used by lets encrypt to generate TLS certificate
# Uncomment if using our nginx proxy example
# networks:
# - proxy-tier
# - default
#networks:
# proxy-tier:
# external: true
```
## Step 5: Start Meet
You are ready to start your Meet application !
```bash
docker compose up -d
```
> [!NOTE]
> Version of the images are set to latest, you should pin it to the desired version to avoid unwanted upgrades when pulling latest image.
## Step 6: Run the database migration and create Django admin user
```bash
docker compose run --rm backend python manage.py migrate
docker compose run --rm backend python manage.py createsuperuser --email <admin email> --password <admin password>
```
Replace `<admin email>` with the email of your admin user and generate a secure password.
Your Meet instance is now available on the domain you defined, https://meet.yourdomain.tld.
THe admin interface is available on https://meet.yourdomain.tld/admin with the admin user you just created.
## How to upgrade your Meet application
Before running an upgrade you must check the [Upgrade document](../../UPGRADE.md) for specific procedures that might be needed.
You can also check the [Changelog](../../CHANGELOG.md) for brief summary of the changes.
### Step 1: Edit the images tag with the desired version
### Step 2: Pull the images
```bash
docker compose pull
```
### Step 3: Restart your containers
```bash
docker compose restart
```
### Step 4: Run the database migration
Your database schema may need to be updated, run:
```bash
docker compose run --rm backend python manage.py migrate
```
-50
View File
@@ -1,50 +0,0 @@
# Django
DJANGO_ALLOWED_HOSTS=${MEET_HOST}
DJANGO_SECRET_KEY=<generate a secret key>
DJANGO_SETTINGS_MODULE=meet.settings
DJANGO_CONFIGURATION=Production
# Python
PYTHONPATH=/app
# Meet settings
# Mail
DJANGO_EMAIL_HOST=<smtp host>
DJANGO_EMAIL_HOST_USER=<smtp user>
DJANGO_EMAIL_HOST_PASSWORD=<smtp password>
DJANGO_EMAIL_PORT=<smtp port>
DJANGO_EMAIL_FROM=<your email address>
#DJANGO_EMAIL_USE_TLS=true # A flag to enable or disable TLS for email sending.
#DJANGO_EMAIL_USE_SSL=true # A flag to enable or disable SSL for email sending.
DJANGO_EMAIL_BRAND_NAME="La Suite Numérique"
DJANGO_EMAIL_LOGO_IMG="https://${meet_HOST}/assets/logo-suite-numerique.png"
# Backend url
MEET_BASE_URL="https://${MEET_HOST}"
# OIDC
OIDC_OP_JWKS_ENDPOINT=https://${KEYCLOAK_HOST}/realms/${REALM_NAME}/protocol/openid-connect/certs
OIDC_OP_AUTHORIZATION_ENDPOINT=https://${KEYCLOAK_HOST}/realms/${REALM_NAME}/protocol/openid-connect/auth
OIDC_OP_TOKEN_ENDPOINT=https://${KEYCLOAK_HOST}/realms/${REALM_NAME}/protocol/openid-connect/token
OIDC_OP_USER_ENDPOINT=https://${KEYCLOAK_HOST}/realms/${REALM_NAME}/protocol/openid-connect/userinfo
OIDC_OP_LOGOUT_ENDPOINT=https://${KEYCLOAK_HOST}/realms/${REALM_NAME}/protocol/openid-connect/logout
OIDC_RP_CLIENT_ID=<client_id>
OIDC_RP_CLIENT_SECRET=<client secret>
OIDC_RP_SIGN_ALGO=RS256
OIDC_RP_SCOPES="openid email"
LOGIN_REDIRECT_URL=https://${MEET_HOST}
LOGIN_REDIRECT_URL_FAILURE=https://${MEET_HOST}
LOGOUT_REDIRECT_URL=https://${MEET_HOST}
OIDC_REDIRECT_ALLOWED_HOSTS=["https://${MEET_HOST}"]
# Livekit Token settings
LIVEKIT_API_SECRET=<generate a secret key>
LIVEKIT_API_KEY=meet
LIVEKIT_API_URL=https://${LIVEKIT_HOST}
ALLOW_UNREGISTERED_ROOMS=False
-7
View File
@@ -1,7 +0,0 @@
MEET_HOST=meet.domain.tld
KEYCLOAK_HOST=id.domain.tld
LIVEKIT_HOST=livekit.domain.tld
BACKEND_INTERNAL_HOST=backend
FRONTEND_INTERNAL_HOST=frontend
LIVEKIT_INTERNAL_HOST=livekit
REALM_NAME=meet
-13
View File
@@ -1,13 +0,0 @@
# Postgresql db container configuration
POSTGRES_DB=keycloak
POSTGRES_USER=keycloak
POSTGRES_PASSWORD=<generate postgres password>
PGDATA=/var/lib/postgresql/data/pgdata
# Keycloak postgresql configuration
KC_DB=postgres
KC_DB_SCHEMA=public
KC_DB_URL_HOST=postgresql
KC_DB_NAME=${POSTGRES_DB}
KC_DB_USER=${POSTGRES_USER}
KC_DB_PASSWORD=${POSTGRES_PASSWORD}
-8
View File
@@ -1,8 +0,0 @@
# Keycloak admin user
KC_BOOTSTRAP_ADMIN_USERNAME=admin
KC_BOOTSTRAP_ADMIN_PASSWORD=<generate your password>
# Keycloak configuration
KC_HOSTNAME=https://id.yourdomain.tld # Change with your own URL
KC_PROXY_HEADERS=xforwarded # in this example we are running behind an nginx proxy
KC_HTTP_ENABLED=true # in this example we are running behind an nginx proxy
-11
View File
@@ -1,11 +0,0 @@
# App database configuration
DB_HOST=postgresql
DB_NAME=meet
DB_USER=meet
DB_PASSWORD=<generate a secure password>
DB_PORT=5432
# Postgresql db container configuration
POSTGRES_DB=meet
POSTGRES_USER=meet
POSTGRES_PASSWORD=${DB_PASSWORD}
-6
View File
@@ -9,12 +9,6 @@
"matchManagers": ["pep621"],
"matchPackageNames": ["redis"]
},
{
"groupName": "allowed pylint versions",
"matchManagers": ["pep621"],
"matchPackageNames": ["pylint"],
"allowedVersions": "<4.0.0"
},
{
"enabled": false,
"groupName": "ignored js dependencies",
+1 -58
View File
@@ -29,72 +29,15 @@ logger = logging.getLogger("transcriber")
TRANSCRIBER_AGENT_NAME = os.getenv("TRANSCRIBER_AGENT_NAME", "multi-user-transcriber")
# Default Deepgram STT configuration
DEEPGRAM_STT_DEFAULTS = {
"model": "nova-3",
"language": "multi",
}
# Supported parameters for LiveKit's deepgram.STT() in streaming mode
# Note: Not all Deepgram API parameters are supported by the LiveKit plugin
# detect_language is NOT supported for real-time streaming
# Use language="multi" instead for automatic multilingual support
DEEPGRAM_STT_SUPPORTED_PARAMS = {
"model",
"language",
}
def _build_deepgram_stt_kwargs():
"""Build Deepgram STT kwargs from DEEPGRAM_STT_* environment variables.
Only parameters supported by LiveKit's deepgram.STT() are included.
Unsupported parameters are logged as warnings.
"""
stt_kwargs = DEEPGRAM_STT_DEFAULTS.copy()
# Scan environment variables for DEEPGRAM_STT_* pattern
for key, value in os.environ.items():
if key.startswith("DEEPGRAM_STT_"):
# Extract parameter name and convert to lowercase
param_name = key.replace("DEEPGRAM_STT_", "", 1).lower()
# Check if parameter is supported by LiveKit plugin
if param_name not in DEEPGRAM_STT_SUPPORTED_PARAMS:
supported = ", ".join(sorted(DEEPGRAM_STT_SUPPORTED_PARAMS))
logger.warning(
f"Ignoring unsupported Deepgram STT parameter: {param_name}. "
f"Supported parameters: {supported}"
)
continue
# Parse value type
value_lower = value.lower()
if value_lower in ("true", "false"):
# Boolean values
stt_kwargs[param_name] = value_lower == "true"
elif value.isdigit():
# Integer values
stt_kwargs[param_name] = int(value)
else:
# String values
stt_kwargs[param_name] = value
logger.info(f"Deepgram STT configuration: {stt_kwargs}")
return stt_kwargs
class Transcriber(Agent):
"""Create a transcription agent for a specific participant."""
def __init__(self, *, participant_identity: str):
"""Init transcription agent."""
# Build STT configuration from environment variables
stt_kwargs = _build_deepgram_stt_kwargs()
super().__init__(
instructions="not-needed",
stt=deepgram.STT(**stt_kwargs),
stt=deepgram.STT(),
)
self.participant_identity = participant_identity
+6 -6
View File
@@ -1,18 +1,18 @@
[project]
name = "agents"
version = "0.1.42"
version = "0.1.40"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.2.18",
"livekit-plugins-deepgram==1.2.18",
"livekit-plugins-silero==1.2.18",
"python-dotenv==1.2.1"
"livekit-agents==1.2.6",
"livekit-plugins-deepgram==1.2.6",
"livekit-plugins-silero==1.2.6",
"python-dotenv==1.1.1"
]
[project.optional-dependencies]
dev = [
"ruff==0.14.4",
"ruff==0.12.0",
]
[build-system]
+3 -83
View File
@@ -1,12 +1,10 @@
"""Admin classes and registrations for core app."""
from django import forms
from django.contrib import admin, messages
from django.contrib import admin
from django.contrib.auth import admin as auth_admin
from django.utils.translation import gettext_lazy as _
from core.recording.event import notification
from . import models
@@ -111,27 +109,10 @@ class RoomAdmin(admin.ModelAdmin):
inlines = (ResourceAccessInline,)
search_fields = ["name", "slug", "=id"]
list_display = ["name", "slug", "access_level", "get_owner", "created_at"]
list_display = ["name", "slug", "access_level", "created_at"]
list_filter = ["access_level", "created_at"]
readonly_fields = ["id", "created_at", "updated_at"]
def get_owner(self, obj):
"""Return the owner of the room for display in the admin list."""
owners = [
access
for access in obj.accesses.all()
if access.role == models.RoleChoices.OWNER
]
if not owners:
return _("No owner")
if len(owners) > 1:
return _("Multiple owners")
return str(owners[0].user)
class RecordingAccessInline(admin.TabularInline):
"""Inline admin class for recording accesses."""
@@ -140,76 +121,15 @@ class RecordingAccessInline(admin.TabularInline):
extra = 0
@admin.action(description=_("Resend notification to external service"))
def resend_notification(modeladmin, request, queryset): # pylint: disable=unused-argument
"""Resend notification to external service for selected recordings."""
notification_service = notification.NotificationService()
processed = 0
skipped = 0
failed = 0
for recording in queryset:
if recording.is_expired:
skipped += 1
continue
try:
success = notification_service.notify_external_services(recording)
if success:
processed += 1
else:
failed += 1
modeladmin.message_user(
request,
_("Failed to notify for recording %(id)s") % {"id": recording.id},
level=messages.ERROR,
)
except Exception as e: # noqa: BLE001 # pylint: disable=broad-except
failed += 1
modeladmin.message_user(
request,
_("Failed to notify for recording %(id)s: %(error)s")
% {"id": recording.id, "error": str(e)},
level=messages.ERROR,
)
if processed > 0:
modeladmin.message_user(
request,
_("Successfully sent notifications for %(count)s recording(s).")
% {"count": processed},
level=messages.SUCCESS,
)
if skipped > 0:
modeladmin.message_user(
request,
_("Skipped %(count)s expired recording(s).") % {"count": skipped},
level=messages.WARNING,
)
@admin.register(models.Recording)
class RecordingAdmin(admin.ModelAdmin):
"""Recording admin interface declaration."""
inlines = (RecordingAccessInline,)
search_fields = ["status", "=id", "worker_id", "room__slug", "=room__id"]
list_display = (
"id",
"status",
"mode",
"room",
"get_owner",
"created_at",
"worker_id",
)
list_display = ("id", "status", "room", "get_owner", "created_at", "worker_id")
list_filter = ["status", "room", "created_at"]
readonly_fields = ["id", "created_at", "updated_at"]
actions = [resend_notification]
def get_queryset(self, request):
"""Optimize queries by prefetching related access and user data to avoid N+1 queries."""
@@ -158,9 +158,9 @@ class NotificationService:
timeout=30,
)
response.raise_for_status()
except requests.RequestException as exc:
except requests.HTTPError as exc:
logger.exception(
"Summary service error for recording %s. URL: %s. Exception: %s",
"Summary service HTTP error for recording %s. URL: %s. Exception: %s",
recording.id,
settings.SUMMARY_SERVICE_ENDPOINT,
exc,
@@ -345,9 +345,7 @@ def test_authentication_getter_existing_user_change_fields(
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
# One and only one additional update query when a field has changed
# Note: .save() triggers uniqueness validation queries for unique fields,
# adding extra SELECT queries before the UPDATE (e.g., checking unique=True on 'sub')
with django_assert_num_queries(3):
with django_assert_num_queries(2):
authenticated_user = klass.get_or_create_user(
access_token="test-token", id_token=None, payload=None
)
+74 -167
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-13 16:21+0000\n"
"POT-Creation-Date: 2025-07-11 11:33+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,153 +17,121 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: core/admin.py:29
#: core/admin.py:26
msgid "Personal info"
msgstr "Persönliche Informationen"
#: core/admin.py:42
#: core/admin.py:39
msgid "Permissions"
msgstr "Berechtigungen"
#: core/admin.py:54
#: core/admin.py:51
msgid "Important dates"
msgstr "Wichtige Daten"
#: core/admin.py:128 core/admin.py:228
#: core/admin.py:147
msgid "No owner"
msgstr "Kein Eigentümer"
#: core/admin.py:131 core/admin.py:231
#: core/admin.py:150
msgid "Multiple owners"
msgstr "Mehrere Eigentümer"
#: core/admin.py:143
msgid "Resend notification to external service"
msgstr "Benachrichtigung erneut an externen Dienst senden"
#: core/admin.py:166
#, python-format
msgid "Failed to notify for recording %(id)s"
msgstr "Benachrichtigung für Aufnahme %(id)s fehlgeschlagen"
#: core/admin.py:174
#, python-format
msgid "Failed to notify for recording %(id)s: %(error)s"
msgstr "Benachrichtigung für Aufnahme %(id)s fehlgeschlagen: %(error)s"
#: core/admin.py:182
#, python-format
msgid "Successfully sent notifications for %(count)s recording(s)."
msgstr "Benachrichtigungen für %(count)s Aufnahme(n) erfolgreich gesendet."
#: core/admin.py:190
#, python-format
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s abgelaufene Aufnahme(n) übersprungen."
#: core/admin.py:294
msgid "No scopes"
msgstr "Keine Scopes"
#: core/admin.py:296
msgid "Scopes"
msgstr "Scopes"
#: core/api/serializers.py:68
#: core/api/serializers.py:67
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Sie müssen Administrator oder Eigentümer eines Raums sein, um Zugriffe "
"hinzuzufügen."
#: core/models.py:34
#: core/models.py:31
msgid "Member"
msgstr "Mitglied"
#: core/models.py:35
#: core/models.py:32
msgid "Administrator"
msgstr "Administrator"
#: core/models.py:36
#: core/models.py:33
msgid "Owner"
msgstr "Eigentümer"
#: core/models.py:52
#: core/models.py:49
msgid "Initiated"
msgstr "Gestartet"
#: core/models.py:53
#: core/models.py:50
msgid "Active"
msgstr "Aktiv"
#: core/models.py:54
#: core/models.py:51
msgid "Stopped"
msgstr "Beendet"
#: core/models.py:55
#: core/models.py:52
msgid "Saved"
msgstr "Gespeichert"
#: core/models.py:56
#: core/models.py:53
msgid "Aborted"
msgstr "Abgebrochen"
#: core/models.py:57
#: core/models.py:54
msgid "Failed to Start"
msgstr "Start fehlgeschlagen"
#: core/models.py:58
#: core/models.py:55
msgid "Failed to Stop"
msgstr "Stopp fehlgeschlagen"
#: core/models.py:59
#: core/models.py:56
msgid "Notification succeeded"
msgstr "Benachrichtigung erfolgreich"
#: core/models.py:86
#: core/models.py:83
msgid "SCREEN_RECORDING"
msgstr "BILDSCHIRMAUFZEICHNUNG"
#: core/models.py:87
#: core/models.py:84
msgid "TRANSCRIPT"
msgstr "TRANSKRIPT"
#: core/models.py:93
#: core/models.py:90
msgid "Public Access"
msgstr "Öffentlicher Zugriff"
#: core/models.py:94
#: core/models.py:91
msgid "Trusted Access"
msgstr "Vertrauenswürdiger Zugriff"
#: core/models.py:95
#: core/models.py:92
msgid "Restricted Access"
msgstr "Eingeschränkter Zugriff"
#: core/models.py:107
#: core/models.py:104
msgid "id"
msgstr "ID"
#: core/models.py:108
#: core/models.py:105
msgid "primary key for the record as UUID"
msgstr "Primärschlüssel des Eintrags als UUID"
#: core/models.py:114
#: core/models.py:111
msgid "created on"
msgstr "erstellt am"
#: core/models.py:115
#: core/models.py:112
msgid "date and time at which a record was created"
msgstr "Datum und Uhrzeit der Erstellung eines Eintrags"
#: core/models.py:120
#: core/models.py:117
msgid "updated on"
msgstr "aktualisiert am"
#: core/models.py:121
#: core/models.py:118
msgid "date and time at which a record was last updated"
msgstr "Datum und Uhrzeit der letzten Aktualisierung eines Eintrags"
#: core/models.py:141
#: core/models.py:138
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -171,11 +139,11 @@ msgstr ""
"Geben Sie einen gültigen Sub ein. Dieser Wert darf nur Buchstaben, Zahlen "
"und die Zeichen @/./+/-/_ enthalten."
#: core/models.py:147
#: core/models.py:144
msgid "sub"
msgstr "Sub"
#: core/models.py:149
#: core/models.py:146
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
@@ -183,55 +151,55 @@ msgstr ""
"Erforderlich. Maximal 255 Zeichen. Nur Buchstaben, Zahlen und @/./+/-/_ sind "
"erlaubt."
#: core/models.py:157
#: core/models.py:154
msgid "identity email address"
msgstr "Identitäts-E-Mail-Adresse"
#: core/models.py:162
#: core/models.py:159
msgid "admin email address"
msgstr "Administrator-E-Mail-Adresse"
#: core/models.py:164
#: core/models.py:161
msgid "full name"
msgstr "Vollständiger Name"
#: core/models.py:166
#: core/models.py:163
msgid "short name"
msgstr "Kurzname"
#: core/models.py:172
#: core/models.py:169
msgid "language"
msgstr "Sprache"
#: core/models.py:173
#: core/models.py:170
msgid "The language in which the user wants to see the interface."
msgstr "Die Sprache, in der der Benutzer die Oberfläche sehen möchte."
#: core/models.py:179
#: core/models.py:176
msgid "The timezone in which the user wants to see times."
msgstr "Die Zeitzone, in der der Benutzer die Zeiten sehen möchte."
#: core/models.py:182
#: core/models.py:179
msgid "device"
msgstr "Gerät"
#: core/models.py:184
#: core/models.py:181
msgid "Whether the user is a device or a real user."
msgstr "Ob es sich um ein Gerät oder einen echten Benutzer handelt."
#: core/models.py:187
#: core/models.py:184
msgid "staff status"
msgstr "Mitarbeiterstatus"
#: core/models.py:189
#: core/models.py:186
msgid "Whether the user can log into this admin site."
msgstr "Ob der Benutzer sich bei dieser Admin-Seite anmelden kann."
#: core/models.py:192
#: core/models.py:189
msgid "active"
msgstr "aktiv"
#: core/models.py:195
#: core/models.py:192
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -239,66 +207,66 @@ msgstr ""
"Ob dieser Benutzer als aktiv behandelt werden soll. Deaktivieren Sie dies "
"anstelle des Löschens des Kontos."
#: core/models.py:208
#: core/models.py:205
msgid "user"
msgstr "Benutzer"
#: core/models.py:209
#: core/models.py:206
msgid "users"
msgstr "Benutzer"
#: core/models.py:268
#: core/models.py:265
msgid "Resource"
msgstr "Ressource"
#: core/models.py:269
#: core/models.py:266
msgid "Resources"
msgstr "Ressourcen"
#: core/models.py:323
#: core/models.py:320
msgid "Resource access"
msgstr "Ressourcenzugriff"
#: core/models.py:324
#: core/models.py:321
msgid "Resource accesses"
msgstr "Ressourcenzugriffe"
#: core/models.py:330
#: core/models.py:327
msgid "Resource access with this User and Resource already exists."
msgstr ""
"Ein Ressourcenzugriff mit diesem Benutzer und dieser Ressource existiert "
"bereits."
#: core/models.py:386
#: core/models.py:383
msgid "Visio room configuration"
msgstr "Visio-Raumkonfiguration"
#: core/models.py:387
#: core/models.py:384
msgid "Values for Visio parameters to configure the room."
msgstr "Werte für Visio-Parameter zur Konfiguration des Raums."
#: core/models.py:394
#: core/models.py:391
msgid "Room PIN code"
msgstr "PIN-Code für den Raum"
#: core/models.py:395
#: core/models.py:392
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Eindeutiger n-stelliger Code, der diesen Raum im Telephonmodus identifiziert."
#: core/models.py:401 core/models.py:555
#: core/models.py:398 core/models.py:552
msgid "Room"
msgstr "Raum"
#: core/models.py:402
#: core/models.py:399
msgid "Rooms"
msgstr "Räume"
#: core/models.py:566
#: core/models.py:563
msgid "Worker ID"
msgstr "Worker-ID"
#: core/models.py:568
#: core/models.py:565
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -307,102 +275,42 @@ msgstr ""
"erhalten, auch wenn der Worker stoppt, was ein einfaches Nachverfolgen "
"ermöglicht."
#: core/models.py:576
#: core/models.py:573
msgid "Recording mode"
msgstr "Aufzeichnungsmodus"
#: core/models.py:577
#: core/models.py:574
msgid "Defines the mode of recording being called."
msgstr "Definiert den aufgerufenen Aufzeichnungsmodus."
#: core/models.py:583
#: core/models.py:580
msgid "Recording"
msgstr "Aufzeichnung"
#: core/models.py:584
#: core/models.py:581
msgid "Recordings"
msgstr "Aufzeichnungen"
#: core/models.py:692
#: core/models.py:689
msgid "Recording/user relation"
msgstr "Beziehung Aufzeichnung/Benutzer"
#: core/models.py:693
#: core/models.py:690
msgid "Recording/user relations"
msgstr "Beziehungen Aufzeichnung/Benutzer"
#: core/models.py:699
#: core/models.py:696
msgid "This user is already in this recording."
msgstr "Dieser Benutzer ist bereits Teil dieser Aufzeichnung."
#: core/models.py:705
#: core/models.py:702
msgid "This team is already in this recording."
msgstr "Dieses Team ist bereits Teil dieser Aufzeichnung."
#: core/models.py:711
#: core/models.py:708
msgid "Either user or team must be set, not both."
msgstr "Entweder Benutzer oder Team muss festgelegt werden, nicht beides."
#: core/models.py:728
msgid "Create rooms"
msgstr "Räume erstellen"
#: core/models.py:729
msgid "List rooms"
msgstr "Räume auflisten"
#: core/models.py:730
msgid "Retrieve room details"
msgstr "Raumdetails abrufen"
#: core/models.py:731
msgid "Update rooms"
msgstr "Räume aktualisieren"
#: core/models.py:732
msgid "Delete rooms"
msgstr "Räume löschen"
#: core/models.py:745
msgid "Application name"
msgstr "Anwendungsname"
#: core/models.py:746
msgid "Descriptive name for this application."
msgstr "Beschreibender Name für diese Anwendung."
#: core/models.py:756
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr "Beim Speichern gehasht. Jetzt kopieren, wenn dies ein neues Geheimnis ist."
#: core/models.py:767
msgid "Application"
msgstr "Anwendung"
#: core/models.py:768
msgid "Applications"
msgstr "Anwendungen"
#: core/models.py:791
msgid "Enter a valid domain"
msgstr "Geben Sie eine gültige Domain ein"
#: core/models.py:794
msgid "Domain"
msgstr "Domain"
#: core/models.py:795
msgid "Email domain this application can act on behalf of."
msgstr "E-Mail-Domain, im Namen der diese Anwendung handeln kann."
#: core/models.py:807
msgid "Application domain"
msgstr "Anwendungsdomain"
#: core/models.py:808
msgid "Application domains"
msgstr "Anwendungsdomains"
#: core/recording/event/notification.py:94
msgid "Your recording is ready"
msgstr "Ihre Aufzeichnung ist bereit"
@@ -493,8 +401,7 @@ msgid ""
" Sharing the recording via link is not yet available. Only organizers can "
"download it. "
msgstr ""
" Die Freigabe der Aufzeichnung per Link ist noch nicht verfügbar. Nur "
"Organisatoren können sie herunterladen. "
" Die Freigabe der Aufzeichnung per Link ist noch nicht verfügbar. Nur Organisatoren können sie herunterladen. "
#: core/templates/mail/html/screen_recording.html:206
#: core/templates/mail/text/screen_recording.txt:11
@@ -531,18 +438,18 @@ msgstr ""
" Wenn Sie Fragen haben oder Unterstützung benötigen, wenden Sie sich bitte "
"an unser Support-Team unter %(support_email)s. "
#: meet/settings.py:167
#: meet/settings.py:163
msgid "English"
msgstr "Englisch"
#: meet/settings.py:168
#: meet/settings.py:164
msgid "French"
msgstr "Französisch"
#: meet/settings.py:169
#: meet/settings.py:165
msgid "Dutch"
msgstr "Niederländisch"
#: meet/settings.py:170
#: meet/settings.py:166
msgid "German"
msgstr "Deutsch"
+74 -170
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-13 16:21+0000\n"
"POT-Creation-Date: 2025-07-11 11:33+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,151 +17,119 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: core/admin.py:29
#: core/admin.py:26
msgid "Personal info"
msgstr "Personal info"
msgstr ""
#: core/admin.py:42
#: core/admin.py:39
msgid "Permissions"
msgstr "Permissions"
#: core/admin.py:54
#: core/admin.py:51
msgid "Important dates"
msgstr "Important dates"
#: core/admin.py:128 core/admin.py:228
#: core/admin.py:147
msgid "No owner"
msgstr "No owner"
#: core/admin.py:131 core/admin.py:231
#: core/admin.py:150
msgid "Multiple owners"
msgstr "Multiple owners"
#: core/admin.py:143
msgid "Resend notification to external service"
msgstr "Resend notification to external service"
#: core/admin.py:166
#, python-format
msgid "Failed to notify for recording %(id)s"
msgstr "Failed to notify for recording %(id)s"
#: core/admin.py:174
#, python-format
msgid "Failed to notify for recording %(id)s: %(error)s"
msgstr "Failed to notify for recording %(id)s: %(error)s"
#: core/admin.py:182
#, python-format
msgid "Successfully sent notifications for %(count)s recording(s)."
msgstr "Successfully sent notifications for %(count)s recording(s)."
#: core/admin.py:190
#, python-format
msgid "Skipped %(count)s expired recording(s)."
msgstr "Skipped %(count)s expired recording(s)."
#: core/admin.py:294
msgid "No scopes"
msgstr "No scopes"
#: core/admin.py:296
msgid "Scopes"
msgstr "Scopes"
#: core/api/serializers.py:68
#: core/api/serializers.py:67
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr "You must be administrator or owner of a room to add accesses to it."
#: core/models.py:34
#: core/models.py:31
msgid "Member"
msgstr "Member"
#: core/models.py:35
#: core/models.py:32
msgid "Administrator"
msgstr "Administrator"
#: core/models.py:36
#: core/models.py:33
msgid "Owner"
msgstr "Owner"
#: core/models.py:52
#: core/models.py:49
msgid "Initiated"
msgstr "Initiated"
#: core/models.py:53
#: core/models.py:50
msgid "Active"
msgstr "Active"
#: core/models.py:54
#: core/models.py:51
msgid "Stopped"
msgstr "Stopped"
#: core/models.py:55
#: core/models.py:52
msgid "Saved"
msgstr "Saved"
#: core/models.py:56
#: core/models.py:53
msgid "Aborted"
msgstr "Aborted"
#: core/models.py:57
#: core/models.py:54
msgid "Failed to Start"
msgstr "Failed to Start"
#: core/models.py:58
#: core/models.py:55
msgid "Failed to Stop"
msgstr "Failed to Stop"
#: core/models.py:59
#: core/models.py:56
msgid "Notification succeeded"
msgstr "Notification succeeded"
#: core/models.py:86
#: core/models.py:83
msgid "SCREEN_RECORDING"
msgstr "SCREEN_RECORDING"
#: core/models.py:87
#: core/models.py:84
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:93
#: core/models.py:90
msgid "Public Access"
msgstr "Public Access"
#: core/models.py:94
#: core/models.py:91
msgid "Trusted Access"
msgstr "Trusted Access"
#: core/models.py:95
#: core/models.py:92
msgid "Restricted Access"
msgstr "Restricted Access"
#: core/models.py:107
#: core/models.py:104
msgid "id"
msgstr "id"
#: core/models.py:108
#: core/models.py:105
msgid "primary key for the record as UUID"
msgstr "primary key for the record as UUID"
#: core/models.py:114
#: core/models.py:111
msgid "created on"
msgstr "created on"
#: core/models.py:115
#: core/models.py:112
msgid "date and time at which a record was created"
msgstr "date and time at which a record was created"
#: core/models.py:120
#: core/models.py:117
msgid "updated on"
msgstr "updated on"
#: core/models.py:121
#: core/models.py:118
msgid "date and time at which a record was last updated"
msgstr "date and time at which a record was last updated"
#: core/models.py:141
#: core/models.py:138
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -169,11 +137,11 @@ msgstr ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
#: core/models.py:147
#: core/models.py:144
msgid "sub"
msgstr "sub"
#: core/models.py:149
#: core/models.py:146
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
@@ -181,55 +149,55 @@ msgstr ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
#: core/models.py:157
#: core/models.py:154
msgid "identity email address"
msgstr "identity email address"
#: core/models.py:162
#: core/models.py:159
msgid "admin email address"
msgstr "admin email address"
#: core/models.py:164
#: core/models.py:161
msgid "full name"
msgstr "full name"
#: core/models.py:166
#: core/models.py:163
msgid "short name"
msgstr "short name"
#: core/models.py:172
#: core/models.py:169
msgid "language"
msgstr "language"
#: core/models.py:173
#: core/models.py:170
msgid "The language in which the user wants to see the interface."
msgstr "The language in which the user wants to see the interface."
#: core/models.py:179
#: core/models.py:176
msgid "The timezone in which the user wants to see times."
msgstr "The timezone in which the user wants to see times."
#: core/models.py:182
#: core/models.py:179
msgid "device"
msgstr "device"
#: core/models.py:184
#: core/models.py:181
msgid "Whether the user is a device or a real user."
msgstr "Whether the user is a device or a real user."
#: core/models.py:187
#: core/models.py:184
msgid "staff status"
msgstr "staff status"
#: core/models.py:189
#: core/models.py:186
msgid "Whether the user can log into this admin site."
msgstr "Whether the user can log into this admin site."
#: core/models.py:192
#: core/models.py:189
msgid "active"
msgstr "active"
#: core/models.py:195
#: core/models.py:192
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -237,63 +205,63 @@ msgstr ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
#: core/models.py:208
#: core/models.py:205
msgid "user"
msgstr "user"
#: core/models.py:209
#: core/models.py:206
msgid "users"
msgstr "users"
#: core/models.py:268
#: core/models.py:265
msgid "Resource"
msgstr "Resource"
#: core/models.py:269
#: core/models.py:266
msgid "Resources"
msgstr "Resources"
#: core/models.py:323
#: core/models.py:320
msgid "Resource access"
msgstr "Resource access"
#: core/models.py:324
#: core/models.py:321
msgid "Resource accesses"
msgstr "Resource accesses"
#: core/models.py:330
#: core/models.py:327
msgid "Resource access with this User and Resource already exists."
msgstr "Resource access with this User and Resource already exists."
#: core/models.py:386
#: core/models.py:383
msgid "Visio room configuration"
msgstr "Visio room configuration"
#: core/models.py:387
#: core/models.py:384
msgid "Values for Visio parameters to configure the room."
msgstr "Values for Visio parameters to configure the room."
#: core/models.py:394
#: core/models.py:391
msgid "Room PIN code"
msgstr "Room PIN code"
#: core/models.py:395
#: core/models.py:392
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr "Unique n-digit code that identifies this room in telephony mode."
#: core/models.py:401 core/models.py:555
#: core/models.py:398 core/models.py:552
msgid "Room"
msgstr "Room"
#: core/models.py:402
#: core/models.py:399
msgid "Rooms"
msgstr "Rooms"
#: core/models.py:566
#: core/models.py:563
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:568
#: core/models.py:565
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -301,106 +269,42 @@ msgstr ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
#: core/models.py:576
#: core/models.py:573
msgid "Recording mode"
msgstr "Recording mode"
#: core/models.py:577
#: core/models.py:574
msgid "Defines the mode of recording being called."
msgstr "Defines the mode of recording being called."
#: core/models.py:583
#: core/models.py:580
msgid "Recording"
msgstr "Recording"
#: core/models.py:584
#: core/models.py:581
msgid "Recordings"
msgstr "Recordings"
#: core/models.py:692
#: core/models.py:689
msgid "Recording/user relation"
msgstr "Recording/user relation"
#: core/models.py:693
#: core/models.py:690
msgid "Recording/user relations"
msgstr "Recording/user relations"
#: core/models.py:699
#: core/models.py:696
msgid "This user is already in this recording."
msgstr "This user is already in this recording."
#: core/models.py:705
#: core/models.py:702
msgid "This team is already in this recording."
msgstr "This team is already in this recording."
#: core/models.py:711
#: core/models.py:708
msgid "Either user or team must be set, not both."
msgstr "Either user or team must be set, not both."
#: core/models.py:728
#, fuzzy
#| msgid "created on"
msgid "Create rooms"
msgstr "Create rooms"
#: core/models.py:729
msgid "List rooms"
msgstr "List rooms"
#: core/models.py:730
msgid "Retrieve room details"
msgstr "Retrieve room details"
#: core/models.py:731
#, fuzzy
#| msgid "updated on"
msgid "Update rooms"
msgstr "Update rooms"
#: core/models.py:732
msgid "Delete rooms"
msgstr "Delete rooms"
#: core/models.py:745
msgid "Application name"
msgstr "Application name"
#: core/models.py:746
msgid "Descriptive name for this application."
msgstr "Descriptive name for this application."
#: core/models.py:756
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr "Hashed on Save. Copy it now if this is a new secret."
#: core/models.py:767
msgid "Application"
msgstr "Application"
#: core/models.py:768
msgid "Applications"
msgstr "Applications"
#: core/models.py:791
msgid "Enter a valid domain"
msgstr "Enter a valid domain"
#: core/models.py:794
msgid "Domain"
msgstr "Domain"
#: core/models.py:795
msgid "Email domain this application can act on behalf of."
msgstr "Email domain this application can act on behalf of."
#: core/models.py:807
msgid "Application domain"
msgstr "Application domain"
#: core/models.py:808
msgid "Application domains"
msgstr "Application domains"
#: core/recording/event/notification.py:94
msgid "Your recording is ready"
msgstr "Your recording is ready"
@@ -529,18 +433,18 @@ msgstr ""
" If you have any questions or need assistance, please contact our support "
"team at %(support_email)s. "
#: meet/settings.py:167
#: meet/settings.py:163
msgid "English"
msgstr "English"
#: meet/settings.py:168
#: meet/settings.py:164
msgid "French"
msgstr "French"
#: meet/settings.py:169
#: meet/settings.py:165
msgid "Dutch"
msgstr "Dutch"
#: meet/settings.py:170
#: meet/settings.py:166
msgid "German"
msgstr "German"
+74 -167
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-13 16:21+0000\n"
"POT-Creation-Date: 2025-07-11 11:33+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: antoine.lebaud@mail.numerique.gouv.fr\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,155 +17,123 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: core/admin.py:29
#: core/admin.py:26
msgid "Personal info"
msgstr "Informations personnelles"
#: core/admin.py:42
#: core/admin.py:39
msgid "Permissions"
msgstr "Permissions"
#: core/admin.py:54
#: core/admin.py:51
msgid "Important dates"
msgstr "Dates importantes"
#: core/admin.py:128 core/admin.py:228
#: core/admin.py:147
msgid "No owner"
msgstr "Pas de propriétaire"
#: core/admin.py:131 core/admin.py:231
#: core/admin.py:150
msgid "Multiple owners"
msgstr "Plusieurs propriétaires"
#: core/admin.py:143
msgid "Resend notification to external service"
msgstr "Renvoyer la notification au service externe"
#: core/admin.py:166
#, python-format
msgid "Failed to notify for recording %(id)s"
msgstr "Échec de la notification pour lenregistrement %(id)s"
#: core/admin.py:174
#, python-format
msgid "Failed to notify for recording %(id)s: %(error)s"
msgstr "Échec de la notification pour lenregistrement %(id)s : %(error)s"
#: core/admin.py:182
#, python-format
msgid "Successfully sent notifications for %(count)s recording(s)."
msgstr "Notifications envoyées avec succès pour %(count)s enregistrement(s)."
#: core/admin.py:190
#, python-format
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s enregistrement(s) expiré(s) ignoré(s)."
#: core/admin.py:294
msgid "No scopes"
msgstr "Aucun scopes"
#: core/admin.py:296
msgid "Scopes"
msgstr "Scopes"
#: core/api/serializers.py:68
#: core/api/serializers.py:67
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Vous devez être administrateur ou propriétaire d'une salle pour y ajouter "
"des accès."
#: core/models.py:34
#: core/models.py:31
msgid "Member"
msgstr "Membre"
#: core/models.py:35
#: core/models.py:32
msgid "Administrator"
msgstr "Administrateur"
#: core/models.py:36
#: core/models.py:33
msgid "Owner"
msgstr "Propriétaire"
#: core/models.py:52
#: core/models.py:49
msgid "Initiated"
msgstr "Initié"
#: core/models.py:53
#: core/models.py:50
msgid "Active"
msgstr "Actif"
#: core/models.py:54
#: core/models.py:51
msgid "Stopped"
msgstr "Arrêté"
#: core/models.py:55
#: core/models.py:52
msgid "Saved"
msgstr "Enregistré"
#: core/models.py:56
#: core/models.py:53
msgid "Aborted"
msgstr "Abandonné"
#: core/models.py:57
#: core/models.py:54
msgid "Failed to Start"
msgstr "Échec au démarrage"
#: core/models.py:58
#: core/models.py:55
msgid "Failed to Stop"
msgstr "Échec à l'arrêt"
#: core/models.py:59
#: core/models.py:56
msgid "Notification succeeded"
msgstr "Notification réussie"
#: core/models.py:86
#: core/models.py:83
msgid "SCREEN_RECORDING"
msgstr "ENREGISTREMENT_ÉCRAN"
#: core/models.py:87
#: core/models.py:84
msgid "TRANSCRIPT"
msgstr "TRANSCRIPTION"
#: core/models.py:93
#: core/models.py:90
msgid "Public Access"
msgstr "Accès public"
#: core/models.py:94
#: core/models.py:91
msgid "Trusted Access"
msgstr "Accès de confiance"
#: core/models.py:95
#: core/models.py:92
msgid "Restricted Access"
msgstr "Accès restreint"
#: core/models.py:107
#: core/models.py:104
msgid "id"
msgstr "id"
#: core/models.py:108
#: core/models.py:105
msgid "primary key for the record as UUID"
msgstr "clé primaire pour l'enregistrement sous forme d'UUID"
#: core/models.py:114
#: core/models.py:111
msgid "created on"
msgstr "créé le"
#: core/models.py:115
#: core/models.py:112
msgid "date and time at which a record was created"
msgstr "date et heure auxquelles un enregistrement a été créé"
#: core/models.py:120
#: core/models.py:117
msgid "updated on"
msgstr "mis à jour le"
#: core/models.py:121
#: core/models.py:118
msgid "date and time at which a record was last updated"
msgstr ""
"date et heure auxquelles un enregistrement a été mis à jour pour la dernière "
"fois"
#: core/models.py:141
#: core/models.py:138
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -173,11 +141,11 @@ msgstr ""
"Entrez un sub valide. Cette valeur ne peut contenir que des lettres, des "
"chiffres et les caractères @/./+/-/_."
#: core/models.py:147
#: core/models.py:144
msgid "sub"
msgstr "sub"
#: core/models.py:149
#: core/models.py:146
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
@@ -185,55 +153,55 @@ msgstr ""
"Obligatoire. 255 caractères ou moins. Lettres, chiffres et caractères @/./"
"+/-/_ uniquement."
#: core/models.py:157
#: core/models.py:154
msgid "identity email address"
msgstr "adresse e-mail d'identité"
#: core/models.py:162
#: core/models.py:159
msgid "admin email address"
msgstr "adresse e-mail d'administrateur"
#: core/models.py:164
#: core/models.py:161
msgid "full name"
msgstr "nom complet"
#: core/models.py:166
#: core/models.py:163
msgid "short name"
msgstr "nom court"
#: core/models.py:172
#: core/models.py:169
msgid "language"
msgstr "langue"
#: core/models.py:173
#: core/models.py:170
msgid "The language in which the user wants to see the interface."
msgstr "La langue dans laquelle l'utilisateur souhaite voir l'interface."
#: core/models.py:179
#: core/models.py:176
msgid "The timezone in which the user wants to see times."
msgstr "Le fuseau horaire dans lequel l'utilisateur souhaite voir les heures."
#: core/models.py:182
#: core/models.py:179
msgid "device"
msgstr "appareil"
#: core/models.py:184
#: core/models.py:181
msgid "Whether the user is a device or a real user."
msgstr "Si l'utilisateur est un appareil ou un utilisateur réel."
#: core/models.py:187
#: core/models.py:184
msgid "staff status"
msgstr "statut du personnel"
#: core/models.py:189
#: core/models.py:186
msgid "Whether the user can log into this admin site."
msgstr "Si l'utilisateur peut se connecter à ce site d'administration."
#: core/models.py:192
#: core/models.py:189
msgid "active"
msgstr "actif"
#: core/models.py:195
#: core/models.py:192
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -241,65 +209,65 @@ msgstr ""
"Si cet utilisateur doit être traité comme actif. Désélectionnez cette option "
"au lieu de supprimer des comptes."
#: core/models.py:208
#: core/models.py:205
msgid "user"
msgstr "utilisateur"
#: core/models.py:209
#: core/models.py:206
msgid "users"
msgstr "utilisateurs"
#: core/models.py:268
#: core/models.py:265
msgid "Resource"
msgstr "Ressource"
#: core/models.py:269
#: core/models.py:266
msgid "Resources"
msgstr "Ressources"
#: core/models.py:323
#: core/models.py:320
msgid "Resource access"
msgstr "Accès aux ressources"
#: core/models.py:324
#: core/models.py:321
msgid "Resource accesses"
msgstr "Accès aux ressources"
#: core/models.py:330
#: core/models.py:327
msgid "Resource access with this User and Resource already exists."
msgstr ""
"L'accès à la ressource avec cet utilisateur et cette ressource existe déjà."
#: core/models.py:386
#: core/models.py:383
msgid "Visio room configuration"
msgstr "Configuration de la salle de visioconférence"
#: core/models.py:387
#: core/models.py:384
msgid "Values for Visio parameters to configure the room."
msgstr "Valeurs des paramètres de visioconférence pour configurer la salle."
#: core/models.py:394
#: core/models.py:391
msgid "Room PIN code"
msgstr "Code PIN de la salle"
#: core/models.py:395
#: core/models.py:392
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Code unique à n chiffres qui identifie cette salle en mode téléphonique."
#: core/models.py:401 core/models.py:555
#: core/models.py:398 core/models.py:552
msgid "Room"
msgstr "Salle"
#: core/models.py:402
#: core/models.py:399
msgid "Rooms"
msgstr "Salles"
#: core/models.py:566
#: core/models.py:563
msgid "Worker ID"
msgstr "ID du Worker"
#: core/models.py:568
#: core/models.py:565
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -307,102 +275,42 @@ msgstr ""
"Entrez un identifiant pour l'enregistrement du Worker. Cet identifiant est "
"conservé même lorsque le Worker s'arrête, permettant un suivi facile."
#: core/models.py:576
#: core/models.py:573
msgid "Recording mode"
msgstr "Mode d'enregistrement"
#: core/models.py:577
#: core/models.py:574
msgid "Defines the mode of recording being called."
msgstr "Définit le mode d'enregistrement appelé."
#: core/models.py:583
#: core/models.py:580
msgid "Recording"
msgstr "Enregistrement"
#: core/models.py:584
#: core/models.py:581
msgid "Recordings"
msgstr "Enregistrements"
#: core/models.py:692
#: core/models.py:689
msgid "Recording/user relation"
msgstr "Relation enregistrement/utilisateur"
#: core/models.py:693
#: core/models.py:690
msgid "Recording/user relations"
msgstr "Relations enregistrement/utilisateur"
#: core/models.py:699
#: core/models.py:696
msgid "This user is already in this recording."
msgstr "Cet utilisateur est déjà dans cet enregistrement."
#: core/models.py:705
#: core/models.py:702
msgid "This team is already in this recording."
msgstr "Cette équipe est déjà dans cet enregistrement."
#: core/models.py:711
#: core/models.py:708
msgid "Either user or team must be set, not both."
msgstr "Soit l'utilisateur, soit l'équipe doit être défini, pas les deux."
#: core/models.py:728
msgid "Create rooms"
msgstr "Créer des salles"
#: core/models.py:729
msgid "List rooms"
msgstr "Lister les salles"
#: core/models.py:730
msgid "Retrieve room details"
msgstr "Afficher les détails dune salle"
#: core/models.py:731
msgid "Update rooms"
msgstr "Mettre à jour les salles"
#: core/models.py:732
msgid "Delete rooms"
msgstr "Supprimer les salles"
#: core/models.py:745
msgid "Application name"
msgstr "Nom de lapplication"
#: core/models.py:746
msgid "Descriptive name for this application."
msgstr "Nom descriptif de cette application."
#: core/models.py:756
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr "Haché lors de lenregistrement. Copiez-le maintenant sil sagit dun nouveau secret."
#: core/models.py:767
msgid "Application"
msgstr "Application"
#: core/models.py:768
msgid "Applications"
msgstr "Applications"
#: core/models.py:791
msgid "Enter a valid domain"
msgstr "Saisissez un domaine valide"
#: core/models.py:794
msgid "Domain"
msgstr "Domaine"
#: core/models.py:795
msgid "Email domain this application can act on behalf of."
msgstr "Domaine de messagerie au nom duquel cette application peut agir."
#: core/models.py:807
msgid "Application domain"
msgstr "Domaine dapplication"
#: core/models.py:808
msgid "Application domains"
msgstr "Domaines dapplication"
#: core/recording/event/notification.py:94
msgid "Your recording is ready"
msgstr "Votre enregistrement est prêt"
@@ -493,8 +401,7 @@ msgid ""
" Sharing the recording via link is not yet available. Only organizers can "
"download it. "
msgstr ""
"Le partage de l'enregistrement via lien n'est pas encore disponible. Seuls "
"les organisateurs peuvent le télécharger."
"Le partage de l'enregistrement via lien n'est pas encore disponible. Seuls les organisateurs peuvent le télécharger."
#: core/templates/mail/html/screen_recording.html:206
#: core/templates/mail/text/screen_recording.txt:11
@@ -531,18 +438,18 @@ msgstr ""
" Si vous avez des questions ou besoin d'assistance, veuillez contacter notre "
"équipe d'assistance à %(support_email)s. "
#: meet/settings.py:167
#: meet/settings.py:163
msgid "English"
msgstr "Anglais"
#: meet/settings.py:168
#: meet/settings.py:164
msgid "French"
msgstr "Français"
#: meet/settings.py:169
#: meet/settings.py:165
msgid "Dutch"
msgstr "Néerlandais"
#: meet/settings.py:170
#: meet/settings.py:166
msgid "German"
msgstr "Allemand"
+74 -167
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-13 16:21+0000\n"
"POT-Creation-Date: 2025-07-11 11:33+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,152 +17,120 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: core/admin.py:29
#: core/admin.py:26
msgid "Personal info"
msgstr "Persoonlijke informatie"
#: core/admin.py:42
#: core/admin.py:39
msgid "Permissions"
msgstr "Rechten"
#: core/admin.py:54
#: core/admin.py:51
msgid "Important dates"
msgstr "Belangrijke datums"
#: core/admin.py:128 core/admin.py:228
#: core/admin.py:147
msgid "No owner"
msgstr "Geen eigenaar"
#: core/admin.py:131 core/admin.py:231
#: core/admin.py:150
msgid "Multiple owners"
msgstr "Meerdere eigenaren"
#: core/admin.py:143
msgid "Resend notification to external service"
msgstr "Melding opnieuw verzenden naar externe dienst"
#: core/admin.py:166
#, python-format
msgid "Failed to notify for recording %(id)s"
msgstr "Melding voor opname %(id)s mislukt"
#: core/admin.py:174
#, python-format
msgid "Failed to notify for recording %(id)s: %(error)s"
msgstr "Melding voor opname %(id)s mislukt: %(error)s"
#: core/admin.py:182
#, python-format
msgid "Successfully sent notifications for %(count)s recording(s)."
msgstr "Meldingen succesvol verzonden voor %(count)s opname(n)."
#: core/admin.py:190
#, python-format
msgid "Skipped %(count)s expired recording(s)."
msgstr "%(count)s verlopen opname(n) overgeslagen."
#: core/admin.py:294
msgid "No scopes"
msgstr "Geen scopes"
#: core/admin.py:296
msgid "Scopes"
msgstr "Scopes"
#: core/api/serializers.py:68
#: core/api/serializers.py:67
msgid "You must be administrator or owner of a room to add accesses to it."
msgstr ""
"Je moet beheerder of eigenaar van een ruimte zijn om toegang toe te voegen."
#: core/models.py:34
#: core/models.py:31
msgid "Member"
msgstr "Lid"
#: core/models.py:35
#: core/models.py:32
msgid "Administrator"
msgstr "Beheerder"
#: core/models.py:36
#: core/models.py:33
msgid "Owner"
msgstr "Eigenaar"
#: core/models.py:52
#: core/models.py:49
msgid "Initiated"
msgstr "Gestart"
#: core/models.py:53
#: core/models.py:50
msgid "Active"
msgstr "Actief"
#: core/models.py:54
#: core/models.py:51
msgid "Stopped"
msgstr "Gestopt"
#: core/models.py:55
#: core/models.py:52
msgid "Saved"
msgstr "Opgeslagen"
#: core/models.py:56
#: core/models.py:53
msgid "Aborted"
msgstr "Afgebroken"
#: core/models.py:57
#: core/models.py:54
msgid "Failed to Start"
msgstr "Starten mislukt"
#: core/models.py:58
#: core/models.py:55
msgid "Failed to Stop"
msgstr "Stoppen mislukt"
#: core/models.py:59
#: core/models.py:56
msgid "Notification succeeded"
msgstr "Notificatie geslaagd"
#: core/models.py:86
#: core/models.py:83
msgid "SCREEN_RECORDING"
msgstr "SCHERM_OPNAME"
#: core/models.py:87
#: core/models.py:84
msgid "TRANSCRIPT"
msgstr "TRANSCRIPT"
#: core/models.py:93
#: core/models.py:90
msgid "Public Access"
msgstr "Openbare toegang"
#: core/models.py:94
#: core/models.py:91
msgid "Trusted Access"
msgstr "Vertrouwde toegang"
#: core/models.py:95
#: core/models.py:92
msgid "Restricted Access"
msgstr "Beperkte toegang"
#: core/models.py:107
#: core/models.py:104
msgid "id"
msgstr "id"
#: core/models.py:108
#: core/models.py:105
msgid "primary key for the record as UUID"
msgstr "primaire sleutel voor het record als UUID"
#: core/models.py:114
#: core/models.py:111
msgid "created on"
msgstr "aangemaakt op"
#: core/models.py:115
#: core/models.py:112
msgid "date and time at which a record was created"
msgstr "datum en tijd waarop een record werd aangemaakt"
#: core/models.py:120
#: core/models.py:117
msgid "updated on"
msgstr "bijgewerkt op"
#: core/models.py:121
#: core/models.py:118
msgid "date and time at which a record was last updated"
msgstr "datum en tijd waarop een record voor het laatst werd bijgewerkt"
#: core/models.py:141
#: core/models.py:138
msgid ""
"Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/"
"_ characters."
@@ -170,66 +138,66 @@ msgstr ""
"Voer een geldige sub in. Deze waarde mag alleen letters, cijfers en @/./+/-/"
"_ tekens bevatten."
#: core/models.py:147
#: core/models.py:144
msgid "sub"
msgstr "sub"
#: core/models.py:149
#: core/models.py:146
msgid ""
"Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_ "
"characters only."
msgstr ""
"Vereist. 255 tekens of minder. Alleen letters, cijfers en @/./+/-/_ tekens."
#: core/models.py:157
#: core/models.py:154
msgid "identity email address"
msgstr "identiteit e-mailadres"
#: core/models.py:162
#: core/models.py:159
msgid "admin email address"
msgstr "beheerder e-mailadres"
#: core/models.py:164
#: core/models.py:161
msgid "full name"
msgstr "volledige naam"
#: core/models.py:166
#: core/models.py:163
msgid "short name"
msgstr "korte naam"
#: core/models.py:172
#: core/models.py:169
msgid "language"
msgstr "taal"
#: core/models.py:173
#: core/models.py:170
msgid "The language in which the user wants to see the interface."
msgstr "De taal waarin de gebruiker de interface wil zien."
#: core/models.py:179
#: core/models.py:176
msgid "The timezone in which the user wants to see times."
msgstr "De tijdzone waarin de gebruiker tijden wil zien."
#: core/models.py:182
#: core/models.py:179
msgid "device"
msgstr "apparaat"
#: core/models.py:184
#: core/models.py:181
msgid "Whether the user is a device or a real user."
msgstr "Of de gebruiker een apparaat is of een echte gebruiker."
#: core/models.py:187
#: core/models.py:184
msgid "staff status"
msgstr "personeelsstatus"
#: core/models.py:189
#: core/models.py:186
msgid "Whether the user can log into this admin site."
msgstr "Of de gebruiker kan inloggen op deze beheersite."
#: core/models.py:192
#: core/models.py:189
msgid "active"
msgstr "actief"
#: core/models.py:195
#: core/models.py:192
msgid ""
"Whether this user should be treated as active. Unselect this instead of "
"deleting accounts."
@@ -237,64 +205,64 @@ msgstr ""
"Of deze gebruiker als actief moet worden behandeld. Deselecteer dit in "
"plaats van accounts te verwijderen."
#: core/models.py:208
#: core/models.py:205
msgid "user"
msgstr "gebruiker"
#: core/models.py:209
#: core/models.py:206
msgid "users"
msgstr "gebruikers"
#: core/models.py:268
#: core/models.py:265
msgid "Resource"
msgstr "Bron"
#: core/models.py:269
#: core/models.py:266
msgid "Resources"
msgstr "Bronnen"
#: core/models.py:323
#: core/models.py:320
msgid "Resource access"
msgstr "Brontoegang"
#: core/models.py:324
#: core/models.py:321
msgid "Resource accesses"
msgstr "Brontoegangsrechten"
#: core/models.py:330
#: core/models.py:327
msgid "Resource access with this User and Resource already exists."
msgstr "Brontoegang met deze gebruiker en bron bestaat al."
#: core/models.py:386
#: core/models.py:383
msgid "Visio room configuration"
msgstr "Visio-ruimteconfiguratie"
#: core/models.py:387
#: core/models.py:384
msgid "Values for Visio parameters to configure the room."
msgstr "Waarden voor Visio-parameters om de ruimte te configureren."
#: core/models.py:394
#: core/models.py:391
msgid "Room PIN code"
msgstr "Pincode van de kamer"
#: core/models.py:395
#: core/models.py:392
msgid "Unique n-digit code that identifies this room in telephony mode."
msgstr ""
"Unieke n-cijferige code die deze kamer identificeert in telefonie-modus."
#: core/models.py:401 core/models.py:555
#: core/models.py:398 core/models.py:552
msgid "Room"
msgstr "Ruimte"
#: core/models.py:402
#: core/models.py:399
msgid "Rooms"
msgstr "Ruimtes"
#: core/models.py:566
#: core/models.py:563
msgid "Worker ID"
msgstr "Worker ID"
#: core/models.py:568
#: core/models.py:565
msgid ""
"Enter an identifier for the worker recording.This ID is retained even when "
"the worker stops, allowing for easy tracking."
@@ -302,102 +270,42 @@ msgstr ""
"Voer een identificatie in voor de worker-opname. Deze ID blijft behouden, "
"zelfs wanneer de worker stopt, waardoor eenvoudige tracking mogelijk is."
#: core/models.py:576
#: core/models.py:573
msgid "Recording mode"
msgstr "Opnamemodus"
#: core/models.py:577
#: core/models.py:574
msgid "Defines the mode of recording being called."
msgstr "Definieert de modus van opname die wordt aangeroepen."
#: core/models.py:583
#: core/models.py:580
msgid "Recording"
msgstr "Opname"
#: core/models.py:584
#: core/models.py:581
msgid "Recordings"
msgstr "Opnames"
#: core/models.py:692
#: core/models.py:689
msgid "Recording/user relation"
msgstr "Opname/gebruiker-relatie"
#: core/models.py:693
#: core/models.py:690
msgid "Recording/user relations"
msgstr "Opname/gebruiker-relaties"
#: core/models.py:699
#: core/models.py:696
msgid "This user is already in this recording."
msgstr "Deze gebruiker is al in deze opname."
#: core/models.py:705
#: core/models.py:702
msgid "This team is already in this recording."
msgstr "Dit team is al in deze opname."
#: core/models.py:711
#: core/models.py:708
msgid "Either user or team must be set, not both."
msgstr "Ofwel gebruiker of team moet worden ingesteld, niet beide."
#: core/models.py:728
msgid "Create rooms"
msgstr "Ruimtes aanmaken"
#: core/models.py:729
msgid "List rooms"
msgstr "Ruimtes weergeven"
#: core/models.py:730
msgid "Retrieve room details"
msgstr "Details van een ruimte ophalen"
#: core/models.py:731
msgid "Update rooms"
msgstr "Ruimtes bijwerken"
#: core/models.py:732
msgid "Delete rooms"
msgstr "Ruimtes verwijderen"
#: core/models.py:745
msgid "Application name"
msgstr "Naam van de applicatie"
#: core/models.py:746
msgid "Descriptive name for this application."
msgstr "Beschrijvende naam voor deze applicatie."
#: core/models.py:756
msgid "Hashed on Save. Copy it now if this is a new secret."
msgstr "Wordt gehasht bij het opslaan. Kopieer het nu als dit een nieuw geheim is."
#: core/models.py:767
msgid "Application"
msgstr "Applicatie"
#: core/models.py:768
msgid "Applications"
msgstr "Applicaties"
#: core/models.py:791
msgid "Enter a valid domain"
msgstr "Voer een geldig domein in"
#: core/models.py:794
msgid "Domain"
msgstr "Domein"
#: core/models.py:795
msgid "Email domain this application can act on behalf of."
msgstr "E-maildomein namens welke deze applicatie kan handelen."
#: core/models.py:807
msgid "Application domain"
msgstr "Applicatiedomein"
#: core/models.py:808
msgid "Application domains"
msgstr "Applicatiedomeinen"
#: core/recording/event/notification.py:94
msgid "Your recording is ready"
msgstr "Je opname is klaar"
@@ -488,8 +396,7 @@ msgid ""
" Sharing the recording via link is not yet available. Only organizers can "
"download it. "
msgstr ""
"Het delen van de opname via een link is nog niet beschikbaar. Alleen "
"organisatoren kunnen deze downloaden."
"Het delen van de opname via een link is nog niet beschikbaar. Alleen organisatoren kunnen deze downloaden."
#: core/templates/mail/html/screen_recording.html:206
#: core/templates/mail/text/screen_recording.txt:11
@@ -526,18 +433,18 @@ msgstr ""
" Als je vragen hebt of hulp nodig hebt, neem dan contact op met ons support "
"team via %(support_email)s. "
#: meet/settings.py:167
#: meet/settings.py:163
msgid "English"
msgstr "Engels"
#: meet/settings.py:168
#: meet/settings.py:164
msgid "French"
msgstr "Frans"
#: meet/settings.py:169
#: meet/settings.py:165
msgid "Dutch"
msgstr "Nederlands"
#: meet/settings.py:170
#: meet/settings.py:166
msgid "German"
msgstr "Duits"
-5
View File
@@ -326,11 +326,6 @@ class Base(Configuration):
"is_silent_login_enabled": values.BooleanValue(
True, environ_name="FRONTEND_IS_SILENT_LOGIN_ENABLED", environ_prefix=None
),
"idle_disconnect_warning_delay": values.PositiveIntegerValue(
None,
environ_name="FRONTEND_IDLE_DISCONNECT_WARNING_DELAY",
environ_prefix=None,
),
"feedback": values.DictValue(
{}, environ_name="FRONTEND_FEEDBACK", environ_prefix=None
),
+31 -31
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "meet"
version = "0.1.42"
version = "0.1.40"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -25,38 +25,38 @@ license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"boto3==1.40.69",
"Brotli==1.2.0",
"brevo-python==1.2.0",
"boto3==1.38.42",
"Brotli==1.1.0",
"brevo-python==1.1.2",
"celery[redis]==5.5.3",
"django-configurations==2.5.1",
"django-cors-headers==4.9.0",
"django-countries==8.0.0",
"django-lasuite[all]==0.0.17",
"django-cors-headers==4.7.0",
"django-countries==7.6.1",
"django-lasuite[all]==0.0.10",
"django-parler==2.3",
"redis==5.2.1",
"django-redis==6.0.0",
"django-storages[s3]==1.14.6",
"django-timezone-field>=5.1",
"django==5.2.8",
"djangorestframework==3.16.1",
"drf_spectacular==0.29.0",
"django==5.2.7",
"djangorestframework==3.16.0",
"drf_spectacular==0.28.0",
"dockerflow==2024.4.2",
"easy_thumbnails==2.10.1",
"easy_thumbnails==2.10",
"factory_boy==3.3.3",
"gunicorn==23.0.0",
"jsonschema==4.25.1",
"markdown==3.10",
"nested-multipart-parser==1.6.0",
"psycopg[binary]==3.2.12",
"jsonschema==4.24.0",
"markdown==3.8.2",
"nested-multipart-parser==1.5.0",
"psycopg[binary]==3.2.9",
"PyJWT==2.10.1",
"python-frontmatter==1.1.0",
"requests==2.32.5",
"sentry-sdk==2.43.0",
"whitenoise==6.11.0",
"requests==2.32.4",
"sentry-sdk==2.30.0",
"whitenoise==6.9.0",
"mozilla-django-oidc==4.0.1",
"livekit-api==1.0.7",
"aiohttp==3.13.2",
"livekit-api==1.0.3",
"aiohttp==3.12.13",
]
[project.urls]
@@ -68,21 +68,21 @@ dependencies = [
[project.optional-dependencies]
dev = [
"django-extensions==4.1",
"drf-spectacular-sidecar==2025.10.1",
"freezegun==1.5.5",
"drf-spectacular-sidecar==2025.6.1",
"freezegun==1.5.2",
"ipdb==0.13.13",
"ipython==9.7.0",
"pyfakefs==5.10.2",
"ipython==9.3.0",
"pyfakefs==5.9.1",
"pylint-django==2.6.1",
"pylint<4.0.0",
"pytest-cov==7.0.0",
"pylint==3.3.7",
"pytest-cov==6.2.1",
"pytest-django==4.11.1",
"pytest==9.0.0",
"pytest==8.4.1",
"pytest-icdiff==0.9",
"pytest-xdist==3.8.0",
"responses==0.25.8",
"ruff==0.14.4",
"types-requests==2.32.4.20250913",
"pytest-xdist==3.7.0",
"responses==0.25.7",
"ruff==0.12.0",
"types-requests==2.32.4.20250611",
]
[tool.setuptools]
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "meet",
"version": "0.1.42",
"version": "0.1.40",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "0.1.42",
"version": "0.1.40",
"dependencies": {
"@livekit/components-react": "2.9.13",
"@livekit/components-styles": "1.1.6",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "0.1.42",
"version": "0.1.40",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
-1
View File
@@ -25,7 +25,6 @@ export interface ApiConfig {
custom_css_url?: string
use_french_gov_footer?: boolean
use_proconnect_button?: boolean
idle_disconnect_warning_delay?: number
recording?: {
is_enabled?: boolean
available_modes?: RecordingMode[]
@@ -71,11 +71,7 @@ export const LaterMeetingDialog = ({
aria-label={t('copyUrl')}
tooltip={t('copyUrl')}
>
{isRoomUrlCopied ? (
<RiCheckLine aria-hidden="true" />
) : (
<RiFileCopyLine aria-hidden="true" />
)}
{isRoomUrlCopied ? <RiCheckLine /> : <RiFileCopyLine />}
</Button>
)}
</div>
@@ -107,18 +103,13 @@ export const LaterMeetingDialog = ({
>
{isCopied ? (
<>
<RiCheckLine
size={18}
style={{ marginRight: '8px' }}
aria-hidden="true"
/>
<RiCheckLine size={18} style={{ marginRight: '8px' }} />
{t('copied')}
</>
) : (
<>
<RiFileCopyLine
style={{ marginRight: '6px', minWidth: '18px' }}
aria-hidden="true"
/>
{t('copy')}
</>
@@ -140,11 +131,7 @@ export const LaterMeetingDialog = ({
>
{isCopied ? (
<>
<RiCheckLine
size={18}
style={{ marginRight: '8px' }}
aria-hidden="true"
/>
<RiCheckLine size={18} style={{ marginRight: '8px' }} />
{t('copied')}
</>
) : (
@@ -152,7 +139,6 @@ export const LaterMeetingDialog = ({
<RiFileCopyLine
size={18}
style={{ marginRight: '8px', minWidth: '18px' }}
aria-hidden="true"
/>
{isHovered ? (
t('copy')
@@ -187,7 +173,6 @@ export const LaterMeetingDialog = ({
className={css({
fill: 'primary.500',
})}
aria-hidden="true"
/>
</div>
<Text variant="sm" style={{ marginTop: '1rem' }}>
@@ -115,11 +115,7 @@ export const InviteDialog = (props: Omit<DialogProps, 'title'>) => {
aria-label={t('copyUrl')}
tooltip={t('copyUrl')}
>
{isRoomUrlCopied ? (
<RiCheckLine aria-hidden="true" />
) : (
<RiFileCopyLine aria-hidden="true" />
)}
{isRoomUrlCopied ? <RiCheckLine /> : <RiFileCopyLine />}
</Button>
)}
</div>
@@ -151,18 +147,13 @@ export const InviteDialog = (props: Omit<DialogProps, 'title'>) => {
>
{isCopied ? (
<>
<RiCheckLine
size={18}
style={{ marginRight: '8px' }}
aria-hidden="true"
/>
<RiCheckLine size={18} style={{ marginRight: '8px' }} />
{t('copied')}
</>
) : (
<>
<RiFileCopyLine
style={{ marginRight: '6px', minWidth: '18px' }}
aria-hidden="true"
/>
{t('copy')}
</>
@@ -71,6 +71,7 @@ export const FullScreenShareWarning = ({
<div
className={css({
position: 'absolute',
zIndex: '1000',
height: '100%',
width: '100%',
})}
@@ -80,6 +81,7 @@ export const FullScreenShareWarning = ({
<div
className={css({
position: 'absolute',
zIndex: '1000',
height: '100%',
width: '100%',
backgroundColor: 'rgba(22, 22, 34, 0.9)',
@@ -78,20 +78,12 @@ export const Info = () => {
>
{isCopied ? (
<>
<RiCheckLine
size={24}
style={{ marginRight: '6px' }}
aria-hidden="true"
/>
<RiCheckLine size={24} style={{ marginRight: '6px' }} />
{t('roomInformation.button.copied')}
</>
) : (
<>
<RiFileCopyLine
size={24}
style={{ marginRight: '6px' }}
aria-hidden="true"
/>
<RiFileCopyLine size={24} style={{ marginRight: '6px' }} />
{t('roomInformation.button.copy')}
</>
)}
@@ -1,102 +0,0 @@
import { Button, Dialog, H, P } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { css } from '@/styled-system/css'
import { useSnapshot } from 'valtio'
import { connectionObserverStore } from '@/stores/connectionObserver'
import { HStack } from '@/styled-system/jsx'
import { useEffect, useState } from 'react'
import { navigateTo } from '@/navigation/navigateTo'
import humanizeDuration from 'humanize-duration'
import i18n from 'i18next'
const IDLE_DISCONNECT_TIMEOUT_MS = 120000 // 2 minutes
export const IsIdleDisconnectModal = () => {
const connectionObserverSnap = useSnapshot(connectionObserverStore)
const [timeRemaining, setTimeRemaining] = useState(IDLE_DISCONNECT_TIMEOUT_MS)
const { t } = useTranslation('rooms', { keyPrefix: 'isIdleDisconnectModal' })
useEffect(() => {
if (connectionObserverSnap.isIdleDisconnectModalOpen) {
setTimeRemaining(IDLE_DISCONNECT_TIMEOUT_MS)
const interval = setInterval(() => {
setTimeRemaining((prev) => {
if (prev <= 1000) {
clearInterval(interval)
connectionObserverStore.isIdleDisconnectModalOpen = false
navigateTo('feedback', { duplicateIdentity: false })
return 0
}
return prev - 1000
})
}, 1000)
return () => clearInterval(interval)
}
}, [connectionObserverSnap.isIdleDisconnectModalOpen])
const minutes = Math.floor(timeRemaining / 1000 / 60)
const seconds = (timeRemaining / 1000) % 60
const formattedTime = `${minutes}:${seconds.toString().padStart(2, '0')}`
return (
<Dialog
isOpen={connectionObserverSnap.isIdleDisconnectModalOpen}
role="alertdialog"
type="alert"
aria-label={t('title')}
onClose={() => {
connectionObserverStore.isIdleDisconnectModalOpen = false
}}
>
{({ close }) => {
return (
<div>
<div
className={css({
height: '50px',
width: '50px',
backgroundColor: 'blue.100',
borderRadius: '25px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontWeight: '500',
color: 'blue.800',
margin: 'auto',
})}
>
{formattedTime}
</div>
<H lvl={2} centered>
{t('title')}
</H>
<P>
{t('body', {
duration: humanizeDuration(IDLE_DISCONNECT_TIMEOUT_MS, {
language: i18n.language,
}),
})}
</P>
<P>{t('settings')}</P>
<HStack marginTop="2rem">
<Button
onPress={() => {
connectionObserverStore.isIdleDisconnectModalOpen = false
navigateTo('feedback', { duplicateIdentity: false })
}}
size="sm"
variant="secondary"
>
{t('leaveButton')}
</Button>
<Button onPress={close} size="sm" variant="primary">
{t('stayButton')}
</Button>
</HStack>
</div>
)
}}
</Dialog>
)
}
@@ -1,72 +1,15 @@
import {
useRemoteParticipants,
useRoomContext,
} from '@livekit/components-react'
import { useRoomContext } from '@livekit/components-react'
import { useEffect, useRef } from 'react'
import { DisconnectReason, RoomEvent } from 'livekit-client'
import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalyticsEnabled'
import posthog from 'posthog-js'
import { connectionObserverStore } from '@/stores/connectionObserver'
import { useConfig } from '@/api/useConfig'
import { userPreferencesStore } from '@/stores/userPreferences'
import { useSnapshot } from 'valtio'
export const useConnectionObserver = () => {
const room = useRoomContext()
const connectionStartTimeRef = useRef<number | null>(null)
const { data } = useConfig()
const isAnalyticsEnabled = useIsAnalyticsEnabled()
const userPreferencesSnap = useSnapshot(userPreferencesStore)
const idleDisconnectModalTimeoutRef = useRef<ReturnType<
typeof setTimeout
> | null>(null)
const remoteParticipants = useRemoteParticipants({
updateOnlyOn: [
RoomEvent.ParticipantConnected,
RoomEvent.ParticipantDisconnected,
],
})
useEffect(() => {
// Always clear existing timer on dependency change
if (idleDisconnectModalTimeoutRef.current) {
clearTimeout(idleDisconnectModalTimeoutRef.current)
idleDisconnectModalTimeoutRef.current = null
}
const isEnabled = userPreferencesSnap.is_idle_disconnect_modal_enabled
const delay = data?.idle_disconnect_warning_delay
// Disabled or invalid delay: ensure modal is closed
if (!isEnabled || !delay) {
connectionObserverStore.isIdleDisconnectModalOpen = false
return
}
if (remoteParticipants.length === 0) {
idleDisconnectModalTimeoutRef.current = setTimeout(() => {
connectionObserverStore.isIdleDisconnectModalOpen = true
}, delay)
} else {
connectionObserverStore.isIdleDisconnectModalOpen = false
}
return () => {
if (idleDisconnectModalTimeoutRef.current) {
clearTimeout(idleDisconnectModalTimeoutRef.current)
idleDisconnectModalTimeoutRef.current = null
}
}
}, [
remoteParticipants.length,
data?.idle_disconnect_warning_delay,
userPreferencesSnap.is_idle_disconnect_modal_enabled,
])
useEffect(() => {
if (!isAnalyticsEnabled) return
@@ -36,7 +36,6 @@ import { useSubtitles } from '@/features/subtitle/hooks/useSubtitles'
import { Subtitles } from '@/features/subtitle/component/Subtitles'
import { CarouselLayout } from '../components/layout/CarouselLayout'
import { GridLayout } from '../components/layout/GridLayout'
import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal'
const LayoutWrapper = styled(
'div',
@@ -192,7 +191,6 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
isOpen={isShareErrorVisible}
onClose={() => setIsShareErrorVisible(false)}
/>
<IsIdleDisconnectModal />
<div
// todo - extract these magic values into constant
style={{
@@ -2,8 +2,6 @@ import { Field, H } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { useLanguageLabels } from '@/i18n/useLanguageLabels'
import { TabPanel, TabPanelProps } from '@/primitives/Tabs'
import { userPreferencesStore } from '@/stores/userPreferences'
import { useSnapshot } from 'valtio'
export type GeneralTabProps = Pick<TabPanelProps, 'id'>
@@ -11,8 +9,6 @@ export const GeneralTab = ({ id }: GeneralTabProps) => {
const { t, i18n } = useTranslation('settings')
const { languagesList, currentLanguage } = useLanguageLabels()
const userPreferencesSnap = useSnapshot(userPreferencesStore)
return (
<TabPanel padding={'md'} flex id={id}>
<H lvl={2}>{t('language.heading')}</H>
@@ -25,20 +21,6 @@ export const GeneralTab = ({ id }: GeneralTabProps) => {
i18n.changeLanguage(lang as string)
}}
/>
<H lvl={2}>{t('preferences.title')}</H>
<Field
type="switch"
label={t('preferences.idleDisconnectModal.label')}
description={t('preferences.idleDisconnectModal.description')}
isSelected={userPreferencesSnap.is_idle_disconnect_modal_enabled}
onChange={(value) =>
(userPreferencesStore.is_idle_disconnect_modal_enabled = value)
}
wrapperProps={{
noMargin: true,
fullWidth: true,
}}
/>
</TabPanel>
)
}
@@ -1,5 +1,5 @@
import { TabPanel, TabPanelProps } from '@/primitives/Tabs'
import { Field, H } from '@/primitives'
import { H, Switch } from '@/primitives'
import { css } from '@/styled-system/css'
import { useTranslation } from 'react-i18next'
import { useSnapshot } from 'valtio'
@@ -23,19 +23,15 @@ export const NotificationsTab = ({ id }: NotificationsTabProps) => {
{Array.from(notificationsSnap.soundNotifications).map(
([key, value]) => (
<li key={key}>
<Field
type="switch"
aria-label={`${t(`actions.${value ? 'disable' : 'enable'}`)} ${t('label')} "${t(`items.${key}.label`)}"`}
label={t(`items.${key}.label`)}
<Switch
aria-label={`${t(`actions.${value ? 'disable' : 'enable'}`)} ${t('label')} "${t(`items.${key}`)}"`}
isSelected={value}
onChange={(v) => {
notificationsStore.soundNotifications.set(key, v)
}}
wrapperProps={{
noMargin: true,
fullWidth: true,
}}
/>
>
{t(`items.${key}`)}
</Switch>
</li>
)
)}
-7
View File
@@ -149,13 +149,6 @@
"newTab": "Neues Fenster"
}
},
"isIdleDisconnectModal": {
"title": "Bist du noch da?",
"body": "Du bist der einzige Teilnehmer. Dieses Gespräch endet in {{duration}}. Möchtest du das Gespräch fortsetzen?",
"settings": "Um diese Nachricht nicht mehr zu sehen, gehe zu Einstellungen > Allgemein.",
"stayButton": "Gespräch fortsetzen",
"leaveButton": "Jetzt verlassen"
},
"controls": {
"microphone": "Mikrofon",
"camera": "Kamera",
+3 -16
View File
@@ -7,13 +7,6 @@
"authentication": "Authentifizierung",
"nameError": "Ihr Name darf nicht leer sein"
},
"preferences": {
"title": "Einstellungen",
"idleDisconnectModal": {
"label": "Anrufe ohne Teilnehmer verlassen",
"description": "Verlässt automatisch einen Anruf nach einigen Minuten, wenn kein anderer Teilnehmer beitritt"
}
},
"audio": {
"microphone": {
"heading": "Mikrofon",
@@ -76,15 +69,9 @@
"enable": "Aktivieren"
},
"items": {
"participantJoined": {
"label": "Teilnehmer beigetreten"
},
"handRaised": {
"label": "Hand gehoben"
},
"messageReceived": {
"label": "Nachricht erhalten"
}
"participantJoined": "Teilnehmer beigetreten",
"handRaised": "Hand gehoben",
"messageReceived": "Nachricht erhalten"
}
},
"dialog": {
-7
View File
@@ -149,13 +149,6 @@
"newTab": "New window"
}
},
"isIdleDisconnectModal": {
"title": "Are you still there?",
"body": "You are the only participant. This call will end in {{duration}}. Would you like to continue the call?",
"settings": "To stop seeing this message, go to Settings > General.",
"stayButton": "Continue the call",
"leaveButton": "Leave now"
},
"controls": {
"microphone": "Microphone",
"camera": "Camera",
+3 -16
View File
@@ -7,13 +7,6 @@
"authentication": "Authentication",
"nameError": "Your name cannot be empty"
},
"preferences": {
"title": "Preferences",
"idleDisconnectModal": {
"label": "Leave calls with no participants",
"description": "Automatically leaves a call after a few minutes if no other participant joins"
}
},
"audio": {
"microphone": {
"heading": "Microphone",
@@ -76,15 +69,9 @@
"enable": "Enable"
},
"items": {
"participantJoined": {
"label": "Participant joined"
},
"handRaised": {
"label": "Hand raised"
},
"messageReceived": {
"label": "Message received"
}
"participantJoined": "Participant joined",
"handRaised": "Hand raised",
"messageReceived": "Message received"
}
},
"dialog": {
-7
View File
@@ -149,13 +149,6 @@
"newTab": "Nouvelle fenêtre"
}
},
"isIdleDisconnectModal": {
"title": "Êtes-vous toujours là ?",
"body": "Vous êtes le seul participant. Cet appel va donc se terminer dans {{duration}}. Voulez-vous poursuivre l'appel ?",
"settings": "Pour ne plus voir ce message, accèder à Paramètres > Général.",
"stayButton": "Poursuivre l'appel",
"leaveButton": "Quitter maintenant"
},
"controls": {
"microphone": "Microphone",
"camera": "Camera",
+3 -16
View File
@@ -7,13 +7,6 @@
"authentication": "Authentification",
"nameError": "Votre Nom ne peut pas être vide"
},
"preferences": {
"title": "Préférences",
"idleDisconnectModal": {
"label": "Quitter les appels sans autre participant",
"description": "Vous fait quitter un appel au bout de quelques minutes si aucun autre participant ne vous rejoint"
}
},
"audio": {
"microphone": {
"heading": "Micro",
@@ -76,15 +69,9 @@
"enable": "Activer"
},
"items": {
"participantJoined": {
"label": "Un nouveau participant"
},
"handRaised": {
"label": "Une main levée"
},
"messageReceived": {
"label": "Un message reçu"
}
"participantJoined": "Un nouveau participant",
"handRaised": "Une main levée",
"messageReceived": "Un message reçu"
}
},
"dialog": {
-7
View File
@@ -149,13 +149,6 @@
"newTab": "Nieuw venster"
}
},
"isIdleDisconnectModal": {
"title": "Ben je er nog?",
"body": "Je bent de enige deelnemer. Dit gesprek wordt over {{duration}} beëindigd. Wil je het gesprek voortzetten?",
"settings": "Om dit bericht niet meer te zien, ga naar Instellingen > Algemeen.",
"stayButton": "Gesprek voortzetten",
"leaveButton": "Nu verlaten"
},
"controls": {
"microphone": "Microfoon",
"camera": "Camera",
+3 -16
View File
@@ -7,13 +7,6 @@
"authentication": "Authenticatie",
"nameError": "Uw naam mag niet leeg zijn"
},
"preferences": {
"title": "Voorkeuren",
"idleDisconnectModal": {
"label": "Verlaat oproepen zonder deelnemers",
"description": "Verlaat automatisch een oproep na een paar minuten als geen andere deelnemer meedoet"
}
},
"audio": {
"microphone": {
"heading": "Microfoon",
@@ -76,15 +69,9 @@
"enable": "Inschakelen"
},
"items": {
"participantJoined": {
"label": "Deelnemer is toegevoegd"
},
"handRaised": {
"label": "Hand opgestoken"
},
"messageReceived": {
"label": "Bericht ontvangen"
}
"participantJoined": "Deelnemer is toegevoegd",
"handRaised": "Hand opgestoken",
"messageReceived": "Bericht ontvangen"
}
},
"dialog": {
-4
View File
@@ -28,10 +28,6 @@ const box = cva({
width: '30rem',
maxWidth: '100%',
},
alert: {
width: '24rem',
maxWidth: '100%',
},
},
variant: {
light: {
+2 -7
View File
@@ -80,7 +80,7 @@ export type DialogProps = RACDialogProps & {
* after user interaction
*/
onOpenChange?: (isOpen: boolean) => void
type?: 'flex' | 'alert'
type?: 'flex'
innerRef?: MutableRefObject<HTMLDivElement | null>
size?: 'full' | 'large'
}
@@ -96,12 +96,7 @@ export const Dialog = ({
...dialogProps
}: DialogProps) => {
const isAlert = dialogProps['role'] === 'alertdialog'
const boxType =
dialogProps['type'] === 'alert'
? 'alert'
: dialogProps['type'] !== 'flex'
? 'dialog'
: undefined
const boxType = dialogProps['type'] !== 'flex' ? 'dialog' : undefined
return (
<StyledModalOverlay
isKeyboardDismissDisabled={isAlert}
+1 -3
View File
@@ -259,7 +259,6 @@ export const Field = <T extends object>({
}
if (type === 'switch') {
const SWITCH_COMPONENT_WIDTH = '41px'
return (
<FieldWrapper {...props.wrapperProps}>
<div
@@ -274,11 +273,10 @@ export const Field = <T extends object>({
{description && (
<Text
variant="note"
wrap={'balance'}
wrap={'pretty'}
className={css({
textStyle: 'sm',
marginBottom: '0.5rem',
marginRight: SWITCH_COMPONENT_WIDTH,
})}
>
{description}
@@ -1,9 +0,0 @@
import { proxy } from 'valtio'
type State = {
isIdleDisconnectModalOpen: boolean
}
export const connectionObserverStore = proxy<State>({
isIdleDisconnectModalOpen: false,
})
@@ -1,38 +0,0 @@
import { proxy } from 'valtio'
import { subscribe } from 'valtio/index'
import { STORAGE_KEYS } from '@/utils/storageKeys'
type State = {
is_idle_disconnect_modal_enabled: boolean
}
const DEFAULT_STATE = {
is_idle_disconnect_modal_enabled: true,
}
function getUserPreferencesState(): State {
try {
const stored = localStorage.getItem(STORAGE_KEYS.USER_PREFERENCES)
if (!stored) return DEFAULT_STATE
const parsed = JSON.parse(stored)
return {
...DEFAULT_STATE,
...parsed,
}
} catch (error: unknown) {
console.error(
'[UserPreferencesStore] Failed to parse stored settings:',
error
)
return DEFAULT_STATE
}
}
export const userPreferencesStore = proxy<State>(getUserPreferencesState())
subscribe(userPreferencesStore, () => {
localStorage.setItem(
STORAGE_KEYS.USER_PREFERENCES,
JSON.stringify(userPreferencesStore)
)
})
-1
View File
@@ -3,5 +3,4 @@
*/
export const STORAGE_KEYS = {
NOTIFICATIONS: 'app_notification_settings',
USER_PREFERENCES: 'app_user_preferences',
} as const
@@ -57,7 +57,6 @@ backend:
FRONTEND_TRANSCRIPT: "{'form_beta_users': 'https://grist.numerique.gouv.fr/o/docs/forms/3fFfvJoTBEQ6ZiMi8zsQwX/17'}"
FRONTEND_FEEDBACK: "{'url': 'https://grist.numerique.gouv.fr/o/docs/cbMv4G7pLY3Z/USER-RESEARCH-or-LA-SUITE/f/26'}"
FRONTEND_MANIFEST_LINK: "https://docs.numerique.gouv.fr/docs/1ef86abf-f7e0-46ce-b6c7-8be8b8af4c3d/"
FRONTEND_IDLE_DISCONNECT_WARNING_DELAY: 9000
AWS_S3_ENDPOINT_URL: http://minio.meet.svc.cluster.local:9000
AWS_S3_ACCESS_KEY_ID: meet
AWS_S3_SECRET_ACCESS_KEY: password
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: v2
type: application
name: meet
version: 0.0.15
version: 0.0.14
@@ -1,15 +0,0 @@
{{- /* Normalize extraObjects to a list, easier to loop over */ -}}
{{- $extraObjects := .Values.extraManifests | default (list) -}}
{{- if kindIs "map" $extraObjects -}}
{{- $extraObjects = values $extraObjects -}}
{{- end -}}
{{- range $extraObjects }}
---
{{- if kindIs "map" . }}
{{- tpl (toYaml .) $ | nindent 0 }}
{{- else if kindIs "string" . }}
{{- tpl . $ | nindent 0 }}
{{- end }}
{{- end }}
-30
View File
@@ -788,21 +788,9 @@ agents:
## @extra agents.envVars.FROM_CONFIGMAP.configMapKeyRef.key Key within a ConfigMap when configuring env vars from a ConfigMap
## @extra agents.envVars.FROM_SECRET.secretKeyRef.name Name of a Secret when configuring env vars from a Secret
## @extra agents.envVars.FROM_SECRET.secretKeyRef.key Key within a Secret when configuring env vars from a Secret
## @extra agents.envVars.DEEPGRAM_STT_MODEL Deepgram model to use for speech-to-text (default: nova-3)
## @extra agents.envVars.DEEPGRAM_STT_LANGUAGE Language code for transcription or 'multi' for automatic multilingual support with real-time code-switching (default: multi, supports: en, es, fr, de, hi, ru, pt, ja, it, nl)
## @skip agents.envVars
envVars:
<<: *commonEnvVars
# Deepgram Speech-to-Text configuration for real-time streaming
# Only 'model' and 'language' parameters are supported by the LiveKit plugin
#
# DEEPGRAM_STT_MODEL: "nova-3" # Model selection (default)
# DEEPGRAM_STT_LANGUAGE: "multi" # Multilingual mode with auto-detection (default)
# DEEPGRAM_STT_LANGUAGE: "fr" # Force French only
#
# Note: Advanced features (diarization, smart_format, punctuate, detect_language)
# are NOT supported by the LiveKit Deepgram plugin in streaming mode.
# Use language="multi" for automatic multilingual support (10 languages).
## @param agents.podAnnotations Annotations to add to the agents Pod
podAnnotations: {}
@@ -834,21 +822,3 @@ agents:
## @param agents.pdb.enabled Enable pdb on agents
pdb:
enabled: false
## @section Extra Manifests
## @param extraManifests Extra Kubernetes manifests to deploy
## @extra extraManifests[].apiVersion API version of the resource
## @extra extraManifests[].kind Kind of the resource (Deployment, Service, ConfigMap, etc.)
## @extra extraManifests[].metadata Resource metadata
## @extra extraManifests[].spec Resource specification
extraManifests: []
# - apiVersion: v1
# kind: ConfigMap
# metadata:
# name: my-custom-config
# data:
# config.json: |
# {
# "feature": "enabled"
# }
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mail_mjml",
"version": "0.1.42",
"version": "0.1.40",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mail_mjml",
"version": "0.1.42",
"version": "0.1.40",
"license": "MIT",
"dependencies": {
"@html-to/text-cli": "0.5.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "0.1.42",
"version": "0.1.40",
"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.42",
"version": "0.1.40",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sdk",
"version": "0.1.42",
"version": "0.1.40",
"license": "ISC",
"workspaces": [
"./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "0.1.42",
"version": "0.1.40",
"author": "",
"license": "ISC",
"description": "",
+7 -7
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "0.1.42"
version = "0.1.40"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",
@@ -9,17 +9,17 @@ dependencies = [
"pydantic-settings>=2.1.0",
"celery==5.5.3",
"redis==5.2.1",
"minio==7.2.18",
"minio==7.2.15",
"mutagen==1.47.0",
"openai==2.7.1",
"posthog==6.9.1",
"requests==2.32.5",
"sentry-sdk[fastapi, celery]==2.43.0",
"openai==1.91.0",
"posthog==6.0.3",
"requests==2.32.4",
"sentry-sdk[fastapi, celery]==2.30.0",
]
[project.optional-dependencies]
dev = [
"ruff==0.14.4",
"ruff==0.12.0",
]
[build-system]
+1 -1
View File
@@ -123,7 +123,7 @@ class MetadataManager:
logger.error("Invalid number of arguments to enable metadata manager.")
return
_, filename, email, _, received_at, *_ = task_args
filename, email, _, received_at, *_ = task_args
initial_metadata = {
**initial_metadata,
+13 -29
View File
@@ -1,6 +1,6 @@
"""Celery workers."""
# ruff: noqa: PLR0913, PLR0915
# ruff: noqa: PLR0913
import json
import os
@@ -233,16 +233,11 @@ def process_audio_transcribe_summarize_v2(
3. Sends the results via webhook
"""
logger.info(
"Notification received | Owner: %s | Room: %s",
owner_id,
room,
)
logger.info("Notification received")
logger.debug("filename: %s", filename)
task_id = self.request.id
logger.info("Download recording | Filename: %s", filename)
minio_client = Minio(
settings.aws_s3_endpoint_url,
access_key=settings.aws_s3_access_key_id,
@@ -283,9 +278,7 @@ def process_audio_transcribe_summarize_v2(
)
try:
logger.info(
"Querying transcription for %s seconds of audio …", audio_file.info.length
)
logger.info("Querying transcription …")
transcription_start_time = time.time()
with open(temp_file_path, "rb") as audio_file:
transcription = whisperx_client.audio.transcriptions.create(
@@ -293,13 +286,15 @@ def process_audio_transcribe_summarize_v2(
file=audio_file,
language=settings.whisperx_default_language,
)
transcription_time = round(time.time() - transcription_start_time, 2)
metadata_manager.track(
task_id,
{"transcription_time": transcription_time},
{
"transcription_time": round(
time.time() - transcription_start_time, 2
)
},
)
logger.info("Transcription received in %s seconds.", transcription_time)
logger.info("Transcription received.")
logger.debug("Transcription: \n %s", transcription)
finally:
if os.path.exists(temp_file_path):
@@ -334,19 +329,8 @@ def process_audio_transcribe_summarize_v2(
response = post_with_retries(settings.webhook_url, data)
try:
response_data = response.json()
document_id = response_data.get("id", "N/A")
except (json.JSONDecodeError, AttributeError):
document_id = "Unable to parse response"
response_data = response.text
logger.info(
"Webhook success | Document %s submitted (HTTP %s)",
document_id,
response.status_code,
)
logger.debug("Full response: %s", response_data)
logger.info("Webhook submitted successfully. Status: %s", response.status_code)
logger.debug("Response body: %s", response.text)
metadata_manager.capture(task_id, settings.posthog_event_success)
@@ -360,7 +344,7 @@ def process_audio_transcribe_summarize_v2(
queue=settings.summarize_queue,
)
else:
logger.info("Summary generation not enabled for this user. Skipping.")
logger.info("Summary generation not enabled for this user.")
@signals.task_prerun.connect(sender=process_audio_transcribe_summarize_v2)