mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-04-28 08:05:19 +00:00
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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
7
docs/.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
.env*
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
23526
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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
13976
docs/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
4129
docs/static/openapi.json
vendored
4129
docs/static/openapi.json
vendored
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
13
server/reflector/_warnings_filter.py
Normal file
13
server/reflector/_warnings_filter.py
Normal 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,
|
||||||
|
)
|
||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
77
server/uv.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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") ? (
|
|
||||||
<>
|
|
||||||
·
|
|
||||||
<NextLink href="/browse" className="font-light px-2">
|
|
||||||
Browse
|
|
||||||
</NextLink>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
{featureEnabled("rooms") ? (
|
|
||||||
<>
|
|
||||||
·
|
|
||||||
<NextLink href="/rooms" className="font-light px-2">
|
|
||||||
Rooms
|
|
||||||
</NextLink>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
{featureEnabled("requireLogin") ? (
|
|
||||||
<>
|
|
||||||
·
|
|
||||||
<NextLink href="/settings/api-keys" className="font-light px-2">
|
|
||||||
Settings
|
|
||||||
</NextLink>
|
|
||||||
·
|
|
||||||
<UserInfo />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<AuthWrapper>{children}</AuthWrapper>
|
<AuthWrapper>{children}</AuthWrapper>
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
46
www/app/components/MainNav.tsx
Normal file
46
www/app/components/MainNav.tsx
Normal 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") && (
|
||||||
|
<>
|
||||||
|
·
|
||||||
|
<NavLink href="/browse">Browse</NavLink>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{featureEnabled("rooms") && (
|
||||||
|
<>
|
||||||
|
·
|
||||||
|
<NavLink href="/rooms">Rooms</NavLink>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{featureEnabled("requireLogin") && (
|
||||||
|
<>
|
||||||
|
·
|
||||||
|
<NavLink href="/settings/api-keys">Settings</NavLink>
|
||||||
|
·
|
||||||
|
<UserInfo />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
98
www/app/components/SearchableLanguageSelect.tsx
Normal file
98
www/app/components/SearchableLanguageSelect.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
--select-search-text: #000;
|
|
||||||
--select-search-subtle-text: #6c6f85;
|
|
||||||
--select-search-highlight: #eff1f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-container,
|
[data-part="control"],
|
||||||
.select-search-container {
|
& input {
|
||||||
max-width: 100%;
|
border-radius: var(--form-radius);
|
||||||
width: auto;
|
border-color: var(--form-border);
|
||||||
}
|
background-color: var(--form-bg);
|
||||||
|
color: var(--form-text);
|
||||||
|
|
||||||
body .select-search-container .select-search--top.select-search-select {
|
&:focus,
|
||||||
top: auto;
|
&[data-focus] {
|
||||||
bottom: 46px;
|
border-color: var(--form-focus-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-part="content"] {
|
||||||
|
border-radius: var(--form-radius);
|
||||||
|
border: 1px solid var(--form-border);
|
||||||
|
background: var(--form-bg);
|
||||||
|
box-shadow: var(--form-dropdown-shadow);
|
||||||
|
color: var(--form-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-part="item"] {
|
||||||
|
color: var(--form-text);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--form-option-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
4559
www/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
"@tailwindcss/postcss": {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user