Compare commits

..

2 Commits

Author SHA1 Message Date
Cyril fa85343147 ️(frontend) fix focus restore when switching side panels
restore trigger focus on panel switch while panel remains open
2026-07-11 16:07:42 +02:00
Cyril ba6a4e971f ️(frontend) focus side panel container on open
Focus aside on side panel open so screen readers announce the aria-label
2026-07-11 16:07:39 +02:00
27 changed files with 170 additions and 604 deletions
-25
View File
@@ -10,31 +10,10 @@ and this project adheres to
### Added
- ✨(summary) report exception type in failure analytics
## Fixed
- 🐛(transcription) fix silent bug in speaker assignment
- 🐛(summary) extend tasks auto retry logic
- 🐛(summary) properly detect when failure webhook should be sent
### Changed
- ⬆️(frontend) upgrade @mediapipe/tasks-vision from 0.10.14 to 0.10.35
- ⬆️(frontend) upgrade i18next from 26.3.1 to 26.3.2
- ⬆️(frontend) upgrade posthog-js from 1.391.2 to 1.395.0
- ⬆️(frontend) upgrade @tanstack/react-query from 5.101.0 to 5.101.1
- ⬆️(frontend) upgrade livekit-client from 2.19.2 to 2.20.0
## [1.24.0] - 2026-07-21
### Added
- ✨(backend) allow searching the recording admin table by owner email
- ✨(frontend) add participant color gradient when camera is off #1490
- ✨(all) allow forcing SSO display name for authenticated users
- (frontend) install vite-plugin-static-copy for MediaPipe WASM assets
- ✨(addon) show add-in tools when creating meetings in shared calendars
### Changed
@@ -45,16 +24,12 @@ and this project adheres to
- ♻️(frontend) inline model weights to avoid loading them from remote
- ♻️(frontend) inline MediaPipe WASM modules to avoid loading from remote
- ⬆️(frontend) upgrade posthog-js from 1.387.0 to 1.391.2
- ⬆️(frontend) upgrade react-stately from 3.47.0 to 3.48.0
- ⬆️(frontend) upgrade react-aria from 3.49.0 to 3.50.0
- ⬆️(frontend) upgrade react-aria-components from 1.18.0 to 1.19.0
### Fixed
- 🩹(backend) identify externally provisioned users to PostHog
- 🐛(backend) fix info panel crash for unregistered rooms
- ♿️(frontend) focus side panel container on open #1452
- 🐛(summary) whisper call error handling
## [1.23.0] - 2026-07-08
+1 -1
View File
@@ -37,7 +37,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev
# ---- mails ----
FROM node:22 AS mail-builder
FROM node:20 AS mail-builder
COPY ./src/mail /mail/app
-1
View File
@@ -164,7 +164,6 @@ echo " - src/backend/pyproject.toml"
echo " - src/backend/uv.lock"
echo " - src/summary/pyproject.toml"
echo " - src/agents/pyproject.toml"
echo " - src/agents/uv.lock"
echo " - CHANGELOG.md"
echo ""
print_warning "Next steps:"
+4 -4
View File
@@ -11,14 +11,14 @@ There are two ways to customize LaSuite Meet:
### How to Use
To use this feature, simply set the `FRONTEND_CUSTOM_CSS_URL` environment variable (of the **backend** service) to the URL of your custom CSS file. For example:
To use this feature, simply set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. For example:
```javascript
FRONTEND_CUSTOM_CSS_URL=https://example.com/custom-style.css
FRONTEND_CSS_URL=https://example.com/custom-style.css
```
> [!TIP]
> If you serve your CSS file on the same domain as LaSuite Meet, paths are supported, i.e. `FRONTEND_CUSTOM_CSS_URL=/custom/style.css` will load `https://your-domain.com/custom/style.css`.
> If you serve your CSS file on the same domain as LaSuite Meet, paths are supported, i.e. `FRONTEND_CSS_URL=/custom/style.css` will load `https://your-domain.com/custom/style.css`.
Setting this variable makes the app load your CSS at runtime, adding a `<link>` to `<head>` so you can override CSS variables and customize the frontend without rebuilding.
@@ -37,7 +37,7 @@ Let's say you want to change the font of our application to a custom font. You c
}
```
Then, set the `FRONTEND_CUSTOM_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the default font to the one you specified.
Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the default font to the one you specified.
> [!IMPORTANT]
> You can override any CSS token—semantic or palette. See [panda.config.ts](../src/frontend/panda.config.ts) for all defined semantic tokens.
+1 -170
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" xmlns:mailappor="http://schemas.microsoft.com/office/mailappversionoverrides/1.0" xsi:type="MailApp">
<Id>a025f0f6-757a-4790-97f3-99c66c4a5795</Id>
<Version>1.0.0.0</Version>
<Version>0.0.2.0</Version>
<ProviderName>__APP_NAME__</ProviderName>
<DefaultLocale>fr-FR</DefaultLocale>
<DisplayName DefaultValue="__APP_NAME__"/>
@@ -205,174 +205,5 @@
</bt:String>
</bt:LongStrings>
</Resources>
<!-- ─── V1.1 override: required for shared folder / delegate support ─── -->
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1" xsi:type="VersionOverridesV1_1">
<Requirements>
<bt:Sets DefaultMinVersion="1.8">
<bt:Set Name="Mailbox"/>
</bt:Sets>
</Requirements>
<Hosts>
<Host xsi:type="MailHost">
<DesktopFormFactor>
<FunctionFile resid="Commands.Url"/>
<SupportsSharedFolders>true</SupportsSharedFolders>
<!-- ─── Mail: Read ─────────────────────────────────────────── -->
<ExtensionPoint xsi:type="MessageReadCommandSurface">
<OfficeTab id="TabDefault">
<Group id="msgReadGroup">
<Label resid="GroupLabel"/>
<Control xsi:type="Button" id="msgReadOpenPaneButton">
<Label resid="TaskpaneButton.Label"/>
<Supertip>
<Title resid="TaskpaneButton.Label"/>
<Description resid="TaskpaneButton.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Icon.16x16"/>
<bt:Image size="32" resid="Icon.32x32"/>
<bt:Image size="80" resid="Icon.80x80"/>
</Icon>
<Action xsi:type="ShowTaskpane">
<SourceLocation resid="Taskpane.Url"/>
</Action>
</Control>
</Group>
</OfficeTab>
</ExtensionPoint>
<!-- ─── Mail: Compose ─────────────────────────────────────── -->
<ExtensionPoint xsi:type="MessageComposeCommandSurface">
<OfficeTab id="TabDefault">
<Group id="msgComposeGroup">
<Label resid="GroupLabel"/>
<Control xsi:type="Button" id="msgComposeGenerateLinkButton">
<Label resid="GenerateLink.Label"/>
<Supertip>
<Title resid="GenerateLink.Label"/>
<Description resid="GenerateLink.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Icon.16x16"/>
<bt:Image size="32" resid="Icon.32x32"/>
<bt:Image size="80" resid="Icon.80x80"/>
</Icon>
<Action xsi:type="ExecuteFunction">
<FunctionName>generateMeetingLinkFromMail</FunctionName>
</Action>
</Control>
<Control xsi:type="Button" id="msgComposeOpenPaneButton">
<Label resid="TaskpaneButton.Label"/>
<Supertip>
<Title resid="TaskpaneButton.Label"/>
<Description resid="TaskpaneButton.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Settings.16x16"/>
<bt:Image size="32" resid="Settings.32x32"/>
<bt:Image size="80" resid="Settings.80x80"/>
</Icon>
<Action xsi:type="ShowTaskpane">
<SourceLocation resid="Taskpane.Url"/>
</Action>
</Control>
</Group>
</OfficeTab>
</ExtensionPoint>
<!-- ─── Calendar: Compose (New/Edit appointment) ──────────── -->
<ExtensionPoint xsi:type="AppointmentOrganizerCommandSurface">
<OfficeTab id="TabDefault">
<Group id="apptComposeGroup">
<Label resid="GroupLabel"/>
<Control xsi:type="Button" id="apptGenerateLinkButton">
<Label resid="GenerateLink.Label"/>
<Supertip>
<Title resid="GenerateLink.Label"/>
<Description resid="GenerateLink.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Icon.16x16"/>
<bt:Image size="32" resid="Icon.32x32"/>
<bt:Image size="80" resid="Icon.80x80"/>
</Icon>
<Action xsi:type="ExecuteFunction">
<FunctionName>generateMeetingLinkFromCalendar</FunctionName>
</Action>
</Control>
<Control xsi:type="Button" id="apptOpenSettingsButton">
<Label resid="OpenSettings.Label"/>
<Supertip>
<Title resid="OpenSettings.Label"/>
<Description resid="OpenSettings.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Settings.16x16"/>
<bt:Image size="32" resid="Settings.32x32"/>
<bt:Image size="80" resid="Settings.80x80"/>
</Icon>
<Action xsi:type="ShowTaskpane">
<SourceLocation resid="Taskpane.Url"/>
</Action>
</Control>
</Group>
</OfficeTab>
</ExtensionPoint>
</DesktopFormFactor>
</Host>
</Hosts>
<Resources>
<bt:Images>
<bt:Image id="Settings.16x16" DefaultValue="https://localhost:3000/addons/outlook/assets/settings-16.png"/>
<bt:Image id="Settings.32x32" DefaultValue="https://localhost:3000/addons/outlook/assets/settings-32.png"/>
<bt:Image id="Settings.80x80" DefaultValue="https://localhost:3000/addons/outlook/assets/settings-80.png"/>
<bt:Image id="Add.16x16" DefaultValue="https://localhost:3000/addons/outlook/assets/add-16.png"/>
<bt:Image id="Add.32x32" DefaultValue="https://localhost:3000/addons/outlook/assets/add-32.png"/>
<bt:Image id="Add.80x80" DefaultValue="https://localhost:3000/addons/outlook/assets/add-80.png"/>
<bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/addons/outlook/assets/icon-16.png"/>
<bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/addons/outlook/assets/icon-32.png"/>
<bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/addons/outlook/assets/icon-80.png"/>
</bt:Images>
<bt:Urls>
<bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/addons/outlook/commands.html"/>
<bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/addons/outlook/taskpane.html"/>
</bt:Urls>
<bt:ShortStrings>
<!-- Default (French) -->
<bt:String id="GroupLabel" DefaultValue="__APP_NAME__"/>
<bt:String id="GenerateLink.Label" DefaultValue="Ajouter un lien __APP_NAME__">
<bt:Override Locale="en-US" Value="Add a __APP_NAME__ link"/>
<bt:Override Locale="de-DE" Value="__APP_NAME__-Link hinzufügen"/>
</bt:String>
<bt:String id="TaskpaneButton.Label" DefaultValue="Ouvrir les paramètres">
<bt:Override Locale="en-US" Value="Open settings"/>
<bt:Override Locale="de-DE" Value="Einstellungen öffnen"/>
</bt:String>
<bt:String id="OpenSettings.Label" DefaultValue="Paramètres">
<bt:Override Locale="en-US" Value="Settings"/>
<bt:Override Locale="de-DE" Value="Einstellungen"/>
</bt:String>
</bt:ShortStrings>
<bt:LongStrings>
<bt:String id="GenerateLink.Tooltip" DefaultValue="Génère un lien de réunion __APP_NAME__ et l'insère dans l'événement.">
<bt:Override Locale="de-DE" Value="Generiert einen __APP_NAME__-Besprechungslink und fügt ihn in den Termin ein."/>
<bt:Override Locale="en-US" Value="Generates a __APP_NAME__ meeting link and inserts it into the item."/>
</bt:String>
<bt:String id="TaskpaneButton.Tooltip" DefaultValue="Ouvre les paramètres de connexion __APP_NAME__.">
<bt:Override Locale="de-DE" Value="Öffnet die __APP_NAME__-Verbindungseinstellungen."/>
<bt:Override Locale="en-US" Value="Opens the __APP_NAME__ connection settings."/>
</bt:String>
<bt:String id="OpenSettings.Tooltip" DefaultValue="Ouvre les paramètres de connexion __APP_NAME__.">
<bt:Override Locale="de-DE" Value="Öffnet die __APP_NAME__-Verbindungseinstellungen."/>
<bt:Override Locale="en-US" Value="Opens the __APP_NAME__ connection settings."/>
</bt:String>
</bt:LongStrings>
</Resources>
</VersionOverrides>
</VersionOverrides>
</OfficeApp>
+4 -4
View File
@@ -10,7 +10,7 @@
"license": "MIT",
"dependencies": {
"core-js": "3.49.0",
"i18next": "^26.3.2",
"i18next": "26.3.1",
"i18next-browser-languagedetector": "8.2.1",
"regenerator-runtime": "0.14.1"
},
@@ -9364,9 +9364,9 @@
}
},
"node_modules/i18next": {
"version": "26.3.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.2.tgz",
"integrity": "sha512-QQkXAM1sPDHqhxMQuBeHVMUn6mJchF+wdpOoQerciLAFqO3ZYdxO0EUbeEhruyutnNwpUQIITDVzLjwnNL0T1w==",
"version": "26.3.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.1.tgz",
"integrity": "sha512-txQqd5EULsqEh9OJqRH15aCaOuy/nLJyhw5EHCSKLKJE1aBbb3Zve2+uQIxgWhPm1QqUQoWyQBm2kfmmIrzkcQ==",
"funding": [
{
"type": "individual",
+1 -1
View File
@@ -27,7 +27,7 @@
},
"dependencies": {
"core-js": "3.49.0",
"i18next": "26.3.2",
"i18next": "26.3.1",
"i18next-browser-languagedetector": "8.2.1",
"regenerator-runtime": "0.14.1"
},
@@ -57,7 +57,8 @@
data-i18n="footer.feedback"
></a>
<div id="footer-right">
<span class="version-number">1.0.0</span>
<span class="version-badge">beta</span>
<span class="version-number">0.0.2</span>
</div>
</footer>
</body>
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "agents"
version = "1.24.0"
version = "1.23.0"
requires-python = ">=3.12"
dependencies = [
"livekit-agents==1.6.4",
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]]
name = "agents"
version = "1.24.0"
version = "1.23.0"
source = { virtual = "." }
dependencies = [
{ name = "livekit-agents" },
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "uv_build"
[project]
name = "meet"
version = "1.24.0"
version = "1.23.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
+1 -1
View File
@@ -1187,7 +1187,7 @@ wheels = [
[[package]]
name = "meet"
version = "1.24.0"
version = "1.23.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
+47 -109
View File
@@ -1,12 +1,12 @@
{
"name": "meet",
"version": "1.24.0",
"version": "1.23.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "meet",
"version": "1.24.0",
"version": "1.23.0",
"dependencies": {
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
"@fontsource-variable/lexend": "5.2.11",
@@ -15,11 +15,11 @@
"@livekit/components-react": "2.9.21",
"@livekit/components-styles": "1.2.0",
"@livekit/track-processors": "0.7.2",
"@mediapipe/tasks-vision": "0.10.35",
"@mediapipe/tasks-vision": "0.10.14",
"@pandacss/preset-panda": "1.11.3",
"@react-types/overlays": "3.10.0",
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.1",
"@tanstack/react-query": "5.101.0",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"derive-valtio": "0.2.0",
@@ -29,14 +29,14 @@
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.20.0",
"posthog-js": "1.395.0",
"livekit-client": "2.19.2",
"posthog-js": "1.391.2",
"react": "18.3.1",
"react-aria": "3.50.0",
"react-aria-components": "1.19.0",
"react-aria": "3.49.0",
"react-aria-components": "1.18.0",
"react-dom": "18.3.1",
"react-i18next": "17.0.8",
"react-stately": "3.48.0",
"react-stately": "3.47.0",
"use-sound": "5.0.0",
"valtio": "2.3.2",
"wouter": "3.10.0"
@@ -112,62 +112,6 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@adobe/react-spectrum/node_modules/react-aria": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.49.0.tgz",
"integrity": "sha512-4+oK9FwJQWYhyA5zLfj/feOGY0zZbkE1muoF4gyxMroHVypjcYaRSTlJwvxph2zIlxt757KX6xIK2wJ5Aw1Kog==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.2",
"@internationalized/number": "^3.6.7",
"@internationalized/string": "^3.2.9",
"@react-types/shared": "^3.35.0",
"@swc/helpers": "^0.5.0",
"aria-hidden": "^1.2.3",
"clsx": "^2.0.0",
"react-stately": "3.47.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@adobe/react-spectrum/node_modules/react-aria-components": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.18.0.tgz",
"integrity": "sha512-FhRQjuDkH4WhgFv+O2sYTzK3JzdZTGpBeaqfRlfTo+DcSZzD8elJEkytHe7SDpcexVKeire8NVd7OruZHfCVoA==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.2",
"@react-types/shared": "^3.35.0",
"@swc/helpers": "^0.5.0",
"client-only": "^0.0.1",
"react-aria": "3.49.0",
"react-stately": "3.47.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@adobe/react-spectrum/node_modules/react-stately": {
"version": "3.47.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.47.0.tgz",
"integrity": "sha512-H3ar+SOWP920EbVg7qWfP3fZjZiwhlEJAEJQqjt+w8oKijCwFgr0+R4941PIHscOXRNRvEOjvWilitImC0DdBg==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.2",
"@internationalized/number": "^3.6.7",
"@internationalized/string": "^3.2.9",
"@react-types/shared": "^3.35.0",
"@swc/helpers": "^0.5.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
@@ -1028,9 +972,9 @@
"license": "Apache-2.0"
},
"node_modules/@livekit/protocol": {
"version": "1.46.6",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.46.6.tgz",
"integrity": "sha512-upzlHP1vi/kZ/QqALZTFskQ0ifqc2f15RKucHYOsIHJsaXvEYanG75mAb7o+Yomfs4XhQ4BaRsdY+TFHXpaqrg==",
"version": "1.45.8",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.45.8.tgz",
"integrity": "sha512-Q+l57E7w/xxOBFVWzdX5rkAZO7ffyF+rlDzNUYq2SU114+5aTyCq+PK4unaEVDNd4952Af7wteKr3sOgasGuaA==",
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "^1.10.0"
@@ -1049,18 +993,12 @@
"livekit-client": "^1.12.0 || ^2.1.0"
}
},
"node_modules/@livekit/track-processors/node_modules/@mediapipe/tasks-vision": {
"node_modules/@mediapipe/tasks-vision": {
"version": "0.10.14",
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.14.tgz",
"integrity": "sha512-vOifgZhkndgybdvoRITzRkIueWWSiCKuEUXXK6Q4FaJsFvRJuwgg++vqFUMlL0Uox62U5aEXFhHxlhV7Ja5e3Q==",
"license": "Apache-2.0"
},
"node_modules/@mediapipe/tasks-vision": {
"version": "0.10.35",
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.35.tgz",
"integrity": "sha512-HOvadwVRE6JC+45nyYhmnywnr5h/J8KZvOeUNVOG9q/0875pZgItznFB9bRTvLc264YSJqiZ1NsIpCStJw/egg==",
"license": "Apache-2.0"
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
@@ -1816,9 +1754,9 @@
}
},
"node_modules/@react-types/shared": {
"version": "3.36.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.0.tgz",
"integrity": "sha512-DkP/H0C2YjjS7gZWKNqOmU8a16qHPjQNdzMwmTq9SzplM6Iw0kVMTZ0OIoe6FOgGqa+FwMsE2QbPjh/n3g/jXQ==",
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.35.0.tgz",
"integrity": "sha512-iNWvuzEwANttpQpdlu8nPBtdHb0mcCMj1ZTH//iRB5E/14IAnyRlR25rxH7pNLyzHINsPGEKnWvpwDMCT6vziQ==",
"license": "Apache-2.0",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
@@ -2421,9 +2359,9 @@
}
},
"node_modules/@tanstack/query-core": {
"version": "5.101.1",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.1.tgz",
"integrity": "sha512-Y6Y92dkXtNqx67m2pMSxUsA3zOCwv862JexZRP8/EPwvKXMPu9m8rv43spiXWzOUIggQ3SQApttALStzhA8B4g==",
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz",
"integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2442,12 +2380,12 @@
}
},
"node_modules/@tanstack/react-query": {
"version": "5.101.1",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.1.tgz",
"integrity": "sha512-ZnONUuQKJe1bJMStXUL1s5uKN9FcfC28j5cK+iDZcdSHtUv1wtin1cGc/Oewhf2Oc4eKY7lggtpvT/AbMmhHew==",
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz",
"integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.101.1"
"@tanstack/query-core": "5.101.0"
},
"funding": {
"type": "github",
@@ -8125,13 +8063,13 @@
"license": "MIT"
},
"node_modules/livekit-client": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.20.0.tgz",
"integrity": "sha512-RIJcpvBmOmwz3jTj3rmdY6Dzr55HrhcaJjMgY+HSmoEM+yIRyA40m7r8UKv0hnZWM3z/AYhP1q8C8ciz5UWFKQ==",
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.19.2.tgz",
"integrity": "sha512-Kvk07QYDWRAbmYNLRll04ZIuxMQobW/oLPYnmR1kCy8GGHpU0gqyHf704Rz+29zfy8IJZRjKqeVbzGSKn9sumw==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/mutex": "1.1.1",
"@livekit/protocol": "1.46.6",
"@livekit/protocol": "1.45.8",
"events": "^3.3.0",
"jose": "^6.1.0",
"loglevel": "^1.9.2",
@@ -9136,13 +9074,13 @@
"license": "MIT"
},
"node_modules/posthog-js": {
"version": "1.395.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.395.0.tgz",
"integrity": "sha512-5iTb00CGt2eQUUiBQysQiX89RAbCN6wK2sDNzvs9zv0alaY8mJ0ZySrUD3LQ+XyLhgM5pCpacBuUwChqiYDLDw==",
"version": "1.391.2",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.391.2.tgz",
"integrity": "sha512-q0DZN6ljchSnAFJIXf+sQFTPlsLjTlRa+TvrL+QRb6413BGtib/MNiQy1bnwLKt8KR+f6xJYvkqdLyty9s4Aww==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@posthog/core": "^1.38.0",
"@posthog/types": "^1.391.1",
"@posthog/core": "^1.35.3",
"@posthog/types": "^1.390.2",
"core-js": "^3.38.1",
"dompurify": "^3.3.2",
"fflate": "^0.4.8",
@@ -9386,19 +9324,19 @@
}
},
"node_modules/react-aria": {
"version": "3.50.0",
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.50.0.tgz",
"integrity": "sha512-S0Os6QZk33fzUAKu1QLT9afoUaCBt1ZNdoiq0n2YMVgKIdNIQS8zxiZ8O9hYE6QyDkHKjD6q39LQZ+qaSAIgjw==",
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.49.0.tgz",
"integrity": "sha512-4+oK9FwJQWYhyA5zLfj/feOGY0zZbkE1muoF4gyxMroHVypjcYaRSTlJwvxph2zIlxt757KX6xIK2wJ5Aw1Kog==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.2",
"@internationalized/number": "^3.6.7",
"@internationalized/string": "^3.2.9",
"@react-types/shared": "^3.36.0",
"@react-types/shared": "^3.35.0",
"@swc/helpers": "^0.5.0",
"aria-hidden": "^1.2.3",
"clsx": "^2.0.0",
"react-stately": "3.48.0",
"react-stately": "3.47.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
@@ -9407,17 +9345,17 @@
}
},
"node_modules/react-aria-components": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.19.0.tgz",
"integrity": "sha512-2smSS5nqJ8cGYMQezuUXveZm7eMyHCqTN6mDpylQBYLYbdF5dxCCuW1DHn1VKLe1DybSfPvX/cZtJlDmvFfn8A==",
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.18.0.tgz",
"integrity": "sha512-FhRQjuDkH4WhgFv+O2sYTzK3JzdZTGpBeaqfRlfTo+DcSZzD8elJEkytHe7SDpcexVKeire8NVd7OruZHfCVoA==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.2",
"@react-types/shared": "^3.36.0",
"@react-types/shared": "^3.35.0",
"@swc/helpers": "^0.5.0",
"client-only": "^0.0.1",
"react-aria": "3.50.0",
"react-stately": "3.48.0"
"react-aria": "3.49.0",
"react-stately": "3.47.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
@@ -9471,15 +9409,15 @@
"license": "MIT"
},
"node_modules/react-stately": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.48.0.tgz",
"integrity": "sha512-ImicSAG+lTotAe5izcs1fz49Zk48w7pDusqYg04WaPhCoej8BJ24soMu3iLXIrsi273s4P1gZrYGrqReMfgEEA==",
"version": "3.47.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.47.0.tgz",
"integrity": "sha512-H3ar+SOWP920EbVg7qWfP3fZjZiwhlEJAEJQqjt+w8oKijCwFgr0+R4941PIHscOXRNRvEOjvWilitImC0DdBg==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.2",
"@internationalized/number": "^3.6.7",
"@internationalized/string": "^3.2.9",
"@react-types/shared": "^3.36.0",
"@react-types/shared": "^3.35.0",
"@swc/helpers": "^0.5.0",
"use-sync-external-store": "^1.6.0"
},
+8 -8
View File
@@ -1,7 +1,7 @@
{
"name": "meet",
"private": true,
"version": "1.24.0",
"version": "1.23.0",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
@@ -22,11 +22,11 @@
"@livekit/components-react": "2.9.21",
"@livekit/components-styles": "1.2.0",
"@livekit/track-processors": "0.7.2",
"@mediapipe/tasks-vision": "0.10.35",
"@mediapipe/tasks-vision": "0.10.14",
"@pandacss/preset-panda": "1.11.3",
"@react-types/overlays": "3.10.0",
"@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.101.1",
"@tanstack/react-query": "5.101.0",
"@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2",
"derive-valtio": "0.2.0",
@@ -36,14 +36,14 @@
"i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.20.0",
"posthog-js": "1.395.0",
"livekit-client": "2.19.2",
"posthog-js": "1.391.2",
"react": "18.3.1",
"react-aria": "3.50.0",
"react-aria-components": "1.19.0",
"react-aria": "3.49.0",
"react-aria-components": "1.18.0",
"react-dom": "18.3.1",
"react-i18next": "17.0.8",
"react-stately": "3.48.0",
"react-stately": "3.47.0",
"use-sound": "5.0.0",
"valtio": "2.3.2",
"wouter": "3.10.0"
+1 -1
View File
@@ -1,4 +1,4 @@
apiVersion: v2
type: application
name: meet
version: 0.0.27
version: 0.0.26
+1 -170
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" xmlns:mailappor="http://schemas.microsoft.com/office/mailappversionoverrides/1.0" xsi:type="MailApp">
<Id>{{ .id }}</Id>
<Version>1.0.0.0</Version>
<Version>0.0.2.0</Version>
<ProviderName>{{ .appName }}</ProviderName>
<DefaultLocale>fr-FR</DefaultLocale>
<DisplayName DefaultValue="{{ .appName }}"/>
@@ -205,174 +205,5 @@
</bt:String>
</bt:LongStrings>
</Resources>
<!-- ─── V1.1 override: MUST be nested inside the V1.0 block, after Resources ─── -->
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1" xsi:type="VersionOverridesV1_1">
<Requirements>
<bt:Sets DefaultMinVersion="1.8">
<bt:Set Name="Mailbox"/>
</bt:Sets>
</Requirements>
<Hosts>
<Host xsi:type="MailHost">
<DesktopFormFactor>
<SupportsSharedFolders>true</SupportsSharedFolders>
<FunctionFile resid="Commands.Url"/>
<!-- ─── Mail: Read ─────────────────────────────────────────── -->
<ExtensionPoint xsi:type="MessageReadCommandSurface">
<OfficeTab id="TabDefault">
<Group id="msgReadGroup">
<Label resid="GroupLabel"/>
<Control xsi:type="Button" id="msgReadOpenPaneButton">
<Label resid="TaskpaneButton.Label"/>
<Supertip>
<Title resid="TaskpaneButton.Label"/>
<Description resid="TaskpaneButton.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Icon.16x16"/>
<bt:Image size="32" resid="Icon.32x32"/>
<bt:Image size="80" resid="Icon.80x80"/>
</Icon>
<Action xsi:type="ShowTaskpane">
<SourceLocation resid="Taskpane.Url"/>
</Action>
</Control>
</Group>
</OfficeTab>
</ExtensionPoint>
<!-- ─── Mail: Compose ─────────────────────────────────────── -->
<ExtensionPoint xsi:type="MessageComposeCommandSurface">
<OfficeTab id="TabDefault">
<Group id="msgComposeGroup">
<Label resid="GroupLabel"/>
<Control xsi:type="Button" id="msgComposeGenerateLinkButton">
<Label resid="GenerateLink.Label"/>
<Supertip>
<Title resid="GenerateLink.Label"/>
<Description resid="GenerateLink.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Icon.16x16"/>
<bt:Image size="32" resid="Icon.32x32"/>
<bt:Image size="80" resid="Icon.80x80"/>
</Icon>
<Action xsi:type="ExecuteFunction">
<FunctionName>generateMeetingLinkFromMail</FunctionName>
</Action>
</Control>
<Control xsi:type="Button" id="msgComposeOpenPaneButton">
<Label resid="TaskpaneButton.Label"/>
<Supertip>
<Title resid="TaskpaneButton.Label"/>
<Description resid="TaskpaneButton.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Settings.16x16"/>
<bt:Image size="32" resid="Settings.32x32"/>
<bt:Image size="80" resid="Settings.80x80"/>
</Icon>
<Action xsi:type="ShowTaskpane">
<SourceLocation resid="Taskpane.Url"/>
</Action>
</Control>
</Group>
</OfficeTab>
</ExtensionPoint>
<!-- ─── Calendar: Compose (New/Edit appointment) ──────────── -->
<ExtensionPoint xsi:type="AppointmentOrganizerCommandSurface">
<OfficeTab id="TabDefault">
<Group id="apptComposeGroup">
<Label resid="GroupLabel"/>
<Control xsi:type="Button" id="apptGenerateLinkButton">
<Label resid="GenerateLink.Label"/>
<Supertip>
<Title resid="GenerateLink.Label"/>
<Description resid="GenerateLink.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Icon.16x16"/>
<bt:Image size="32" resid="Icon.32x32"/>
<bt:Image size="80" resid="Icon.80x80"/>
</Icon>
<Action xsi:type="ExecuteFunction">
<FunctionName>generateMeetingLinkFromCalendar</FunctionName>
</Action>
</Control>
<Control xsi:type="Button" id="apptOpenSettingsButton">
<Label resid="OpenSettings.Label"/>
<Supertip>
<Title resid="OpenSettings.Label"/>
<Description resid="OpenSettings.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="Settings.16x16"/>
<bt:Image size="32" resid="Settings.32x32"/>
<bt:Image size="80" resid="Settings.80x80"/>
</Icon>
<Action xsi:type="ShowTaskpane">
<SourceLocation resid="Taskpane.Url"/>
</Action>
</Control>
</Group>
</OfficeTab>
</ExtensionPoint>
</DesktopFormFactor>
</Host>
</Hosts>
<Resources>
<bt:Images>
<bt:Image id="Settings.16x16" DefaultValue="{{ .baseUrl }}/assets/settings-16.png"/>
<bt:Image id="Settings.32x32" DefaultValue="{{ .baseUrl }}/assets/settings-32.png"/>
<bt:Image id="Settings.80x80" DefaultValue="{{ .baseUrl }}/assets/settings-80.png"/>
<bt:Image id="Add.16x16" DefaultValue="{{ .baseUrl }}/assets/add-16.png"/>
<bt:Image id="Add.32x32" DefaultValue="{{ .baseUrl }}/assets/add-32.png"/>
<bt:Image id="Add.80x80" DefaultValue="{{ .baseUrl }}/assets/add-80.png"/>
<bt:Image id="Icon.16x16" DefaultValue="{{ .baseUrl }}/assets/icon-16.png"/>
<bt:Image id="Icon.32x32" DefaultValue="{{ .baseUrl }}/assets/icon-32.png"/>
<bt:Image id="Icon.80x80" DefaultValue="{{ .baseUrl }}/assets/icon-80.png"/>
</bt:Images>
<bt:Urls>
<bt:Url id="Commands.Url" DefaultValue="{{ .baseUrl }}/commands.html"/>
<bt:Url id="Taskpane.Url" DefaultValue="{{ .baseUrl }}/taskpane.html"/>
</bt:Urls>
<bt:ShortStrings>
<!-- Default (French) -->
<bt:String id="GroupLabel" DefaultValue="{{ .appName }}"/>
<bt:String id="GenerateLink.Label" DefaultValue="Ajouter un lien {{ .appName }}">
<bt:Override Locale="en-US" Value="Add a {{ .appName }} link"/>
<bt:Override Locale="de-DE" Value="{{ .appName }}-Link hinzufügen"/>
</bt:String>
<bt:String id="TaskpaneButton.Label" DefaultValue="Ouvrir les paramètres">
<bt:Override Locale="en-US" Value="Open settings"/>
<bt:Override Locale="de-DE" Value="Einstellungen öffnen"/>
</bt:String>
<bt:String id="OpenSettings.Label" DefaultValue="Paramètres">
<bt:Override Locale="en-US" Value="Settings"/>
<bt:Override Locale="de-DE" Value="Einstellungen"/>
</bt:String>
</bt:ShortStrings>
<bt:LongStrings>
<bt:String id="GenerateLink.Tooltip" DefaultValue="Génère un lien de réunion {{ .appName }} et l'insère dans l'événement.">
<bt:Override Locale="de-DE" Value="Generiert einen {{ .appName }}-Besprechungslink und fügt ihn in den Termin ein."/>
<bt:Override Locale="en-US" Value="Generates a {{ .appName }} meeting link and inserts it into the item."/>
</bt:String>
<bt:String id="TaskpaneButton.Tooltip" DefaultValue="Ouvre les paramètres de connexion {{ .appName }}.">
<bt:Override Locale="de-DE" Value="Öffnet die {{ .appName }}-Verbindungseinstellungen."/>
<bt:Override Locale="en-US" Value="Opens the {{ .appName }} connection settings."/>
</bt:String>
<bt:String id="OpenSettings.Tooltip" DefaultValue="Ouvre les paramètres de connexion {{ .appName }}.">
<bt:Override Locale="de-DE" Value="Öffnet die {{ .appName }}-Verbindungseinstellungen."/>
<bt:Override Locale="en-US" Value="Opens the {{ .appName }} connection settings."/>
</bt:String>
</bt:LongStrings>
</Resources>
</VersionOverrides>
</VersionOverrides>
</OfficeApp>
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mail_mjml",
"version": "1.24.0",
"version": "1.23.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mail_mjml",
"version": "1.24.0",
"version": "1.23.0",
"license": "MIT",
"dependencies": {
"@html-to/text-cli": "0.6.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "1.24.0",
"version": "1.23.0",
"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": "1.24.0",
"version": "1.23.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sdk",
"version": "1.24.0",
"version": "1.23.0",
"license": "ISC",
"workspaces": [
"./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sdk",
"version": "1.24.0",
"version": "1.23.0",
"author": "",
"license": "ISC",
"description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "summary"
version = "1.24.0"
version = "1.23.0"
dependencies = [
"fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0",
+1 -4
View File
@@ -202,16 +202,13 @@ class MetadataManager:
},
)
def capture(self, task_id, event_name, extra_properties=None):
def capture(self, task_id, event_name):
"""Capture analytics event with task metadata and clean up."""
if self._is_disabled or not self.has_task_id(task_id):
return
metadata = self._get_metadata(task_id)
if extra_properties:
metadata = {**metadata, **extra_properties}
if "start_time" in metadata:
metadata["execution_time"] = round(time.time() - metadata["start_time"], 2)
del metadata["start_time"]
+63 -60
View File
@@ -18,12 +18,7 @@ from requests import exceptions
from summary.core.analytics import MetadataManager, get_analytics
from summary.core.config import get_settings
from summary.core.docs_service import create_document_in_lasuite_docs
from summary.core.file_service import (
CorruptedAudioFile,
FileService,
FileServiceException,
TranscribeError,
)
from summary.core.file_service import FileService, FileServiceException, TranscribeError
from summary.core.llm_service import LLMException, LLMObservability, LLMService
from summary.core.locales import get_locale
from summary.core.models import (
@@ -152,20 +147,10 @@ def transcribe_audio(
# Mimic OpenAI's timeout settings
timeout=(60, 10 * 60),
)
if res.status_code == 400:
logger.info(
"WhisperX transcription failed, "
"likely due to a corrupted audio file: %s",
res.text,
)
raise CorruptedAudioFile("WhisperX coudln't decode the audio file.")
try:
res.raise_for_status()
except requests.exceptions.HTTPError:
logger.exception("WhisperX transcription failed")
# We reraise the error so that it can be retried by celery
raise
except requests.exceptions.HTTPError as e:
raise RuntimeError("WhisperX transcription failed, %s", res.text) from e
transcription_json: dict[str, Any] = res.json()
# We remove the "usage" key from the transcription_json dictionary
@@ -431,7 +416,7 @@ def _should_auto_create_summary(payload: TranscribeTaskJob) -> bool:
@celery.task(
max_retries=3,
queue=settings.call_webhook_queue_v2,
autoretry_for=[exceptions.RequestException],
autoretry_for=[exceptions.HTTPError],
)
def call_webhook_v2_task(
payload: dict,
@@ -445,9 +430,7 @@ def call_webhook_v2_task(
@celery.task(
bind=True,
autoretry_for=[
exceptions.RequestException,
],
autoretry_for=[exceptions.HTTPError],
max_retries=settings.celery_max_retries,
queue=settings.transcribe_queue_v2,
)
@@ -577,6 +560,12 @@ def task_retry_handler_transcript(request=None, reason=None, einfo=None, **kwarg
metadata_manager.retry(request.id)
@signals.task_failure.connect(sender=process_audio_transcribe_v2_task)
def task_failure_handler_transcript(task_id, exception=None, **kwargs):
"""Signal handler called when task execution fails permanently."""
metadata_manager.capture(task_id, settings.posthog_transcript_failure)
@signals.task_failure.connect(sender=process_audio_transcribe_v2_task)
def handle_transcribe_v2_failed(
sender,
@@ -590,26 +579,30 @@ def handle_transcribe_v2_failed(
):
"""Handle the failure of transcribe_v2_task.
Tracks the failure event in analytics and sends a failure webhook to the client.
This function is triggered when the transcribe_v2_task fails.
It sends a webhook failure payload to notify the client of the failure.
"""
logger.error(
"Transcribe task %s failed, no more retries left, sending failure webhook.",
task_id,
)
metadata_manager.capture(
task_id,
settings.posthog_transcript_failure,
{"exception_type": type(exception).__name__},
)
call_webhook_v2_task.apply_async(
args=[
TranscribeWebhookFailurePayload(
job_id=task_id,
error_code="unknown_error",
).model_dump(),
args[0]["tenant_id"],
]
)
n_retries_left = sender.max_retries - sender.request.retries - 1
if n_retries_left > 0:
logger.info(
"Transcribe task %s failed, %s retries left.",
task_id,
n_retries_left,
)
else:
logger.warn(
"Transcribe task %s failed, no more retries left, sending failure webhook.",
task_id,
)
call_webhook_v2_task.apply_async(
args=[
TranscribeWebhookFailurePayload(
job_id=task_id,
error_code="unknown_error",
).model_dump(),
args[0]["tenant_id"],
]
)
@celery.task(
@@ -673,6 +666,12 @@ def task_retry_handler_summary(request=None, reason=None, einfo=None, **kwargs):
metadata_manager.retry(request.id)
@signals.task_failure.connect(sender=summarize_v2_task)
def task_failure_handler_summary(task_id, exception=None, **kwargs):
"""Signal handler called when task execution fails permanently."""
metadata_manager.capture(task_id, settings.posthog_summary_failure)
@signals.task_failure.connect(sender=summarize_v2_task)
def handle_summarize_v2_failed(
sender,
@@ -686,23 +685,27 @@ def handle_summarize_v2_failed(
):
"""Handle the failure of summarize_v2_task.
Tracks the failure event in analytics and sends a failure webhook to the client.
This function is triggered when the summarize_v2_task fails.
It sends a webhook failure payload to notify the client of the failure.
"""
logger.warn(
"Summary task %s failed, no more retries left, sending failure webhook.",
task_id,
)
metadata_manager.capture(
task_id,
settings.posthog_summary_failure,
{"exception_type": type(exception).__name__},
)
call_webhook_v2_task.apply_async(
args=[
SummarizeWebhookFailurePayload(
job_id=task_id,
error_code="unknown_error",
).model_dump(),
args[0]["tenant_id"],
]
)
n_retries_left = sender.max_retries - sender.request.retries - 1
if n_retries_left > 0:
logger.info(
"Summary task %s failed, %s retries left.",
task_id,
n_retries_left,
)
else:
logger.warn(
"Summary task %s failed, no more retries left, sending failure webhook.",
task_id,
)
call_webhook_v2_task.apply_async(
args=[
SummarizeWebhookFailurePayload(
job_id=task_id,
error_code="unknown_error",
).model_dump(),
args[0]["tenant_id"],
]
)
-6
View File
@@ -41,12 +41,6 @@ class NoAudioInFileError(TranscribeError):
error_code = "no_audio_in_file"
class CorruptedAudioFile(TranscribeError):
"""Raised when a media file does not contain any audio."""
error_code = "corrupted_audio_file"
def _get_duration_from_packets(local_path: Path) -> float:
"""Estimate duration from audio packet timestamps."""
# Run ffprobe to inspect the first audio stream in the file.
+1 -7
View File
@@ -96,13 +96,7 @@ class TranscribeWebhookFailurePayload(BaseWebhook):
# we authorized any other string than the one in the literal
# to avoid causing a breaking change on the client side
error_code: (
Literal[
"unknown_error",
"no_audio_in_file",
"media_duration_too_long",
"corrupted_audio_file",
]
| str
Literal["unknown_error", "no_audio_in_file", "media_duration_too_long"] | str
) = Field(title="Error code", description="The error code.")
+1 -1
View File
@@ -275,7 +275,7 @@ def _build_participant_timelines(
def _build_speaker_timelines(transcription: Any) -> dict[str, list[Interval]]:
"""Build interval timelines from WhisperX transcription segments."""
intervals: dict[str, list[Interval]] = {}
segments = transcription.get("segments") or []
segments = transcription.segments if hasattr(transcription, "segments") else []
max_word_duration = settings.resolve_speaker_identities_max_word_duration
for segment in segments:
+23 -20
View File
@@ -1,6 +1,7 @@
"""Tests for the speaker-to-user assignment service."""
import math
from dataclasses import dataclass, field
from datetime import datetime
from summary.core import user_assign
@@ -16,9 +17,11 @@ from summary.core.user_assign import (
)
def make_transcription(segments: list | None = None) -> dict:
"""Build a WhisperX transcription dict, matching WhisperXResponse.model_dump()."""
return {"segments": segments or []}
@dataclass
class FakeTranscription:
"""Mimics the OpenAI Transcription pydantic model for testing."""
segments: list = field(default_factory=list)
RECORDING_START = datetime.fromisoformat("2026-03-17T15:30:33.000001")
@@ -65,7 +68,7 @@ METADATA_SINGLE_USER = {
],
}
DIARIZATION_SINGLE_SPEAKER = make_transcription(
DIARIZATION_SINGLE_SPEAKER = FakeTranscription(
segments=[
{
"start": 1.363,
@@ -179,7 +182,7 @@ class TestBuildSpeakerTimelines:
def test_segment_without_words_falls_back_to_segment_bounds(self):
"""Segments missing a `words` key use the segment start/end as one interval."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[{"start": 1.5, "end": 3.5, "speaker": "SPEAKER_00"}],
)
result = _build_speaker_timelines(transcription)
@@ -187,7 +190,7 @@ class TestBuildSpeakerTimelines:
def test_segment_with_only_none_word_timestamps_falls_back(self):
"""If every word has None start/end, fall back to segment bounds."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 1.0,
@@ -205,7 +208,7 @@ class TestBuildSpeakerTimelines:
def test_short_words_only_uses_segment_start_and_last_word_end(self):
"""With no overly long words, the interval runs segment start to end."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 1.0,
@@ -228,7 +231,7 @@ class TestBuildSpeakerTimelines:
max_word_duration = (
user_assign.settings.resolve_speaker_identities_max_word_duration
)
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -249,7 +252,7 @@ class TestBuildSpeakerTimelines:
def test_long_word_in_middle_splits_segment(self):
"""Short words around a long word produce two intervals (before-cap + after)."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -272,7 +275,7 @@ class TestBuildSpeakerTimelines:
def test_tail_word_is_capped_at_max_duration(self):
"""The trailing word's end is capped at word.start + max_word_duration."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -299,7 +302,7 @@ class TestBuildSpeakerTimelines:
update={"resolve_speaker_identities_enable_split_on_words": False},
),
)
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -319,7 +322,7 @@ class TestBuildSpeakerTimelines:
def test_multiple_speakers_keep_separate_timelines(self):
"""Segments from different speakers populate independent timeline entries."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{
"start": 0.0,
@@ -399,7 +402,7 @@ class TestResolveSpeakerIdentities:
],
"participants": [{"participantId": "user-a", "name": "Shared Mic"}],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.0, "end": 3.0, "speaker": "SPEAKER_00"},
{"start": 5.0, "end": 7.0, "speaker": "SPEAKER_01"},
@@ -442,7 +445,7 @@ class TestResolveSpeakerIdentities:
{"participantId": "user-b", "name": "Bob"},
],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.5, "end": 3.5, "speaker": "SPEAKER_00"},
{"start": 5.5, "end": 7.5, "speaker": "SPEAKER_01"},
@@ -490,7 +493,7 @@ class TestResolveSpeakerIdentities:
{"participantId": "user-b", "name": "Bob"},
],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.5, "end": 5.5, "speaker": "SPEAKER_00"},
{"start": 4.0, "end": 7.5, "speaker": "SPEAKER_01"},
@@ -526,7 +529,7 @@ class TestResolveSpeakerIdentities:
],
"participants": [{"participantId": "user-a", "name": "Brief User"}],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.0, "end": 10.0, "speaker": "SPEAKER_00"},
],
@@ -558,7 +561,7 @@ class TestResolveSpeakerIdentities:
],
"participants": [{"participantId": "user-a", "name": "Early User"}],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 0.0, "end": 3.0, "speaker": "SPEAKER_00"},
],
@@ -573,7 +576,7 @@ class TestResolveSpeakerIdentities:
"""No segments produces empty result."""
result = resolve_speaker_identities(
METADATA_SINGLE_USER,
make_transcription(segments=[]),
FakeTranscription(segments=[]),
RECORDING_START,
RECORDING_END,
)
@@ -581,7 +584,7 @@ class TestResolveSpeakerIdentities:
def test_segment_without_speaker_ignored(self):
"""Segments missing speaker key are skipped."""
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 1.0, "end": 3.0, "text": "no speaker"},
],
@@ -605,7 +608,7 @@ class TestResolveSpeakerIdentities:
],
"participants": [{"participantId": "user-a", "name": "Still Talking"}],
}
transcription = make_transcription(
transcription = FakeTranscription(
segments=[
{"start": 2.0, "end": 9.0, "speaker": "SPEAKER_00"},
],