Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34a0301b02 |
@@ -34,32 +34,6 @@ jobs:
|
||||
if: always()
|
||||
run: ~/.local/bin/gitlint --commits origin/${{ github.event.pull_request.base.ref }}..HEAD
|
||||
|
||||
check-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
contains(github.event.pull_request.labels.*.name, 'noChangeLog') == false &&
|
||||
github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Check that the CHANGELOG has been modified in the current branch
|
||||
run: git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.after }} | grep 'CHANGELOG.md'
|
||||
|
||||
lint-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Check CHANGELOG max line length
|
||||
run: |
|
||||
max_line_length=$(cat CHANGELOG.md | grep -Ev "^\[.*\]: https://github.com" | wc -L)
|
||||
if [ $max_line_length -ge 80 ]; then
|
||||
echo "ERROR: CHANGELOG has lines longer than 80 characters."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build-mails:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
|
||||
+1
-1
@@ -9,4 +9,4 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
-
|
||||
- 🔧(backend) support `_FILE` for secret environment variables #566
|
||||
|
||||
@@ -42,12 +42,7 @@ COPY ./docker/dinum-frontend/fonts/ \
|
||||
FROM nginxinc/nginx-unprivileged:alpine3.21 AS frontend-production
|
||||
|
||||
USER root
|
||||
RUN apk update && apk upgrade libssl3 \
|
||||
libcrypto3 \
|
||||
libxml2>=2.12.7-r2 \
|
||||
libxslt>=1.1.39-r2 \
|
||||
libexpat>=2.7.2-r0 \
|
||||
libpng>=1.6.53-r0
|
||||
RUN apk update && apk upgrade libssl3 libcrypto3 libxml2>=2.12.7-r2 libxslt>=1.1.39-r2 libexpat>=2.7.2-r0
|
||||
|
||||
USER nginx
|
||||
|
||||
|
||||
@@ -1,393 +0,0 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Meet External API
|
||||
version: 1.0.0
|
||||
description: |
|
||||
External API for room management with resource server authentication.
|
||||
[[description by Oauth 2.0]](https://www.oauth.com/oauth2-servers/the-resource-server/)
|
||||
|
||||
#### Authentication Flow
|
||||
|
||||
1. Authenticate with the authorization server using your credentials
|
||||
2. During authentication, request the scopes you need: `lasuite_visio` (mandatory) plus action-specific scopes
|
||||
3. Receive an access token and a refresh token that includes the requested scopes
|
||||
4. Use the access token in the `Authorization: Bearer <token>` header for all API requests
|
||||
5. When the access token expires, use the refresh token to obtain a new access token without re-authenticating
|
||||
|
||||
#### Scopes
|
||||
|
||||
* `lasuite_visio` - **Mandatory** Base scope required for any API access
|
||||
* `lasuite_visio:rooms:list` – List rooms accessible to the delegated user.
|
||||
* `lasuite_visio:rooms:retrieve` – Retrieve details of a specific room.
|
||||
* `lasuite_visio:rooms:create` – Create new rooms.
|
||||
* `lasuite_visio:rooms:update` – **Coming soon** Update existing rooms, e.g., add attendees to a room.
|
||||
* `lasuite_visio:rooms:delete` – **Coming soon** Delete rooms generated by the application.
|
||||
|
||||
#### Upcoming Features
|
||||
|
||||
* **Add attendees to a room:** You will be able to update a room to include a list of attendees, allowing them to bypass the lobby system automatically.
|
||||
* **Delete application-generated rooms:** Rooms created via the application can be deleted when no longer needed.
|
||||
|
||||
contact:
|
||||
name: API Support
|
||||
email: antoine.lebaud@mail.numerique.gouv.fr
|
||||
|
||||
servers:
|
||||
- url: https://visio-sandbox.beta.numerique.gouv.fr/external-api/v1.0
|
||||
description: Sandbox server
|
||||
|
||||
tags:
|
||||
- name: Rooms
|
||||
description: Room management operations
|
||||
|
||||
paths:
|
||||
/rooms:
|
||||
get:
|
||||
tags:
|
||||
- Rooms
|
||||
summary: List rooms
|
||||
description: |
|
||||
Returns a list of rooms accessible to the authenticated user.
|
||||
Only rooms where the delegated user has access will be returned.
|
||||
operationId: listRooms
|
||||
security:
|
||||
- BearerAuth: [rooms:list]
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: Page number for pagination
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
default: 1
|
||||
- name: page_size
|
||||
in: query
|
||||
description: Number of items per page
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
default: 20
|
||||
responses:
|
||||
'200':
|
||||
description: List of accessible rooms
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
description: Total number of rooms
|
||||
next:
|
||||
type: string
|
||||
nullable: true
|
||||
description: URL to next page
|
||||
previous:
|
||||
type: string
|
||||
nullable: true
|
||||
description: URL to previous page
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Room'
|
||||
examples:
|
||||
roomList:
|
||||
summary: Paginated room list
|
||||
value:
|
||||
count: 2
|
||||
next: "https://visio-sandbox.beta.numerique.gouv.fr/external-api/v1.0/rooms?page=2"
|
||||
previous: null
|
||||
results:
|
||||
- id: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
|
||||
slug: "aae-erez-aaz"
|
||||
access_level: "trusted"
|
||||
url: "https://visio-sandbox.beta.numerique.gouv.fr/aae-erez-aaz"
|
||||
telephony:
|
||||
enabled: true
|
||||
pin_code: "123456"
|
||||
phone_number: "+1-555-0100"
|
||||
default_country: "US"
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'403':
|
||||
$ref: '#/components/responses/ForbiddenError'
|
||||
|
||||
post:
|
||||
tags:
|
||||
- Rooms
|
||||
summary: Create a room
|
||||
description: |
|
||||
Creates a new room with secure defaults for external API usage.
|
||||
|
||||
**Restrictions:**
|
||||
- Rooms are always created with `trusted` access (no public rooms via API)
|
||||
- Room access_level can be updated from the webapp interface.
|
||||
|
||||
**Defaults:**
|
||||
- Delegated user is set as owner
|
||||
- Room slug auto-generated for uniqueness
|
||||
- Telephony PIN auto-generated when enabled
|
||||
- Creation tracked with application client_id for auditing
|
||||
operationId: createRoom
|
||||
security:
|
||||
- BearerAuth: [rooms:create]
|
||||
requestBody:
|
||||
required: false
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RoomCreate'
|
||||
examples:
|
||||
emptyBody:
|
||||
summary: No parameters (default)
|
||||
value: {}
|
||||
responses:
|
||||
'201':
|
||||
description: Room created successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Room'
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'403':
|
||||
$ref: '#/components/responses/ForbiddenError'
|
||||
|
||||
/rooms/{id}:
|
||||
get:
|
||||
tags:
|
||||
- Rooms
|
||||
summary: Retrieve a room
|
||||
description: Get detailed information about a specific room by its ID
|
||||
operationId: retrieveRoom
|
||||
security:
|
||||
- BearerAuth: [rooms:retrieve]
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Room UUID
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: Room details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Room'
|
||||
examples:
|
||||
room:
|
||||
summary: Room details
|
||||
value:
|
||||
id: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
|
||||
slug: "aae-erez-aaz"
|
||||
access_level: "trusted"
|
||||
url: "https://visio-sandbox.beta.numerique.gouv.fr/aae-erez-aaz"
|
||||
telephony:
|
||||
enabled: true
|
||||
pin_code: "123456"
|
||||
phone_number: "+1-555-0100"
|
||||
default_country: "US"
|
||||
'401':
|
||||
$ref: '#/components/responses/UnauthorizedError'
|
||||
'403':
|
||||
$ref: '#/components/responses/ForbiddenError'
|
||||
'404':
|
||||
$ref: '#/components/responses/RoomNotFoundError'
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT token obtained from the `/application/token` endpoint.
|
||||
Include in requests as: `Authorization: Bearer <token>`
|
||||
|
||||
schemas:
|
||||
TokenRequest:
|
||||
type: object
|
||||
required:
|
||||
- client_id
|
||||
- client_secret
|
||||
- grant_type
|
||||
- scope
|
||||
properties:
|
||||
client_id:
|
||||
type: string
|
||||
description: Application client identifier
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
client_secret:
|
||||
type: string
|
||||
format: password
|
||||
writeOnly: true
|
||||
description: Application secret key
|
||||
example: "1234567890abcdefghijklmnopqrstuvwxyz"
|
||||
grant_type:
|
||||
type: string
|
||||
enum:
|
||||
- client_credentials
|
||||
description: OAuth2 grant type (must be 'client_credentials')
|
||||
example: "client_credentials"
|
||||
scope:
|
||||
type: string
|
||||
format: email
|
||||
description: |
|
||||
Email address of the user to delegate.
|
||||
The application will act on behalf of this user.
|
||||
Note: This parameter is named 'scope' to align with OAuth2 conventions,
|
||||
but accepts an email address to identify the user. This design allows
|
||||
for future extensibility.
|
||||
example: "user@example.com"
|
||||
|
||||
TokenResponse:
|
||||
type: object
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
description: JWT access token
|
||||
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtZWV0LWFwaSIsImF1ZCI6Im1lZXQtY2xpZW50cyIsImlhdCI6MTcwOTQ5MTIwMCwiZXhwIjoxNzA5NDk0ODAwLCJjbGllbnRfaWQiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAiLCJzY29wZSI6InJvb21zOmxpc3Qgcm9vbXM6cmV0cmlldmUgcm9vbXM6Y3JlYXRlIiwidXNlcl9pZCI6IjdiOGQ5YzQwLTNhMmItNGVkZi04NzFjLTJmM2Q0ZTVmNmE3YiIsImRlbGVnYXRlZCI6dHJ1ZX0.signature"
|
||||
token_type:
|
||||
type: string
|
||||
description: Token type (always 'Bearer')
|
||||
example: "Bearer"
|
||||
expires_in:
|
||||
type: integer
|
||||
description: Token lifetime in seconds
|
||||
example: 3600
|
||||
scope:
|
||||
type: string
|
||||
description: Space-separated list of granted permission scopes
|
||||
example: "rooms:list rooms:retrieve rooms:create"
|
||||
|
||||
RoomCreate:
|
||||
type: object
|
||||
description: Empty object - all room properties are auto-generated
|
||||
properties: {}
|
||||
|
||||
Room:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
description: Unique room identifier
|
||||
example: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
|
||||
slug:
|
||||
type: string
|
||||
readOnly: true
|
||||
description: URL-friendly room identifier (auto-generated)
|
||||
example: "aze-eere-zer"
|
||||
access_level:
|
||||
type: string
|
||||
readOnly: true
|
||||
description: Room access level (always 'trusted' for API-created rooms)
|
||||
example: "trusted"
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
readOnly: true
|
||||
description: Full URL to access the room
|
||||
example: "https://visio-sandbox.beta.numerique.gouv.fr/aze-eere-zer"
|
||||
telephony:
|
||||
type: object
|
||||
readOnly: true
|
||||
description: Telephony dial-in information (if enabled)
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
description: Whether telephony is available
|
||||
example: true
|
||||
pin_code:
|
||||
type: string
|
||||
description: PIN code for dial-in access
|
||||
example: "123456"
|
||||
phone_number:
|
||||
type: string
|
||||
description: Phone number to dial
|
||||
example: "+1-555-0100"
|
||||
default_country:
|
||||
type: string
|
||||
description: Default country code
|
||||
example: "US"
|
||||
|
||||
OAuthError:
|
||||
type: object
|
||||
description: OAuth2-compliant error response
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: Human-readable error description
|
||||
example: "Invalid credentials"
|
||||
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
detail:
|
||||
type: string
|
||||
description: Error message
|
||||
example: "Invalid token."
|
||||
|
||||
ValidationError:
|
||||
type: object
|
||||
properties:
|
||||
field_name:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: List of validation errors for this field
|
||||
example: ["This field is required."]
|
||||
|
||||
responses:
|
||||
UnauthorizedError:
|
||||
description: The access token is expired, revoked, malformed, or invalid for other reasons. The client can obtain a new access token and try again.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
examples:
|
||||
invalidToken:
|
||||
summary: Invalid token
|
||||
value:
|
||||
error: "Invalid token."
|
||||
|
||||
ForbiddenError:
|
||||
description: Insufficient scope for this operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
examples:
|
||||
insufficientScope:
|
||||
summary: Missing required scope
|
||||
value:
|
||||
detail: "Insufficient permissions. Required scope: 'rooms:xxxx'"
|
||||
|
||||
RoomNotFoundError:
|
||||
description: Room not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
examples:
|
||||
roomNotFound:
|
||||
summary: Room does not exist
|
||||
value:
|
||||
detail: "Not found."
|
||||
|
||||
BadRequestError:
|
||||
description: Invalid request data
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
examples:
|
||||
validationError:
|
||||
summary: Field validation failed
|
||||
value:
|
||||
scope: ["Invalid email address."]
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
[project]
|
||||
name = "agents"
|
||||
version = "1.0.0"
|
||||
version = "0.1.42"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"livekit-agents==1.2.18",
|
||||
|
||||
@@ -159,8 +159,6 @@ class RoomSerializer(serializers.ModelSerializer):
|
||||
configuration=configuration,
|
||||
is_admin_or_owner=is_admin_or_owner,
|
||||
)
|
||||
else:
|
||||
del output["pin_code"]
|
||||
|
||||
output["is_administrable"] = is_admin_or_owner
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ def test_api_rooms_retrieve_anonymous_private_pk():
|
||||
"id": str(room.id),
|
||||
"is_administrable": False,
|
||||
"name": room.name,
|
||||
"pin_code": room.pin_code,
|
||||
"slug": room.slug,
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ def test_api_rooms_retrieve_anonymous_trusted_pk():
|
||||
"id": str(room.id),
|
||||
"is_administrable": False,
|
||||
"name": room.name,
|
||||
"pin_code": room.pin_code,
|
||||
"slug": room.slug,
|
||||
}
|
||||
|
||||
@@ -69,6 +71,7 @@ def test_api_rooms_retrieve_anonymous_private_pk_no_dashes():
|
||||
"id": str(room.id),
|
||||
"is_administrable": False,
|
||||
"name": room.name,
|
||||
"pin_code": room.pin_code,
|
||||
"slug": room.slug,
|
||||
}
|
||||
|
||||
@@ -85,6 +88,7 @@ def test_api_rooms_retrieve_anonymous_private_slug():
|
||||
"id": str(room.id),
|
||||
"is_administrable": False,
|
||||
"name": room.name,
|
||||
"pin_code": room.pin_code,
|
||||
"slug": room.slug,
|
||||
}
|
||||
|
||||
@@ -101,6 +105,7 @@ def test_api_rooms_retrieve_anonymous_private_slug_not_normalized():
|
||||
"id": str(room.id),
|
||||
"is_administrable": False,
|
||||
"name": room.name,
|
||||
"pin_code": room.pin_code,
|
||||
"slug": room.slug,
|
||||
}
|
||||
|
||||
@@ -342,6 +347,7 @@ def test_api_rooms_retrieve_authenticated():
|
||||
"id": str(room.id),
|
||||
"is_administrable": False,
|
||||
"name": room.name,
|
||||
"pin_code": room.pin_code,
|
||||
"slug": room.slug,
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "meet"
|
||||
version = "1.0.0"
|
||||
version = "0.1.42"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -38,7 +38,7 @@ dependencies = [
|
||||
"django-redis==6.0.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-timezone-field>=5.1",
|
||||
"django==5.2.9",
|
||||
"django==5.2.8",
|
||||
"djangorestframework==3.16.1",
|
||||
"drf_spectacular==0.29.0",
|
||||
"dockerflow==2024.4.2",
|
||||
|
||||
@@ -38,12 +38,7 @@ RUN npm run build
|
||||
FROM nginxinc/nginx-unprivileged:alpine3.21 AS frontend-production
|
||||
|
||||
USER root
|
||||
RUN apk update && apk upgrade libssl3 \
|
||||
libcrypto3 \
|
||||
libxml2>=2.12.7-r2 \
|
||||
libxslt>=1.1.39-r2 \
|
||||
libexpat>=2.7.2-r0 \
|
||||
libpng>=1.6.53-r0
|
||||
RUN apk update && apk upgrade libssl3 libcrypto3 libxml2>=2.12.7-r2 libxslt>=1.1.39-r2 libexpat>=2.7.2-r0
|
||||
|
||||
USER nginx
|
||||
|
||||
|
||||
Generated
+30
-39
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.42",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "meet",
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.42",
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "2.9.13",
|
||||
"@livekit/components-styles": "1.1.6",
|
||||
@@ -55,7 +55,7 @@
|
||||
"postcss": "8.5.6",
|
||||
"prettier": "3.6.2",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "7.0.8",
|
||||
"vite": "7.0.2",
|
||||
"vite-tsconfig-paths": "5.1.4"
|
||||
}
|
||||
},
|
||||
@@ -104,7 +104,6 @@
|
||||
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@@ -3819,6 +3818,17 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rrweb/types": {
|
||||
"version": "2.0.0-alpha.17",
|
||||
"resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.0.0-alpha.17.tgz",
|
||||
"integrity": "sha512-AfDTVUuCyCaIG0lTSqYtrZqJX39ZEYzs4fYKnexhQ+id+kbZIpIJtaut5cto6dWZbB3SEe4fW0o90Po3LvTmfg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"rrweb-snapshot": "^2.0.0-alpha.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz",
|
||||
@@ -3870,7 +3880,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.81.5.tgz",
|
||||
"integrity": "sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.81.5"
|
||||
},
|
||||
@@ -3962,7 +3971,8 @@
|
||||
"version": "1.0.22",
|
||||
"resolved": "https://registry.npmjs.org/@types/dom-mediacapture-record/-/dom-mediacapture-record-1.0.22.tgz",
|
||||
"integrity": "sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/dom-mediacapture-transform": {
|
||||
"version": "0.1.11",
|
||||
@@ -3978,7 +3988,8 @@
|
||||
"version": "0.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.15.tgz",
|
||||
"integrity": "sha512-omOlCPvTWyPm4ZE5bZUhlSvnHM2ZWM2U+1cPiYFL/e8aV5O9MouELp+L4dMKNTON0nTeHqEg+KWDfFQMY5Wkaw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
@@ -4005,7 +4016,6 @@
|
||||
"integrity": "sha512-B2egV9wALML1JCpv3VQoQ+yesQKAmNMBIAY7OteVrikcOcAkWm+dGL6qpeCktPjAv6N1JLnhbNiqS35UpFyBsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -4023,7 +4033,6 @@
|
||||
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -4090,7 +4099,6 @@
|
||||
"integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.35.1",
|
||||
"@typescript-eslint/types": "8.35.1",
|
||||
@@ -4382,7 +4390,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4797,7 +4804,6 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"electron-to-chromium": "^1.5.4",
|
||||
@@ -5607,7 +5613,6 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
|
||||
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -6575,7 +6580,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
@@ -7460,7 +7464,6 @@
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.15.7.tgz",
|
||||
"integrity": "sha512-19m8Q1cvRl5PslRawDUgWXeP8vL8584tX8kiZEJaPZo83U/L6VPS/O7pP06phfJaBWeeV8sAOVtEPlQiZEHtpg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@livekit/mutex": "1.1.1",
|
||||
"@livekit/protocol": "1.39.3",
|
||||
@@ -8120,7 +8123,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -8234,7 +8236,6 @@
|
||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
@@ -8388,7 +8389,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -8495,7 +8495,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -8745,6 +8744,7 @@
|
||||
"integrity": "sha512-GBg5pV8LHOTbeVmH2VHLEFR0mc2QpQMzAvcoxEGfPNWgWHc8UvKCyq7pqN1vA+fDZ+yXXbixeO0kB2pzVvFCBw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"postcss": "^8.4.38"
|
||||
}
|
||||
@@ -9248,7 +9248,6 @@
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -9367,8 +9366,7 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
@@ -9480,7 +9478,6 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -9641,7 +9638,6 @@
|
||||
"resolved": "https://registry.npmjs.org/valtio/-/valtio-2.1.5.tgz",
|
||||
"integrity": "sha512-vsh1Ixu5mT0pJFZm+Jspvhga5GzHUTYv0/+Th203pLfh3/wbHwxhu/Z2OkZDXIgHfjnjBns7SN9HNcbDvPmaGw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"proxy-compare": "^3.0.1"
|
||||
},
|
||||
@@ -9737,16 +9733,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.8.tgz",
|
||||
"integrity": "sha512-cJBdq0/u+8rgstg9t7UkBilf8ipLmeXJO30NxD5HAHOivnj10ocV8YtR/XBvd2wQpN3TmcaxNKaHX3tN7o5F5A==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.2.tgz",
|
||||
"integrity": "sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"picomatch": "^4.0.3",
|
||||
"picomatch": "^4.0.2",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.40.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
@@ -9854,14 +9849,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
||||
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
@@ -9872,12 +9864,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.42",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "panda codegen && vite",
|
||||
@@ -60,7 +60,7 @@
|
||||
"postcss": "8.5.6",
|
||||
"prettier": "3.6.2",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "7.0.8",
|
||||
"vite": "7.0.2",
|
||||
"vite-tsconfig-paths": "5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import { MediaDeviceErrorAlert } from './MediaDeviceErrorAlert'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
import { isFireFox } from '@/utils/livekit'
|
||||
import { useIsMobile } from '@/utils/useIsMobile'
|
||||
|
||||
export const Conference = ({
|
||||
roomId,
|
||||
@@ -170,8 +169,6 @@ export const Conference = ({
|
||||
kind: null,
|
||||
})
|
||||
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
/*
|
||||
* Ensure stable WebSocket connection URL. This is critical for legacy browser compatibility
|
||||
* (Firefox <124, Chrome <125, Edge <125) where HTTPS URLs in WebSocket() constructor
|
||||
@@ -241,7 +238,7 @@ export const Conference = ({
|
||||
}}
|
||||
>
|
||||
<VideoConference />
|
||||
{showInviteDialog && !isMobile && (
|
||||
{showInviteDialog && (
|
||||
<InviteDialog
|
||||
isOpen={showInviteDialog}
|
||||
onOpenChange={setShowInviteDialog}
|
||||
|
||||
@@ -113,7 +113,6 @@ export const FullScreenShareWarning = ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: '1rem',
|
||||
zIndex: '1000',
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.42",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mail_mjml",
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.42",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@html-to/text-cli": "0.5.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.42",
|
||||
"description": "An util to generate html and text django's templates from mjml templates",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
"globals": "16.3.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.35.1",
|
||||
"vite": "7.0.8"
|
||||
"vite": "7.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"sass": "1.89.2",
|
||||
"prettier": "3.6.2",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "7.0.8",
|
||||
"vite": "7.0.2",
|
||||
"vite-plugin-dts": "4.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+14
-17
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.42",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sdk",
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.42",
|
||||
"license": "ISC",
|
||||
"workspaces": [
|
||||
"./library",
|
||||
@@ -30,7 +30,7 @@
|
||||
"globals": "16.3.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.35.1",
|
||||
"vite": "7.0.8"
|
||||
"vite": "7.0.2"
|
||||
}
|
||||
},
|
||||
"consumer/node_modules/@eslint/js": {
|
||||
@@ -103,7 +103,7 @@
|
||||
"prettier": "3.6.2",
|
||||
"sass": "1.89.2",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "7.0.8",
|
||||
"vite": "7.0.2",
|
||||
"vite-plugin-dts": "4.5.4"
|
||||
}
|
||||
},
|
||||
@@ -6677,15 +6677,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.8.tgz",
|
||||
"integrity": "sha512-cJBdq0/u+8rgstg9t7UkBilf8ipLmeXJO30NxD5HAHOivnj10ocV8YtR/XBvd2wQpN3TmcaxNKaHX3tN7o5F5A==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.2.tgz",
|
||||
"integrity": "sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.6",
|
||||
"picomatch": "^4.0.3",
|
||||
"picomatch": "^4.0.2",
|
||||
"postcss": "^8.5.6",
|
||||
"rollup": "^4.40.0",
|
||||
"tinyglobby": "^0.2.14"
|
||||
@@ -6779,14 +6779,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
||||
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
@@ -6797,9 +6794,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.42",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
[project]
|
||||
name = "summary"
|
||||
version = "1.0.0"
|
||||
version = "0.1.42"
|
||||
dependencies = [
|
||||
"fastapi[standard]>=0.105.0",
|
||||
"uvicorn>=0.24.0",
|
||||
|
||||
@@ -31,7 +31,6 @@ from summary.core.prompt import (
|
||||
PROMPT_SYSTEM_TLDR,
|
||||
PROMPT_USER_PART,
|
||||
)
|
||||
from summary.core.transcript_formatter import TranscriptFormatter
|
||||
|
||||
settings = get_settings()
|
||||
analytics = get_analytics()
|
||||
@@ -57,6 +56,28 @@ if settings.sentry_dsn and settings.sentry_is_enabled:
|
||||
sentry_sdk.init(dsn=settings.sentry_dsn, enable_tracing=True)
|
||||
|
||||
|
||||
DEFAULT_EMPTY_TRANSCRIPTION = """
|
||||
**Aucun contenu audio n’a été détecté dans votre transcription.**
|
||||
|
||||
|
||||
*Si vous pensez qu’il s’agit d’une erreur, n’hésitez pas à contacter
|
||||
notre support technique : visio@numerique.gouv.fr*
|
||||
|
||||
.
|
||||
|
||||
.
|
||||
|
||||
.
|
||||
|
||||
Quelques points que nous vous conseillons de vérifier :
|
||||
- Un micro était-il activé ?
|
||||
- Étiez-vous suffisamment proche ?
|
||||
- Le micro est-il de bonne qualité ?
|
||||
- L’enregistrement dure-t-il plus de 30 secondes ?
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class AudioValidationError(Exception):
|
||||
"""Custom exception for audio validation errors."""
|
||||
|
||||
@@ -145,6 +166,36 @@ def format_actions(llm_output: dict) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def format_segments(transcription_data):
|
||||
"""Format transcription segments from WhisperX into a readable conversation format.
|
||||
|
||||
Processes transcription data with segments containing speaker information and text,
|
||||
combining consecutive segments from the same speaker and formatting them as a
|
||||
conversation with speaker labels.
|
||||
"""
|
||||
formatted_output = ""
|
||||
if not transcription_data or not hasattr(transcription_data, "segments"):
|
||||
if isinstance(transcription_data, dict) and "segments" in transcription_data:
|
||||
segments = transcription_data["segments"]
|
||||
else:
|
||||
return "Error: Invalid transcription data format"
|
||||
else:
|
||||
segments = transcription_data.segments
|
||||
|
||||
previous_speaker = None
|
||||
|
||||
for segment in segments:
|
||||
speaker = segment.get("speaker", "UNKNOWN_SPEAKER")
|
||||
text = segment.get("text", "")
|
||||
if text:
|
||||
if speaker != previous_speaker:
|
||||
formatted_output += f"\n\n **{speaker}**: {text}"
|
||||
else:
|
||||
formatted_output += f" {text}"
|
||||
previous_speaker = speaker
|
||||
return formatted_output
|
||||
|
||||
|
||||
def post_with_retries(url, data):
|
||||
"""Send POST request with automatic retries."""
|
||||
session = create_retry_session()
|
||||
@@ -255,20 +306,25 @@ def process_audio_transcribe_summarize_v2(
|
||||
os.remove(temp_file_path)
|
||||
logger.debug("Temporary file removed: %s", temp_file_path)
|
||||
|
||||
metadata_manager.track_transcription_metadata(task_id, transcription)
|
||||
|
||||
formatter = TranscriptFormatter()
|
||||
|
||||
content, title = formatter.format(
|
||||
transcription,
|
||||
room=room,
|
||||
recording_date=recording_date,
|
||||
recording_time=recording_time,
|
||||
formatted_transcription = (
|
||||
DEFAULT_EMPTY_TRANSCRIPTION
|
||||
if not transcription.segments
|
||||
else format_segments(transcription)
|
||||
)
|
||||
|
||||
metadata_manager.track_transcription_metadata(task_id, transcription)
|
||||
|
||||
if not room or not recording_date or not recording_time:
|
||||
title = settings.document_default_title
|
||||
else:
|
||||
title = settings.document_title_template.format(
|
||||
room=room,
|
||||
room_recording_date=recording_date,
|
||||
room_recording_time=recording_time,
|
||||
)
|
||||
data = {
|
||||
"title": title,
|
||||
"content": content,
|
||||
"content": formatted_transcription,
|
||||
"email": email,
|
||||
"sub": sub,
|
||||
}
|
||||
@@ -300,7 +356,7 @@ def process_audio_transcribe_summarize_v2(
|
||||
):
|
||||
logger.info("Queuing summary generation task.")
|
||||
summarize_transcription.apply_async(
|
||||
args=[content, email, sub, title],
|
||||
args=[formatted_transcription, email, sub, title],
|
||||
queue=settings.summarize_queue,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -45,10 +45,6 @@ class Settings(BaseSettings):
|
||||
llm_api_key: str
|
||||
llm_model: str
|
||||
|
||||
# Transcription processing
|
||||
hallucination_patterns: List[str] = ["Vap'n'Roll Thierry"]
|
||||
hallucination_replacement_text: str = "[Texte impossible à transcrire]"
|
||||
|
||||
# Webhook-related settings
|
||||
webhook_max_retries: int = 2
|
||||
webhook_status_forcelist: List[int] = [502, 503, 504]
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
"""Transcript formatting into readable conversation format with speaker labels."""
|
||||
|
||||
import logging
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from summary.core.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
DEFAULT_EMPTY_TRANSCRIPTION = """
|
||||
**Aucun contenu audio n’a été détecté dans votre transcription.**
|
||||
|
||||
|
||||
*Si vous pensez qu’il s’agit d’une erreur, n’hésitez pas à contacter
|
||||
notre support technique : visio@numerique.gouv.fr*
|
||||
|
||||
.
|
||||
|
||||
.
|
||||
|
||||
.
|
||||
|
||||
Quelques points que nous vous conseillons de vérifier :
|
||||
- Un micro était-il activé ?
|
||||
- Étiez-vous suffisamment proche ?
|
||||
- Le micro est-il de bonne qualité ?
|
||||
- L’enregistrement dure-t-il plus de 30 secondes ?
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class TranscriptFormatter:
|
||||
"""Formats WhisperX transcription output into readable conversation format.
|
||||
|
||||
Handles:
|
||||
- Extracting segments from transcription objects or dictionaries
|
||||
- Combining consecutive segments from the same speaker
|
||||
- Removing hallucination patterns from content
|
||||
- Generating descriptive titles from context
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize formatter with settings."""
|
||||
self.hallucination_patterns = settings.hallucination_patterns
|
||||
self.hallucination_replacement_text = settings.hallucination_replacement_text
|
||||
self.default_title = settings.document_default_title
|
||||
self.default_empty_transcription = DEFAULT_EMPTY_TRANSCRIPTION
|
||||
|
||||
def _get_segments(self, transcription):
|
||||
"""Extract segments from transcription object or dictionary."""
|
||||
if hasattr(transcription, "segments"):
|
||||
return transcription.segments
|
||||
|
||||
if isinstance(transcription, dict):
|
||||
return transcription.get("segments", None)
|
||||
|
||||
return None
|
||||
|
||||
def format(
|
||||
self,
|
||||
transcription,
|
||||
room: Optional[str] = None,
|
||||
recording_date: Optional[str] = None,
|
||||
recording_time: Optional[str] = None,
|
||||
) -> Tuple[str, str]:
|
||||
"""Format transcription into the final document and its title."""
|
||||
segments = self._get_segments(transcription)
|
||||
|
||||
if not segments:
|
||||
content = self.default_empty_transcription
|
||||
else:
|
||||
content = self._format_speaker(segments)
|
||||
content = self._remove_hallucinations(content)
|
||||
|
||||
title = self._generate_title(room, recording_date, recording_time)
|
||||
|
||||
return content, title
|
||||
|
||||
def _remove_hallucinations(self, content: str) -> str:
|
||||
"""Remove hallucination patterns from content."""
|
||||
replacement = self.hallucination_replacement_text or ""
|
||||
|
||||
for pattern in self.hallucination_patterns:
|
||||
content = content.replace(pattern, replacement)
|
||||
return content
|
||||
|
||||
def _format_speaker(self, segments) -> str:
|
||||
"""Format segments with speaker labels, combining consecutive speakers."""
|
||||
formatted_output = ""
|
||||
previous_speaker = None
|
||||
|
||||
for segment in segments:
|
||||
speaker = segment.get("speaker", "UNKNOWN_SPEAKER")
|
||||
text = segment.get("text", "")
|
||||
if text:
|
||||
if speaker != previous_speaker:
|
||||
formatted_output += f"\n\n **{speaker}**: {text}"
|
||||
else:
|
||||
formatted_output += f" {text}"
|
||||
previous_speaker = speaker
|
||||
|
||||
return formatted_output
|
||||
|
||||
def _generate_title(
|
||||
self,
|
||||
room: Optional[str] = None,
|
||||
recording_date: Optional[str] = None,
|
||||
recording_time: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Generate title from context or return default."""
|
||||
if not room or not recording_date or not recording_time:
|
||||
return self.default_title
|
||||
|
||||
return settings.document_title_template.format(
|
||||
room=room,
|
||||
room_recording_date=recording_date,
|
||||
room_recording_time=recording_time,
|
||||
)
|
||||
Reference in New Issue
Block a user