Compare commits

...

15 Commits

Author SHA1 Message Date
lebaudantoine fdb5d447b0 🩹(backend) identify externally provisioned users to PostHog
In the external API, applications authenticate with a
client_id/secret and can provision users with only an email. In that
flow, users are not identified to PostHog, so the user DB id alone
is not enough to identify them in analytics afterwards.

The issue does not exist in the public API, where users are
authenticated and therefore already identified.

Inspired by @flochehab's approach in summary.
2026-07-10 00:07:36 +02:00
lebaudantoine 8ac4c2409d 💚(mail) update the node version for the mail building step
Broken with the dependencies update.
2026-07-09 16:22:54 +02:00
lebaudantoine e47ad42656 ⬆️(mail) update mjml to v5 and @html-to/text-cli
@html-to/text-cli was containing CVEs reported by the audit.
2026-07-09 16:22:54 +02:00
Florent Chehab 2509452c52 🗑️(settings) deprecate SUMMARY_SERVICE_VERSION=1
Add a deprecation warning when SUMMARY_SERVICE_VERSION=1 and
an endpoint is configured meaning summary service is in use.
2026-07-09 16:14:23 +02:00
Florent Chehab 1022780027 📝(upgrade) add note related to summary v1 removal
Add upgrade notes related to the summary changes in v1.23.0
2026-07-09 15:49:53 +02:00
Florent Chehab 4a1a04f86e 🔖(minor) bump release to 1.23.0 2026-07-08 10:07:09 +02:00
Florent Chehab 8702638cf3 (release) uv lock agents
In release script also run uv lock for agents.
2026-07-08 10:06:53 +02:00
Florent Chehab f88c0307ea 🐛(summary) do not save null emails in analytics
In case the email is not provided we should not update
the analytics email value.
2026-07-08 02:09:30 +02:00
Florent Chehab e7f15b50ff (summary) more precise analytics events
* Specific transcript and summary events
* Improve observability on summary tasks
2026-07-08 02:09:30 +02:00
snyk-bot 75ba2ff146 🔒️(frontend) update docker image to nginx-unprivileged:1.30.3-alpine3.23
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE323-EXPAT-17675120
- https://snyk.io/vuln/SNYK-ALPINE323-EXPAT-17675112
- https://snyk.io/vuln/SNYK-ALPINE323-EXPAT-17675114
- https://snyk.io/vuln/SNYK-ALPINE323-EXPAT-17675124
- https://snyk.io/vuln/SNYK-ALPINE323-EXPAT-17675125
2026-07-08 01:48:59 +02:00
lebaudantoine bbe2a32efc ⬆️(frontend) update the frontend build image to Node 22
Bump the Node.js version used in the frontend build image from
Node 20 to Node 22.
2026-07-08 01:39:20 +02:00
snyk-bot 86fff16eed ⬆️(frontend) upgrade @tanstack/react-query from 5.100.14 to 5.101.0
Snyk has created this PR to upgrade @tanstack/react-query from 5.100.14 to 5.101.0.
2026-07-08 01:02:50 +02:00
snyk-bot 195d2b5006 ⬆️(frontend) upgrade posthog-js from 1.386.5 to 1.387.0
Snyk has created this PR to upgrade posthog-js from 1.386.5 to 1.387.0.
2026-07-08 00:56:41 +02:00
snyk-bot 30cf264276 ⬆️(frontend) upgrade livekit-client from 2.19.0 to 2.19.2
Snyk has created this PR to upgrade livekit-client from 2.19.0 to 2.19.2.
2026-07-08 00:48:27 +02:00
lebaudantoine 945d779b45 ♻️(frontend) wrap MuteEveryoneButton with AdminOrOwnerOnly
Avoids mounting the button's hooks and rendering its logic for users who
are not admin or owner, which previously happened on every re-render
of the parent.
2026-07-07 19:11:41 +02:00
28 changed files with 2082 additions and 1028 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: "18" node-version: "22"
- name: Restore the mail templates - name: Restore the mail templates
uses: actions/cache@v5 uses: actions/cache@v5
+18
View File
@@ -8,6 +8,17 @@ and this project adheres to
## [Unreleased] ## [Unreleased]
### Changed
- 🗑️(settings) deprecate SUMMARY_SERVICE_VERSION=1
- ⬆️(mail) update mjml to v5 and @html-to/text-cli
### Fixed
- 🩹(backend) identify externally provisioned users to PostHog
## [1.23.0] - 2026-07-08
### Added ### Added
- ✨(backend) extend analytics module to support feature flags - ✨(backend) extend analytics module to support feature flags
@@ -22,6 +33,13 @@ and this project adheres to
- ✨(meet) use compatible with summary v2 #1362 - ✨(meet) use compatible with summary v2 #1362
- ♻️(backend) refactor analytics backend from Protocol to abstract class - ♻️(backend) refactor analytics backend from Protocol to abstract class
- 🔥(summary) remove call to summary enabled feature flag - 🔥(summary) remove call to summary enabled feature flag
- ♻️(frontend) wrap MuteEveryoneButton with AdminOrOwnerOnly
- ⬆️(frontend) upgrade livekit-client from 2.19.0 to 2.19.2
- ⬆️(frontend) upgrade posthog-js from 1.386.5 to 1.387.0
- ⬆️(frontend) upgrade @tanstack/react-query from 5.100.14 to 5.101.0
- ⬆️(frontend) update the frontend build image to Node 22
- 🔒️(frontend) update docker image to nginx-unprivileged:1.30.3-alpine3.23
- ✨(summary) more precise analytics events
### Fixed ### Fixed
+12
View File
@@ -15,3 +15,15 @@ the following command inside your docker container:
(Note : in your development environment, you can `make migrate`.) (Note : in your development environment, you can `make migrate`.)
## [Unreleased] ## [Unreleased]
## v1.23.0
As part of the 1.23.0 release, the legacy `api/v1` implementation has been removed from the _experimental_ Summary service and Meet has been migrated to the new `api/v2`.
**To avoid a breaking change, the Meet backend continues to use the Summary service's v1-compatible API format by default (`SUMMARY_SERVICE_VERSION` setting defaults to `1`).**
If you are deploying both Meet and Summary from this repository, you must configure the Meet backend to use the v2 API by setting the following environment variable `SUMMARY_SERVICE_VERSION=2`.
If you are upgrading only the Meet deployment while keeping an older Summary v1 compatible deployment, no action is required, as the v1-compatible API remains the default.
Note that we plan on removing the legacy `v1` summary compatibility in a future major version. If you have your own implementation for the summary service, we recommend updating its API contract and setting `SUMMARY_SERVICE_VERSION=2`.
+6
View File
@@ -113,6 +113,12 @@ update_python_version "summary"
# Update agents pyproject.toml # Update agents pyproject.toml
update_python_version "agents" update_python_version "agents"
# Run uv lock in agents
print_info "Running uv lock in agents..."
cd "src/agents"
uv lock
cd -
# Update CHANGELOG # Update CHANGELOG
print_info "Updating CHANGELOG..." print_info "Updating CHANGELOG..."
+2 -2
View File
@@ -1,5 +1,5 @@
# ---- Front-end image ---- # ---- Front-end image ----
FROM node:20-alpine AS frontend-deps FROM node:22-alpine AS frontend-deps
WORKDIR /home/frontend/ WORKDIR /home/frontend/
@@ -54,7 +54,7 @@ RUN npx webpack --mode production
# ---- Front-end image ---- # ---- Front-end image ----
FROM nginxinc/nginx-unprivileged:alpine3.23 AS frontend-production FROM nginxinc/nginx-unprivileged:1.30.3-alpine3.23 AS frontend-production
USER root USER root
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "agents" name = "agents"
version = "1.22.0" version = "1.23.0"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"livekit-agents==1.6.4", "livekit-agents==1.6.4",
+1 -1
View File
@@ -9,7 +9,7 @@ resolution-markers = [
[[package]] [[package]]
name = "agents" name = "agents"
version = "1.22.0" version = "1.23.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "livekit-agents" }, { name = "livekit-agents" },
@@ -215,5 +215,6 @@ class RoomViewSet(
"client_id": client_id, "client_id": client_id,
"external_api": True, "external_api": True,
"auth_method": auth_method, "auth_method": auth_method,
"$set": {"email": self.request.user.email},
}, },
) )
+15
View File
@@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
import json import json
import warnings
from os import path from os import path
from socket import gethostbyname, gethostname from socket import gethostbyname, gethostname
@@ -1128,6 +1129,20 @@ class Base(Configuration):
"FILE_UPLOAD_TMP_PATH cannot be the same as FILE_UPLOAD_PATH" "FILE_UPLOAD_TMP_PATH cannot be the same as FILE_UPLOAD_PATH"
) )
if (
cls.SUMMARY_SERVICE_VERSION == 1
and cls.SUMMARY_SERVICE_ENDPOINT is not None
):
warnings.warn(
"SUMMARY_SERVICE_VERSION=1 is deprecated. "
"The legacy v1 API has been removed from the experimental "
"Summary service. Please update your Summary service deployment to "
"the v2 API and set SUMMARY_SERVICE_VERSION=2.",
# We use UserWarning to make sure it shows up in production deployment
UserWarning,
stacklevel=2,
)
# The SENTRY_DSN setting should be available to activate sentry for an environment # The SENTRY_DSN setting should be available to activate sentry for an environment
if cls.SENTRY_DSN is not None: if cls.SENTRY_DSN is not None:
sentry_sdk.init( sentry_sdk.init(
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "uv_build"
[project] [project]
name = "meet" name = "meet"
version = "1.22.0" version = "1.23.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }] authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [ classifiers = [
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
+1 -1
View File
@@ -1187,7 +1187,7 @@ wheels = [
[[package]] [[package]]
name = "meet" name = "meet"
version = "1.22.0" version = "1.23.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiohttp" }, { name = "aiohttp" },
+2 -2
View File
@@ -1,4 +1,4 @@
FROM node:20-alpine AS frontend-deps FROM node:22-alpine AS frontend-deps
USER node USER node
@@ -39,7 +39,7 @@ ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
RUN npm run build RUN npm run build
# ---- Front-end image ---- # ---- Front-end image ----
FROM nginxinc/nginx-unprivileged:alpine3.23 AS frontend-production FROM nginxinc/nginx-unprivileged:1.30.3-alpine3.23 AS frontend-production
USER root USER root
+20 -20
View File
@@ -1,12 +1,12 @@
{ {
"name": "meet", "name": "meet",
"version": "1.22.0", "version": "1.23.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "meet", "name": "meet",
"version": "1.22.0", "version": "1.23.0",
"dependencies": { "dependencies": {
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6", "@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
"@fontsource-variable/lexend": "5.2.11", "@fontsource-variable/lexend": "5.2.11",
@@ -18,7 +18,7 @@
"@pandacss/preset-panda": "1.11.3", "@pandacss/preset-panda": "1.11.3",
"@react-types/overlays": "3.10.0", "@react-types/overlays": "3.10.0",
"@remixicon/react": "4.9.0", "@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.100.14", "@tanstack/react-query": "5.101.0",
"@timephy/rnnoise-wasm": "1.0.0", "@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2", "crisp-sdk-web": "1.1.2",
"derive-valtio": "0.2.0", "derive-valtio": "0.2.0",
@@ -28,8 +28,8 @@
"i18next-browser-languagedetector": "8.2.1", "i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0", "i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1", "i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.19.0", "livekit-client": "2.19.2",
"posthog-js": "1.386.5", "posthog-js": "1.387.0",
"react": "18.3.1", "react": "18.3.1",
"react-aria": "3.49.0", "react-aria": "3.49.0",
"react-aria-components": "1.18.0", "react-aria-components": "1.18.0",
@@ -2357,9 +2357,9 @@
} }
}, },
"node_modules/@tanstack/query-core": { "node_modules/@tanstack/query-core": {
"version": "5.100.14", "version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.14.tgz", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz",
"integrity": "sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==", "integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "github", "type": "github",
@@ -2378,12 +2378,12 @@
} }
}, },
"node_modules/@tanstack/react-query": { "node_modules/@tanstack/react-query": {
"version": "5.100.14", "version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.14.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz",
"integrity": "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==", "integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@tanstack/query-core": "5.100.14" "@tanstack/query-core": "5.101.0"
}, },
"funding": { "funding": {
"type": "github", "type": "github",
@@ -8035,9 +8035,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/livekit-client": { "node_modules/livekit-client": {
"version": "2.19.0", "version": "2.19.2",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.19.0.tgz", "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.19.2.tgz",
"integrity": "sha512-aolY1XDAtx0nHKBNm29W9OhzBnSz1CP5kq3phvRhFfi1NbvMXs8tcACjAkZTnIKgihkp+BiJScZZ3tZv0Gz8sA==", "integrity": "sha512-Kvk07QYDWRAbmYNLRll04ZIuxMQobW/oLPYnmR1kCy8GGHpU0gqyHf704Rz+29zfy8IJZRjKqeVbzGSKn9sumw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@livekit/mutex": "1.1.1", "@livekit/mutex": "1.1.1",
@@ -9033,13 +9033,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/posthog-js": { "node_modules/posthog-js": {
"version": "1.386.5", "version": "1.387.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.386.5.tgz", "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.387.0.tgz",
"integrity": "sha512-ASejQQf5Xw0XolMwH/KCLZlZtoyLK6VsvORwGagAtfa8/ElIOF76BMQspkDsRTybEI+uzHqRDm2m/na1Dki2mA==", "integrity": "sha512-Pv1jUMySMN62zoAxdJBJPV8n62lkHdjuWhpeU7izczc5Dqbx3hhqO2hkrNTI8Yx1ezmWk2qUHZs03FuOBubdFQ==",
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"dependencies": { "dependencies": {
"@posthog/core": "^1.32.3", "@posthog/core": "^1.33.0",
"@posthog/types": "^1.386.3", "@posthog/types": "^1.387.0",
"core-js": "^3.38.1", "core-js": "^3.38.1",
"dompurify": "^3.3.2", "dompurify": "^3.3.2",
"fflate": "^0.4.8", "fflate": "^0.4.8",
+4 -4
View File
@@ -1,7 +1,7 @@
{ {
"name": "meet", "name": "meet",
"private": true, "private": true,
"version": "1.22.0", "version": "1.23.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "panda codegen && vite", "dev": "panda codegen && vite",
@@ -25,7 +25,7 @@
"@pandacss/preset-panda": "1.11.3", "@pandacss/preset-panda": "1.11.3",
"@react-types/overlays": "3.10.0", "@react-types/overlays": "3.10.0",
"@remixicon/react": "4.9.0", "@remixicon/react": "4.9.0",
"@tanstack/react-query": "5.100.14", "@tanstack/react-query": "5.101.0",
"@timephy/rnnoise-wasm": "1.0.0", "@timephy/rnnoise-wasm": "1.0.0",
"crisp-sdk-web": "1.1.2", "crisp-sdk-web": "1.1.2",
"derive-valtio": "0.2.0", "derive-valtio": "0.2.0",
@@ -35,8 +35,8 @@
"i18next-browser-languagedetector": "8.2.1", "i18next-browser-languagedetector": "8.2.1",
"i18next-parser": "9.4.0", "i18next-parser": "9.4.0",
"i18next-resources-to-backend": "1.2.1", "i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.19.0", "livekit-client": "2.19.2",
"posthog-js": "1.386.5", "posthog-js": "1.387.0",
"react": "18.3.1", "react": "18.3.1",
"react-aria": "3.49.0", "react-aria": "3.49.0",
"react-aria-components": "1.18.0", "react-aria-components": "1.18.0",
@@ -1,23 +1,20 @@
import { Button } from '@/primitives' import { Button } from '@/primitives'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { Participant } from 'livekit-client' import type { Participant } from 'livekit-client'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
import { useMuteParticipants } from '@/features/rooms/api/muteParticipants' import { useMuteParticipants } from '@/features/rooms/api/muteParticipants'
import { RiMicOffLine } from '@remixicon/react' import { RiMicOffLine } from '@remixicon/react'
import { css } from '@/styled-system/css' import { css } from '@/styled-system/css'
import { AdminOrOwnerOnly } from '@/features/rooms/components/AdminOrOwnerOnly'
type MuteEveryoneButtonProps = { type MuteEveryoneButtonProps = {
participants: Array<Participant> participants: Array<Participant>
} }
export const MuteEveryoneButton = ({ const MuteEveryoneButtonInner = ({ participants }: MuteEveryoneButtonProps) => {
participants,
}: MuteEveryoneButtonProps) => {
const { muteParticipants } = useMuteParticipants() const { muteParticipants } = useMuteParticipants()
const { t } = useTranslation('rooms') const { t } = useTranslation('rooms')
const isAdminOrOwner = useIsAdminOrOwner() if (!participants.length) return null
if (!isAdminOrOwner || !participants.length) return null
return ( return (
<Button <Button
@@ -36,3 +33,13 @@ export const MuteEveryoneButton = ({
</Button> </Button>
) )
} }
export const MuteEveryoneButton = ({
participants,
}: MuteEveryoneButtonProps) => {
return (
<AdminOrOwnerOnly>
<MuteEveryoneButtonInner participants={participants} />
</AdminOrOwnerOnly>
)
}
+1 -1
View File
@@ -6,4 +6,4 @@ DIR_MAILS="../backend/core/templates/mail/html/"
if [ ! -d "${DIR_MAILS}" ]; then if [ ! -d "${DIR_MAILS}" ]; then
mkdir -p "${DIR_MAILS}"; mkdir -p "${DIR_MAILS}";
fi fi
mjml mjml/*.mjml -o "${DIR_MAILS}"; mjml mjml/*.mjml -o "${DIR_MAILS}" --config.allowIncludes true;
+7 -2
View File
@@ -2,6 +2,11 @@
<mj-include path="./partial/header.mjml" /> <mj-include path="./partial/header.mjml" />
<mj-body mj-class="bg--blue-100"> <mj-body mj-class="bg--blue-100">
<!--
We load django tags here so they appear in the body of the HTML output.
This ensures html-to-text also includes them in the plain text template.
-->
<mj-raw>{% load i18n static extra_tags %}</mj-raw>
<mj-wrapper css-class="wrapper" padding="0 40px 40px 40px"> <mj-wrapper css-class="wrapper" padding="0 40px 40px 40px">
<mj-section css-class="wrapper-logo"> <mj-section css-class="wrapper-logo">
<mj-column> <mj-column>
@@ -59,7 +64,7 @@
</mj-column> </mj-column>
</mj-section> </mj-section>
</mj-wrapper> </mj-wrapper>
</mj-body>
<mj-include path="./partial/footer.mjml" /> <mj-include path="./partial/footer.mjml" />
</mj-body>
</mjml> </mjml>
+4 -9
View File
@@ -1,21 +1,16 @@
<mj-head> <mj-head>
<mj-title>{{ title }}</mj-title> <mj-title>{{ title }}</mj-title>
<mj-preview> <mj-preview>{{ title }}</mj-preview>
<!-- <mj-font name="Roboto" href="https://fonts.bunny.net/css?family=roboto:400,700,900" />
We load django tags here, in this way there are put within the body in html output
so the html-to-text command includes it within its output
-->
{% load i18n static extra_tags %}
{{ title }}
</mj-preview>
<mj-attributes> <mj-attributes>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700;900&display=swap" />
<mj-all <mj-all
font-family="Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif" font-family="Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif"
font-size="16px" font-size="16px"
line-height="1.5em" line-height="1.5em"
color="#3A3A3A" color="#3A3A3A"
/> />
<mj-text font-family="Roboto, sans-serif" />
<mj-button font-family="Roboto, sans-serif" />
</mj-attributes> </mj-attributes>
<mj-style> <mj-style>
/* Reset */ /* Reset */
+5
View File
@@ -2,6 +2,11 @@
<mj-include path="./partial/header.mjml" /> <mj-include path="./partial/header.mjml" />
<mj-body mj-class="bg--blue-100"> <mj-body mj-class="bg--blue-100">
<!--
We load django tags here so they appear in the body of the HTML output.
This ensures html-to-text also includes them in the plain text template.
-->
<mj-raw>{% load i18n static extra_tags %}</mj-raw>
<mj-wrapper css-class="wrapper" padding="5px 25px 0px 25px"> <mj-wrapper css-class="wrapper" padding="5px 25px 0px 25px">
<mj-section css-class="wrapper-logo"> <mj-section css-class="wrapper-logo">
<mj-column> <mj-column>
+1904 -942
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,11 +1,11 @@
{ {
"name": "mail_mjml", "name": "mail_mjml",
"version": "1.22.0", "version": "1.23.0",
"description": "An util to generate html and text django's templates from mjml templates", "description": "An util to generate html and text django's templates from mjml templates",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@html-to/text-cli": "0.5.4", "@html-to/text-cli": "0.6.0",
"mjml": "4.18.0" "mjml": "5.4.0"
}, },
"private": true, "private": true,
"scripts": { "scripts": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "sdk", "name": "sdk",
"version": "1.22.0", "version": "1.23.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sdk", "name": "sdk",
"version": "1.22.0", "version": "1.23.0",
"license": "ISC", "license": "ISC",
"workspaces": [ "workspaces": [
"./library", "./library",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "sdk", "name": "sdk",
"version": "1.22.0", "version": "1.23.0",
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"description": "", "description": "",
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "summary" name = "summary"
version = "1.22.0" version = "1.23.0"
dependencies = [ dependencies = [
"fastapi[standard]>=0.105.0", "fastapi[standard]>=0.105.0",
"uvicorn>=0.24.0", "uvicorn>=0.24.0",
+13 -14
View File
@@ -59,17 +59,16 @@ async def create_transcribe_task_v2(
] ]
) )
properties = {}
if request.user_email:
properties["$set"] = {"email": request.user_email}
# We track the request, this also properly initializes the user in the # We track the request, this also properly initializes the user in the
# analytics system, so that later feature flags work properly # analytics system, so that later feature flags work properly
analytics.capture( analytics.capture(
settings.posthog_event_request, settings.posthog_transcript_request,
request.user_sub, request.user_sub,
properties={ properties=properties,
"kind": "transcribe",
"$set": {
"email": request.user_email,
},
},
) )
return TranscribeWebhookPendingPayload(job_id=task.id).model_dump() return TranscribeWebhookPendingPayload(job_id=task.id).model_dump()
@@ -93,15 +92,15 @@ async def create_summarize_task_v2(
# We track the request, this also properly initializes the user in the # We track the request, this also properly initializes the user in the
# analytics system, so that later feature flags work properly # analytics system, so that later feature flags work properly
properties = {}
if request.user_email:
properties["$set"] = {"email": request.user_email}
analytics.capture( analytics.capture(
settings.posthog_event_request, settings.posthog_summary_request,
request.user_sub, request.user_sub,
properties={ properties=properties,
"kind": "summarize",
"$set": {
"email": request.user_email,
},
},
) )
return SummarizeWebhookPendingPayload(job_id=task.id).model_dump() return SummarizeWebhookPendingPayload(job_id=task.id).model_dump()
+11 -6
View File
@@ -11,7 +11,7 @@ from celery.utils.log import get_task_logger
from posthog import Posthog from posthog import Posthog
from summary.core.config import get_settings from summary.core.config import get_settings
from summary.core.models import TranscribeTaskJob from summary.core.models import SummarizeTaskJob, TranscribeTaskJob
logger = get_task_logger(__name__) logger = get_task_logger(__name__)
settings = get_settings() settings = get_settings()
@@ -109,19 +109,15 @@ class MetadataManager:
"""Check if task_id exists in tasks metadata cache.""" """Check if task_id exists in tasks metadata cache."""
return self._redis.exists(self._get_redis_key(task_id)) return self._redis.exists(self._get_redis_key(task_id))
def create(self, task_id: str, task_payload: TranscribeTaskJob): def create(self, task_id: str, task_payload: TranscribeTaskJob | SummarizeTaskJob):
"""Create initial metadata entry for a new task.""" """Create initial metadata entry for a new task."""
if self._is_disabled or self.has_task_id(task_id): if self._is_disabled or self.has_task_id(task_id):
return return
start_time = time.time() start_time = time.time()
parts = urlsplit(task_payload.cloud_storage_url)
clean_url = urlunsplit((parts.scheme, parts.netloc, parts.path, "", ""))
initial_metadata = { initial_metadata = {
"start_time": start_time, "start_time": start_time,
"asr_model": settings.whisperx_asr_model,
"retries": 0, "retries": 0,
"filename": clean_url,
"sub": task_payload.user_sub, "sub": task_payload.user_sub,
# avoid None in redis, it shouldn't happen anyway in prod # avoid None in redis, it shouldn't happen anyway in prod
"email": task_payload.user_email or "", "email": task_payload.user_email or "",
@@ -129,6 +125,15 @@ class MetadataManager:
"queuing_time": round(start_time - task_payload.received_at.timestamp(), 2), "queuing_time": round(start_time - task_payload.received_at.timestamp(), 2),
} }
if isinstance(task_payload, TranscribeTaskJob):
parts = urlsplit(task_payload.cloud_storage_url)
clean_url = urlunsplit((parts.scheme, parts.netloc, parts.path, "", ""))
initial_metadata["source_url"] = clean_url
initial_metadata["asr_model"] = settings.whisperx_asr_model
elif isinstance(task_payload, SummarizeTaskJob):
initial_metadata["content_length"] = len(task_payload.content)
initial_metadata["llm_model"] = settings.llm_model
self._save_metadata(task_id, initial_metadata) self._save_metadata(task_id, initial_metadata)
def retry(self, task_id): def retry(self, task_id):
+26 -5
View File
@@ -542,28 +542,28 @@ def process_audio_transcribe_v2_task(
call_webhook_v2_task.apply_async( call_webhook_v2_task.apply_async(
args=[success_payload.model_dump(), payload.tenant_id] args=[success_payload.model_dump(), payload.tenant_id]
) )
metadata_manager.capture(job_id, settings.posthog_event_success) metadata_manager.capture(job_id, settings.posthog_transcript_success)
return success_payload.model_dump() return success_payload.model_dump()
@signals.task_prerun.connect(sender=process_audio_transcribe_v2_task) @signals.task_prerun.connect(sender=process_audio_transcribe_v2_task)
def task_started(task_id=None, task=None, args=None, **kwargs): def task_started_transcript(task_id=None, task=None, args=None, **kwargs):
"""Signal handler called before task execution begins.""" """Signal handler called before task execution begins."""
if args: if args:
metadata_manager.create(task_id, TranscribeTaskJob.model_validate(args[0])) metadata_manager.create(task_id, TranscribeTaskJob.model_validate(args[0]))
@signals.task_retry.connect(sender=process_audio_transcribe_v2_task) @signals.task_retry.connect(sender=process_audio_transcribe_v2_task)
def task_retry_handler(request=None, reason=None, einfo=None, **kwargs): def task_retry_handler_transcript(request=None, reason=None, einfo=None, **kwargs):
"""Signal handler called when task execution retries.""" """Signal handler called when task execution retries."""
metadata_manager.retry(request.id) metadata_manager.retry(request.id)
@signals.task_failure.connect(sender=process_audio_transcribe_v2_task) @signals.task_failure.connect(sender=process_audio_transcribe_v2_task)
def task_failure_handler(task_id, exception=None, **kwargs): def task_failure_handler_transcript(task_id, exception=None, **kwargs):
"""Signal handler called when task execution fails permanently.""" """Signal handler called when task execution fails permanently."""
metadata_manager.capture(task_id, settings.posthog_event_failure) metadata_manager.capture(task_id, settings.posthog_transcript_failure)
@signals.task_failure.connect(sender=process_audio_transcribe_v2_task) @signals.task_failure.connect(sender=process_audio_transcribe_v2_task)
@@ -648,9 +648,30 @@ def summarize_v2_task(
call_webhook_v2_task.apply_async( call_webhook_v2_task.apply_async(
args=[success_payload.model_dump(), payload.tenant_id] args=[success_payload.model_dump(), payload.tenant_id]
) )
metadata_manager.capture(job_id, settings.posthog_summary_success)
return success_payload.model_dump() return success_payload.model_dump()
@signals.task_prerun.connect(sender=summarize_v2_task)
def task_started_summary(task_id=None, task=None, args=None, **kwargs):
"""Signal handler called before task execution begins."""
if args:
metadata_manager.create(task_id, SummarizeTaskJob.model_validate(args[0]))
@signals.task_retry.connect(sender=summarize_v2_task)
def task_retry_handler_summary(request=None, reason=None, einfo=None, **kwargs):
"""Signal handler called when task execution retries."""
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) @signals.task_failure.connect(sender=summarize_v2_task)
def handle_summarize_v2_failed( def handle_summarize_v2_failed(
sender, sender,
+6 -3
View File
@@ -130,9 +130,12 @@ class Settings(BaseSettings):
posthog_enabled: bool = False posthog_enabled: bool = False
posthog_api_key: Optional[str] = None posthog_api_key: Optional[str] = None
posthog_api_host: Optional[str] = "https://eu.i.posthog.com" posthog_api_host: Optional[str] = "https://eu.i.posthog.com"
posthog_event_failure: str = "transcript-failure" posthog_transcript_request: str = "transcript-request"
posthog_event_success: str = "transcript-success" posthog_transcript_failure: str = "transcript-failure"
posthog_event_request: str = "transcript-request" posthog_transcript_success: str = "transcript-success"
posthog_summary_request: str = "summary-request"
posthog_summary_failure: str = "summary-failure"
posthog_summary_success: str = "summary-success"
# Langfuse (LLM Observability) # Langfuse (LLM Observability)
langfuse_enabled: bool = False langfuse_enabled: bool = False