Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f2308e040 | |||
| a9e9cffc21 | |||
| 398afe17c5 | |||
| 983c7fae9f | |||
| 4ad6b79624 | |||
| 0d51cdc948 | |||
| 0f73bccfdd | |||
| 244ed6e9ca | |||
| ebf7a1956e | |||
| c2e6927978 | |||
| 1b4a144650 | |||
| 7004b7e2c8 | |||
| 848893a79f | |||
| 849f8ac08c | |||
| 9fd264ae0e | |||
| bfdf5548a0 | |||
| 0102b428f1 | |||
| 91a8d85db3 | |||
| e301c5deed | |||
| 67b046c9ba | |||
| 1b3b9ff858 | |||
| 64fca531fa | |||
| e73b0777e3 | |||
| 0489033e03 | |||
| 04710f5ecd | |||
| 4afa03d7c8 | |||
| e57685ebe3 | |||
| 381b7c4eb7 | |||
| e0fe78e3fa | |||
| 2a85f45e69 | |||
| 8aa035ae00 | |||
| d39d02d445 | |||
| e28e0024be | |||
| c492243ab1 | |||
| 38e6adf811 | |||
| 88dbfae925 | |||
| bf7e17921d | |||
| 3393858552 | |||
| e7c12b4253 | |||
| 45a69aaaf0 | |||
| 9565e7a5d2 | |||
| a02cd26044 | |||
| 34353b5cba | |||
| b34a1c8695 | |||
| 7c16852a56 | |||
| bcdc93f405 | |||
| fa588ee147 |
@@ -45,6 +45,7 @@ COMPOSE_RUN = $(COMPOSE) run --rm
|
||||
COMPOSE_RUN_APP = $(COMPOSE_RUN) app-dev
|
||||
COMPOSE_RUN_CROWDIN = $(COMPOSE_RUN) crowdin crowdin
|
||||
WAIT_DB = @$(COMPOSE_RUN) dockerize -wait tcp://$(DB_HOST):$(DB_PORT) -timeout 60s
|
||||
WAIT_MINIO = @$(COMPOSE_RUN) dockerize -wait tcp://minio:9000 -timeout 60s
|
||||
|
||||
# -- Backend
|
||||
MANAGE = $(COMPOSE_RUN_APP) python manage.py
|
||||
@@ -53,6 +54,21 @@ MAIL_NPM = $(COMPOSE_RUN) -w /app/src/mail node npm
|
||||
# -- Frontend
|
||||
PATH_FRONT = ./src/frontend
|
||||
|
||||
# -- MinIO / Webhook
|
||||
MINIO_ALIAS ?= meet
|
||||
MINIO_ENDPOINT ?= http://127.0.0.1:9000
|
||||
MINIO_ACCESS_KEY ?= meet
|
||||
MINIO_SECRET_KEY ?= password
|
||||
MINIO_BUCKET ?= meet-media-storage
|
||||
MINIO_WEBHOOK_NAME ?= recording
|
||||
|
||||
MINIO_WEBHOOK_ENDPOINT ?= http://app-dev:8000/api/v1.0/recordings/storage-hook/
|
||||
|
||||
MINIO_QUEUE_DIR ?= /data/minio/events
|
||||
MINIO_QUEUE_LIMIT ?= 100000
|
||||
|
||||
STORAGE_EVENT_TOKEN ?= password
|
||||
|
||||
# ==============================================================================
|
||||
# RULES
|
||||
|
||||
@@ -71,7 +87,8 @@ create-env-files: \
|
||||
env.d/development/common \
|
||||
env.d/development/crowdin \
|
||||
env.d/development/postgresql \
|
||||
env.d/development/kc_postgresql
|
||||
env.d/development/kc_postgresql \
|
||||
env.d/development/summary
|
||||
.PHONY: create-env-files
|
||||
|
||||
bootstrap: ## Prepare Docker images for the project
|
||||
@@ -116,9 +133,15 @@ run-backend: ## start only the backend application and all needed services
|
||||
@$(WAIT_DB)
|
||||
.PHONY: run-backend
|
||||
|
||||
run-summary: ## start only the summary application and all needed services
|
||||
@$(COMPOSE) up --force-recreate -d celery-summary-transcribe
|
||||
@$(COMPOSE) up --force-recreate -d celery-summary-summarize
|
||||
.PHONY: run-summary
|
||||
|
||||
run:
|
||||
run: ## start the wsgi (production) and development server
|
||||
@$(MAKE) run-backend
|
||||
@$(MAKE) run-summary
|
||||
@$(COMPOSE) up --force-recreate -d frontend
|
||||
.PHONY: run
|
||||
|
||||
@@ -130,6 +153,55 @@ stop: ## stop the development server using Docker
|
||||
@$(COMPOSE) stop
|
||||
.PHONY: stop
|
||||
|
||||
# -- MinIO webhook (configuration & events)
|
||||
minio-wait: ## wait for minio to be ready
|
||||
@echo "$(BOLD)Waiting for MinIO$(RESET)"
|
||||
$(WAIT_MINIO)
|
||||
.PHONY: minio-wait
|
||||
|
||||
minio-queue-dir: minio-wait ## ensure queue dir exists in MinIO container
|
||||
@echo "$(BOLD)Ensuring MinIO queue dir$(RESET)"
|
||||
@$(COMPOSE) exec minio sh -lc 'mkdir -p $(MINIO_QUEUE_DIR) && chmod -R 777 $(dir $(MINIO_QUEUE_DIR)) || true'
|
||||
.PHONY: minio-queue-dir
|
||||
|
||||
minio-alias: minio-wait ## set mc alias to MinIO
|
||||
@echo "$(BOLD)Setting mc alias $(MINIO_ALIAS) -> $(MINIO_ENDPOINT)$(RESET)"
|
||||
@mc alias set $(MINIO_ALIAS) $(MINIO_ENDPOINT) $(MINIO_ACCESS_KEY) $(MINIO_SECRET_KEY) >/dev/null
|
||||
.PHONY: minio-alias
|
||||
|
||||
minio-webhook-config: minio-alias minio-queue-dir ## configure webhook on MinIO
|
||||
@echo "$(BOLD)Configuring MinIO webhook $(MINIO_WEBHOOK_NAME)$(RESET)"
|
||||
@mc admin config set $(MINIO_ALIAS) notify_webhook:$(MINIO_WEBHOOK_NAME) \
|
||||
endpoint="$(MINIO_WEBHOOK_ENDPOINT)" \
|
||||
auth_token="Bearer $(STORAGE_EVENT_TOKEN)" \
|
||||
queue_dir="$(MINIO_QUEUE_DIR)" \
|
||||
queue_limit="$(MINIO_QUEUE_LIMIT)"
|
||||
.PHONY: minio-webhook-config
|
||||
|
||||
minio-restart: minio-alias ## restart MinIO after config change
|
||||
@echo "$(BOLD)Restarting MinIO service$(RESET)"
|
||||
@mc admin service restart $(MINIO_ALIAS)
|
||||
.PHONY: minio-restart
|
||||
|
||||
minio-events-reset: minio-alias ## remove all bucket notifications
|
||||
@echo "$(BOLD)Removing existing bucket events on $(MINIO_BUCKET)$(RESET)"
|
||||
@mc event remove --force $(MINIO_ALIAS)/$(MINIO_BUCKET) >/dev/null || true
|
||||
.PHONY: minio-events-reset
|
||||
|
||||
minio-event-add: minio-alias ## add put event -> webhook
|
||||
@echo "$(BOLD)Adding put event -> webhook $(MINIO_WEBHOOK_NAME)$(RESET)"
|
||||
@mc event add $(MINIO_ALIAS)/$(MINIO_BUCKET) arn:minio:sqs::$(MINIO_WEBHOOK_NAME):webhook --event put
|
||||
@mc event list $(MINIO_ALIAS)/$(MINIO_BUCKET)
|
||||
.PHONY: minio-event-add
|
||||
|
||||
minio-webhook-setup: ## full setup: alias, config, restart, reset events, add event
|
||||
minio-webhook-setup: \
|
||||
minio-webhook-config \
|
||||
minio-restart \
|
||||
minio-events-reset \
|
||||
minio-event-add
|
||||
.PHONY: minio-webhook-setup
|
||||
|
||||
# -- Front
|
||||
|
||||
frontend-development-install: ## install the frontend locally
|
||||
@@ -246,6 +318,9 @@ env.d/development/postgresql:
|
||||
env.d/development/kc_postgresql:
|
||||
cp -n env.d/development/kc_postgresql.dist env.d/development/kc_postgresql
|
||||
|
||||
env.d/development/summary:
|
||||
cp -n env.d/development/summary.dist env.d/development/summary
|
||||
|
||||
# -- Internationalization
|
||||
|
||||
env.d/development/crowdin:
|
||||
|
||||
@@ -221,3 +221,64 @@ services:
|
||||
- ./docker/livekit/out:/out
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
redis-summary:
|
||||
image: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
app-summary-dev:
|
||||
build:
|
||||
context: src/summary
|
||||
target: development
|
||||
args:
|
||||
DOCKER_USER: ${DOCKER_USER:-1000}
|
||||
user: ${DOCKER_USER:-1000}
|
||||
env_file:
|
||||
- env.d/development/summary
|
||||
ports:
|
||||
- "8001:8000"
|
||||
volumes:
|
||||
- ./src/summary:/app
|
||||
depends_on:
|
||||
- redis-summary
|
||||
|
||||
celery-summary-transcribe:
|
||||
container_name: celery-summary-transcribe
|
||||
build:
|
||||
context: ./src/summary
|
||||
dockerfile: Dockerfile
|
||||
target: production
|
||||
command: celery -A summary.core.celery_worker worker --pool=solo --loglevel=debug -Q transcribe-queue
|
||||
env_file:
|
||||
- env.d/development/summary
|
||||
volumes:
|
||||
- ./src/summary:/app
|
||||
depends_on:
|
||||
- redis-summary
|
||||
- app-summary-dev
|
||||
- minio
|
||||
develop:
|
||||
watch:
|
||||
- action: rebuild
|
||||
path: ./src/summary
|
||||
|
||||
celery-summary-summarize:
|
||||
container_name: celery-summary-summarize
|
||||
build:
|
||||
context: ./src/summary
|
||||
dockerfile: Dockerfile
|
||||
target: production
|
||||
command: celery -A summary.core.celery_worker worker --pool=solo --loglevel=debug -Q summarize-queue
|
||||
env_file:
|
||||
- env.d/development/summary
|
||||
volumes:
|
||||
- ./src/summary:/app
|
||||
depends_on:
|
||||
- redis-summary
|
||||
- app-summary-dev
|
||||
- minio
|
||||
develop:
|
||||
watch:
|
||||
- action: rebuild
|
||||
path: ./src/summary
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM livekit/livekit-server:v1.8.0
|
||||
FROM livekit/livekit-server:v1.9.0
|
||||
|
||||
# We inject the nip.io certificate manually because the livekit chart doesn't support volume mounting
|
||||
COPY rootCA.pem /etc/ssl/certs/
|
||||
|
||||
@@ -142,4 +142,4 @@ Once the Kubernetes cluster is ready, start the application stack locally:
|
||||
$ make start-tilt-keycloak
|
||||
```
|
||||
|
||||
Monitor Tilt’s progress at [http://localhost:10350/](http://localhost:10350/). After Tilt actions finish, you can access the app at [https://visio.127.0.0.1.nip.io/](https://visio.127.0.0.1.nip.io/).
|
||||
Monitor Tilt’s progress at [http://localhost:10350/](http://localhost:10350/). After Tilt actions finish, you can access the app at [https://meet.127.0.0.1.nip.io/](https://meet.127.0.0.1.nip.io/).
|
||||
|
||||
@@ -53,6 +53,11 @@ LIVEKIT_VERIFY_SSL=False
|
||||
ALLOW_UNREGISTERED_ROOMS=False
|
||||
|
||||
# Recording
|
||||
RECORDING_ENABLE=True
|
||||
RECORDING_STORAGE_EVENT_ENABLE=True
|
||||
RECORDING_STORAGE_EVENT_TOKEN=password
|
||||
SUMMARY_SERVICE_ENDPOINT=http://app-summary-dev:8000/api/v1/tasks/
|
||||
SUMMARY_SERVICE_API_TOKEN=password
|
||||
SCREEN_RECORDING_BASE_URL=http://localhost:3000/recordings
|
||||
|
||||
# Telephony
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
APP_NAME="meet-app-summary-dev"
|
||||
APP_API_TOKEN="password"
|
||||
|
||||
AWS_STORAGE_BUCKET_NAME="meet-media-storage"
|
||||
AWS_S3_ENDPOINT_URL="minio:9000"
|
||||
AWS_S3_SECURE_ACCESS=false
|
||||
|
||||
AWS_S3_ACCESS_KEY_ID="meet"
|
||||
AWS_S3_SECRET_ACCESS_KEY="password"
|
||||
|
||||
WHISPERX_BASE_URL="https://configure-your-url.com"
|
||||
WHISPERX_ASR_MODEL="large-v2"
|
||||
WHISPERX_API_KEY="your-secret-key"
|
||||
|
||||
LLM_BASE_URL="https://configure-your-url.com"
|
||||
LLM_API_KEY="dev-apikey"
|
||||
LLM_MODEL="Qwen/Qwen2.5-Coder-32B-Instruct-AWQ"
|
||||
|
||||
WEBHOOK_API_TOKEN="secret"
|
||||
WEBHOOK_URL="https://configure-your-url.com"
|
||||
|
||||
POSTHOG_API_KEY="your-posthog-key"
|
||||
POSTHOG_ENABLED="False"
|
||||
|
||||
LANGFUSE_SECRET_KEY="your-secret-key"
|
||||
LANGFUSE_PUBLIC_KEY="your-public-key"
|
||||
LANGFUSE_HOST="https://cloud.langfuse.com"
|
||||
LANFUSE_ENABLED="False"
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
[project]
|
||||
name = "agents"
|
||||
version = "0.1.23"
|
||||
version = "0.1.38"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"livekit-agents==1.2.6",
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "meet"
|
||||
version = "0.1.33"
|
||||
version = "0.1.38"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -38,7 +38,7 @@ dependencies = [
|
||||
"django-redis==6.0.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-timezone-field>=5.1",
|
||||
"django==5.2.3",
|
||||
"django==5.2.6",
|
||||
"djangorestframework==3.16.0",
|
||||
"drf_spectacular==0.28.0",
|
||||
"dockerflow==2024.4.2",
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<html lang="en" data-lk-theme="visio-light">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/icon.svg" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
</head>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.38",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "meet",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.38",
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "2.9.13",
|
||||
"@livekit/components-styles": "1.1.6",
|
||||
"@livekit/track-processors": "0.6.0",
|
||||
"@livekit/track-processors": "0.6.1",
|
||||
"@pandacss/preset-panda": "0.54.0",
|
||||
"@react-aria/toast": "3.0.5",
|
||||
"@react-types/overlays": "3.9.0",
|
||||
@@ -26,7 +26,7 @@
|
||||
"i18next-parser": "9.3.0",
|
||||
"i18next-resources-to-backend": "1.2.1",
|
||||
"libphonenumber-js": "1.12.10",
|
||||
"livekit-client": "2.15.5",
|
||||
"livekit-client": "2.15.7",
|
||||
"posthog-js": "1.256.2",
|
||||
"react": "18.3.1",
|
||||
"react-aria-components": "1.10.1",
|
||||
@@ -1281,9 +1281,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/track-processors": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/track-processors/-/track-processors-0.6.0.tgz",
|
||||
"integrity": "sha512-h0Ewdp2/u44QnfLsmhL/IBCkFJsl10eyodErOedP9yWTS4c8m8ibqBWaNH0bHDeqg4Ue+OzzUb7dogUb2nJ0Ow==",
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/track-processors/-/track-processors-0.6.1.tgz",
|
||||
"integrity": "sha512-t9JMDvMUlaaURDDRZFQEkRYR4q2qROPOOIs3aZXQVL6v/QYgJ0tPg/QfbvHC8b6mYPwcaJgVz3KTk5XQ07fEMg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@mediapipe/tasks-vision": "0.10.14"
|
||||
@@ -7460,9 +7460,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/livekit-client": {
|
||||
"version": "2.15.5",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.15.5.tgz",
|
||||
"integrity": "sha512-zn36akmDlqZxlrTOUgYXtxtj35HQ44aJ+mgKat9BTSPiZru4RjEHOtp8RJE6jGoN2miJlWiOeEKHB2+ae3YrSw==",
|
||||
"version": "2.15.7",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.15.7.tgz",
|
||||
"integrity": "sha512-19m8Q1cvRl5PslRawDUgWXeP8vL8584tX8kiZEJaPZo83U/L6VPS/O7pP06phfJaBWeeV8sAOVtEPlQiZEHtpg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@livekit/mutex": "1.1.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "meet",
|
||||
"private": true,
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.38",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "panda codegen && vite",
|
||||
@@ -15,7 +15,7 @@
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "2.9.13",
|
||||
"@livekit/components-styles": "1.1.6",
|
||||
"@livekit/track-processors": "0.6.0",
|
||||
"@livekit/track-processors": "0.6.1",
|
||||
"@pandacss/preset-panda": "0.54.0",
|
||||
"@react-aria/toast": "3.0.5",
|
||||
"@react-types/overlays": "3.9.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
"i18next-parser": "9.3.0",
|
||||
"i18next-resources-to-backend": "1.2.1",
|
||||
"libphonenumber-js": "1.12.10",
|
||||
"livekit-client": "2.15.5",
|
||||
"livekit-client": "2.15.7",
|
||||
"posthog-js": "1.256.2",
|
||||
"react": "18.3.1",
|
||||
"react-aria-components": "1.10.1",
|
||||
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.61135 59.6951C5 62.832 5 66.6949 5 73.1656V88.7307C5 96.7588 5 100.773 6.16756 104.366C7.20062 107.546 8.89041 110.472 11.1274 112.957C13.6555 115.765 17.1318 117.772 24.0842 121.786L37.564 129.568C43.7169 133.121 47.1472 135.101 50.4165 136.054V88.0288C50.4165 85.6281 49.1052 83.4192 46.9976 82.2696L5.61135 59.6951Z" fill="#C9191E"/>
|
||||
<path d="M118.313 66.7489V91.7898C118.313 95.2521 119.818 98.5435 122.436 100.809L140.459 116.406C141.513 117.298 142.608 118.028 143.744 118.596C144.92 119.123 146.056 119.387 147.151 119.387C149.503 119.387 151.389 118.616 152.809 117.075C154.269 115.493 154.999 113.445 154.999 110.93V47.6577C154.999 45.1431 154.269 43.1151 152.809 41.5738C151.389 39.992 149.503 39.2011 147.151 39.2011C146.056 39.2011 144.92 39.4647 143.744 39.992C142.608 40.5193 141.513 41.2494 140.459 42.1822L122.45 57.7172C119.823 59.983 118.313 63.28 118.313 66.7489Z" fill="#000091"/>
|
||||
<path d="M11.6345 50.7522C11.1333 50.4788 10.6078 50.2937 10.0757 50.1915C10.4114 49.7628 10.7622 49.3452 11.1276 48.9394C13.6558 46.1315 17.132 44.1245 24.0845 40.1105L37.5643 32.3279C44.5167 28.3139 47.993 26.3069 51.6887 25.5213C54.9587 24.8262 58.3383 24.8262 61.6083 25.5213C65.304 26.3069 68.7803 28.3139 75.7327 32.3279L89.0535 40.0187C89.1066 40.0492 89.1597 40.0798 89.2129 40.1105C96.1653 44.1245 99.6416 46.1316 102.17 48.9394C104.407 51.4238 106.096 54.3506 107.13 57.5301C108.297 61.1235 108.297 65.1375 108.297 73.1656V88.7307C108.297 96.7588 108.297 100.773 107.13 104.366C106.096 107.546 104.407 110.473 102.17 112.957C99.6416 115.765 96.1655 117.772 89.2133 121.785C89.1537 121.82 89.0936 121.855 89.0341 121.889L75.7327 129.568C68.7803 133.582 65.304 135.589 61.6083 136.375C61.4564 136.407 61.3043 136.438 61.1519 136.467L61.1519 88.0288C61.1519 81.6997 57.6948 75.8761 52.1386 72.8455L11.6345 50.7522Z" fill="#000091"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 503 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
@@ -23,6 +23,8 @@ import posthog from 'posthog-js'
|
||||
import { useSnapshot } from 'valtio/index'
|
||||
import { Spinner } from '@/primitives/Spinner'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
import humanizeDuration from 'humanize-duration'
|
||||
import i18n from 'i18next'
|
||||
|
||||
export const ScreenRecordingSidePanel = () => {
|
||||
const { data } = useConfig()
|
||||
@@ -189,7 +191,7 @@ export const ScreenRecordingSidePanel = () => {
|
||||
</H>
|
||||
<Text
|
||||
variant="note"
|
||||
wrap={'pretty'}
|
||||
wrap="balance"
|
||||
centered
|
||||
className={css({
|
||||
textStyle: 'sm',
|
||||
@@ -198,7 +200,18 @@ export const ScreenRecordingSidePanel = () => {
|
||||
marginTop: '0.25rem',
|
||||
})}
|
||||
>
|
||||
{t('start.body')} <br />{' '}
|
||||
{t('start.body', {
|
||||
duration_message: data?.recording?.max_duration
|
||||
? t('durationMessage', {
|
||||
max_duration: humanizeDuration(
|
||||
data?.recording?.max_duration,
|
||||
{
|
||||
language: i18n.language,
|
||||
}
|
||||
),
|
||||
})
|
||||
: '',
|
||||
})}{' '}
|
||||
{data?.support?.help_article_recording && (
|
||||
<A href={data.support.help_article_recording} target="_blank">
|
||||
{t('start.linkMore')}
|
||||
|
||||
@@ -25,6 +25,8 @@ import posthog from 'posthog-js'
|
||||
import { useSnapshot } from 'valtio/index'
|
||||
import { Spinner } from '@/primitives/Spinner'
|
||||
import { useConfig } from '@/api/useConfig'
|
||||
import humanizeDuration from 'humanize-duration'
|
||||
import i18n from 'i18next'
|
||||
|
||||
export const TranscriptSidePanel = () => {
|
||||
const { data } = useConfig()
|
||||
@@ -263,7 +265,7 @@ export const TranscriptSidePanel = () => {
|
||||
</H>
|
||||
<Text
|
||||
variant="note"
|
||||
wrap={'pretty'}
|
||||
wrap="balance"
|
||||
centered
|
||||
className={css({
|
||||
textStyle: 'sm',
|
||||
@@ -272,7 +274,18 @@ export const TranscriptSidePanel = () => {
|
||||
marginTop: '0.25rem',
|
||||
})}
|
||||
>
|
||||
{t('start.body')} <br />{' '}
|
||||
{t('start.body', {
|
||||
duration_message: data?.recording?.max_duration
|
||||
? t('durationMessage', {
|
||||
max_duration: humanizeDuration(
|
||||
data?.recording?.max_duration,
|
||||
{
|
||||
language: i18n.language,
|
||||
}
|
||||
),
|
||||
})
|
||||
: '',
|
||||
})}{' '}
|
||||
{data?.support?.help_article_transcript && (
|
||||
<A
|
||||
href={data.support.help_article_transcript}
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
MediaDeviceFailure,
|
||||
Room,
|
||||
RoomOptions,
|
||||
supportsAdaptiveStream,
|
||||
supportsDynacast,
|
||||
VideoPresets,
|
||||
} from 'livekit-client'
|
||||
import { keys } from '@/api/queryKeys'
|
||||
@@ -87,13 +85,10 @@ export const Conference = ({
|
||||
retry: false,
|
||||
})
|
||||
|
||||
const isAdaptiveStreamSupported = supportsAdaptiveStream()
|
||||
const isDynacastSupported = supportsDynacast()
|
||||
|
||||
const roomOptions = useMemo((): RoomOptions => {
|
||||
return {
|
||||
adaptiveStream: isAdaptiveStreamSupported,
|
||||
dynacast: isDynacastSupported,
|
||||
adaptiveStream: true,
|
||||
dynacast: true,
|
||||
publishDefaults: {
|
||||
videoCodec: 'vp9',
|
||||
},
|
||||
@@ -116,8 +111,6 @@ export const Conference = ({
|
||||
userConfig.videoPublishResolution,
|
||||
userConfig.audioDeviceId,
|
||||
userConfig.audioOutputDeviceId,
|
||||
isAdaptiveStreamSupported,
|
||||
isDynacastSupported,
|
||||
])
|
||||
|
||||
const room = useMemo(() => new Room(roomOptions), [roomOptions])
|
||||
|
||||
@@ -474,7 +474,7 @@ export const Join = ({
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
gap: { base: '1rem', sm: '2rem', lg: 0 },
|
||||
gap: { base: '1rem', sm: '2rem', lg: '2rem' },
|
||||
lg: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
@@ -638,7 +638,7 @@ export const Join = ({
|
||||
<Button
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
onPress={openPermissionsDialog}
|
||||
onPress={() => openPermissionsDialog('videoinput')}
|
||||
>
|
||||
{t(`permissionsButton.${permissionsButtonLabel}`)}
|
||||
</Button>
|
||||
@@ -771,7 +771,7 @@ export const Join = ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
flex: '0 0 448px',
|
||||
flex: '0 0 360px',
|
||||
position: 'relative',
|
||||
margin: '1rem 1rem 1rem 0.5rem',
|
||||
})}
|
||||
|
||||
@@ -37,6 +37,22 @@ export const Permissions = () => {
|
||||
injectIconIntoTranslation(t('body.openMenu.others'))
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
permissions.isPermissionDialogOpen &&
|
||||
permissions.isMicrophoneGranted &&
|
||||
permissions.requestOrigin == 'audioinput'
|
||||
) {
|
||||
closePermissionsDialog()
|
||||
}
|
||||
|
||||
if (
|
||||
permissions.isPermissionDialogOpen &&
|
||||
permissions.isCameraGranted &&
|
||||
permissions.requestOrigin == 'videoinput'
|
||||
) {
|
||||
closePermissionsDialog()
|
||||
}
|
||||
|
||||
if (
|
||||
permissions.isPermissionDialogOpen &&
|
||||
permissions.isCameraGranted &&
|
||||
@@ -64,13 +80,17 @@ export const Permissions = () => {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
md: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<img
|
||||
src="/assets/camera_mic_permission.svg"
|
||||
alt=""
|
||||
className={css({
|
||||
minWidth: '290px',
|
||||
width: '100%',
|
||||
minHeight: '290px',
|
||||
maxWidth: '290px',
|
||||
})}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SettingsButton } from './SettingsButton'
|
||||
import { SettingsDialogExtendedKey } from '@/features/settings/type'
|
||||
import { TrackSource } from '@livekit/protocol'
|
||||
import Source = Track.Source
|
||||
import { isSafari } from '@/utils/livekit'
|
||||
|
||||
type AudioDevicesControlProps = Omit<
|
||||
UseTrackToggleProps<Source.Microphone>,
|
||||
@@ -111,19 +112,21 @@ export const AudioDevicesControl = ({
|
||||
onSubmit={saveAudioInputDeviceId}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flex: '1 1 0',
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<SelectDevice
|
||||
context="room"
|
||||
kind="audiooutput"
|
||||
id={audioOutputDeviceId}
|
||||
onSubmit={saveAudioOutputDeviceId}
|
||||
/>
|
||||
</div>
|
||||
{!isSafari() && (
|
||||
<div
|
||||
style={{
|
||||
flex: '1 1 0',
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<SelectDevice
|
||||
context="room"
|
||||
kind="audiooutput"
|
||||
id={audioOutputDeviceId}
|
||||
onSubmit={saveAudioOutputDeviceId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<SettingsButton
|
||||
settingTab={SettingsDialogExtendedKey.AUDIO}
|
||||
onPress={close}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const PermissionNeededButton = () => {
|
||||
<Button
|
||||
aria-label={t('ariaLabel')}
|
||||
tooltip={t('tooltip')}
|
||||
onPress={openPermissionsDialog}
|
||||
onPress={() => openPermissionsDialog()}
|
||||
variant="permission"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -107,9 +107,7 @@ export const ToggleDevice = <T extends ToggleSource>({
|
||||
}, [enabled, kind, deviceShortcut, t])
|
||||
|
||||
const Icon =
|
||||
isDisabled || cannotUseDevice || !enabled
|
||||
? deviceIcons.toggleOff
|
||||
: deviceIcons.toggleOn
|
||||
isDisabled || !enabled ? deviceIcons.toggleOff : deviceIcons.toggleOn
|
||||
|
||||
const roomContext = useMaybeRoomContext()
|
||||
if (kind === 'audioinput' && pushToTalk && roomContext) {
|
||||
@@ -126,7 +124,12 @@ export const ToggleDevice = <T extends ToggleSource>({
|
||||
isDisabled || cannotUseDevice || !enabled ? errorVariant : variant
|
||||
}
|
||||
shySelected
|
||||
onPress={() => (cannotUseDevice ? openPermissionsDialog() : toggle())}
|
||||
onPress={() => {
|
||||
if (cannotUseDevice) {
|
||||
openPermissionsDialog(kind)
|
||||
}
|
||||
toggle()
|
||||
}}
|
||||
aria-label={toggleLabel}
|
||||
tooltip={
|
||||
cannotUseDevice
|
||||
|
||||
@@ -5,6 +5,8 @@ import { SettingsMenuItem } from './SettingsMenuItem'
|
||||
import { FeedbackMenuItem } from './FeedbackMenuItem'
|
||||
import { EffectsMenuItem } from './EffectsMenuItem'
|
||||
import { SupportMenuItem } from './SupportMenuItem'
|
||||
import { TranscriptMenuItem } from './TranscriptMenuItem'
|
||||
import { ScreenRecordingMenuItem } from './ScreenRecordingMenuItem'
|
||||
|
||||
// @todo try refactoring it to use MenuList component
|
||||
export const OptionsMenuItems = () => {
|
||||
@@ -16,6 +18,8 @@ export const OptionsMenuItems = () => {
|
||||
}}
|
||||
>
|
||||
<MenuSection>
|
||||
<TranscriptMenuItem />
|
||||
<ScreenRecordingMenuItem />
|
||||
<FullScreenMenuItem />
|
||||
<EffectsMenuItem />
|
||||
</MenuSection>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { RiRecordCircleLine } from '@remixicon/react'
|
||||
import { MenuItem } from 'react-aria-components'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { menuRecipe } from '@/primitives/menuRecipe'
|
||||
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
|
||||
import { RecordingMode, useHasRecordingAccess } from '@/features/recording'
|
||||
import { FeatureFlags } from '@/features/analytics/enums'
|
||||
|
||||
export const ScreenRecordingMenuItem = () => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'options.items' })
|
||||
const { isScreenRecordingOpen, openScreenRecording, toggleTools } =
|
||||
useSidePanel()
|
||||
|
||||
const hasScreenRecordingAccess = useHasRecordingAccess(
|
||||
RecordingMode.ScreenRecording,
|
||||
FeatureFlags.ScreenRecording
|
||||
)
|
||||
|
||||
if (!hasScreenRecordingAccess) return null
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
className={menuRecipe({ icon: true, variant: 'dark' }).item}
|
||||
onAction={() =>
|
||||
!isScreenRecordingOpen ? openScreenRecording() : toggleTools()
|
||||
}
|
||||
>
|
||||
<RiRecordCircleLine size={20} />
|
||||
{t('screenRecording')}
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { RiFileTextLine } from '@remixicon/react'
|
||||
import { MenuItem } from 'react-aria-components'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { menuRecipe } from '@/primitives/menuRecipe'
|
||||
import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
|
||||
import { RecordingMode, useHasRecordingAccess } from '@/features/recording'
|
||||
import { FeatureFlags } from '@/features/analytics/enums'
|
||||
|
||||
export const TranscriptMenuItem = () => {
|
||||
const { t } = useTranslation('rooms', { keyPrefix: 'options.items' })
|
||||
const { isTranscriptOpen, openTranscript, toggleTools } = useSidePanel()
|
||||
|
||||
const hasTranscriptAccess = useHasRecordingAccess(
|
||||
RecordingMode.Transcript,
|
||||
FeatureFlags.Transcript
|
||||
)
|
||||
|
||||
if (!hasTranscriptAccess) return null
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
className={menuRecipe({ icon: true, variant: 'dark' }).item}
|
||||
onAction={() => (!isTranscriptOpen ? openTranscript() : toggleTools())}
|
||||
>
|
||||
<RiFileTextLine size={20} />
|
||||
{t('transcript')}
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { Avatar } from '@/components/Avatar'
|
||||
import { getParticipantColor } from '@/features/rooms/utils/getParticipantColor'
|
||||
import { getParticipantIsRoomAdmin } from '@/features/rooms/utils/getParticipantIsRoomAdmin'
|
||||
import { Participant, Track } from 'livekit-client'
|
||||
import { LocalParticipant, Participant, Track } from 'livekit-client'
|
||||
import { isLocal } from '@/utils/livekit'
|
||||
import {
|
||||
useIsSpeaking,
|
||||
@@ -54,9 +54,11 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
|
||||
tooltip={label}
|
||||
aria-label={label}
|
||||
isDisabled={isMuted || !canMute}
|
||||
onPress={() =>
|
||||
onPress={async () =>
|
||||
!isMuted && isLocal(participant)
|
||||
? muteParticipant(participant)
|
||||
? await (participant as LocalParticipant)?.setMicrophoneEnabled(
|
||||
false
|
||||
)
|
||||
: setIsAlertOpen(true)
|
||||
}
|
||||
data-attr="participants-mute"
|
||||
|
||||
@@ -40,6 +40,7 @@ export const ScreenShareToggle = ({
|
||||
isDisabled={!canShareScreen}
|
||||
square
|
||||
variant={variant}
|
||||
aria-label={t(tooltipLabel)}
|
||||
tooltip={t(tooltipLabel)}
|
||||
onPress={(e) => {
|
||||
buttonProps.onClick?.(
|
||||
|
||||
@@ -63,11 +63,13 @@ export function MobileControlBar({
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.Microphone, error })
|
||||
}
|
||||
hideMenu={true}
|
||||
/>
|
||||
<VideoDeviceControl
|
||||
onDeviceError={(error) =>
|
||||
onDeviceError?.({ source: Track.Source.Camera, error })
|
||||
}
|
||||
hideMenu={true}
|
||||
/>
|
||||
<HandToggle />
|
||||
<Button
|
||||
|
||||
@@ -206,6 +206,8 @@
|
||||
"items": {
|
||||
"feedback": "Feedback geben",
|
||||
"settings": "Einstellungen",
|
||||
"screenRecording": "Aufzeichnung",
|
||||
"transcript": "Transkription",
|
||||
"username": "Ihren Namen aktualisieren",
|
||||
"effects": "Effekte anwenden",
|
||||
"switchCamera": "Kamera wechseln",
|
||||
@@ -303,7 +305,7 @@
|
||||
"transcript": {
|
||||
"start": {
|
||||
"heading": "Dieses Gespräch transkribieren",
|
||||
"body": "Dieses Gespräch automatisch transkribieren und die Zusammenfassung in Docs erhalten.",
|
||||
"body": "Dieses Gespräch automatisch transkribieren {{duration_message}} und die Zusammenfassung in Docs erhalten.",
|
||||
"button": "Transkription starten",
|
||||
"loading": "Transkription wird gestartet",
|
||||
"linkMore": "Mehr erfahren"
|
||||
@@ -334,12 +336,13 @@
|
||||
"start": "Die Transkription konnte nicht gestartet werden. Bitte versuche es in einem Moment erneut."
|
||||
},
|
||||
"button": "OK"
|
||||
}
|
||||
},
|
||||
"durationMessage": "(begrenzt auf {{max_duration}}) "
|
||||
},
|
||||
"screenRecording": {
|
||||
"start": {
|
||||
"heading": "Dieses Gespräch aufzeichnen",
|
||||
"body": "Zeichne dieses Gespräch auf, um es später anzusehen. Du erhältst die Videoaufnahme per E-Mail.",
|
||||
"body": "Zeichne dieses Gespräch auf, um es später anzusehen {{duration_message}}. Du erhältst die Videoaufnahme per E-Mail.",
|
||||
"button": "Aufzeichnung starten",
|
||||
"loading": "Aufzeichnung wird gestartet",
|
||||
"linkMore": "Mehr erfahren"
|
||||
@@ -365,7 +368,8 @@
|
||||
"start": "Die Aufzeichnung konnte nicht gestartet werden. Bitte versuche es in einem Moment erneut."
|
||||
},
|
||||
"button": "OK"
|
||||
}
|
||||
},
|
||||
"durationMessage": "(begrenzt auf {{max_duration}})"
|
||||
},
|
||||
"admin": {
|
||||
"description": "Diese Einstellungen für Organisatoren ermöglichen dir die Kontrolle über dein Meeting. Nur Organisatoren haben Zugriff auf diese Optionen.",
|
||||
|
||||
@@ -206,6 +206,8 @@
|
||||
"items": {
|
||||
"feedback": "Give us feedback",
|
||||
"settings": "Settings",
|
||||
"screenRecording": "Screen recording",
|
||||
"transcript": "Transcription",
|
||||
"username": "Update Your Name",
|
||||
"effects": "Apply effects",
|
||||
"switchCamera": "Switch camera",
|
||||
@@ -303,7 +305,7 @@
|
||||
"transcript": {
|
||||
"start": {
|
||||
"heading": "Transcribe this call",
|
||||
"body": "Automatically transcribe this call and receive the summary in Docs.",
|
||||
"body": "Automatically transcribe this call {{duration_message}} and receive the summary in Docs.",
|
||||
"button": "Start transcription",
|
||||
"loading": "Transcription starting",
|
||||
"linkMore": "Learn more"
|
||||
@@ -334,12 +336,13 @@
|
||||
"start": "We were unable to start the transcription. Please try again in a moment."
|
||||
},
|
||||
"button": "OK"
|
||||
}
|
||||
},
|
||||
"durationMessage": "(limited to {{max_duration}}) "
|
||||
},
|
||||
"screenRecording": {
|
||||
"start": {
|
||||
"heading": "Record this call",
|
||||
"body": "Record this call to watch it later and receive the video recording by email.",
|
||||
"body": "Record this call to watch it later {{duration_message}} and receive the video recording by email.",
|
||||
"button": "Start recording",
|
||||
"loading": "Recording starting",
|
||||
"linkMore": "Learn more"
|
||||
@@ -365,7 +368,8 @@
|
||||
"start": "We were unable to start the recording. Please try again in a moment."
|
||||
},
|
||||
"button": "OK"
|
||||
}
|
||||
},
|
||||
"durationMessage": "(limited to {{max_duration}}) "
|
||||
},
|
||||
"admin": {
|
||||
"description": "These organizer settings allow you to maintain control of your meeting. Only organizers can access these controls.",
|
||||
|
||||
@@ -206,6 +206,8 @@
|
||||
"items": {
|
||||
"feedback": "Partager votre avis",
|
||||
"settings": "Paramètres",
|
||||
"screenRecording": "Enregistrement",
|
||||
"transcript": "Transcription",
|
||||
"username": "Choisir votre nom",
|
||||
"effects": "Effets d'arrière-plan",
|
||||
"switchCamera": "Changer de caméra",
|
||||
@@ -303,7 +305,7 @@
|
||||
"transcript": {
|
||||
"start": {
|
||||
"heading": "Transcrire cet appel",
|
||||
"body": "Transcrivez cet appel automatiquement et recevez le compte rendu dans Docs.",
|
||||
"body": "Transcrivez cet appel automatiquement {{duration_message}} et recevez le compte rendu dans Docs.",
|
||||
"button": "Démarrer la transcription",
|
||||
"loading": "Démarrage de la transcription",
|
||||
"linkMore": "En savoir plus"
|
||||
@@ -334,12 +336,13 @@
|
||||
"start": "Nous n'avons pas pu démarrer la transcription. Veuillez réessayer dans quelques instants."
|
||||
},
|
||||
"button": "OK"
|
||||
}
|
||||
},
|
||||
"durationMessage": "(limité à {{max_duration}}) "
|
||||
},
|
||||
"screenRecording": {
|
||||
"start": {
|
||||
"heading": "Enregistrer cet appel",
|
||||
"body": "Enregistrez cet appel pour plus tard et recevez l'enregistrement vidéo par mail.",
|
||||
"body": "Enregistrez cet appel pour plus tard {{duration_message}} et recevez l'enregistrement vidéo par mail.",
|
||||
"button": "Démarrer l'enregistrement",
|
||||
"loading": "Démarrage de l'enregistrement",
|
||||
"linkMore": "En savoir plus"
|
||||
@@ -365,7 +368,8 @@
|
||||
"start": "Nous n'avons pas pu démarrer l'enregistrement. Veuillez réessayer dans quelques instants."
|
||||
},
|
||||
"button": "OK"
|
||||
}
|
||||
},
|
||||
"durationMessage": "(limité à {{max_duration}}) "
|
||||
},
|
||||
"admin": {
|
||||
"description": "Ces paramètres organisateur vous permettent de garder le contrôle de votre réunion. Seuls les organisateurs peuvent accéder à ces commandes.",
|
||||
|
||||
@@ -206,6 +206,8 @@
|
||||
"items": {
|
||||
"feedback": "Geef ons feedback",
|
||||
"settings": "Instellingen",
|
||||
"transcript": "Transcriptie",
|
||||
"screenRecording": "Schermopname",
|
||||
"username": "Verander uw naam",
|
||||
"effects": "Pas effecten toe",
|
||||
"switchCamera": "Selecteer camera",
|
||||
@@ -303,7 +305,7 @@
|
||||
"transcript": {
|
||||
"start": {
|
||||
"heading": "Transcribeer dit gesprek",
|
||||
"body": "Transcribeer dit gesprek automatisch en ontvang het verslag in Docs.",
|
||||
"body": "Transcribeer dit gesprek automatisch {{duration_message}} en ontvang het verslag in Docs.",
|
||||
"button": "Transcriptie starten",
|
||||
"loading": "Transcriptie begint",
|
||||
"linkMore": "Meer informatie"
|
||||
@@ -334,12 +336,13 @@
|
||||
"start": "We konden de transcriptie niet starten. Probeer het over enkele ogenblikken opnieuw."
|
||||
},
|
||||
"button": "Opnieuw proberen"
|
||||
}
|
||||
},
|
||||
"durationMessage": "(beperkt tot {{max_duration}}) "
|
||||
},
|
||||
"screenRecording": {
|
||||
"start": {
|
||||
"heading": "Dit gesprek opnemen",
|
||||
"body": "Neem dit gesprek op om het later terug te kijken. Je ontvangt de video-opname per e-mail.",
|
||||
"body": "Neem dit gesprek op om het later terug te kijken {{duration_message}}. Je ontvangt de video-opname per e-mail.",
|
||||
"button": "Opname starten",
|
||||
"loading": "Opname gestarten",
|
||||
"linkMore": "Meer informatie"
|
||||
@@ -365,7 +368,8 @@
|
||||
"start": "We konden de opname niet starten. Probeer het over enkele ogenblikken opnieuw."
|
||||
},
|
||||
"button": "Opnieuw proberen"
|
||||
}
|
||||
},
|
||||
"durationMessage": "(beperkt tot {{max_duration}})"
|
||||
},
|
||||
"admin": {
|
||||
"description": "Deze organisatorinstellingen geven u controle over uw vergadering. Alleen organisatoren hebben toegang tot deze bedieningselementen.",
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
} from 'react-aria-components'
|
||||
import { styled } from '@/styled-system/jsx'
|
||||
import { StyledVariantProps } from '@/styled-system/types'
|
||||
import { RiCheckLine, RiCloseFill } from '@remixicon/react'
|
||||
|
||||
export const StyledSwitch = styled(RACSwitch, {
|
||||
base: {
|
||||
@@ -37,7 +38,7 @@ export const StyledSwitch = styled(RACSwitch, {
|
||||
position: 'absolute',
|
||||
display: 'block',
|
||||
top: '50%',
|
||||
right: '0.25rem',
|
||||
right: '0.1rem',
|
||||
transform: 'translateY(-50%)',
|
||||
color: 'primary.800',
|
||||
fontSize: '0.75rem',
|
||||
@@ -50,7 +51,7 @@ export const StyledSwitch = styled(RACSwitch, {
|
||||
position: 'absolute',
|
||||
display: 'block',
|
||||
top: '50%',
|
||||
left: '0.375rem',
|
||||
left: '0.13rem',
|
||||
transform: 'translateY(-50%)',
|
||||
color: 'white',
|
||||
fontSize: '0.70rem',
|
||||
@@ -110,10 +111,10 @@ export const Switch = ({ children, ...props }: SwitchProps) => (
|
||||
<>
|
||||
<div className="indicator">
|
||||
<span className="checkmark" aria-hidden="true">
|
||||
✓
|
||||
<RiCheckLine size={16} />
|
||||
</span>
|
||||
<span className="cross" aria-hidden="true">
|
||||
✕
|
||||
<RiCloseFill size={16} />
|
||||
</span>
|
||||
</div>
|
||||
{typeof children === 'function' ? children(renderProps) : children}
|
||||
|
||||
@@ -13,6 +13,7 @@ type BaseState = {
|
||||
microphonePermission: PermissionState
|
||||
isLoading: boolean
|
||||
isPermissionDialogOpen: boolean
|
||||
requestOrigin?: 'audioinput' | 'videoinput'
|
||||
}
|
||||
|
||||
type DerivedState = {
|
||||
@@ -31,6 +32,7 @@ export const permissionsStore = proxy<BaseState>({
|
||||
microphonePermission: undefined,
|
||||
isLoading: true,
|
||||
isPermissionDialogOpen: false,
|
||||
requestOrigin: undefined,
|
||||
}) as State
|
||||
|
||||
derive(
|
||||
@@ -52,8 +54,11 @@ derive(
|
||||
}
|
||||
)
|
||||
|
||||
export const openPermissionsDialog = () => {
|
||||
export const openPermissionsDialog = (
|
||||
requestOrigin?: 'audioinput' | 'videoinput'
|
||||
) => {
|
||||
permissionsStore.isPermissionDialogOpen = true
|
||||
permissionsStore.requestOrigin = requestOrigin
|
||||
}
|
||||
|
||||
export const closePermissionsDialog = () => {
|
||||
|
||||
@@ -29,6 +29,10 @@ body:has(.lk-video-conference) #crisp-chatbox-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body:has(.lk-video-conference) #crisp-chatbox > * > div[role='button'] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@keyframes slide-full {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
|
||||
@@ -144,11 +144,13 @@ summary:
|
||||
AWS_S3_ENDPOINT_URL: minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
AWS_S3_SECRET_ACCESS_KEY: password
|
||||
OPENAI_API_KEY: password
|
||||
OPENAI_BASE_URL: https://albertine.beta.numerique.gouv.fr/v1
|
||||
OPENAI_ASR_MODEL: large-v2
|
||||
OPENAI_LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
AWS_S3_SECURE_ACCESS: False
|
||||
WHISPERX_API_KEY: your-secret-value
|
||||
WHISPERX_BASE_URL: https://configure-your-url.com
|
||||
WHISPERX_ASR_MODEL: large-v2
|
||||
LLM_BASE_URL: https://configure-your-url.com
|
||||
LLM_API_KEY: your-secret-value
|
||||
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
WEBHOOK_API_TOKEN: password
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
@@ -168,7 +170,7 @@ summary:
|
||||
- "8000"
|
||||
- "--reload"
|
||||
|
||||
celery:
|
||||
celeryTranscribe:
|
||||
replicas: 1
|
||||
envVars:
|
||||
APP_NAME: summary-microservice
|
||||
@@ -177,16 +179,19 @@ celery:
|
||||
AWS_S3_ENDPOINT_URL: minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
AWS_S3_SECRET_ACCESS_KEY: password
|
||||
OPENAI_API_KEY: password
|
||||
OPENAI_BASE_URL: https://albertine.beta.numerique.gouv.fr/v1
|
||||
OPENAI_ASR_MODEL: large-v2
|
||||
OPENAI_LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
AWS_S3_SECURE_ACCESS: False
|
||||
WHISPERX_API_KEY: your-secret-value
|
||||
WHISPERX_BASE_URL: https://configure-your-url.com
|
||||
WHISPERX_ASR_MODEL: large-v2
|
||||
LLM_BASE_URL: https://configure-your-url.com
|
||||
LLM_API_KEY: your-secret-value
|
||||
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
WEBHOOK_API_TOKEN: password
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
@@ -199,6 +204,43 @@ celery:
|
||||
- "worker"
|
||||
- "--pool=solo"
|
||||
- "--loglevel=info"
|
||||
- "-Q transcribe-queue"
|
||||
|
||||
celerySummarize:
|
||||
replicas: 1
|
||||
envVars:
|
||||
APP_NAME: summary-microservice
|
||||
APP_API_TOKEN: password
|
||||
AWS_STORAGE_BUCKET_NAME: meet-media-storage
|
||||
AWS_S3_ENDPOINT_URL: minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
AWS_S3_SECRET_ACCESS_KEY: password
|
||||
AWS_S3_SECURE_ACCESS: False
|
||||
WHISPERX_API_KEY: your-secret-value
|
||||
WHISPERX_BASE_URL: https://configure-your-url.com
|
||||
WHISPERX_ASR_MODEL: large-v2
|
||||
LLM_BASE_URL: https://configure-your-url.com
|
||||
LLM_API_KEY: your-secret-value
|
||||
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
WEBHOOK_API_TOKEN: password
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
tag: "latest"
|
||||
|
||||
command:
|
||||
- "celery"
|
||||
- "-A"
|
||||
- "summary.core.celery_worker"
|
||||
- "worker"
|
||||
- "--pool=solo"
|
||||
- "--loglevel=info"
|
||||
- "-Q summarize-queue"
|
||||
|
||||
ingressMedia:
|
||||
enabled: true
|
||||
|
||||
@@ -151,11 +151,13 @@ summary:
|
||||
AWS_S3_ENDPOINT_URL: minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
AWS_S3_SECRET_ACCESS_KEY: password
|
||||
OPENAI_API_KEY: password
|
||||
OPENAI_BASE_URL: https://albertine.beta.numerique.gouv.fr/v1
|
||||
OPENAI_ASR_MODEL: large-v2
|
||||
OPENAI_LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
AWS_S3_SECURE_ACCESS: False
|
||||
WHISPERX_API_KEY: your-secret-value
|
||||
WHISPERX_BASE_URL: https://configure-your-url.com
|
||||
WHISPERX_ASR_MODEL: large-v2
|
||||
LLM_BASE_URL: https://configure-your-url.com
|
||||
LLM_API_KEY: your-secret-value
|
||||
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
WEBHOOK_API_TOKEN: password
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
@@ -176,7 +178,7 @@ summary:
|
||||
- "8000"
|
||||
- "--reload"
|
||||
|
||||
celery:
|
||||
celeryTranscribe:
|
||||
replicas: 1
|
||||
envVars:
|
||||
APP_NAME: summary-microservice
|
||||
@@ -185,11 +187,13 @@ celery:
|
||||
AWS_S3_ENDPOINT_URL: minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
AWS_S3_SECRET_ACCESS_KEY: password
|
||||
OPENAI_API_KEY: password
|
||||
OPENAI_BASE_URL: https://albertine.beta.numerique.gouv.fr/v1
|
||||
OPENAI_ASR_MODEL: large-v2
|
||||
OPENAI_LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
AWS_S3_SECURE_ACCESS: False
|
||||
WHISPERX_API_KEY: your-secret-value
|
||||
WHISPERX_BASE_URL: https://configure-your-url.com
|
||||
WHISPERX_ASR_MODEL: large-v2
|
||||
LLM_BASE_URL: https://configure-your-url.com
|
||||
LLM_API_KEY: your-secret-value
|
||||
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
WEBHOOK_API_TOKEN: password
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
@@ -208,6 +212,43 @@ celery:
|
||||
- "worker"
|
||||
- "--pool=solo"
|
||||
- "--loglevel=info"
|
||||
- "-Q transcribe-queue"
|
||||
|
||||
celerySummarize:
|
||||
replicas: 1
|
||||
envVars:
|
||||
APP_NAME: summary-microservice
|
||||
APP_API_TOKEN: password
|
||||
AWS_STORAGE_BUCKET_NAME: meet-media-storage
|
||||
AWS_S3_ENDPOINT_URL: minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
AWS_S3_SECRET_ACCESS_KEY: password
|
||||
AWS_S3_SECURE_ACCESS: False
|
||||
WHISPERX_API_KEY: your-secret-value
|
||||
WHISPERX_BASE_URL: https://configure-your-url.com
|
||||
WHISPERX_ASR_MODEL: large-v2
|
||||
LLM_BASE_URL: https://configure-your-url.com
|
||||
LLM_API_KEY: your-secret-value
|
||||
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
WEBHOOK_API_TOKEN: password
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
tag: "latest"
|
||||
|
||||
command:
|
||||
- "celery"
|
||||
- "-A"
|
||||
- "summary.core.celery_worker"
|
||||
- "worker"
|
||||
- "--pool=solo"
|
||||
- "--loglevel=info"
|
||||
- "-Q summarize-queue"
|
||||
|
||||
agents:
|
||||
replicas: 1
|
||||
|
||||
@@ -171,11 +171,13 @@ summary:
|
||||
AWS_S3_ENDPOINT_URL: minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
AWS_S3_SECRET_ACCESS_KEY: password
|
||||
OPENAI_API_KEY: password
|
||||
OPENAI_BASE_URL: https://albertine.beta.numerique.gouv.fr/v1
|
||||
OPENAI_ASR_MODEL: openai/whisper-large-v3
|
||||
OPENAI_LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
AWS_S3_SECURE_ACCESS: False
|
||||
WHISPERX_API_KEY: your-secret-value
|
||||
WHISPERX_BASE_URL: https://configure-your-url.com
|
||||
WHISPERX_ASR_MODEL: large-v2
|
||||
LLM_BASE_URL: https://configure-your-url.com
|
||||
LLM_API_KEY: your-secret-value
|
||||
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
WEBHOOK_API_TOKEN: password
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
@@ -195,7 +197,7 @@ summary:
|
||||
- "8000"
|
||||
- "--reload"
|
||||
|
||||
celery:
|
||||
celeryTranscribe:
|
||||
replicas: 1
|
||||
envVars:
|
||||
APP_NAME: summary-microservice
|
||||
@@ -204,15 +206,18 @@ celery:
|
||||
AWS_S3_ENDPOINT_URL: minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
AWS_S3_SECRET_ACCESS_KEY: password
|
||||
OPENAI_API_KEY: password
|
||||
OPENAI_BASE_URL: https://albertine.beta.numerique.gouv.fr/v1
|
||||
OPENAI_ASR_MODEL: openai/whisper-large-v3
|
||||
OPENAI_LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
AWS_S3_SECURE_ACCESS: False
|
||||
WHISPERX_API_KEY: your-secret-value
|
||||
WHISPERX_BASE_URL: https://configure-your-url.com
|
||||
WHISPERX_ASR_MODEL: large-v2
|
||||
LLM_BASE_URL: https://configure-your-url.com
|
||||
LLM_API_KEY: your-secret-value
|
||||
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
WEBHOOK_API_TOKEN: password
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
@@ -226,6 +231,43 @@ celery:
|
||||
- "worker"
|
||||
- "--pool=solo"
|
||||
- "--loglevel=info"
|
||||
- "-Q transcribe-queue"
|
||||
|
||||
celerySummarize:
|
||||
replicas: 1
|
||||
envVars:
|
||||
APP_NAME: summary-microservice
|
||||
APP_API_TOKEN: password
|
||||
AWS_STORAGE_BUCKET_NAME: meet-media-storage
|
||||
AWS_S3_ENDPOINT_URL: minio.meet.svc.cluster.local:9000
|
||||
AWS_S3_ACCESS_KEY_ID: meet
|
||||
AWS_S3_SECRET_ACCESS_KEY: password
|
||||
AWS_S3_SECURE_ACCESS: False
|
||||
WHISPERX_API_KEY: your-secret-value
|
||||
WHISPERX_BASE_URL: https://configure-your-url.com
|
||||
WHISPERX_ASR_MODEL: large-v2
|
||||
LLM_BASE_URL: https://configure-your-url.com
|
||||
LLM_API_KEY: your-secret-value
|
||||
LLM_MODEL: meta-llama/Llama-3.1-8B-Instruct
|
||||
WEBHOOK_API_TOKEN: password
|
||||
WEBHOOK_URL: https://www.mock-impress.com/webhook/
|
||||
CELERY_BROKER_URL: redis://default:pass@redis-master:6379/1
|
||||
CELERY_RESULT_BACKEND: redis://default:pass@redis-master:6379/1
|
||||
TASK_TRACKER_REDIS_URL: redis://default:pass@redis-master:6379/1
|
||||
|
||||
image:
|
||||
repository: localhost:5001/meet-summary
|
||||
pullPolicy: Always
|
||||
tag: "latest"
|
||||
|
||||
command:
|
||||
- "celery"
|
||||
- "-A"
|
||||
- "summary.core.celery_worker"
|
||||
- "worker"
|
||||
- "--pool=solo"
|
||||
- "--loglevel=info"
|
||||
- "-Q summarize-queue"
|
||||
|
||||
ingressMedia:
|
||||
enabled: true
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
environments:
|
||||
dev-keycloak:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
- env.d/{{ .Environment.Name }}/values.secrets.yaml
|
||||
dev-dinum:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
- env.d/{{ .Environment.Name }}/values.secrets.yaml
|
||||
dev:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
- env.d/{{ .Environment.Name }}/values.secrets.yaml
|
||||
|
||||
repositories:
|
||||
- name: livekit
|
||||
url: https://helm.livekit.io
|
||||
|
||||
releases:
|
||||
- name: extra
|
||||
installed: {{ regexMatch "^dev.*" .Environment.Name | toYaml }}
|
||||
missingFileHandler: Warn
|
||||
namespace: {{ .Namespace }}
|
||||
chart: ./extra
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
values:
|
||||
- env.d/{{ .Environment.Name }}/values.meet.yaml.gotmpl
|
||||
- addRedirect: {{ .Values | get "addRedirect" "False" }}
|
||||
enablePermanentRedirect: {{ .Values | get "enablePermanentRedirect" "False"}}
|
||||
oldDomain: {{ .Values | get "oldDomain" "demo.com" }}
|
||||
newDomain: {{ .Values | get "newDomain" "demo.com" }}
|
||||
- realm: |
|
||||
{{ readFile "../../docker/auth/realm.json" | replace "http://localhost:3200" "https://meet.127.0.0.1.nip.io" | indent 8 }}
|
||||
|
||||
- name: meet
|
||||
version: {{ .Values.version }}
|
||||
namespace: {{ .Namespace }}
|
||||
missingFileHandler: Warn
|
||||
chart: ./meet
|
||||
values:
|
||||
- env.d/{{ .Environment.Name }}/values.meet.yaml.gotmpl
|
||||
- env.d/{{ .Environment.Name }}/values.secrets.yaml
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
|
||||
- name: livekit
|
||||
installed: {{ regexMatch "^dev.*" .Environment.Name | toYaml }}
|
||||
missingFileHandler: Warn
|
||||
namespace: {{ .Namespace }}
|
||||
chart: livekit/livekit-server
|
||||
values:
|
||||
- env.d/{{ .Environment.Name }}/values.livekit.yaml.gotmpl
|
||||
- env.d/{{ .Environment.Name }}/values.secrets.yaml
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
|
||||
- name: livekit-egress
|
||||
installed: {{ regexMatch "^dev.*" .Environment.Name | toYaml }}
|
||||
missingFileHandler: Warn
|
||||
namespace: {{ .Namespace }}
|
||||
chart: livekit/egress
|
||||
values:
|
||||
- env.d/{{ .Environment.Name }}/values.egress.yaml.gotmpl
|
||||
- env.d/{{ .Environment.Name }}/values.secrets.yaml
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
@@ -0,0 +1,69 @@
|
||||
environments:
|
||||
dev-keycloak:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
- env.d/dev-keycloak/values.secrets.yaml
|
||||
dev-dinum:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
- env.d/dev-dinum/values.secrets.yaml
|
||||
dev:
|
||||
values:
|
||||
- version: 0.0.1
|
||||
- env.d/dev/values.secrets.yaml
|
||||
|
||||
---
|
||||
repositories:
|
||||
- name: livekit
|
||||
url: https://helm.livekit.io
|
||||
|
||||
releases:
|
||||
- name: extra
|
||||
installed: {{ regexMatch "^dev.*" .Environment.Name }}
|
||||
missingFileHandler: Warn
|
||||
namespace: {{ .Namespace }}
|
||||
chart: ./extra
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
values:
|
||||
- env.d/{{ .Environment.Name }}/values.meet.yaml.gotmpl
|
||||
- addRedirect: {{ .Values | get "addRedirect" "False" }}
|
||||
- enablePermanentRedirect: {{ .Values | get "enablePermanentRedirect" "False"}}
|
||||
- oldDomain: {{ .Values | get "oldDomain" "demo.com" }}
|
||||
- newDomain: {{ .Values | get "newDomain" "demo.com" }}
|
||||
- realm: |
|
||||
{{ readFile "../../docker/auth/realm.json" | replace "http://localhost:3200" "https://meet.127.0.0.1.nip.io" | indent 8 }}
|
||||
|
||||
|
||||
- name: meet
|
||||
version: {{ .Values.version }}
|
||||
namespace: {{ .Namespace }}
|
||||
missingFileHandler: Warn
|
||||
chart: ./meet
|
||||
values:
|
||||
- env.d/{{ .Environment.Name }}/values.meet.yaml.gotmpl
|
||||
- env.d/{{ .Environment.Name }}/values.secrets.yaml
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
|
||||
- name: livekit
|
||||
installed: {{ regexMatch "^dev.*" .Environment.Name }}
|
||||
missingFileHandler: Warn
|
||||
namespace: {{ .Namespace }}
|
||||
chart: livekit/livekit-server
|
||||
values:
|
||||
- env.d/{{ .Environment.Name }}/values.livekit.yaml.gotmpl
|
||||
- env.d/{{ .Environment.Name }}/values.secrets.yaml
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
|
||||
- name: livekit-egress
|
||||
installed: {{ regexMatch "^dev.*" .Environment.Name }}
|
||||
missingFileHandler: Warn
|
||||
namespace: {{ .Namespace }}
|
||||
chart: livekit/egress
|
||||
values:
|
||||
- env.d/{{ .Environment.Name }}/values.egress.yaml.gotmpl
|
||||
- env.d/{{ .Environment.Name }}/values.secrets.yaml
|
||||
secrets:
|
||||
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v2
|
||||
type: application
|
||||
name: meet
|
||||
version: 0.0.11
|
||||
version: 0.0.12
|
||||
|
||||
@@ -176,12 +176,21 @@ Requires top level scope
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Full name for the Celery
|
||||
Full name for the Celery Transcribe
|
||||
|
||||
Requires top level scope
|
||||
*/}}
|
||||
{{- define "meet.celery.fullname" -}}
|
||||
{{ include "meet.fullname" . }}-celery
|
||||
{{- define "meet.celeryTranscribe.fullname" -}}
|
||||
{{ include "meet.fullname" . }}-celery-transcribe
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Full name for the Celery Summarize
|
||||
|
||||
Requires top level scope
|
||||
*/}}
|
||||
{{- define "meet.celerySummarize.fullname" -}}
|
||||
{{ include "meet.fullname" . }}-celery-summarize
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{{- $envVars := include "meet.common.env" (list . .Values.celery) -}}
|
||||
{{- $fullName := include "meet.celery.fullname" . -}}
|
||||
{{- $component := "celery" -}}
|
||||
{{- $envVars := include "meet.common.env" (list . .Values.celerySummarize) -}}
|
||||
{{- $fullName := include "meet.celerySummarize.fullname" . -}}
|
||||
{{- $component := "celery-summarize" -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
annotations:
|
||||
{{- with .Values.celery.dpAnnotations }}
|
||||
{{- with .Values.celerySummarize.dpAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.celery.replicas }}
|
||||
replicas: {{ .Values.celerySummarize.replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
{{- with .Values.celery.podAnnotations }}
|
||||
{{- with .Values.celerySummarize.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
@@ -30,19 +30,19 @@ spec:
|
||||
imagePullSecrets:
|
||||
- name: {{ include "meet.secret.dockerconfigjson.name" (dict "fullname" (include "meet.fullname" .) "imageCredentials" $.Values.image.credentials) }}
|
||||
{{- end }}
|
||||
shareProcessNamespace: {{ .Values.celery.shareProcessNamespace }}
|
||||
shareProcessNamespace: {{ .Values.celerySummarize.shareProcessNamespace }}
|
||||
containers:
|
||||
{{- with .Values.celery.sidecars }}
|
||||
{{- with .Values.celerySummarize.sidecars }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ (.Values.celery.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.celery.image | default dict).tag | default .Values.image.tag }}"
|
||||
imagePullPolicy: {{ (.Values.celery.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
|
||||
{{- with .Values.celery.command }}
|
||||
image: "{{ (.Values.celerySummarize.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.celerySummarize.image | default dict).tag | default .Values.image.tag }}"
|
||||
imagePullPolicy: {{ (.Values.celerySummarize.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
|
||||
{{- with .Values.celerySummarize.command }}
|
||||
command:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celery.args }}
|
||||
{{- with .Values.celerySummarize.args }}
|
||||
args:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
@@ -50,27 +50,27 @@ spec:
|
||||
{{- if $envVars }}
|
||||
{{- $envVars | indent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celery.securityContext }}
|
||||
{{- with .Values.celerySummarize.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.celery.service.targetPort }}
|
||||
containerPort: {{ .Values.celerySummarize.service.targetPort }}
|
||||
protocol: TCP
|
||||
{{- if .Values.celery.probes.liveness }}
|
||||
{{- if .Values.celerySummarize.probes.liveness }}
|
||||
livenessProbe:
|
||||
{{- include "meet.probes.abstract" (merge .Values.celery.probes.liveness (dict "targetPort" .Values.celery.service.targetPort )) | nindent 12 }}
|
||||
{{- include "meet.probes.abstract" (merge .Values.celerySummarize.probes.liveness (dict "targetPort" .Values.celerySummarize.service.targetPort )) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.celery.probes.readiness }}
|
||||
{{- if .Values.celerySummarize.probes.readiness }}
|
||||
readinessProbe:
|
||||
{{- include "meet.probes.abstract" (merge .Values.celery.probes.readiness (dict "targetPort" .Values.celery.service.targetPort )) | nindent 12 }}
|
||||
{{- include "meet.probes.abstract" (merge .Values.celerySummarize.probes.readiness (dict "targetPort" .Values.celerySummarize.service.targetPort )) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.celery.probes.startup }}
|
||||
{{- if .Values.celerySummarize.probes.startup }}
|
||||
startupProbe:
|
||||
{{- include "meet.probes.abstract" (merge .Values.celery.probes.startup (dict "targetPort" .Values.celery.service.targetPort )) | nindent 12 }}
|
||||
{{- include "meet.probes.abstract" (merge .Values.celerySummarize.probes.startup (dict "targetPort" .Values.celerySummarize.service.targetPort )) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celery.resources }}
|
||||
{{- with .Values.celerySummarize.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
@@ -80,25 +80,25 @@ spec:
|
||||
mountPath: {{ $value.path }}
|
||||
subPath: content
|
||||
{{- end }}
|
||||
{{- range $name, $volume := .Values.celery.persistence }}
|
||||
{{- range $name, $volume := .Values.celerySummarize.persistence }}
|
||||
- name: "{{ $name }}"
|
||||
mountPath: "{{ $volume.mountPath }}"
|
||||
{{- end }}
|
||||
{{- range .Values.celery.extraVolumeMounts }}
|
||||
{{- range .Values.celerySummarize.extraVolumeMounts }}
|
||||
- name: {{ .name }}
|
||||
mountPath: {{ .mountPath }}
|
||||
subPath: {{ .subPath | default "" }}
|
||||
readOnly: {{ .readOnly }}
|
||||
{{- end }}
|
||||
{{- with .Values.celery.nodeSelector }}
|
||||
{{- with .Values.celerySummarize.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celery.affinity }}
|
||||
{{- with .Values.celerySummarize.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celery.tolerations }}
|
||||
{{- with .Values.celerySummarize.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -108,7 +108,7 @@ spec:
|
||||
configMap:
|
||||
name: "{{ include "meet.fullname" $ }}-files-{{ $index }}"
|
||||
{{- end }}
|
||||
{{- range $name, $volume := .Values.celery.persistence }}
|
||||
{{- range $name, $volume := .Values.celerySummarize.persistence }}
|
||||
- name: "{{ $name }}"
|
||||
{{- if eq $volume.type "emptyDir" }}
|
||||
emptyDir: {}
|
||||
@@ -117,7 +117,7 @@ spec:
|
||||
claimName: "{{ $fullName }}-{{ $name }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range .Values.celery.extraVolumes }}
|
||||
{{- range .Values.celerySummarize.extraVolumes }}
|
||||
- name: {{ .name }}
|
||||
{{- if .existingClaim }}
|
||||
persistentVolumeClaim:
|
||||
@@ -139,7 +139,7 @@ spec:
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
---
|
||||
{{ if .Values.celery.pdb.enabled }}
|
||||
{{ if .Values.celerySummarize.pdb.enabled }}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
@@ -0,0 +1,153 @@
|
||||
{{- $envVars := include "meet.common.env" (list . .Values.celeryTranscribe) -}}
|
||||
{{- $fullName := include "meet.celeryTranscribe.fullname" . -}}
|
||||
{{- $component := "celery-transcribe" -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
annotations:
|
||||
{{- with .Values.celeryTranscribe.dpAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "meet.common.labels" (list . $component) | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.celeryTranscribe.replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
{{- with .Values.celeryTranscribe.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "meet.common.selectorLabels" (list . $component) | nindent 8 }}
|
||||
spec:
|
||||
{{- if $.Values.image.credentials }}
|
||||
imagePullSecrets:
|
||||
- name: {{ include "meet.secret.dockerconfigjson.name" (dict "fullname" (include "meet.fullname" .) "imageCredentials" $.Values.image.credentials) }}
|
||||
{{- end }}
|
||||
shareProcessNamespace: {{ .Values.celeryTranscribe.shareProcessNamespace }}
|
||||
containers:
|
||||
{{- with .Values.celeryTranscribe.sidecars }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ (.Values.celeryTranscribe.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.celeryTranscribe.image | default dict).tag | default .Values.image.tag }}"
|
||||
imagePullPolicy: {{ (.Values.celeryTranscribe.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
|
||||
{{- with .Values.celeryTranscribe.command }}
|
||||
command:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celeryTranscribe.args }}
|
||||
args:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
env:
|
||||
{{- if $envVars }}
|
||||
{{- $envVars | indent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celeryTranscribe.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.celeryTranscribe.service.targetPort }}
|
||||
protocol: TCP
|
||||
{{- if .Values.celeryTranscribe.probes.liveness }}
|
||||
livenessProbe:
|
||||
{{- include "meet.probes.abstract" (merge .Values.celeryTranscribe.probes.liveness (dict "targetPort" .Values.celeryTranscribe.service.targetPort )) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.celeryTranscribe.probes.readiness }}
|
||||
readinessProbe:
|
||||
{{- include "meet.probes.abstract" (merge .Values.celeryTranscribe.probes.readiness (dict "targetPort" .Values.celeryTranscribe.service.targetPort )) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.celeryTranscribe.probes.startup }}
|
||||
startupProbe:
|
||||
{{- include "meet.probes.abstract" (merge .Values.celeryTranscribe.probes.startup (dict "targetPort" .Values.celeryTranscribe.service.targetPort )) | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celeryTranscribe.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
{{- range $index, $value := .Values.mountFiles }}
|
||||
- name: "files-{{ $index }}"
|
||||
mountPath: {{ $value.path }}
|
||||
subPath: content
|
||||
{{- end }}
|
||||
{{- range $name, $volume := .Values.celeryTranscribe.persistence }}
|
||||
- name: "{{ $name }}"
|
||||
mountPath: "{{ $volume.mountPath }}"
|
||||
{{- end }}
|
||||
{{- range .Values.celeryTranscribe.extraVolumeMounts }}
|
||||
- name: {{ .name }}
|
||||
mountPath: {{ .mountPath }}
|
||||
subPath: {{ .subPath | default "" }}
|
||||
readOnly: {{ .readOnly }}
|
||||
{{- end }}
|
||||
{{- with .Values.celeryTranscribe.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celeryTranscribe.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.celeryTranscribe.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
{{- range $index, $value := .Values.mountFiles }}
|
||||
- name: "files-{{ $index }}"
|
||||
configMap:
|
||||
name: "{{ include "meet.fullname" $ }}-files-{{ $index }}"
|
||||
{{- end }}
|
||||
{{- range $name, $volume := .Values.celeryTranscribe.persistence }}
|
||||
- name: "{{ $name }}"
|
||||
{{- if eq $volume.type "emptyDir" }}
|
||||
emptyDir: {}
|
||||
{{- else }}
|
||||
persistentVolumeClaim:
|
||||
claimName: "{{ $fullName }}-{{ $name }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range .Values.celeryTranscribe.extraVolumes }}
|
||||
- name: {{ .name }}
|
||||
{{- if .existingClaim }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .existingClaim }}
|
||||
{{- else if .hostPath }}
|
||||
hostPath:
|
||||
{{ toYaml .hostPath | nindent 12 }}
|
||||
{{- else if .csi }}
|
||||
csi:
|
||||
{{- toYaml .csi | nindent 12 }}
|
||||
{{- else if .configMap }}
|
||||
configMap:
|
||||
{{- toYaml .configMap | nindent 12 }}
|
||||
{{- else if .emptyDir }}
|
||||
emptyDir:
|
||||
{{- toYaml .emptyDir | nindent 12 }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
---
|
||||
{{ if .Values.celeryTranscribe.pdb.enabled }}
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
spec:
|
||||
maxUnavailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "meet.common.selectorLabels" (list . $component) | nindent 6 }}
|
||||
{{ end }}
|
||||
@@ -524,96 +524,189 @@ summary:
|
||||
pdb:
|
||||
enabled: true
|
||||
|
||||
## @section celery
|
||||
## @section celeryTranscribe
|
||||
|
||||
celery:
|
||||
## @param celery.dpAnnotations Annotations to add to the celery Deployment
|
||||
celeryTranscribe:
|
||||
## @param celeryTranscribe.dpAnnotations Annotations to add to the celeryTranscribe Deployment
|
||||
dpAnnotations: {}
|
||||
|
||||
## @param celery.command Override the celery container command
|
||||
## @param celeryTranscribe.command Override the celeryTranscribe container command
|
||||
command: []
|
||||
|
||||
## @param celery.args Override the celery container args
|
||||
## @param celeryTranscribe.args Override the celeryTranscribe container args
|
||||
args: []
|
||||
|
||||
## @param celery.replicas Amount of celery replicas
|
||||
## @param celeryTranscribe.replicas Amount of celeryTranscribe replicas
|
||||
replicas: 1
|
||||
|
||||
## @param celery.shareProcessNamespace Enable share process namespace between containers
|
||||
## @param celeryTranscribe.shareProcessNamespace Enable share process namespace between containers
|
||||
shareProcessNamespace: false
|
||||
|
||||
## @param celery.sidecars Add sidecars containers to celery deployment
|
||||
## @param celeryTranscribe.sidecars Add sidecars containers to celeryTranscribe deployment
|
||||
sidecars: []
|
||||
|
||||
## @param celery.migrateJobAnnotations Annotations for the migrate job
|
||||
## @param celeryTranscribe.migrateJobAnnotations Annotations for the migrate job
|
||||
migrateJobAnnotations: {}
|
||||
|
||||
## @param celery.securityContext Configure celery Pod security context
|
||||
## @param celeryTranscribe.securityContext Configure celeryTranscribe Pod security context
|
||||
securityContext: null
|
||||
|
||||
## @param celery.envVars Configure celery container environment variables
|
||||
## @extra celery.envVars.BY_VALUE Example environment variable by setting value directly
|
||||
## @extra celery.envVars.FROM_CONFIGMAP.configMapKeyRef.name Name of a ConfigMap when configuring env vars from a ConfigMap
|
||||
## @extra celery.envVars.FROM_CONFIGMAP.configMapKeyRef.key Key within a ConfigMap when configuring env vars from a ConfigMap
|
||||
## @extra celery.envVars.FROM_SECRET.secretKeyRef.name Name of a Secret when configuring env vars from a Secret
|
||||
## @extra celery.envVars.FROM_SECRET.secretKeyRef.key Key within a Secret when configuring env vars from a Secret
|
||||
## @skip celery.envVars
|
||||
## @param celeryTranscribe.envVars Configure celeryTranscribe container environment variables
|
||||
## @extra celeryTranscribe.envVars.BY_VALUE Example environment variable by setting value directly
|
||||
## @extra celeryTranscribe.envVars.FROM_CONFIGMAP.configMapKeyRef.name Name of a ConfigMap when configuring env vars from a ConfigMap
|
||||
## @extra celeryTranscribe.envVars.FROM_CONFIGMAP.configMapKeyRef.key Key within a ConfigMap when configuring env vars from a ConfigMap
|
||||
## @extra celeryTranscribe.envVars.FROM_SECRET.secretKeyRef.name Name of a Secret when configuring env vars from a Secret
|
||||
## @extra celeryTranscribe.envVars.FROM_SECRET.secretKeyRef.key Key within a Secret when configuring env vars from a Secret
|
||||
## @skip celeryTranscribe.envVars
|
||||
envVars:
|
||||
<<: *commonEnvVars
|
||||
|
||||
## @param celery.podAnnotations Annotations to add to the celery Pod
|
||||
## @param celeryTranscribe.podAnnotations Annotations to add to the celeryTranscribe Pod
|
||||
podAnnotations: {}
|
||||
|
||||
## @param celery.service.type celery Service type
|
||||
## @param celery.service.port celery Service listening port
|
||||
## @param celery.service.targetPort celery container listening port
|
||||
## @param celery.service.annotations Annotations to add to the celery Service
|
||||
## @param celeryTranscribe.service.type celeryTranscribe Service type
|
||||
## @param celeryTranscribe.service.port celeryTranscribe Service listening port
|
||||
## @param celeryTranscribe.service.targetPort celeryTranscribe container listening port
|
||||
## @param celeryTranscribe.service.annotations Annotations to add to the celeryTranscribe Service
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
targetPort: 8000
|
||||
annotations: {}
|
||||
|
||||
## @param celery.probes Configure celery probes
|
||||
## @param celery.probes.liveness.path [nullable] Configure path for celery HTTP liveness probe
|
||||
## @param celery.probes.liveness.targetPort [nullable] Configure port for celery HTTP liveness probe
|
||||
## @param celery.probes.liveness.initialDelaySeconds [nullable] Configure initial delay for celery liveness probe
|
||||
## @param celery.probes.liveness.initialDelaySeconds [nullable] Configure timeout for celery liveness probe
|
||||
## @param celery.probes.startup.path [nullable] Configure path for celery HTTP startup probe
|
||||
## @param celery.probes.startup.targetPort [nullable] Configure port for celery HTTP startup probe
|
||||
## @param celery.probes.startup.initialDelaySeconds [nullable] Configure initial delay for celery startup probe
|
||||
## @param celery.probes.startup.initialDelaySeconds [nullable] Configure timeout for celery startup probe
|
||||
## @param celery.probes.readiness.path [nullable] Configure path for celery HTTP readiness probe
|
||||
## @param celery.probes.readiness.targetPort [nullable] Configure port for celery HTTP readiness probe
|
||||
## @param celery.probes.readiness.initialDelaySeconds [nullable] Configure initial delay for celery readiness probe
|
||||
## @param celery.probes.readiness.initialDelaySeconds [nullable] Configure timeout for celery readiness probe
|
||||
## @param celeryTranscribe.probes Configure celeryTranscribe probes
|
||||
## @param celeryTranscribe.probes.liveness.path [nullable] Configure path for celeryTranscribe HTTP liveness probe
|
||||
## @param celeryTranscribe.probes.liveness.targetPort [nullable] Configure port for celeryTranscribe HTTP liveness probe
|
||||
## @param celeryTranscribe.probes.liveness.initialDelaySeconds [nullable] Configure initial delay for celeryTranscribe liveness probe
|
||||
## @param celeryTranscribe.probes.liveness.initialDelaySeconds [nullable] Configure timeout for celeryTranscribe liveness probe
|
||||
## @param celeryTranscribe.probes.startup.path [nullable] Configure path for celeryTranscribe HTTP startup probe
|
||||
## @param celeryTranscribe.probes.startup.targetPort [nullable] Configure port for celeryTranscribe HTTP startup probe
|
||||
## @param celeryTranscribe.probes.startup.initialDelaySeconds [nullable] Configure initial delay for celeryTranscribe startup probe
|
||||
## @param celeryTranscribe.probes.startup.initialDelaySeconds [nullable] Configure timeout for celeryTranscribe startup probe
|
||||
## @param celeryTranscribe.probes.readiness.path [nullable] Configure path for celeryTranscribe HTTP readiness probe
|
||||
## @param celeryTranscribe.probes.readiness.targetPort [nullable] Configure port for celeryTranscribe HTTP readiness probe
|
||||
## @param celeryTranscribe.probes.readiness.initialDelaySeconds [nullable] Configure initial delay for celeryTranscribe readiness probe
|
||||
## @param celeryTranscribe.probes.readiness.initialDelaySeconds [nullable] Configure timeout for celeryTranscribe readiness probe
|
||||
probes: {}
|
||||
|
||||
## @param celery.resources Resource requirements for the celery container
|
||||
## @param celeryTranscribe.resources Resource requirements for the celeryTranscribe container
|
||||
resources: {}
|
||||
|
||||
## @param celery.nodeSelector Node selector for the celery Pod
|
||||
## @param celeryTranscribe.nodeSelector Node selector for the celeryTranscribe Pod
|
||||
nodeSelector: {}
|
||||
|
||||
## @param celery.tolerations Tolerations for the celery Pod
|
||||
## @param celeryTranscribe.tolerations Tolerations for the celeryTranscribe Pod
|
||||
tolerations: []
|
||||
|
||||
## @param celery.affinity Affinity for the celery Pod
|
||||
## @param celeryTranscribe.affinity Affinity for the celeryTranscribe Pod
|
||||
affinity: {}
|
||||
|
||||
## @param celery.persistence Additional volumes to create and mount on the celery. Used for debugging purposes
|
||||
## @extra celery.persistence.volume-name.size Size of the additional volume
|
||||
## @extra celery.persistence.volume-name.type Type of the additional volume, persistentVolumeClaim or emptyDir
|
||||
## @extra celery.persistence.volume-name.mountPath Path where the volume should be mounted to
|
||||
## @param celeryTranscribe.persistence Additional volumes to create and mount on the celeryTranscribe. Used for debugging purposes
|
||||
## @extra celeryTranscribe.persistence.volume-name.size Size of the additional volume
|
||||
## @extra celeryTranscribe.persistence.volume-name.type Type of the additional volume, persistentVolumeClaim or emptyDir
|
||||
## @extra celeryTranscribe.persistence.volume-name.mountPath Path where the volume should be mounted to
|
||||
persistence: {}
|
||||
|
||||
## @param celery.extraVolumeMounts Additional volumes to mount on the celery.
|
||||
## @param celeryTranscribe.extraVolumeMounts Additional volumes to mount on the celeryTranscribe.
|
||||
extraVolumeMounts: []
|
||||
|
||||
## @param celery.extraVolumes Additional volumes to mount on the celery.
|
||||
## @param celeryTranscribe.extraVolumes Additional volumes to mount on the celeryTranscribe.
|
||||
extraVolumes: []
|
||||
|
||||
## @param celery.pdb.enabled Enable pdb on celery
|
||||
## @param celeryTranscribe.pdb.enabled Enable pdb on celeryTranscribe
|
||||
pdb:
|
||||
enabled: false
|
||||
|
||||
## @section celerySummarize
|
||||
|
||||
celerySummarize:
|
||||
## @param celerySummarize.dpAnnotations Annotations to add to the celerySummarize Deployment
|
||||
dpAnnotations: {}
|
||||
|
||||
## @param celerySummarize.command Override the celerySummarize container command
|
||||
command: []
|
||||
|
||||
## @param celerySummarize.args Override the celerySummarize container args
|
||||
args: []
|
||||
|
||||
## @param celerySummarize.replicas Amount of celerySummarize replicas
|
||||
replicas: 1
|
||||
|
||||
## @param celerySummarize.shareProcessNamespace Enable share process namespace between containers
|
||||
shareProcessNamespace: false
|
||||
|
||||
## @param celerySummarize.sidecars Add sidecars containers to celerySummarize deployment
|
||||
sidecars: []
|
||||
|
||||
## @param celerySummarize.migrateJobAnnotations Annotations for the migrate job
|
||||
migrateJobAnnotations: {}
|
||||
|
||||
## @param celerySummarize.securityContext Configure celerySummarize Pod security context
|
||||
securityContext: null
|
||||
|
||||
## @param celerySummarize.envVars Configure celerySummarize container environment variables
|
||||
## @extra celerySummarize.envVars.BY_VALUE Example environment variable by setting value directly
|
||||
## @extra celerySummarize.envVars.FROM_CONFIGMAP.configMapKeyRef.name Name of a ConfigMap when configuring env vars from a ConfigMap
|
||||
## @extra celerySummarize.envVars.FROM_CONFIGMAP.configMapKeyRef.key Key within a ConfigMap when configuring env vars from a ConfigMap
|
||||
## @extra celerySummarize.envVars.FROM_SECRET.secretKeyRef.name Name of a Secret when configuring env vars from a Secret
|
||||
## @extra celerySummarize.envVars.FROM_SECRET.secretKeyRef.key Key within a Secret when configuring env vars from a Secret
|
||||
## @skip celerySummarize.envVars
|
||||
envVars:
|
||||
<<: *commonEnvVars
|
||||
|
||||
## @param celerySummarize.podAnnotations Annotations to add to the celerySummarize Pod
|
||||
podAnnotations: {}
|
||||
|
||||
## @param celerySummarize.service.type celerySummarize Service type
|
||||
## @param celerySummarize.service.port celerySummarize Service listening port
|
||||
## @param celerySummarize.service.targetPort celerySummarize container listening port
|
||||
## @param celerySummarize.service.annotations Annotations to add to the celerySummarize Service
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
targetPort: 8000
|
||||
annotations: {}
|
||||
|
||||
## @param celerySummarize.probes Configure celerySummarize probes
|
||||
## @param celerySummarize.probes.liveness.path [nullable] Configure path for celerySummarize HTTP liveness probe
|
||||
## @param celerySummarize.probes.liveness.targetPort [nullable] Configure port for celerySummarize HTTP liveness probe
|
||||
## @param celerySummarize.probes.liveness.initialDelaySeconds [nullable] Configure initial delay for celerySummarize liveness probe
|
||||
## @param celerySummarize.probes.liveness.initialDelaySeconds [nullable] Configure timeout for celerySummarize liveness probe
|
||||
## @param celerySummarize.probes.startup.path [nullable] Configure path for celerySummarize HTTP startup probe
|
||||
## @param celerySummarize.probes.startup.targetPort [nullable] Configure port for celerySummarize HTTP startup probe
|
||||
## @param celerySummarize.probes.startup.initialDelaySeconds [nullable] Configure initial delay for celerySummarize startup probe
|
||||
## @param celerySummarize.probes.startup.initialDelaySeconds [nullable] Configure timeout for celerySummarize startup probe
|
||||
## @param celerySummarize.probes.readiness.path [nullable] Configure path for celerySummarize HTTP readiness probe
|
||||
## @param celerySummarize.probes.readiness.targetPort [nullable] Configure port for celerySummarize HTTP readiness probe
|
||||
## @param celerySummarize.probes.readiness.initialDelaySeconds [nullable] Configure initial delay for celerySummarize readiness probe
|
||||
## @param celerySummarize.probes.readiness.initialDelaySeconds [nullable] Configure timeout for celerySummarize readiness probe
|
||||
probes: {}
|
||||
|
||||
## @param celerySummarize.resources Resource requirements for the celerySummarize container
|
||||
resources: {}
|
||||
|
||||
## @param celerySummarize.nodeSelector Node selector for the celerySummarize Pod
|
||||
nodeSelector: {}
|
||||
|
||||
## @param celerySummarize.tolerations Tolerations for the celerySummarize Pod
|
||||
tolerations: []
|
||||
|
||||
## @param celerySummarize.affinity Affinity for the celerySummarize Pod
|
||||
affinity: {}
|
||||
|
||||
## @param celerySummarize.persistence Additional volumes to create and mount on the celerySummarize. Used for debugging purposes
|
||||
## @extra celerySummarize.persistence.volume-name.size Size of the additional volume
|
||||
## @extra celerySummarize.persistence.volume-name.type Type of the additional volume, persistentVolumeClaim or emptyDir
|
||||
## @extra celerySummarize.persistence.volume-name.mountPath Path where the volume should be mounted to
|
||||
persistence: {}
|
||||
|
||||
## @param celerySummarize.extraVolumeMounts Additional volumes to mount on the celerySummarize.
|
||||
extraVolumeMounts: []
|
||||
|
||||
## @param celerySummarize.extraVolumes Additional volumes to mount on the celerySummarize.
|
||||
extraVolumes: []
|
||||
|
||||
## @param celerySummarize.pdb.enabled Enable pdb on celerySummarize
|
||||
pdb:
|
||||
enabled: false
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.38",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.38",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@html-to/text-cli": "0.5.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.38",
|
||||
"description": "An util to generate html and text django's templates from mjml templates",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.38",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sdk",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.38",
|
||||
"license": "ISC",
|
||||
"workspaces": [
|
||||
"./library",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sdk",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.38",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
|
||||
@@ -8,6 +8,14 @@ COPY pyproject.toml .
|
||||
|
||||
RUN pip3 install --no-cache-dir .
|
||||
|
||||
FROM base AS development
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
RUN pip3 install --no-cache-dir -e ".[dev]" || pip3 install --no-cache-dir -e .
|
||||
|
||||
CMD ["uvicorn", "summary.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||
|
||||
FROM base AS production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Experimental Stack
|
||||
|
||||
This is an experimental part of the stack. It currently lacks proper observability, unit tests, and other production-grade features. This serves as the base for AI features in Visio.
|
||||
|
||||
## Usage
|
||||
|
||||
From the root of the project:
|
||||
|
||||
```sh
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
Configure your env values in `env.d/summary` to properly set up WhisperX and the LLM API you will call.
|
||||
|
||||
```sh
|
||||
make run
|
||||
```
|
||||
|
||||
When the stack is up, configure the MinIO webhook
|
||||
*(TODO: add this step to `make bootstrap`)*
|
||||
|
||||
```sh
|
||||
make minio-webhook-setup
|
||||
```
|
||||
|
||||
If you want to develop on the Celery workers with hot reloading, run:
|
||||
|
||||
```sh
|
||||
docker compose watch celery-summary-transcribe celery-summary-summarize
|
||||
```
|
||||
|
||||
Celery workers will hot reload on any change.
|
||||
@@ -1,27 +0,0 @@
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
app:
|
||||
container_name: app
|
||||
build: .
|
||||
command: uvicorn summary.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
volumes:
|
||||
- .:/app
|
||||
ports:
|
||||
- "8000:8000"
|
||||
restart: always
|
||||
env_file:
|
||||
".env"
|
||||
depends_on:
|
||||
- redis
|
||||
celery_worker:
|
||||
container_name: celery_worker
|
||||
build: .
|
||||
command: celery -A summary.core.celery_worker worker --pool=solo --loglevel=debug
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- redis
|
||||
- app
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
[project]
|
||||
name = "summary"
|
||||
version = "0.1.33"
|
||||
version = "0.1.38"
|
||||
dependencies = [
|
||||
"fastapi[standard]>=0.105.0",
|
||||
"uvicorn>=0.24.0",
|
||||
@@ -15,6 +15,7 @@ dependencies = [
|
||||
"posthog==6.0.3",
|
||||
"requests==2.32.4",
|
||||
"sentry-sdk[fastapi, celery]==2.30.0",
|
||||
"langfuse==3.4.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@@ -8,9 +8,11 @@ from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
|
||||
from summary.core.celery_worker import (
|
||||
process_audio_transcribe_summarize,
|
||||
process_audio_transcribe_summarize_v2,
|
||||
)
|
||||
from summary.core.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
class TaskCreation(BaseModel):
|
||||
@@ -31,22 +33,18 @@ router = APIRouter(prefix="/tasks")
|
||||
@router.post("/")
|
||||
async def create_task(request: TaskCreation):
|
||||
"""Create a task."""
|
||||
if request.version == 1:
|
||||
task = process_audio_transcribe_summarize.delay(
|
||||
request.filename, request.email, request.sub
|
||||
)
|
||||
else:
|
||||
task = process_audio_transcribe_summarize_v2.apply_async(
|
||||
args=[
|
||||
request.filename,
|
||||
request.email,
|
||||
request.sub,
|
||||
time.time(),
|
||||
request.room,
|
||||
request.recording_date,
|
||||
request.recording_time,
|
||||
]
|
||||
)
|
||||
task = process_audio_transcribe_summarize_v2.apply_async(
|
||||
args=[
|
||||
request.filename,
|
||||
request.email,
|
||||
request.sub,
|
||||
time.time(),
|
||||
request.room,
|
||||
request.recording_date,
|
||||
request.recording_time,
|
||||
],
|
||||
queue=settings.transcribe_queue,
|
||||
)
|
||||
|
||||
return {"id": task.id, "message": "Task created"}
|
||||
|
||||
|
||||
@@ -48,6 +48,17 @@ class Analytics:
|
||||
except Exception as e:
|
||||
raise AnalyticsException("Failed to capture analytics event") from e
|
||||
|
||||
def is_feature_enabled(self, feature_name: str, distinct_id: str = None) -> bool:
|
||||
"""Check if a feature flag is enabled for a user."""
|
||||
if self.is_disabled:
|
||||
return False
|
||||
|
||||
try:
|
||||
return self._client.feature_enabled(feature_name, distinct_id)
|
||||
except Exception as e:
|
||||
logger.error("Error checking feature flag %s: %s", feature_name, e)
|
||||
return False
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_analytics():
|
||||
@@ -103,7 +114,7 @@ class MetadataManager:
|
||||
|
||||
initial_metadata = {
|
||||
"start_time": time.time(),
|
||||
"asr_model": settings.openai_asr_model,
|
||||
"asr_model": settings.whisperx_asr_model,
|
||||
"retries": 0,
|
||||
}
|
||||
|
||||
|
||||
@@ -9,19 +9,27 @@ import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import openai
|
||||
import sentry_sdk
|
||||
from celery import Celery, signals
|
||||
from celery.utils.log import get_task_logger
|
||||
from minio import Minio
|
||||
from mutagen import File
|
||||
from openai import OpenAI
|
||||
from requests import Session, exceptions
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util import Retry
|
||||
|
||||
from summary.core.analytics import MetadataManager, get_analytics
|
||||
from summary.core.config import get_settings
|
||||
from summary.core.prompt import get_instructions
|
||||
from summary.core.observability import Observability
|
||||
from summary.core.prompt import (
|
||||
PROMPT_SYSTEM_CLEANING,
|
||||
PROMPT_SYSTEM_NEXT_STEP,
|
||||
PROMPT_SYSTEM_PART,
|
||||
PROMPT_SYSTEM_PLAN,
|
||||
PROMPT_SYSTEM_TLDR,
|
||||
PROMPT_USER_PART,
|
||||
)
|
||||
|
||||
settings = get_settings()
|
||||
analytics = get_analytics()
|
||||
@@ -39,6 +47,14 @@ celery = Celery(
|
||||
|
||||
celery.config_from_object("summary.core.celery_config")
|
||||
|
||||
obs = Observability(
|
||||
is_enabled=settings.langfuse_is_enabled,
|
||||
langfuse_host=settings.langfuse_host,
|
||||
langfuse_public_key=settings.langfuse_public_key,
|
||||
langfuse_secret_key=settings.langfuse_secret_key,
|
||||
)
|
||||
logger.info("Observability enabled: %s", obs.is_enabled)
|
||||
|
||||
if settings.sentry_dsn and settings.sentry_is_enabled:
|
||||
|
||||
@signals.celeryd_init.connect
|
||||
@@ -95,6 +111,48 @@ def create_retry_session():
|
||||
return session
|
||||
|
||||
|
||||
class LLMException(Exception):
|
||||
"""LLM call failed."""
|
||||
|
||||
|
||||
class LLMService:
|
||||
"""Service for performing calls to the LLM configured in the settings."""
|
||||
|
||||
def __init__(self):
|
||||
"""Init the LLMService once."""
|
||||
self._client = OpenAI(
|
||||
base_url=settings.llm_base_url, api_key=settings.llm_api_key
|
||||
)
|
||||
self.gen_ctx = obs.generation
|
||||
|
||||
def call(self, system_prompt: str, user_prompt: str):
|
||||
"""Call the LLM service.
|
||||
|
||||
Takes a system prompt and a user prompt, and returns the LLM's response
|
||||
Returns None if the call fails.
|
||||
"""
|
||||
try:
|
||||
response = self._client.chat.completions.create(
|
||||
model=settings.llm_model,
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt},
|
||||
],
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
except Exception as e:
|
||||
logger.error("LLM call failed: %s", e)
|
||||
raise LLMException("LLM call failed.") from e
|
||||
|
||||
def call_llm_gen(self, name, system, user):
|
||||
"""Call the LLM service within a generation context."""
|
||||
with self.gen_ctx(
|
||||
name=name,
|
||||
model=settings.llm_model,
|
||||
):
|
||||
return self.call(system, user)
|
||||
|
||||
|
||||
def format_segments(transcription_data):
|
||||
"""Format transcription segments from WhisperX into a readable conversation format.
|
||||
|
||||
@@ -156,88 +214,13 @@ def task_failure_handler(task_id, exception=None, **kwargs):
|
||||
metadata_manager.capture(task_id, settings.posthog_event_failure)
|
||||
|
||||
|
||||
@celery.task(max_retries=settings.celery_max_retries)
|
||||
def process_audio_transcribe_summarize(filename: str, email: str, sub: str):
|
||||
"""Process an audio file by transcribing it and generating a summary.
|
||||
|
||||
This Celery task performs the following operations:
|
||||
1. Retrieves the audio file from MinIO storage
|
||||
2. Transcribes the audio using OpenAI-compliant API's ASR model
|
||||
3. Generates a summary of the transcription using OpenAI-compliant API's LLM
|
||||
4. Sends the results via webhook
|
||||
"""
|
||||
logger.info("Notification received")
|
||||
logger.debug("filename: %s", filename)
|
||||
|
||||
minio_client = Minio(
|
||||
settings.aws_s3_endpoint_url,
|
||||
access_key=settings.aws_s3_access_key_id,
|
||||
secret_key=settings.aws_s3_secret_access_key,
|
||||
secure=settings.aws_s3_secure_access,
|
||||
)
|
||||
|
||||
logger.debug("Connection to the Minio bucket successful")
|
||||
|
||||
audio_file_stream = minio_client.get_object(
|
||||
settings.aws_storage_bucket_name, object_name=filename
|
||||
)
|
||||
|
||||
temp_file_path = save_audio_stream(audio_file_stream)
|
||||
logger.debug("Recording successfully downloaded, filepath: %s", temp_file_path)
|
||||
|
||||
logger.info("Initiating OpenAI client")
|
||||
|
||||
openai_client = openai.OpenAI(
|
||||
api_key=settings.openai_api_key,
|
||||
base_url=settings.openai_base_url,
|
||||
max_retries=settings.openai_max_retries,
|
||||
)
|
||||
|
||||
try:
|
||||
logger.info("Querying transcription …")
|
||||
with open(temp_file_path, "rb") as audio_file:
|
||||
transcription = openai_client.audio.transcriptions.create(
|
||||
model=settings.openai_asr_model, file=audio_file
|
||||
)
|
||||
transcription = transcription.text
|
||||
|
||||
logger.debug("Transcription: \n %s", transcription)
|
||||
finally:
|
||||
if os.path.exists(temp_file_path):
|
||||
os.remove(temp_file_path)
|
||||
logger.debug("Temporary file removed: %s", temp_file_path)
|
||||
|
||||
instructions = get_instructions(transcription)
|
||||
summary_response = openai_client.chat.completions.create(
|
||||
model=settings.openai_llm_model, messages=instructions
|
||||
)
|
||||
|
||||
summary = summary_response.choices[0].message.content
|
||||
logger.debug("Summary: \n %s", summary)
|
||||
|
||||
# fixme - generate a title using LLM
|
||||
data = {
|
||||
"title": "Votre résumé",
|
||||
"content": summary,
|
||||
"email": email,
|
||||
"sub": sub,
|
||||
}
|
||||
|
||||
logger.debug("Submitting webhook to %s", settings.webhook_url)
|
||||
logger.debug("Request payload: %s", json.dumps(data, indent=2))
|
||||
|
||||
response = post_with_retries(settings.webhook_url, data)
|
||||
|
||||
logger.info("Webhook submitted successfully. Status: %s", response.status_code)
|
||||
logger.debug("Response body: %s", response.text)
|
||||
|
||||
|
||||
@celery.task(
|
||||
bind=True,
|
||||
autoretry_for=[exceptions.HTTPError],
|
||||
max_retries=settings.celery_max_retries,
|
||||
)
|
||||
def process_audio_transcribe_summarize_v2(
|
||||
@obs.observe(name="process-audio", capture_input=True, capture_output=False)
|
||||
def process_audio_transcribe_summarize_v2( # noqa: PLR0915
|
||||
self,
|
||||
filename: str,
|
||||
email: str,
|
||||
@@ -258,6 +241,21 @@ def process_audio_transcribe_summarize_v2(
|
||||
logger.info("Notification received")
|
||||
logger.debug("filename: %s", filename)
|
||||
|
||||
try:
|
||||
obs.update_current_trace(
|
||||
user_id=sub or email,
|
||||
tags=["celery", "transcription", "whisperx"],
|
||||
metadata={
|
||||
"filename": filename,
|
||||
"room": room,
|
||||
"recording_date": recording_date,
|
||||
"recording_time": recording_time,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("Langfuse update trace failed: %s", e)
|
||||
pass
|
||||
|
||||
task_id = self.request.id
|
||||
|
||||
minio_client = Minio(
|
||||
@@ -272,7 +270,6 @@ def process_audio_transcribe_summarize_v2(
|
||||
audio_file_stream = minio_client.get_object(
|
||||
settings.aws_storage_bucket_name, object_name=filename
|
||||
)
|
||||
|
||||
temp_file_path = save_audio_stream(audio_file_stream)
|
||||
|
||||
logger.info("Recording successfully downloaded")
|
||||
@@ -292,30 +289,35 @@ def process_audio_transcribe_summarize_v2(
|
||||
logger.error(error_msg)
|
||||
raise AudioValidationError(error_msg)
|
||||
|
||||
logger.info("Initiating OpenAI client")
|
||||
openai_client = openai.OpenAI(
|
||||
api_key=settings.openai_api_key,
|
||||
base_url=settings.openai_base_url,
|
||||
max_retries=settings.openai_max_retries,
|
||||
logger.info("Initiating WhisperX client")
|
||||
whisperx_client = OpenAI(
|
||||
api_key=settings.whisperx_api_key,
|
||||
base_url=settings.whisperx_base_url,
|
||||
max_retries=settings.whisperx_max_retries,
|
||||
)
|
||||
|
||||
try:
|
||||
logger.info("Querying transcription …")
|
||||
transcription_start_time = time.time()
|
||||
with open(temp_file_path, "rb") as audio_file:
|
||||
transcription = openai_client.audio.transcriptions.create(
|
||||
model=settings.openai_asr_model, file=audio_file
|
||||
)
|
||||
metadata_manager.track(
|
||||
task_id,
|
||||
{
|
||||
"transcription_time": round(
|
||||
time.time() - transcription_start_time, 2
|
||||
)
|
||||
},
|
||||
)
|
||||
logger.info("Transcription received.")
|
||||
logger.debug("Transcription: \n %s", transcription)
|
||||
with obs.span(
|
||||
name="whisperx.transcribe",
|
||||
input={
|
||||
"model": settings.whisperx_asr_model,
|
||||
"audio_seconds": round(audio_file.info.length, 2),
|
||||
"endpoint": settings.whisperx_base_url,
|
||||
},
|
||||
):
|
||||
with open(temp_file_path, "rb") as audio_file_rb:
|
||||
transcription = whisperx_client.audio.transcriptions.create(
|
||||
model=settings.whisperx_asr_model,
|
||||
file=audio_file_rb,
|
||||
)
|
||||
metadata_manager.track(
|
||||
task_id,
|
||||
{"transcription_time": round(time.time() - transcription_start_time, 2)},
|
||||
)
|
||||
logger.info("Transcription received.")
|
||||
logger.debug("Transcription: \n %s", transcription)
|
||||
finally:
|
||||
if os.path.exists(temp_file_path):
|
||||
os.remove(temp_file_path)
|
||||
@@ -354,4 +356,102 @@ def process_audio_transcribe_summarize_v2(
|
||||
|
||||
metadata_manager.capture(task_id, settings.posthog_event_success)
|
||||
|
||||
# TODO - integrate summarize the transcript and create a new document.
|
||||
if (
|
||||
analytics.is_feature_enabled("summary-enabled", distinct_id=sub)
|
||||
and settings.is_summary_enabled
|
||||
):
|
||||
logger.info("Queuing summary generation task.")
|
||||
summarize_transcription.apply_async(
|
||||
args=[formatted_transcription, email, sub, title],
|
||||
queue=settings.summarize_queue,
|
||||
)
|
||||
else:
|
||||
logger.info("Summary generation not enabled for this user.")
|
||||
|
||||
|
||||
@celery.task(
|
||||
bind=True,
|
||||
autoretry_for=[LLMException, Exception],
|
||||
max_retries=settings.celery_max_retries,
|
||||
queue=settings.summarize_queue,
|
||||
)
|
||||
@obs.observe(name="summarize-transcription", capture_input=False, capture_output=False)
|
||||
def summarize_transcription(self, transcript: str, email: str, sub: str, title: str):
|
||||
"""Generate a summary from the provided transcription text.
|
||||
|
||||
This Celery task performs the following operations:
|
||||
1. Uses an LLM to generate a TL;DR summary of the transcription.
|
||||
2. Breaks the transcription into parts and summarizes each part.
|
||||
3. Cleans up the combined summary
|
||||
4. Generates next steps.
|
||||
5. Sends the final summary via webhook.
|
||||
"""
|
||||
logger.info("Starting summarization task")
|
||||
|
||||
llm_service = LLMService()
|
||||
|
||||
try:
|
||||
obs.update_current_trace(
|
||||
user_id=sub or email,
|
||||
tags=["celery", "summarization"],
|
||||
metadata={"title": title},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("Langfuse update trace failed: %s", e)
|
||||
pass
|
||||
|
||||
tldr = llm_service.call_llm_gen("tldr", PROMPT_SYSTEM_TLDR, transcript)
|
||||
|
||||
logger.info("TLDR generated")
|
||||
|
||||
parts = llm_service.call_llm_gen("plan", PROMPT_SYSTEM_PLAN, transcript)
|
||||
logger.info("Plan generated")
|
||||
|
||||
parts = parts.split("\n")
|
||||
parts = [x for x in parts if x.strip() != ""]
|
||||
logger.info("Empty parts removed")
|
||||
|
||||
parts_summarized = []
|
||||
for part in parts:
|
||||
prompt_user_part = PROMPT_USER_PART.format(part=part, transcript=transcript)
|
||||
logger.info("Summarizing part: %s", part)
|
||||
parts_summarized.append(
|
||||
llm_service.call_llm_gen("part", PROMPT_SYSTEM_PART, prompt_user_part)
|
||||
)
|
||||
|
||||
logger.info("Parts summarized")
|
||||
|
||||
raw_summary = "\n\n".join(parts_summarized)
|
||||
|
||||
next_steps = llm_service.call_llm_gen(
|
||||
"next_steps", PROMPT_SYSTEM_NEXT_STEP, transcript
|
||||
)
|
||||
logger.info("Next steps generated")
|
||||
|
||||
cleaned_summary = llm_service.call_llm_gen(
|
||||
"cleaning", PROMPT_SYSTEM_CLEANING, raw_summary
|
||||
)
|
||||
logger.info("Summary cleaned")
|
||||
|
||||
summary = tldr + "\n\n" + cleaned_summary + "\n\n" + next_steps
|
||||
|
||||
data = {
|
||||
"title": settings.summary_title_template.format(
|
||||
title=title,
|
||||
),
|
||||
"content": summary,
|
||||
"email": email,
|
||||
"sub": sub,
|
||||
}
|
||||
|
||||
logger.debug("Submitting webhook to %s", settings.webhook_url)
|
||||
|
||||
response = post_with_retries(settings.webhook_url, data)
|
||||
|
||||
logger.info("Webhook submitted successfully. Status: %s", response.status_code)
|
||||
logger.debug("Response body: %s", response.text)
|
||||
try:
|
||||
obs.flush()
|
||||
except Exception as e:
|
||||
logger.warning("Langfuse flush failed: %s", e)
|
||||
pass
|
||||
|
||||
@@ -4,6 +4,7 @@ from functools import lru_cache
|
||||
from typing import Annotated, List, Optional
|
||||
|
||||
from fastapi import Depends
|
||||
from pydantic import SecretStr
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
@@ -24,6 +25,9 @@ class Settings(BaseSettings):
|
||||
celery_result_backend: str = "redis://redis/0"
|
||||
celery_max_retries: int = 1
|
||||
|
||||
transcribe_queue: str = "transcribe-queue"
|
||||
summarize_queue: str = "summarize-queue"
|
||||
|
||||
# Minio settings
|
||||
aws_storage_bucket_name: str
|
||||
aws_s3_endpoint_url: str
|
||||
@@ -32,11 +36,13 @@ class Settings(BaseSettings):
|
||||
aws_s3_secure_access: bool = True
|
||||
|
||||
# AI-related settings
|
||||
openai_api_key: str
|
||||
openai_base_url: str = "https://api.openai.com/v1"
|
||||
openai_asr_model: str = "whisper-1"
|
||||
openai_llm_model: str = "gpt-4o"
|
||||
openai_max_retries: int = 0
|
||||
whisperx_api_key: str
|
||||
whisperx_base_url: str = "https://api.openai.com/v1"
|
||||
whisperx_asr_model: str = "whisper-1"
|
||||
whisperx_max_retries: int = 0
|
||||
llm_base_url: str
|
||||
llm_api_key: str
|
||||
llm_model: str
|
||||
|
||||
# Webhook-related settings
|
||||
webhook_max_retries: int = 2
|
||||
@@ -50,6 +56,10 @@ class Settings(BaseSettings):
|
||||
document_title_template: Optional[str] = (
|
||||
'Réunion "{room}" du {room_recording_date} à {room_recording_time}'
|
||||
)
|
||||
summary_title_template: Optional[str] = "Résumé de {title}"
|
||||
|
||||
# Summary related settings
|
||||
is_summary_enabled: bool = True
|
||||
|
||||
# Sentry
|
||||
sentry_is_enabled: bool = False
|
||||
@@ -66,6 +76,12 @@ class Settings(BaseSettings):
|
||||
task_tracker_redis_url: str = "redis://redis/0"
|
||||
task_tracker_prefix: str = "task_metadata:"
|
||||
|
||||
# Langfuse
|
||||
langfuse_is_enabled: bool = True
|
||||
langfuse_host: Optional[str] = None
|
||||
langfuse_public_key: Optional[str] = None
|
||||
langfuse_secret_key: Optional[SecretStr] = None
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings():
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
"""Wrapper around Langfuse observability."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import nullcontext
|
||||
from typing import Any, Callable, ContextManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from langfuse import Langfuse as _Langfuse
|
||||
from langfuse import observe as _lf_observe
|
||||
except Exception as e:
|
||||
logger.debug("Langfuse import failed: %s", e)
|
||||
_Langfuse = None
|
||||
_lf_observe = None
|
||||
|
||||
|
||||
class Observability:
|
||||
"""Wrapper around Langfuse observability."""
|
||||
|
||||
def __init__(
|
||||
self, is_enabled, langfuse_host, langfuse_public_key, langfuse_secret_key
|
||||
) -> None:
|
||||
"""Initialize the Observability instance."""
|
||||
self._client = None
|
||||
if hasattr(langfuse_secret_key, "get_secret_value"):
|
||||
langfuse_secret_key = langfuse_secret_key.get_secret_value()
|
||||
|
||||
self._enabled = bool(
|
||||
is_enabled and langfuse_host and langfuse_public_key and langfuse_secret_key
|
||||
)
|
||||
|
||||
if not self._enabled or _Langfuse is None:
|
||||
self._enabled = False
|
||||
return
|
||||
|
||||
try:
|
||||
self._client = _Langfuse(
|
||||
public_key=langfuse_public_key,
|
||||
secret_key=langfuse_secret_key,
|
||||
host=langfuse_host,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("Langfuse init failed: %s", e)
|
||||
self._enabled = False
|
||||
self._client = None
|
||||
|
||||
def observe(
|
||||
self, **decorator_kwargs
|
||||
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
||||
"""Decorator to observe a function with Langfuse. If disabled, returns a no-op decorator.""" # noqa: E501
|
||||
if self._enabled and self._client and _lf_observe is not None:
|
||||
return _lf_observe(**decorator_kwargs)
|
||||
|
||||
def _noop(fn):
|
||||
return fn
|
||||
|
||||
return _noop
|
||||
|
||||
def span(self, name: str, **kwargs) -> ContextManager[Any]:
|
||||
"""Context manager to create a span with Langfuse."""
|
||||
if self._enabled and self._client:
|
||||
start_span = getattr(self._client, "start_as_current_span", None)
|
||||
if callable(start_span):
|
||||
return start_span(name=name, **kwargs)
|
||||
return nullcontext()
|
||||
|
||||
def generation(self, **kwargs) -> ContextManager[Any]:
|
||||
"""Context manager to create a generation with Langfuse."""
|
||||
if self._enabled and self._client:
|
||||
start_gen = getattr(self._client, "start_as_current_generation", None)
|
||||
if callable(start_gen):
|
||||
return start_gen(**kwargs)
|
||||
return nullcontext()
|
||||
|
||||
def update_current_trace(self, **kwargs) -> None:
|
||||
"""Update the current trace with additional metadata."""
|
||||
if not (self._enabled and self._client):
|
||||
return
|
||||
try:
|
||||
self._client.update_current_trace(**kwargs)
|
||||
except Exception as e:
|
||||
logger.warning("Langfuse update_current_trace failed: %s", e)
|
||||
pass
|
||||
|
||||
def flush(self) -> None:
|
||||
"""Flush any buffered data to Langfuse."""
|
||||
if not (self._enabled and self._client):
|
||||
return
|
||||
try:
|
||||
self._client.flush()
|
||||
except Exception as e:
|
||||
logger.warning("Langfuse flush failed: %s", e)
|
||||
pass
|
||||
|
||||
@property
|
||||
def is_enabled(self) -> bool:
|
||||
"""Check if observability is enabled."""
|
||||
return bool(self._enabled and self._client)
|
||||
@@ -1,52 +1,28 @@
|
||||
# ruff: noqa
|
||||
|
||||
PROMPT_SYSTEM_TLDR = """Tu es un agent dont le rôle est de créer un TL;DR (résumé très concis) d'un compte rendu de réunion. Tu utiliseras un style synthétique, administratif, à la troisième personne, sans affect. Tu recevras en entrée le transcript. Ta tâche est de rédiger un résumé concis et structuré, en te concentrant uniquement sur les informations essentielles et pertinentes. Tu répondras en un paragraphe structuré (3 à 6 phrases), sans rien ajouter d'autre. Tu répondras dans le format suivant sans rien ajouter d'autre:
|
||||
### Résumé TL;DR
|
||||
[Résumé concis et structuré]"""
|
||||
|
||||
def get_instructions(transcript):
|
||||
"""Declare the summarize instructions."""
|
||||
prompt = f"""
|
||||
Audience: Coworkers.
|
||||
|
||||
**Do:**
|
||||
- Detect the language of the transcript and provide your entire response in the same language.
|
||||
- If any part of the transcript is unclear or lacks detail, politely inform the user, specifying which areas need further clarification.
|
||||
- Ensure the accuracy of all information and refrain from adding unverified details.
|
||||
- Format the response using proper markdown and structured sections.
|
||||
- Be concise and avoid repeating yourself between the sections.
|
||||
- Be super precise on nickname
|
||||
- Be a nit-picker
|
||||
- Auto-evaluate your response
|
||||
|
||||
**Don't:**
|
||||
- Write something your are not sure.
|
||||
- Write something that is not mention in the transcript.
|
||||
- Don't make mistake while mentioning someone
|
||||
**Task:**
|
||||
Summarize the provided meeting transcript into clear and well-organized meeting minutes. The summary should be structured into the following sections, excluding irrelevant or inapplicable details:
|
||||
|
||||
1. **Summary**: Write a TL;DR of the meeting.
|
||||
2. **Subjects Discussed**: List the key points or issues in bullet points.
|
||||
4. **Next Steps**: Provide action items as bullet points, assigning each task to a responsible individual and including deadlines (if mentioned). Format action items as tickable checkboxes. Ensure every action is assigned and, if a deadline is provided, that it is clearly stated.
|
||||
|
||||
**Transcript**:
|
||||
{transcript}
|
||||
|
||||
**Response:**
|
||||
|
||||
### Summary [Translate this title based on the transcript’s language]
|
||||
[Provide a brief overview of the key points discussed]
|
||||
|
||||
### Subjects Discussed [Translate this title based on the transcript’s language]
|
||||
- [Summarize each topic concisely]
|
||||
|
||||
### Next Steps [Translate this title based on the transcript’s language]
|
||||
- [ ] Action item [Assign to the responsible individual(s) and include a deadline if applicable, follow this strict format: Action - List of owner(s), deadline.]
|
||||
PROMPT_SYSTEM_PLAN = """Ta tâche est de diviser le contenu du transcript en sujets concrets correspondant aux grands axes discutés durant la réunion. Ne crée pas de catégories génériques. Les titres doivent être courts, précis et représentatifs des échanges. Veille à ce que chaque sujet soit distinct et qu’aucun thème ne soit répété. Tu te limiteras à 5 ou 6 sujets maximum.
|
||||
L'introduction, ordre du jour, conclusion, etc. seront rajoutés a posteriori. Tu répondras dans le format suivant sans rien ajouter d'autre:
|
||||
"Titre du sujet 1
|
||||
Titre du sujet 2
|
||||
Titre du sujet 3
|
||||
..."
|
||||
"""
|
||||
|
||||
PROMPT_SYSTEM_PART = """Tu es un agent dont le rôle est de créer une partie du résumé d'un compte rendu de réunion. Tu utiliseras un style synthétique, administratif, à la troisième personne, sans affect. Tu recevras en entrée le transcript, et le titre du sujet correspondant. Ta tâche est de rédiger un résumé concis de cette partie et uniquement cette partie, en te concentrant uniquement sur les informations essentielles et pertinentes. Le résumé de chaque partie doit tenir en 4 à 6 phrases maximum, sans entrer dans les détails mineurs. Tu répondras dans le format suivant :
|
||||
### Titre du sujet [Traduire ce titre selon la langue du transcript]
|
||||
[Résumé concis et structuré de la partie du transcript]
|
||||
"""
|
||||
|
||||
return [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a concise and structured assistant, that summarizes meeting transcripts.",
|
||||
},
|
||||
{"role": "user", "content": prompt},
|
||||
]
|
||||
PROMPT_USER_PART = """Titre de la partie à résumer : {part}
|
||||
Transcript complet :
|
||||
{transcript}"""
|
||||
|
||||
PROMPT_SYSTEM_CLEANING = """Tu es un agent dont le rôle est de nettoyer un résumé de compte rendu de réunion. Tu recevras en entrée le résumé brut, potentiellement avec des erreurs de formatage, des incohérences ou des redondances. Ta tâche est de corriger les erreurs de formatage, d'améliorer la clarté et la cohérence du texte, et de t'assurer que le résumé est bien structuré et facile à lire. Ton but principal est de retirer les redondances et les répétitions. Assure la cohérence entre les titres et homogénéise le style d’écriture entre les parties. Supprime les doublons d’informations entre les parties si présents. Si certaines parties sont plus secondaires, tu peux les fusionner ou les réduire en 1 à 2 phrases. Mets en avant les points centraux qui ont fait l’objet de décisions ou d’actions. Tu répondras uniquement avec le résumé sans rien ajouter d'autre"""
|
||||
|
||||
PROMPT_SYSTEM_NEXT_STEP = """Tu es un agent dont le rôle est d'extraire les prochaines étapes d'un transcript de réunion. Tu utiliseras un style synthétique, administratif, à la troisième personne, sans affect. Tu recevras en entrée le transcript. Ta tâche est d'identifier et de lister toutes les actions à entreprendre, en indiquant la ou les personnes assignées et en précisant les échéances si elles sont mentionnées. Ne retiens que les actions concrètes et à venir. Ignore les remarques générales ou les constats sans suite. Les actions doivent suivre ce format strict :
|
||||
### Prochaines étapes
|
||||
- [ ] [Action à effectuer] Assignée à : [Nom], Échéance : [Date si mentionnée]"""
|
||||
|
||||