mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-04-18 11:16:55 +00:00
Compare commits
9 Commits
feat/file-
...
local-llm-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d4c5c463c | ||
|
|
f6a23cfddd | ||
|
|
b1405af8c7 | ||
|
|
71ad8a294f | ||
|
|
bba272505f | ||
|
|
67aea78243 | ||
|
|
2d81321733 | ||
|
|
8c2b720564 | ||
|
|
88e945ec00 |
10
COMPOSE_STANDALONE_TODO.md
Normal file
10
COMPOSE_STANDALONE_TODO.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Standalone Compose: Remaining Production Work
|
||||||
|
|
||||||
|
## Server/worker/beat: remove host network mode + bind mounts
|
||||||
|
|
||||||
|
Currently `server` uses `network_mode: host` and all three services bind-mount `./server/:/app/`. For full standalone prod:
|
||||||
|
|
||||||
|
- Remove `network_mode: host` from server
|
||||||
|
- Remove bind-mount volumes from server, worker, beat (use built image only)
|
||||||
|
- Update `compose_cmd` in `setup-standalone.sh` to not rely on host network
|
||||||
|
- Change `SERVER_API_URL` from `http://host.docker.internal:1250` to `http://server:1250` (server reachable via Docker network once off host mode)
|
||||||
@@ -63,21 +63,34 @@ services:
|
|||||||
server:
|
server:
|
||||||
environment:
|
environment:
|
||||||
TRANSCRIPT_BACKEND: modal
|
TRANSCRIPT_BACKEND: modal
|
||||||
TRANSCRIPT_URL: http://cpu:8000
|
TRANSCRIPT_URL: http://localhost:8100
|
||||||
|
TRANSCRIPT_MODAL_API_KEY: local
|
||||||
DIARIZATION_BACKEND: modal
|
DIARIZATION_BACKEND: modal
|
||||||
DIARIZATION_URL: http://cpu:8000
|
DIARIZATION_URL: http://localhost:8100
|
||||||
|
|
||||||
worker:
|
worker:
|
||||||
environment:
|
environment:
|
||||||
TRANSCRIPT_BACKEND: modal
|
TRANSCRIPT_BACKEND: modal
|
||||||
TRANSCRIPT_URL: http://cpu:8000
|
TRANSCRIPT_URL: http://cpu:8000
|
||||||
|
TRANSCRIPT_MODAL_API_KEY: local
|
||||||
DIARIZATION_BACKEND: modal
|
DIARIZATION_BACKEND: modal
|
||||||
DIARIZATION_URL: http://cpu:8000
|
DIARIZATION_URL: http://cpu:8000
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: reflector-frontend-standalone
|
||||||
|
build:
|
||||||
|
context: ./www
|
||||||
|
command: ["node", "server.js"]
|
||||||
|
volumes: !reset []
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
|
||||||
cpu:
|
cpu:
|
||||||
build:
|
build:
|
||||||
context: ./gpu/self_hosted
|
context: ./gpu/self_hosted
|
||||||
dockerfile: Dockerfile.cpu
|
dockerfile: Dockerfile.cpu
|
||||||
|
ports:
|
||||||
|
- "8100:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- gpu_cache:/root/.cache
|
- gpu_cache:/root/.cache
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ services:
|
|||||||
server:
|
server:
|
||||||
build:
|
build:
|
||||||
context: server
|
context: server
|
||||||
ports:
|
network_mode: host
|
||||||
- 1250:1250
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./server/:/app/
|
- ./server/:/app/
|
||||||
- /app/.venv
|
- /app/.venv
|
||||||
@@ -11,8 +10,12 @@ services:
|
|||||||
- ./server/.env
|
- ./server/.env
|
||||||
environment:
|
environment:
|
||||||
ENTRYPOINT: server
|
ENTRYPOINT: server
|
||||||
extra_hosts:
|
DATABASE_URL: postgresql+asyncpg://reflector:reflector@localhost:5432/reflector
|
||||||
- "host.docker.internal:host-gateway"
|
REDIS_HOST: localhost
|
||||||
|
CELERY_BROKER_URL: redis://localhost:6379/1
|
||||||
|
CELERY_RESULT_BACKEND: redis://localhost:6379/1
|
||||||
|
HATCHET_CLIENT_SERVER_URL: http://localhost:8889
|
||||||
|
HATCHET_CLIENT_HOST_PORT: localhost:7078
|
||||||
|
|
||||||
worker:
|
worker:
|
||||||
build:
|
build:
|
||||||
@@ -24,6 +27,11 @@ services:
|
|||||||
- ./server/.env
|
- ./server/.env
|
||||||
environment:
|
environment:
|
||||||
ENTRYPOINT: worker
|
ENTRYPOINT: worker
|
||||||
|
HATCHET_CLIENT_SERVER_URL: http://hatchet:8888
|
||||||
|
HATCHET_CLIENT_HOST_PORT: hatchet:7077
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
beat:
|
beat:
|
||||||
build:
|
build:
|
||||||
@@ -35,6 +43,9 @@ services:
|
|||||||
- ./server/.env
|
- ./server/.env
|
||||||
environment:
|
environment:
|
||||||
ENTRYPOINT: beat
|
ENTRYPOINT: beat
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
hatchet-worker-cpu:
|
hatchet-worker-cpu:
|
||||||
build:
|
build:
|
||||||
@@ -46,6 +57,8 @@ services:
|
|||||||
- ./server/.env
|
- ./server/.env
|
||||||
environment:
|
environment:
|
||||||
ENTRYPOINT: hatchet-worker-cpu
|
ENTRYPOINT: hatchet-worker-cpu
|
||||||
|
HATCHET_CLIENT_SERVER_URL: http://hatchet:8888
|
||||||
|
HATCHET_CLIENT_HOST_PORT: hatchet:7077
|
||||||
depends_on:
|
depends_on:
|
||||||
hatchet:
|
hatchet:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -59,8 +72,8 @@ services:
|
|||||||
- ./server/.env
|
- ./server/.env
|
||||||
environment:
|
environment:
|
||||||
ENTRYPOINT: hatchet-worker-llm
|
ENTRYPOINT: hatchet-worker-llm
|
||||||
extra_hosts:
|
HATCHET_CLIENT_SERVER_URL: http://hatchet:8888
|
||||||
- "host.docker.internal:host-gateway"
|
HATCHET_CLIENT_HOST_PORT: hatchet:7077
|
||||||
depends_on:
|
depends_on:
|
||||||
hatchet:
|
hatchet:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -84,6 +97,11 @@ services:
|
|||||||
- ./www/.env.local
|
- ./www/.env.local
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
|
- SERVER_API_URL=http://host.docker.internal:1250
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17
|
image: postgres:17
|
||||||
@@ -99,13 +117,14 @@ services:
|
|||||||
- ./server/docker/init-hatchet-db.sql:/docker-entrypoint-initdb.d/init-hatchet-db.sql:ro
|
- ./server/docker/init-hatchet-db.sql:/docker-entrypoint-initdb.d/init-hatchet-db.sql:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -d reflector -U reflector"]
|
test: ["CMD-SHELL", "pg_isready -d reflector -U reflector"]
|
||||||
interval: 10s
|
interval: 5s
|
||||||
timeout: 10s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 10
|
||||||
start_period: 10s
|
start_period: 15s
|
||||||
|
|
||||||
hatchet:
|
hatchet:
|
||||||
image: ghcr.io/hatchet-dev/hatchet/hatchet-lite:latest
|
image: ghcr.io/hatchet-dev/hatchet/hatchet-lite:latest
|
||||||
|
restart: on-failure
|
||||||
ports:
|
ports:
|
||||||
- "8889:8888"
|
- "8889:8888"
|
||||||
- "7078:7077"
|
- "7078:7077"
|
||||||
@@ -113,7 +132,7 @@ services:
|
|||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: "postgresql://reflector:reflector@postgres:5432/hatchet?sslmode=disable"
|
DATABASE_URL: "postgresql://reflector:reflector@postgres:5432/hatchet?sslmode=disable&connect_timeout=30"
|
||||||
SERVER_AUTH_COOKIE_DOMAIN: localhost
|
SERVER_AUTH_COOKIE_DOMAIN: localhost
|
||||||
SERVER_AUTH_COOKIE_INSECURE: "t"
|
SERVER_AUTH_COOKIE_INSECURE: "t"
|
||||||
SERVER_GRPC_BIND_ADDRESS: "0.0.0.0"
|
SERVER_GRPC_BIND_ADDRESS: "0.0.0.0"
|
||||||
@@ -135,7 +154,3 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
next_cache:
|
next_cache:
|
||||||
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
attachable: true
|
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ If the frontend or backend behaves unexpectedly (e.g., env vars seem ignored, ch
|
|||||||
lsof -i :3000 # frontend
|
lsof -i :3000 # frontend
|
||||||
lsof -i :1250 # backend
|
lsof -i :1250 # backend
|
||||||
lsof -i :5432 # postgres
|
lsof -i :5432 # postgres
|
||||||
|
lsof -i :3900 # Garage S3 API
|
||||||
|
lsof -i :6379 # Redis
|
||||||
|
|
||||||
# Kill stale processes on a port
|
# Kill stale processes on a port
|
||||||
lsof -ti :3000 | xargs kill
|
lsof -ti :3000 | xargs kill
|
||||||
@@ -175,9 +177,13 @@ lsof -ti :3000 | xargs kill
|
|||||||
|
|
||||||
Common causes:
|
Common causes:
|
||||||
- A stale `next dev` or `pnpm dev` process from another terminal/worktree
|
- A stale `next dev` or `pnpm dev` process from another terminal/worktree
|
||||||
- Another Docker Compose project (different worktree) with containers on the same ports
|
- Another Docker Compose project (different worktree) with containers on the same ports — the setup script only manages its own project; containers from other projects must be stopped manually (`docker ps` to find them, `docker stop` to kill them)
|
||||||
|
|
||||||
The setup script checks for port conflicts before starting services.
|
The setup script checks ports 3000, 1250, 5432, 6379, 3900, 3903 for conflicts before starting services. It ignores OrbStack/Docker Desktop port forwarding processes (which always bind these ports but are not real conflicts).
|
||||||
|
|
||||||
|
### OrbStack false port-conflict warnings (Mac)
|
||||||
|
|
||||||
|
If you use OrbStack as your Docker runtime, `lsof` will show OrbStack binding ports like 3000, 1250, etc. even when no containers are running. This is OrbStack's port forwarding mechanism — not a real conflict. The setup script filters these out automatically.
|
||||||
|
|
||||||
### Re-enabling authentication
|
### Re-enabling authentication
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ step_llm() {
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Pull model if not already present
|
# Pull model if not already present
|
||||||
if ollama list 2>/dev/null | grep -q "$MODEL"; then
|
if ollama list 2>/dev/null | awk '{print $1}' | grep -qx "$MODEL"; then
|
||||||
ok "Model $MODEL already pulled"
|
ok "Model $MODEL already pulled"
|
||||||
else
|
else
|
||||||
info "Pulling model $MODEL (this may take a while)..."
|
info "Pulling model $MODEL (this may take a while)..."
|
||||||
@@ -143,7 +143,7 @@ step_llm() {
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Pull model inside container
|
# Pull model inside container
|
||||||
if compose_cmd exec "$OLLAMA_SVC" ollama list 2>/dev/null | grep -q "$MODEL"; then
|
if compose_cmd exec "$OLLAMA_SVC" ollama list 2>/dev/null | awk '{print $1}' | grep -qx "$MODEL"; then
|
||||||
ok "Model $MODEL already pulled"
|
ok "Model $MODEL already pulled"
|
||||||
else
|
else
|
||||||
info "Pulling model $MODEL inside container (this may take a while)..."
|
info "Pulling model $MODEL inside container (this may take a while)..."
|
||||||
@@ -290,16 +290,23 @@ ENVEOF
|
|||||||
step_services() {
|
step_services() {
|
||||||
info "Step 5: Starting Docker services"
|
info "Step 5: Starting Docker services"
|
||||||
|
|
||||||
# Check for port conflicts — stale processes silently shadow Docker port mappings
|
# Check for port conflicts — stale processes silently shadow Docker port mappings.
|
||||||
|
# OrbStack/Docker Desktop bind ports for forwarding; ignore those PIDs.
|
||||||
local ports_ok=true
|
local ports_ok=true
|
||||||
for port in 3000 1250; do
|
for port in 3000 1250 5432 6379 3900 3903; do
|
||||||
local pid
|
local pids
|
||||||
pid=$(lsof -ti :"$port" 2>/dev/null || true)
|
pids=$(lsof -ti :"$port" 2>/dev/null || true)
|
||||||
if [[ -n "$pid" ]]; then
|
for pid in $pids; do
|
||||||
warn "Port $port already in use by PID $pid"
|
local pname
|
||||||
|
pname=$(ps -p "$pid" -o comm= 2>/dev/null || true)
|
||||||
|
# OrbStack and Docker Desktop own port forwarding — not real conflicts
|
||||||
|
if [[ "$pname" == *"OrbStack"* ]] || [[ "$pname" == *"com.docker"* ]] || [[ "$pname" == *"vpnkit"* ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
warn "Port $port already in use by PID $pid ($pname)"
|
||||||
warn "Kill it with: lsof -ti :$port | xargs kill"
|
warn "Kill it with: lsof -ti :$port | xargs kill"
|
||||||
ports_ok=false
|
ports_ok=false
|
||||||
fi
|
done
|
||||||
done
|
done
|
||||||
if [[ "$ports_ok" == "false" ]]; then
|
if [[ "$ports_ok" == "false" ]]; then
|
||||||
warn "Port conflicts detected — Docker containers may not be reachable"
|
warn "Port conflicts detected — Docker containers may not be reachable"
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
from typing import Annotated
|
|
||||||
|
|
||||||
from fastapi import Depends
|
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
|
|
||||||
|
|
||||||
|
|
||||||
class UserInfo(BaseModel):
|
class UserInfo(BaseModel):
|
||||||
sub: str
|
sub: str
|
||||||
@@ -15,13 +9,13 @@ class AccessTokenInfo(BaseModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def authenticated(token: Annotated[str, Depends(oauth2_scheme)]):
|
def authenticated():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def current_user(token: Annotated[str, Depends(oauth2_scheme)]):
|
def current_user():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def current_user_optional(token: Annotated[str, Depends(oauth2_scheme)]):
|
def current_user_optional():
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -48,7 +48,15 @@ class RedisPubSubManager:
|
|||||||
if not self.redis_connection:
|
if not self.redis_connection:
|
||||||
await self.connect()
|
await self.connect()
|
||||||
message = json.dumps(message)
|
message = json.dumps(message)
|
||||||
await self.redis_connection.publish(room_id, message)
|
try:
|
||||||
|
await self.redis_connection.publish(room_id, message)
|
||||||
|
except RuntimeError:
|
||||||
|
# Celery workers run each task in a new event loop (asyncio.run),
|
||||||
|
# which closes the previous loop. Cached Redis connection is dead.
|
||||||
|
# Reconnect on the current loop and retry.
|
||||||
|
self.redis_connection = None
|
||||||
|
await self.connect()
|
||||||
|
await self.redis_connection.publish(room_id, message)
|
||||||
|
|
||||||
async def subscribe(self, room_id: str) -> redis.Redis:
|
async def subscribe(self, room_id: str) -> redis.Redis:
|
||||||
await self.pubsub.subscribe(room_id)
|
await self.pubsub.subscribe(room_id)
|
||||||
|
|||||||
@@ -291,7 +291,12 @@ async def test_validation_idle_transcript_with_recording_allowed():
|
|||||||
recording_id="test-recording-id",
|
recording_id="test-recording-id",
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await validate_transcript_for_processing(mock_transcript)
|
with patch(
|
||||||
|
"reflector.services.transcript_process.task_is_scheduled_or_active"
|
||||||
|
) as mock_celery_check:
|
||||||
|
mock_celery_check.return_value = False
|
||||||
|
|
||||||
|
result = await validate_transcript_for_processing(mock_transcript)
|
||||||
|
|
||||||
assert isinstance(result, ValidationOk)
|
assert isinstance(result, ValidationOk)
|
||||||
assert result.recording_id == "test-recording-id"
|
assert result.recording_id == "test-recording-id"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useTranscriptGet } from "../../../../lib/apiHooks";
|
import { useTranscriptGet } from "../../../../lib/apiHooks";
|
||||||
import { parseNonEmptyString } from "../../../../lib/utils";
|
import { parseNonEmptyString } from "../../../../lib/utils";
|
||||||
|
import { useWebSockets } from "../../useWebSockets";
|
||||||
|
|
||||||
type TranscriptProcessing = {
|
type TranscriptProcessing = {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
@@ -24,6 +25,7 @@ export default function TranscriptProcessing(details: TranscriptProcessing) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const transcript = useTranscriptGet(transcriptId);
|
const transcript = useTranscriptGet(transcriptId);
|
||||||
|
useWebSockets(transcriptId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const status = transcript.data?.status;
|
const status = transcript.data?.status;
|
||||||
|
|||||||
@@ -23,7 +23,16 @@ const useWebRTC = (
|
|||||||
let p: Peer;
|
let p: Peer;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
p = new Peer({ initiator: true, stream: stream });
|
p = new Peer({
|
||||||
|
initiator: true,
|
||||||
|
stream: stream,
|
||||||
|
// Disable trickle ICE: single SDP exchange (offer + answer) with all candidates.
|
||||||
|
// Required for HTTP-based signaling; trickle needs WebSocket for candidate exchange.
|
||||||
|
trickle: false,
|
||||||
|
config: {
|
||||||
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error as Error, "Error creating WebRTC");
|
setError(error as Error, "Error creating WebRTC");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"build-production": "next build --experimental-build-mode compile",
|
"build-production": "next build --experimental-build-mode compile",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
|
|||||||
Reference in New Issue
Block a user