fix: remaining dependabot security issues (#890)

* Upgrade docs deps

* Upgrade frontend to latest deps

* Update package overrides

* Remove redundant deps

* Add tailwind postcss plugin

* Replace language select with chakra

* Fix main nav

* Patch gray matter

* Fix webpack override

* Replace python-jose with pyjwt

* Override kv url for frontend in compose

* Upgrade hatchet sdk

* Update docs

* Supress pydantic warnings
This commit is contained in:
Sergey Mankovsky
2026-03-02 17:17:40 +01:00
committed by GitHub
parent 4d915e2a9f
commit 0931095f49
34 changed files with 20117 additions and 26696 deletions

1
.gitignore vendored
View File

@@ -21,7 +21,6 @@ CLAUDE.local.md
www/.env.development www/.env.development
www/.env.production www/.env.production
.playwright-mcp .playwright-mcp
docs/pnpm-lock.yaml
.secrets .secrets
opencode.json opencode.json

View File

@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
Reflector is an AI-powered audio transcription and meeting analysis platform with real-time processing capabilities. The system consists of: Reflector is an AI-powered audio transcription and meeting analysis platform with real-time processing capabilities. The system consists of:
- **Frontend**: Next.js 14 React application (`www/`) with Chakra UI, real-time WebSocket integration - **Frontend**: Next.js 16 React application (`www/`) with Chakra UI, real-time WebSocket integration
- **Backend**: Python FastAPI server (`server/`) with async database operations and background processing - **Backend**: Python FastAPI server (`server/`) with async database operations and background processing
- **Processing**: GPU-accelerated ML pipeline for transcription, diarization, summarization via Modal.com - **Processing**: GPU-accelerated ML pipeline for transcription, diarization, summarization via Modal.com
- **Infrastructure**: Redis, PostgreSQL/SQLite, Celery workers, WebRTC streaming - **Infrastructure**: Redis, PostgreSQL/SQLite, Celery workers, WebRTC streaming

View File

@@ -93,6 +93,7 @@ services:
environment: environment:
NODE_ENV: development NODE_ENV: development
SERVER_API_URL: http://host.docker.internal:1250 SERVER_API_URL: http://host.docker.internal:1250
KV_URL: redis://redis:6379
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
depends_on: depends_on:

7
docs/.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
node_modules
build
.git
.gitignore
*.log
.DS_Store
.env*

View File

@@ -1,14 +1,17 @@
FROM node:18-alpine AS builder FROM node:20-alpine AS builder
WORKDIR /app WORKDIR /app
# Install curl for fetching OpenAPI spec # Install curl for fetching OpenAPI spec
RUN apk add --no-cache curl RUN apk add --no-cache curl
# Copy package files # Enable pnpm
COPY package*.json ./ RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy package files and lockfile
COPY package.json pnpm-lock.yaml* ./
# Install dependencies # Install dependencies
RUN npm ci RUN pnpm install --frozen-lockfile
# Copy source # Copy source
COPY . . COPY . .
@@ -21,7 +24,7 @@ RUN mkdir -p ./static && curl -sf "${OPENAPI_URL}" -o ./static/openapi.json || e
RUN sed -i "s/onBrokenLinks: 'throw'/onBrokenLinks: 'warn'/g" docusaurus.config.ts RUN sed -i "s/onBrokenLinks: 'throw'/onBrokenLinks: 'warn'/g" docusaurus.config.ts
# Build static site (skip prebuild hook by calling docusaurus directly) # Build static site (skip prebuild hook by calling docusaurus directly)
RUN npx docusaurus build RUN pnpm exec docusaurus build
# Production image # Production image
FROM nginx:alpine FROM nginx:alpine

View File

@@ -5,13 +5,13 @@ This website is built using [Docusaurus](https://docusaurus.io/), a modern stati
### Installation ### Installation
``` ```
$ yarn $ pnpm install
``` ```
### Local Development ### Local Development
``` ```
$ yarn start $ pnpm start
``` ```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
@@ -19,7 +19,7 @@ This command starts a local development server and opens up a browser window. Mo
### Build ### Build
``` ```
$ yarn build $ pnpm build
``` ```
This command generates static content into the `build` directory and can be served using any static contents hosting service. This command generates static content into the `build` directory and can be served using any static contents hosting service.
@@ -29,13 +29,13 @@ This command generates static content into the `build` directory and can be serv
Using SSH: Using SSH:
``` ```
$ USE_SSH=true yarn deploy $ USE_SSH=true pnpm deploy
``` ```
Not using SSH: Not using SSH:
``` ```
$ GIT_USER=<Your GitHub username> yarn deploy $ GIT_USER=<Your GitHub username> pnpm deploy
``` ```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

View File

@@ -11,7 +11,7 @@ Reflector is built as a modern, scalable, microservices-based application design
### Frontend Application ### Frontend Application
The user interface is built with **Next.js 15** using the App Router pattern, providing: The user interface is built with **Next.js 16** using the App Router pattern, providing:
- Server-side rendering for optimal performance - Server-side rendering for optimal performance
- Real-time WebSocket connections for live transcription - Real-time WebSocket connections for live transcription

View File

@@ -36,14 +36,15 @@ This creates `docs/static/openapi.json` (should be ~70KB) which will be copied d
The Dockerfile is already in `docs/Dockerfile`: The Dockerfile is already in `docs/Dockerfile`:
```dockerfile ```dockerfile
FROM node:18-alpine AS builder FROM node:20-alpine AS builder
WORKDIR /app WORKDIR /app
# Copy package files # Enable pnpm and copy package files + lockfile
COPY package*.json ./ RUN corepack enable && corepack prepare pnpm@latest --activate
COPY package.json pnpm-lock.yaml* ./
# Inshall dependencies # Install dependencies
RUN npm ci RUN pnpm install --frozen-lockfile
# Copy source (includes static/openapi.json if pre-fetched) # Copy source (includes static/openapi.json if pre-fetched)
COPY . . COPY . .
@@ -52,7 +53,7 @@ COPY . .
RUN sed -i "s/onBrokenLinks: 'throw'/onBrokenLinks: 'warn'/g" docusaurus.config.ts RUN sed -i "s/onBrokenLinks: 'throw'/onBrokenLinks: 'warn'/g" docusaurus.config.ts
# Build static site # Build static site
RUN npx docusaurus build RUN pnpm exec docusaurus build
FROM nginx:alpine FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html COPY --from=builder /app/build /usr/share/nginx/html

View File

@@ -46,7 +46,7 @@ Reflector consists of three main components:
Ready to deploy Reflector? Head over to our [Installation Guide](./installation/overview) to set up your own instance. Ready to deploy Reflector? Head over to our [Installation Guide](./installation/overview) to set up your own instance.
For a quick overview of how Reflector processes audio, check out our [Pipeline Documentation](./pipelines/overview). For a quick overview of how Reflector processes audio, check out our [Pipeline Documentation](./concepts/pipeline).
## Open Source ## Open Source

View File

@@ -124,11 +124,11 @@ const config: Config = {
items: [ items: [
{ {
label: 'Architecture', label: 'Architecture',
to: '/docs/reference/architecture/overview', to: '/docs/concepts/overview',
}, },
{ {
label: 'Pipelines', label: 'Pipelines',
to: '/docs/pipelines/overview', to: '/docs/concepts/pipeline',
}, },
{ {
label: 'Roadmap', label: 'Roadmap',

23526
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,26 +14,26 @@
"write-heading-ids": "docusaurus write-heading-ids", "write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc", "typecheck": "tsc",
"fetch-openapi": "./scripts/fetch-openapi.sh", "fetch-openapi": "./scripts/fetch-openapi.sh",
"gen-api-docs": "npm run fetch-openapi && docusaurus gen-api-docs reflector", "gen-api-docs": "pnpm run fetch-openapi && docusaurus gen-api-docs reflector",
"prebuild": "npm run fetch-openapi" "prebuild": "pnpm run fetch-openapi"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "3.6.3", "@docusaurus/core": "3.9.2",
"@docusaurus/preset-classic": "3.6.3", "@docusaurus/preset-classic": "3.9.2",
"@mdx-js/react": "^3.0.0", "@docusaurus/theme-mermaid": "3.9.2",
"clsx": "^2.0.0", "@mdx-js/react": "^3.1.1",
"docusaurus-plugin-openapi-docs": "^4.5.1", "clsx": "^2.1.1",
"docusaurus-theme-openapi-docs": "^4.5.1", "docusaurus-plugin-openapi-docs": "^4.7.1",
"@docusaurus/theme-mermaid": "3.6.3", "docusaurus-theme-openapi-docs": "^4.7.1",
"prism-react-renderer": "^2.3.0", "prism-react-renderer": "^2.4.1",
"react": "^18.0.0", "react": "^19.2.4",
"react-dom": "^18.0.0" "react-dom": "^19.2.4"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "3.6.3", "@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.6.3", "@docusaurus/tsconfig": "3.9.2",
"@docusaurus/types": "3.6.3", "@docusaurus/types": "3.9.2",
"typescript": "~5.6.2" "typescript": "~5.9.3"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
@@ -49,5 +49,15 @@
}, },
"engines": { "engines": {
"node": ">=18.0" "node": ">=18.0"
},
"pnpm": {
"overrides": {
"minimatch@<3.1.4": "3.1.5",
"minimatch@>=5.0.0 <5.1.8": "5.1.8",
"minimatch@>=9.0.0 <9.0.7": "9.0.7",
"lodash@<4.17.23": "4.17.23",
"js-yaml@<4.1.1": "4.1.1",
"gray-matter": "github:jonschlinkert/gray-matter#234163e"
}
} }
} }

13976
docs/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

4129
docs/static/openapi.json vendored

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,7 @@ dependencies = [
"protobuf>=4.24.3", "protobuf>=4.24.3",
"celery>=5.3.4", "celery>=5.3.4",
"redis>=5.0.1", "redis>=5.0.1",
"python-jose[cryptography]>=3.3.0", "pyjwt[crypto]>=2.8.0",
"python-multipart>=0.0.6", "python-multipart>=0.0.6",
"transformers>=4.36.2", "transformers>=4.36.2",
"jsonschema>=4.23.0", "jsonschema>=4.23.0",

View File

@@ -0,0 +1,13 @@
"""
Suppress known dependency warnings. Import this before any reflector/hatchet_sdk
imports that pull in pydantic (e.g. llama_index) to hide UnsupportedFieldAttributeWarning
about validate_default.
"""
import warnings
warnings.filterwarnings(
"ignore",
message=".*validate_default.*",
category=UserWarning,
)

View File

@@ -4,8 +4,8 @@ from fastapi import Depends, HTTPException
if TYPE_CHECKING: if TYPE_CHECKING:
from fastapi import WebSocket from fastapi import WebSocket
import jwt
from fastapi.security import APIKeyHeader, OAuth2PasswordBearer from fastapi.security import APIKeyHeader, OAuth2PasswordBearer
from jose import JWTError, jwt
from pydantic import BaseModel from pydantic import BaseModel
from reflector.db.user_api_keys import user_api_keys_controller from reflector.db.user_api_keys import user_api_keys_controller
@@ -54,7 +54,7 @@ class JWTAuth:
audience=jwt_audience, audience=jwt_audience,
) )
return payload return payload
except JWTError as e: except jwt.PyJWTError as e:
logger.error(f"JWT error: {e}") logger.error(f"JWT error: {e}")
raise raise
@@ -94,7 +94,7 @@ async def _authenticate_user(
) )
user_infos.append(UserInfo(sub=user.id, email=email)) user_infos.append(UserInfo(sub=user.id, email=email))
except JWTError as e: except jwt.PyJWTError as e:
logger.error(f"JWT error: {e}") logger.error(f"JWT error: {e}")
raise HTTPException(status_code=401, detail="Invalid authentication") raise HTTPException(status_code=401, detail="Invalid authentication")

View File

@@ -9,9 +9,9 @@ from collections import defaultdict
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING, Annotated, Optional from typing import TYPE_CHECKING, Annotated, Optional
import jwt
from fastapi import APIRouter, Depends, HTTPException, Request from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.security import APIKeyHeader, OAuth2PasswordBearer from fastapi.security import APIKeyHeader, OAuth2PasswordBearer
from jose import JWTError, jwt
from pydantic import BaseModel from pydantic import BaseModel
from reflector.auth.password_utils import verify_password from reflector.auth.password_utils import verify_password
@@ -110,7 +110,7 @@ async def _authenticate_user(
user_id = payload["sub"] user_id = payload["sub"]
email = payload.get("email") email = payload.get("email")
user_infos.append(UserInfo(sub=user_id, email=email)) user_infos.append(UserInfo(sub=user_id, email=email))
except JWTError as e: except jwt.PyJWTError as e:
logger.error(f"JWT error: {e}") logger.error(f"JWT error: {e}")
raise HTTPException(status_code=401, detail="Invalid authentication") raise HTTPException(status_code=401, detail="Invalid authentication")

View File

@@ -7,6 +7,7 @@ Configuration:
- Worker affinity: pool=cpu-heavy - Worker affinity: pool=cpu-heavy
""" """
import reflector._warnings_filter # noqa: F401 -- side effect: suppress pydantic validate_default warning
from reflector.hatchet.client import HatchetClientManager from reflector.hatchet.client import HatchetClientManager
from reflector.hatchet.workflows.daily_multitrack_pipeline import ( from reflector.hatchet.workflows.daily_multitrack_pipeline import (
daily_multitrack_pipeline, daily_multitrack_pipeline,

View File

@@ -5,6 +5,7 @@ Handles: all tasks except mixdown_tracks (transcription, LLM inference, orchestr
import asyncio import asyncio
import reflector._warnings_filter # noqa: F401 -- side effect: suppress pydantic validate_default warning
from reflector.hatchet.client import HatchetClientManager from reflector.hatchet.client import HatchetClientManager
from reflector.hatchet.workflows.daily_multitrack_pipeline import ( from reflector.hatchet.workflows.daily_multitrack_pipeline import (
daily_multitrack_pipeline, daily_multitrack_pipeline,

View File

@@ -17,6 +17,7 @@ from typing import Callable
from celery.result import AsyncResult from celery.result import AsyncResult
from hatchet_sdk.clients.rest.models import V1TaskStatus from hatchet_sdk.clients.rest.models import V1TaskStatus
import reflector._warnings_filter # noqa: F401 -- side effect: suppress pydantic validate_default warning
from reflector.db import get_database from reflector.db import get_database
from reflector.db.transcripts import Transcript, transcripts_controller from reflector.db.transcripts import Transcript, transcripts_controller
from reflector.hatchet.client import HatchetClientManager from reflector.hatchet.client import HatchetClientManager

View File

@@ -1,10 +1,10 @@
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Annotated, Literal, Optional, assert_never from typing import Annotated, Literal, Optional, assert_never
import jwt
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi_pagination import Page from fastapi_pagination import Page
from fastapi_pagination.ext.databases import apaginate from fastapi_pagination.ext.databases import apaginate
from jose import jwt
from pydantic import ( from pydantic import (
AwareDatetime, AwareDatetime,
BaseModel, BaseModel,

View File

@@ -7,8 +7,8 @@ Transcripts audio related endpoints
from typing import Annotated, Optional from typing import Annotated, Optional
import httpx import httpx
import jwt
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from jose import jwt
import reflector.auth as auth import reflector.auth as auth
from reflector.db.transcripts import AudioWaveform, transcripts_controller from reflector.db.transcripts import AudioWaveform, transcripts_controller
@@ -44,7 +44,7 @@ async def transcript_get_audio_mp3(
try: try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM]) payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
user_id: str = payload.get("sub") user_id: str = payload.get("sub")
except jwt.JWTError: except jwt.PyJWTError:
raise unauthorized_exception raise unauthorized_exception
transcript = await transcripts_controller.get_by_id_for_http( transcript = await transcripts_controller.get_by_id_for_http(

View File

@@ -1,8 +1,8 @@
"""Tests for the password auth backend.""" """Tests for the password auth backend."""
import jwt
import pytest import pytest
from httpx import AsyncClient from httpx import AsyncClient
from jose import jwt
from reflector.auth.password_utils import hash_password from reflector.auth.password_utils import hash_password
from reflector.settings import settings from reflector.settings import settings

View File

@@ -67,7 +67,7 @@ def appserver_ws_user(setup_database):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def patch_jwt_verification(monkeypatch): def patch_jwt_verification(monkeypatch):
"""Patch JWT verification to accept HS256 tokens signed with SECRET_KEY for tests.""" """Patch JWT verification to accept HS256 tokens signed with SECRET_KEY for tests."""
from jose import jwt import jwt
from reflector.settings import settings from reflector.settings import settings
@@ -84,7 +84,7 @@ def _make_dummy_jwt(sub: str = "user123") -> str:
# Create a short HS256 JWT using the app secret to pass verification in tests # Create a short HS256 JWT using the app secret to pass verification in tests
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from jose import jwt import jwt
from reflector.settings import settings from reflector.settings import settings

77
server/uv.lock generated
View File

@@ -861,18 +861,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" },
] ]
[[package]]
name = "ecdsa"
version = "0.19.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" },
]
[[package]] [[package]]
name = "email-validator" name = "email-validator"
version = "2.2.0" version = "2.2.0"
@@ -1195,7 +1183,7 @@ wheels = [
[[package]] [[package]]
name = "hatchet-sdk" name = "hatchet-sdk"
version = "1.21.6" version = "1.27.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "aiohttp" }, { name = "aiohttp" },
@@ -1207,11 +1195,12 @@ dependencies = [
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "python-dateutil" }, { name = "python-dateutil" },
{ name = "tenacity" }, { name = "tenacity" },
{ name = "typing-inspection" },
{ name = "urllib3" }, { name = "urllib3" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/7c/df/75dd02e1dc6b99f7151a57f084876c50f739ad4d643b060078f65d51d717/hatchet_sdk-1.21.6.tar.gz", hash = "sha256:b65741324ad721ce57f5fe3f960e2942c4ac2ceec6ca483dd35f84137ff7c46c", size = 219345, upload-time = "2025-12-11T15:04:24.899Z" } sdist = { url = "https://files.pythonhosted.org/packages/5b/02/e8bcc42654f03af3a39f9319d21fc42ab36abca9514cee275c04b2810186/hatchet_sdk-1.27.0.tar.gz", hash = "sha256:c312a83c8e6c13040cc2512a6ed7e60085af2496587a2dbd5c18a62d84217cb8", size = 246838, upload-time = "2026-02-27T18:21:40.236Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/00/86/e4cd7928bcabd33c634c33d4e878e2454e03f97c87b72947c7ff5762d813/hatchet_sdk-1.21.6-py3-none-any.whl", hash = "sha256:589fba9104a6517e1ba677b9865fa0a20e221863a8c2a2724051198994c11399", size = 529167, upload-time = "2025-12-11T15:04:23.697Z" }, { url = "https://files.pythonhosted.org/packages/ef/5b/3c2a8b6908a68d42489d903c41fa460cd6d61e07a27252737fcec8d97b31/hatchet_sdk-1.27.0-py3-none-any.whl", hash = "sha256:3cea10e68d3551881588ec941b50f0e383855b191eb79905ee57ee806b08430b", size = 574642, upload-time = "2026-02-27T18:21:37.611Z" },
] ]
[[package]] [[package]]
@@ -2240,15 +2229,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031, upload-time = "2024-10-16T11:21:34.211Z" }, { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031, upload-time = "2024-10-16T11:21:34.211Z" },
] ]
[[package]]
name = "pyasn1"
version = "0.6.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" },
]
[[package]] [[package]]
name = "pycparser" name = "pycparser"
version = "2.22" version = "2.22"
@@ -2405,6 +2385,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/7f/113b16d55e8d2dd9143628eec39b138fd6c52f72dcd11b4dae4a3845da4d/pyinstrument-5.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:88df7e3ab11604ae7cef1f576c097a08752bf8fc13c5755803bd3cd92f15aba3", size = 124314, upload-time = "2025-07-02T14:13:26.708Z" }, { url = "https://files.pythonhosted.org/packages/0c/7f/113b16d55e8d2dd9143628eec39b138fd6c52f72dcd11b4dae4a3845da4d/pyinstrument-5.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:88df7e3ab11604ae7cef1f576c097a08752bf8fc13c5755803bd3cd92f15aba3", size = 124314, upload-time = "2025-07-02T14:13:26.708Z" },
] ]
[[package]]
name = "pyjwt"
version = "2.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" },
]
[package.optional-dependencies]
crypto = [
{ name = "cryptography" },
]
[[package]] [[package]]
name = "pylibsrtp" name = "pylibsrtp"
version = "0.12.0" version = "0.12.0"
@@ -2619,25 +2613,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
] ]
[[package]]
name = "python-jose"
version = "3.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ecdsa" },
{ name = "pyasn1" },
{ name = "rsa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/77/3a1c9039db7124eb039772b935f2244fbb73fc8ee65b9acf2375da1c07bf/python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b", size = 92726, upload-time = "2025-05-28T17:31:54.288Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/c3/0bd11992072e6a1c513b16500a5d07f91a24017c5909b02c72c62d7ad024/python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771", size = 34624, upload-time = "2025-05-28T17:31:52.802Z" },
]
[package.optional-dependencies]
cryptography = [
{ name = "cryptography" },
]
[[package]] [[package]]
name = "python-multipart" name = "python-multipart"
version = "0.0.22" version = "0.0.22"
@@ -2791,8 +2766,8 @@ dependencies = [
{ name = "psycopg2-binary" }, { name = "psycopg2-binary" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "pyjwt", extra = ["crypto"] },
{ name = "pytest-env" }, { name = "pytest-env" },
{ name = "python-jose", extra = ["cryptography"] },
{ name = "python-multipart" }, { name = "python-multipart" },
{ name = "redis" }, { name = "redis" },
{ name = "requests" }, { name = "requests" },
@@ -2867,8 +2842,8 @@ requires-dist = [
{ name = "psycopg2-binary", specifier = ">=2.9.10" }, { name = "psycopg2-binary", specifier = ">=2.9.10" },
{ name = "pydantic", specifier = ">=2.12.5" }, { name = "pydantic", specifier = ">=2.12.5" },
{ name = "pydantic-settings", specifier = ">=2.0.2" }, { name = "pydantic-settings", specifier = ">=2.0.2" },
{ name = "pyjwt", extras = ["crypto"], specifier = ">=2.8.0" },
{ name = "pytest-env", specifier = ">=1.1.5" }, { name = "pytest-env", specifier = ">=1.1.5" },
{ name = "python-jose", extras = ["cryptography"], specifier = ">=3.3.0" },
{ name = "python-multipart", specifier = ">=0.0.6" }, { name = "python-multipart", specifier = ">=0.0.6" },
{ name = "redis", specifier = ">=5.0.1" }, { name = "redis", specifier = ">=5.0.1" },
{ name = "requests", specifier = ">=2.31.0" }, { name = "requests", specifier = ">=2.31.0" },
@@ -3087,18 +3062,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" }, { url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" },
] ]
[[package]]
name = "rsa"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
]
[[package]] [[package]]
name = "s3transfer" name = "s3transfer"
version = "0.13.0" version = "0.13.0"

View File

@@ -1,10 +1,8 @@
import { Container, Flex } from "@chakra-ui/react"; import { Container, Flex } from "@chakra-ui/react";
import { featureEnabled } from "../lib/features";
import NextLink from "next/link"; import NextLink from "next/link";
import Image from "next/image"; import Image from "next/image";
import UserInfo from "../(auth)/userInfo";
import AuthWrapper from "./AuthWrapper"; import AuthWrapper from "./AuthWrapper";
import { RECORD_A_MEETING_URL } from "../api/urls"; import MainNav from "../components/MainNav";
export default async function AppLayout({ export default async function AppLayout({
children, children,
@@ -47,44 +45,7 @@ export default async function AppLayout({
</p> </p>
</div> </div>
</NextLink> </NextLink>
<div> <MainNav />
{/* Text link on the right */}
<NextLink href={RECORD_A_MEETING_URL} className="font-light px-2">
Create
</NextLink>
{featureEnabled("browse") ? (
<>
&nbsp;·&nbsp;
<NextLink href="/browse" className="font-light px-2">
Browse
</NextLink>
</>
) : (
<></>
)}
{featureEnabled("rooms") ? (
<>
&nbsp;·&nbsp;
<NextLink href="/rooms" className="font-light px-2">
Rooms
</NextLink>
</>
) : (
<></>
)}
{featureEnabled("requireLogin") ? (
<>
&nbsp;·&nbsp;
<NextLink href="/settings/api-keys" className="font-light px-2">
Settings
</NextLink>
&nbsp;·&nbsp;
<UserInfo />
</>
) : (
<></>
)}
</div>
</Flex> </Flex>
<AuthWrapper>{children}</AuthWrapper> <AuthWrapper>{children}</AuthWrapper>

View File

@@ -1,13 +1,11 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import useAudioDevice from "../useAudioDevice"; import useAudioDevice from "../useAudioDevice";
import "react-select-search/style.css";
import "../../../styles/form.scss"; import "../../../styles/form.scss";
import About from "../../../(aboutAndPrivacy)/about"; import About from "../../../(aboutAndPrivacy)/about";
import Privacy from "../../../(aboutAndPrivacy)/privacy"; import Privacy from "../../../(aboutAndPrivacy)/privacy";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import useCreateTranscript from "../createTranscript"; import useCreateTranscript from "../createTranscript";
import SelectSearch from "react-select-search";
import { supportedLanguages } from "../../../supportedLanguages"; import { supportedLanguages } from "../../../supportedLanguages";
import { import {
Flex, Flex,
@@ -21,6 +19,7 @@ import {
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { useAuth } from "../../../lib/AuthProvider"; import { useAuth } from "../../../lib/AuthProvider";
import { featureEnabled } from "../../../lib/features"; import { featureEnabled } from "../../../lib/features";
import { SearchableLanguageSelect } from "../../../components/SearchableLanguageSelect";
const TranscriptCreate = () => { const TranscriptCreate = () => {
const router = useRouter(); const router = useRouter();
@@ -147,31 +146,27 @@ const TranscriptCreate = () => {
p={8} p={8}
flexDir="column" flexDir="column"
my={4} my={4}
className="form-on-primary"
> >
<Heading size="xl" mb={4}> <Heading size="xl" mb={4}>
Try Reflector Try Reflector
</Heading> </Heading>
<Box mb={4}> <Box mb={4}>
<Text>Recording name</Text> <Text mb={1}>Recording name</Text>
<div className="select-search-container">
<input <input
className="select-search-input" className="form-field-input"
type="text" type="text"
onChange={nameChange} onChange={nameChange}
placeholder="Optional" placeholder="Optional"
/> />
</div>
</Box> </Box>
<Box mb={4}> <Box mb={4}>
<Text>Do you want to enable live translation?</Text> <Text mb={1}>Do you want to enable live translation?</Text>
<SelectSearch <SearchableLanguageSelect
search
options={supportedLanguages} options={supportedLanguages}
value={targetLanguage} value={targetLanguage}
onChange={onLanguageChange} onChange={onLanguageChange}
onBlur={() => {}} placeholder="No translation"
onFocus={() => {}}
placeholder="Choose your language"
/> />
</Box> </Box>
{!loading ? ( {!loading ? (

View File

@@ -0,0 +1,46 @@
import NextLink from "next/link";
import { featureEnabled } from "../lib/features";
import UserInfo from "../(auth)/userInfo";
import { RECORD_A_MEETING_URL } from "../api/urls";
function NavLink({
href,
children,
}: {
href: string;
children: React.ReactNode;
}) {
return (
<NextLink href={href} className="font-light px-10">
{children}
</NextLink>
);
}
export default function MainNav() {
return (
<nav>
<NavLink href={RECORD_A_MEETING_URL}>Create</NavLink>
{featureEnabled("browse") && (
<>
&nbsp;·&nbsp;
<NavLink href="/browse">Browse</NavLink>
</>
)}
{featureEnabled("rooms") && (
<>
&nbsp;·&nbsp;
<NavLink href="/rooms">Rooms</NavLink>
</>
)}
{featureEnabled("requireLogin") && (
<>
&nbsp;·&nbsp;
<NavLink href="/settings/api-keys">Settings</NavLink>
&nbsp;·&nbsp;
<UserInfo />
</>
)}
</nav>
);
}

View File

@@ -0,0 +1,98 @@
"use client";
import React, { useMemo } from "react";
import {
Combobox,
createListCollection,
useComboboxContext,
} from "@chakra-ui/react";
export type LangOption = { value: string | undefined; name: string };
type Item = { label: string; value: string };
function FilteredComboboxItems({ items }: { items: Item[] }) {
const ctx = useComboboxContext();
const inputValue = (ctx as { inputValue?: string }).inputValue ?? "";
const filtered = useMemo(() => {
const q = inputValue.trim().toLowerCase();
if (!q) return items;
return items.filter((item) => item.label.toLowerCase().includes(q));
}, [items, inputValue]);
return (
<>
<Combobox.Empty>No matches</Combobox.Empty>
{filtered.map((item) => (
<Combobox.Item key={item.value} item={item}>
{item.label}
</Combobox.Item>
))}
</>
);
}
type Props = {
options: LangOption[];
value: string;
onChange: (value: string) => void;
placeholder: string;
};
export function SearchableLanguageSelect({
options,
value,
onChange,
placeholder,
}: Props) {
const items = useMemo(() => {
const result: Item[] = [];
let addedNone = false;
for (const opt of options) {
const val = opt.value ?? "NOTRANSLATION";
if (val === "NOTRANSLATION" || val === "") {
if (addedNone) continue;
addedNone = true;
result.push({ label: "No translation", value: "NOTRANSLATION" });
} else {
result.push({ label: opt.name, value: val });
}
}
return result.sort((a, b) => {
if (a.value === "NOTRANSLATION") return -1;
if (b.value === "NOTRANSLATION") return 1;
return a.label.localeCompare(b.label);
});
}, [options]);
const collection = useMemo(() => createListCollection({ items }), [items]);
const selectedValues = value && value !== "NOTRANSLATION" ? [value] : [];
return (
<Combobox.Root
collection={collection}
value={selectedValues}
onValueChange={(e) => onChange(e.value[0] ?? "NOTRANSLATION")}
openOnClick
closeOnSelect
selectionBehavior="replace"
placeholder={placeholder}
className="form-combobox"
size="md"
positioning={{ strategy: "fixed", hideWhenDetached: true }}
>
<Combobox.Control>
<Combobox.Input />
<Combobox.IndicatorGroup>
<Combobox.Trigger />
</Combobox.IndicatorGroup>
</Combobox.Control>
<Combobox.Positioner>
<Combobox.Content>
<FilteredComboboxItems items={items} />
</Combobox.Content>
</Combobox.Positioner>
</Combobox.Root>
);
}

View File

@@ -1,42 +1,74 @@
@media (prefers-color-scheme: dark) { /* Form fields on primary (blue) card white inputs like previous react-select */
.select-search-container, .form-on-primary {
.input-container { --form-bg: #fff;
--select-search-background: #fff; --form-border: #dce0e8;
--select-search-border: #dce0e8; --form-focus-border: #1e66f5;
--select-search-selected: #1e66f5; --form-text: #000;
--select-search-text: #000; --form-placeholder: #6c6f85;
--select-search-subtle-text: #6c6f85; --form-option-bg: #fff;
--select-search-highlight: #eff1f5; --form-option-hover: #eff1f5;
--form-dropdown-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
--form-radius: 0.5rem; /* 8px, matches rounded-lg elsewhere */
}
.form-on-primary .form-field-input,
.form-on-primary .form-field-select,
.form-on-primary .form-field-search-input {
box-sizing: border-box;
width: 100%;
padding: 0.5rem 0.75rem;
border-radius: var(--form-radius);
border: 1px solid var(--form-border);
background-color: var(--form-bg);
color: var(--form-text);
font-size: 0.9375rem;
outline: none;
cursor: pointer;
&::placeholder {
color: var(--form-placeholder);
}
&:focus {
border-color: var(--form-focus-border);
} }
} }
body.is-dark-mode .select-search-container, .form-on-primary .form-field-select option {
body.is-dark-mode .input-container { background: var(--form-option-bg);
--select-search-background: #fff; color: var(--form-text);
--select-search-border: #dce0e8;
--select-search-selected: #1e66f5;
--select-search-text: #000;
--select-search-subtle-text: #6c6f85;
--select-search-highlight: #eff1f5;
} }
body.is-light-mode .select-search-container, /* Chakra Combobox inside form-on-primary: white input + dropdown, dark text */
body.is-light-mode .input-container { .form-on-primary .form-combobox {
--select-search-background: #fff; width: 100%;
--select-search-border: #dce0e8;
--select-search-selected: #1e66f5; [data-part="control"],
--select-search-text: #000; & input {
--select-search-subtle-text: #6c6f85; border-radius: var(--form-radius);
--select-search-highlight: #eff1f5; border-color: var(--form-border);
background-color: var(--form-bg);
color: var(--form-text);
&:focus,
&[data-focus] {
border-color: var(--form-focus-border);
}
} }
.input-container, [data-part="content"] {
.select-search-container { border-radius: var(--form-radius);
max-width: 100%; border: 1px solid var(--form-border);
width: auto; background: var(--form-bg);
box-shadow: var(--form-dropdown-shadow);
color: var(--form-text);
} }
body .select-search-container .select-search--top.select-search-select { [data-part="item"] {
top: auto; color: var(--form-text);
bottom: 46px;
&:hover {
background: var(--form-option-hover);
}
}
} }

View File

@@ -13,61 +13,66 @@
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/react": "^3.24.2", "@chakra-ui/react": "^3.33.0",
"@daily-co/daily-js": "^0.84.0", "@daily-co/daily-js": "^0.87.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^7.2.0",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^3.2.0",
"@sentry/nextjs": "^10.40.0", "@sentry/nextjs": "^10.40.0",
"@tanstack/react-query": "^5.85.9", "@tanstack/react-query": "^5.90.21",
"@whereby.com/browser-sdk": "^3.3.4", "@whereby.com/browser-sdk": "^3.18.21",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.27",
"axios": "^1.13.5", "axios": "^1.13.6",
"eslint": "^9.33.0", "eslint": "^10.0.2",
"eslint-config-next": "^16.1.6", "eslint-config-next": "^16.1.6",
"fontawesome": "^5.6.3", "fontawesome": "^5.6.3",
"ioredis": "^5.7.0", "ioredis": "^5.10.0",
"jest-worker": "^29.6.2", "jest-worker": "^30.2.0",
"lucide-react": "^0.525.0", "lucide-react": "^0.575.0",
"next": "^16.1.6", "next": "^16.1.6",
"next-auth": "^4.24.12", "next-auth": "^4.24.13",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"nuqs": "^2.4.3", "nuqs": "^2.8.9",
"openapi-fetch": "^0.14.0", "openapi-fetch": "^0.17.0",
"openapi-react-query": "^0.5.0", "openapi-react-query": "^0.5.4",
"postcss": "8.4.31", "postcss": "8.5.6",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-dropdown": "^1.11.0", "react-dropdown": "^1.11.0",
"react-icons": "^5.0.1", "react-icons": "^5.5.0",
"react-markdown": "^9.0.0", "react-markdown": "^10.1.0",
"react-qr-code": "^2.0.12", "react-qr-code": "^2.0.18",
"react-select-search": "^4.1.7",
"react-uuid-hook": "^0.0.6", "react-uuid-hook": "^0.0.6",
"redlock": "5.0.0-beta.2", "redlock": "5.0.0-beta.2",
"remeda": "^2.31.1", "remeda": "^2.33.6",
"sass": "^1.63.6", "sass": "^1.97.3",
"simple-peer": "^9.11.1", "simple-peer": "^9.11.1",
"tailwindcss": "^3.3.2", "tailwindcss": "^4.2.1",
"typescript": "^5.1.6", "typescript": "^5.9.3",
"wavesurfer.js": "^7.4.2", "wavesurfer.js": "^7.12.1",
"zod": "^4.1.5" "zod": "^4.3.6"
}, },
"main": "index.js", "main": "index.js",
"repository": "https://github.com/Monadical-SAS/reflector-ui.git", "repository": "https://github.com/Monadical-SAS/reflector-ui.git",
"author": "Andreas <andreas@monadical.com>", "author": "Andreas <andreas@monadical.com>",
"license": "All Rights Reserved", "license": "All Rights Reserved",
"devDependencies": { "devDependencies": {
"@types/ioredis": "^5.0.0", "@tailwindcss/postcss": "^4.2.1",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/react": "19.2.14", "@types/react": "19.2.14",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"jest": "^30.2.0", "jest": "^30.2.0",
"openapi-typescript": "^7.9.1", "openapi-typescript": "^7.13.0",
"prettier": "^3.0.0", "prettier": "^3.8.1",
"ts-jest": "^29.4.6" "ts-jest": "^29.4.6"
}, },
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748" "pnpm": {
"overrides": {
"minimatch@>=5.0.0 <5.1.8": "5.1.8",
"js-yaml@<4.1.1": "4.1.1",
"webpack": "5.105.3"
}
}
} }

4559
www/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {}, "@tailwindcss/postcss": {},
autoprefixer: {}, autoprefixer: {},
}, },
}; };