-
Notifications
You must be signed in to change notification settings - Fork 19
✨(backend) add audio transcription #433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ __pycache__ | |
| **/*.pyc | ||
| venv | ||
| .venv | ||
| **/venv | ||
| **/.venv | ||
|
|
||
| # System-specific files | ||
| .DS_Store | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,13 @@ | ||
| load('ext://uibutton', 'cmd_button', 'bool_input', 'location') | ||
| load('ext://namespace', 'namespace_create', 'namespace_inject') | ||
| load('ext://secret', 'secret_yaml_generic') | ||
| namespace_create('conversations') | ||
|
|
||
| DEV_ENV = os.getenv('DEV_ENV', 'dev') | ||
|
|
||
| def clean_old_images(image_name): | ||
| local('docker images -q %s | tail -n +2 | xargs -r docker rmi' % image_name) | ||
|
|
||
| docker_build( | ||
| 'localhost:5001/conversations-backend:latest', | ||
| context='..', | ||
|
|
@@ -10,8 +16,13 @@ docker_build( | |
| target = 'backend-production', | ||
| live_update=[ | ||
| sync('../src/backend', '/app'), | ||
| run( | ||
| 'pip install -r /app/requirements.txt', | ||
| trigger=['./src/backend/requirements.txt'] | ||
| ) | ||
|
Comment on lines
+19
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify the actual path structure relative to the Tiltfile
fd -t f 'requirements.txt' src/backend
# Check the bin directory location relative to repo root
ls -la bin/Tiltfile 2>/dev/null || echo "Tiltfile not at expected location"Repository: suitenumerique/conversations Length of output: 1818 🌐 Web query:
💡 Result: In Tilt, relative paths in Tiltfile Starlark are evaluated relative to the Tiltfile’s own directory (the file whose Tiltfile is being executed). The result is that a referenced path is resolved against that Tiltfile’s directory, not necessarily against the directory you ran the tilt CLI from. However, for the special case of where the “main/root Tiltfile” lives, Tilt also exposes config.main_dir, which is the directory containing the Tiltfile that started the session. This is commonly used when you have nested Tiltfiles and need a stable anchor for resolving paths relative to the root Tiltfile directory. So, for your question “Tilt trigger path resolution relative to Tiltfile location”: - tilt trigger itself takes a resource name, not a path; it triggers an update for that named resource, and any path resolution behavior comes from how that resource was defined (e.g., via docker_build, k8s_yaml, local_resource, file deps, etc.). - When the resource definition uses relative paths (Tiltfile functions’ path parameters, or any file paths you read/declare as deps), those relative paths are resolved relative to the Tiltfile that defined them. - If you specifically need “relative to the root Tiltfile directory” (useful with nested Tiltfiles), use config.main_dir to build absolute paths from a stable base. Example pattern for root-anchored path building in a nested Tiltfile: repo_root = config.main_dir load_yaml = repo_root + '/path/to/common.yaml' (Constructing the exact string/using it with k8s_yaml/load depends on the API.) Citations:
Fix trigger path to use correct relative path from Tiltfile location. The trigger path 🤖 Prompt for AI Agents |
||
| ] | ||
| ) | ||
| clean_old_images('localhost:5001/conversations-backend') | ||
|
|
||
| docker_build( | ||
| 'localhost:5001/conversations-frontend:latest', | ||
|
|
@@ -23,11 +34,21 @@ docker_build( | |
| sync('../src/frontend', '/home/frontend'), | ||
| ] | ||
| ) | ||
| clean_old_images('localhost:5001/conversations-frontend') | ||
|
|
||
| k8s_yaml(secret_yaml_generic( | ||
| name='secret-dev', | ||
| from_env_file='../env.d/development/kube-secret' | ||
| )) | ||
|
|
||
| k8s_yaml(local('cd ../src/helm && helmfile -n conversations -e %s template .' % DEV_ENV)) | ||
|
|
||
| k8s_resource('conversations-backend-migrate', resource_deps=['postgres-postgresql']) | ||
| k8s_resource('minio', port_forwards=['9000:9000', '9001:9001']) | ||
| k8s_resource('minio-bucket', resource_deps=['minio']) | ||
| k8s_resource('conversations-backend-migrate', resource_deps=['postgresql', 'minio', 'redis']) | ||
| k8s_resource('conversations-backend-createsuperuser', resource_deps=['conversations-backend-migrate']) | ||
| k8s_resource('conversations-backend', resource_deps=['conversations-backend-migrate']) | ||
| k8s_yaml(local('cd ../src/helm && helmfile -n conversations -e dev template .')) | ||
| k8s_resource('keycloak', resource_deps=['kc-postgresql']) | ||
|
|
||
| migration = ''' | ||
| set -eu | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # Running the app locally with Tilt | ||
|
|
||
| [Tilt](https://tilt.dev) orchestrates the local Kubernetes development environment: it builds Docker images, deploys all services via Helm, and keeps everything in sync as you edit code. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| Install the following tools before getting started: | ||
|
|
||
| - [Docker](https://docs.docker.com/get-docker/) | ||
| - [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) — local Kubernetes cluster | ||
| - [kubectl](https://kubernetes.io/docs/tasks/tools/) | ||
| - [Helm](https://helm.sh/docs/intro/install/) + [Helmfile](https://helmfile.readthedocs.io/en/latest/#installation) | ||
| - [mkcert](https://github.com/FiloSottile/mkcert#installation) — local TLS certificates | ||
| - [Tilt](https://docs.tilt.dev/install.html) | ||
|
|
||
| ## Step 1 — Create the Kubernetes cluster | ||
|
|
||
| ```bash | ||
| make build-k8s-cluster | ||
| ``` | ||
|
|
||
| This runs `bin/start-kind.sh`, which: | ||
|
|
||
| 1. Creates a local Docker registry at `localhost:5001` | ||
| 2. Creates a Kind cluster named `conversations` | ||
| 3. Installs the ingress-nginx controller | ||
| 4. Generates mkcert TLS certificates for `*.127.0.0.1.nip.io` | ||
|
|
||
| All local domains resolve to `127.0.0.1` via [nip.io](https://nip.io) — no `/etc/hosts` edits needed. | ||
|
|
||
| ## Step 2 — Configure secrets | ||
|
|
||
| Copy the secrets template and fill in the required values: | ||
|
|
||
| ```bash | ||
| cp env.d/development/kube-secret.dist env.d/development/kube-secret | ||
| ``` | ||
|
|
||
| Then edit `env.d/development/kube-secret`: | ||
|
|
||
| | Variable | Required | Description | | ||
| |---|---|---| | ||
| | `AI_BASE_URL` | Yes | LLM provider base URL | | ||
| | `AI_API_KEY` | Yes | LLM provider API key | | ||
| | `ALBERT_API_URL` | No | Albert API URL (if using Albert provider) | | ||
| | `ALBERT_API_KEY` | No | Albert API key | | ||
| | `BRAVE_API_KEY` | No | Brave Search API key (web search tool) | | ||
| | `STT_SERVICE_URL` | No | Speech-to-text service URL | | ||
| | `STT_SERVICE_API_KEY` | No | Speech-to-text service API key | | ||
| | `STT_WEBHOOK_API_KEY` | No | Bearer token the STT service uses when calling back the transcription webhook | | ||
| | `LANGFUSE_SECRET_KEY` | No | Langfuse secret key | | ||
| | `LANGFUSE_PUBLIC_KEY` | No | Langfuse public key | | ||
| | `LANGFUSE_HOST` | No | Langfuse instance URL | | ||
|
|
||
| ## Step 3 — Start the app | ||
|
|
||
| ```bash | ||
| make start-tilt | ||
| ``` | ||
|
|
||
| Tilt will: | ||
|
|
||
| 1. Build the backend and frontend Docker images and push them to `localhost:5001` | ||
| 2. Deploy supporting services (PostgreSQL, Keycloak, MinIO, Redis) via the `extra` Helm chart | ||
| 3. Deploy the backend and frontend via the `conversations` Helm chart | ||
| 4. Run database migrations and create a superuser (`admin@example.com` / `admin`) | ||
| 5. Watch source files and sync changes live | ||
|
|
||
| The Tilt dashboard opens at `http://localhost:10350`. Wait for all resources to turn green before accessing the app. | ||
|
|
||
| ## Accessing the services | ||
|
|
||
| | Service | URL | Credentials | | ||
| |---|---|---| | ||
| | App | `https://conversations.127.0.0.1.nip.io` | via Keycloak | | ||
| | Keycloak admin | `https://conversations-keycloak.127.0.0.1.nip.io` | `su` / `su` | | ||
| | MinIO console | `http://localhost:9001` | `conversations` / `password` | | ||
| | Tilt dashboard | `http://localhost:10350` | — | | ||
|
|
||
| ## Django management commands | ||
|
|
||
| The Tilt dashboard exposes two buttons on the `conversations-backend` resource: | ||
|
|
||
| - **Run makemigration** — runs `python manage.py makemigrations` | ||
| - **Run database migration** — runs `python manage.py migrate --no-input` | ||
|
Comment on lines
+84
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix command label typo for consistency.
🤖 Prompt for AI Agents |
||
|
|
||
| ## Stopping | ||
|
|
||
| ```bash | ||
| make stop-tilt | ||
| ``` | ||
|
|
||
| This shuts down Tilt but leaves the Kind cluster running. To also delete the cluster: | ||
|
|
||
| ```bash | ||
| kind delete cluster --name conversations | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Secrets — copy this file to kube-secret and fill in real values | ||
| AI_BASE_URL=changeme | ||
| AI_API_KEY=changeme | ||
| ALBERT_API_URL=changeme | ||
| ALBERT_API_KEY=changeme | ||
| BRAVE_API_KEY=changeme | ||
| STT_SERVICE_URL=https://ai-service.example.com | ||
| STT_SERVICE_API_KEY=changeme | ||
| STT_WEBHOOK_API_KEY=changeme | ||
| LANGFUSE_SECRET_KEY=changeme | ||
| LANGFUSE_PUBLIC_KEY=changeme | ||
| LANGFUSE_HOST=changeme |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||
| """Custom authentication classes for chat webhooks.""" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from django.conf import settings | ||||||||||||||||||||||||||||||||||||||||||
| from django.contrib.auth.models import AnonymousUser | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from rest_framework.authentication import BaseAuthentication | ||||||||||||||||||||||||||||||||||||||||||
| from rest_framework.exceptions import AuthenticationFailed | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| class AiWebhookAuthentication(BaseAuthentication): | ||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||
| Custom authentication class for AI webhook requests. | ||||||||||||||||||||||||||||||||||||||||||
| Validates the API key in the Authorization header. | ||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| def authenticate(self, request): | ||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||
| Authenticate the request and return a two-tuple of (user, token). | ||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||
| if not settings.STT_WEBHOOK_API_KEY: | ||||||||||||||||||||||||||||||||||||||||||
| raise AuthenticationFailed("STT_WEBHOOK_API_KEY is not configured.") | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| authorization_header: str = request.headers.get("Authorization") or "" | ||||||||||||||||||||||||||||||||||||||||||
| token = authorization_header.removeprefix("Bearer ") | ||||||||||||||||||||||||||||||||||||||||||
| if not token or token != settings.STT_WEBHOOK_API_KEY: | ||||||||||||||||||||||||||||||||||||||||||
| logger.warning( | ||||||||||||||||||||||||||||||||||||||||||
| "Authentication failed: Bad Authorization header (ip: %s)", | ||||||||||||||||||||||||||||||||||||||||||
| request.META.get("REMOTE_ADDR"), | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| raise AuthenticationFailed() | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parse the HTTP auth schemes are case-insensitive, so this rejects valid headers like Possible fix+import secrets
+
authorization_header: str = request.headers.get("Authorization") or ""
- token = authorization_header.removeprefix("Bearer ")
- if not token or token != settings.STT_WEBHOOK_API_KEY:
+ scheme, _, token = authorization_header.partition(" ")
+ if (
+ scheme.lower() != "bearer"
+ or not token
+ or not secrets.compare_digest(token, settings.STT_WEBHOOK_API_KEY)
+ ):
logger.warning(
"Authentication failed: Bad Authorization header (ip: %s)",
request.META.get("REMOTE_ADDR"),
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # No users are associated with the transcribe webhooks | ||||||||||||||||||||||||||||||||||||||||||
| return AnonymousUser(), None | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the right changelog section for this entry.
At Line 35, this item reads as an addition, not a fix. Moving it to
### Added(or### Changed) will keep release notes consistent.✍️ Suggested changelog adjustment
📝 Committable suggestion
🤖 Prompt for AI Agents