diff --git a/.claude/role-comm-backend.md b/.claude/role-comm-backend.md index 81b9483..d8375d4 100644 --- a/.claude/role-comm-backend.md +++ b/.claude/role-comm-backend.md @@ -126,3 +126,208 @@ None 2. Add integration tests for resilience patterns 3. Document monitoring/alerting setup 4. Optional: Implement retry logic with exponential backoff + +--- + +## Update 2025-12-30T09:52:00Z +agent: backend-developer +status: completed +task: Fix streaming chat endpoint - token-level streaming + +### Progress +- Fixed `/api/chat/stream` endpoint to properly stream tokens from LLM +- Changed from LangGraph's `streamEvents()` to direct model `.stream()` call +- Added support for Claude's content block format (array of {type, text}) +- Streaming now emits proper `text_delta` events in real-time +- Verified all tests pass (29/29 in chat.test.ts) + +### Root Cause +The issue was that: +1. LangGraph's `streamEvents()` doesn't reliably emit token-level events +2. Claude API returns content as array of content blocks, not plain strings +3. Code was checking `typeof content === 'string'` but content was `Array<{type: 'text', text: string}>` + +### Solution +Bypass LangGraph agent loop and stream directly from the model: +```typescript +const stream = await modelWithTools.stream(messages, { callbacks }); +for await (const chunk of stream) { + // Handle both string content and array of content blocks + if (Array.isArray(content)) { + for (const block of content) { + if (block.type === 'text' && block.text) { + yield { type: 'text_delta', content: block.text }; + } + } + } +} +``` + +### API Contract +**Endpoint**: GET /api/chat/stream +**Status**: WORKING (previously broken) + +**Query Parameters**: +- `message` (required): string, 1-10000 chars +- `threadId` (optional): UUID +- `persona` (optional): string, max 100 chars + +**SSE Events**: +```typescript +// Text token streaming (now working!) +event: text_delta +data: {"type":"text_delta","content":"Hello","traceId":"..."} + +// Tool calls (emitted but not executed) +event: tool_call +data: {"type":"tool_call","toolCallId":"...","toolName":"calculator","toolInput":{...}} + +// Completion +event: done +data: {"type":"done","traceId":"..."} +``` + +### Limitations (Documented in Code) +Current streaming implementation has intentional trade-offs: +- **No tool execution**: Tool calls are emitted but NOT executed (use POST /api/chat for tool execution) +- **No memory**: Single-turn only, no conversation history +- **Reason**: Direct model streaming bypasses LangGraph agent loop + +For multi-turn conversations with tools, clients should use POST /api/chat (non-streaming). + +### Files Modified +- `packages/backend/src/agents/chat-agent.ts` + - Rewrote `chatStream()` function (lines 365-471) + - Added content block parsing for Claude format + - Removed unused ToolMessage import + +### Testing +```bash +# Works now - streams tokens in real-time +curl -N 'http://localhost:4000/api/chat/stream?message=Count%20to%205' + +# Output: +event: text_delta +data: {"type":"text_delta","content":"I'll count"} +event: text_delta +data: {"type":"text_delta","content":" to 5 for you:"} +# ... streams continue in real-time +event: done +data: {"type":"done","traceId":"..."} +``` + +### Blockers +None + +### Next Steps +- Consider implementing full streaming with tool execution using LangGraph's callback system +- Monitor Langfuse traces to verify streaming sessions are captured correctly + +--- + +## Update 2025-12-30T10:00:00Z +agent: backend-developer +status: completed +task: Implement proper LangGraph streamEvents() for chat streaming with tool execution + +### Progress +- Replaced `model.stream()` with `agent.streamEvents()` to use full LangGraph agent loop +- Added `extractTextContent()` helper to handle both OpenAI (string) and Anthropic (content blocks array) formats +- Implemented proper event handling: + - `on_chat_model_stream` for token-level streaming + - `on_tool_start` for tool call events + - `on_tool_end` for tool result events +- Streaming now includes full agent capabilities: tool execution, memory, and checkpointer +- All tests pass (29/29 in chat.test.ts) +- Manual testing confirms streaming works correctly + +### Key Insight +Anthropic models return content as `Array<{type: 'text', text: string}>`, NOT a string. The previous implementation broke because it only checked `typeof content === 'string'`. The new `extractTextContent()` helper handles both formats universally. + +### Solution +```typescript +// Helper to extract text from both OpenAI and Anthropic formats +function extractTextContent(content: unknown): string { + if (typeof content === 'string') return content; + if (Array.isArray(content)) { + let text = ''; + for (const block of content) { + if (block?.type === 'text' && typeof block.text === 'string') { + text += block.text; + } + } + return text; + } + return ''; +} + +// Use streamEvents() with version: 'v2' +const eventStream = agent.streamEvents(streamParams, { + configurable: { thread_id: input.threadId }, + callbacks, + version: 'v2' as const, +}); + +for await (const event of eventStream) { + if (event.event === 'on_chat_model_stream') { + const text = extractTextContent(event.data?.chunk?.content); + if (text) yield { type: 'text_delta', content: text }; + } + // Handle tool_start and tool_end events... +} +``` + +### API Contract +**Endpoint**: GET /api/chat/stream +**Status**: FULLY FUNCTIONAL with LangGraph agent loop + +**Capabilities (Enhanced)**: +- Token-level streaming via `on_chat_model_stream` events +- Tool execution with `on_tool_start` and `on_tool_end` events +- Conversation history/memory via PostgresSaver checkpointer +- Support for both OpenAI and Anthropic models + +**SSE Events**: +```typescript +// Text streaming (works with both OpenAI and Anthropic) +event: text_delta +data: {"type":"text_delta","content":"Hello","traceId":"..."} + +// Tool execution (now actually executes!) +event: tool_call +data: {"type":"tool_call","toolCallId":"run_xyz","toolName":"calculator","toolInput":{"expression":"2+2"}} + +event: tool_result +data: {"type":"tool_result","toolCallId":"run_xyz","result":"4"} + +// Completion +event: done +data: {"type":"done","traceId":"..."} +``` + +### Files Modified +- `packages/backend/src/agents/chat-agent.ts` (lines 365-517) + - Added `extractTextContent()` helper function + - Rewrote `chatStream()` to use `agent.streamEvents()` with version: 'v2' + - Added event handlers for `on_chat_model_stream`, `on_tool_start`, `on_tool_end` + - Updated documentation to reflect full agent capabilities + +### Testing +```bash +# TypeScript check - passes +cd packages/backend && pnpm run typecheck + +# Tests - all pass (29/29) +cd packages/backend && pnpm test:run + +# Manual test - streams correctly +curl -N 'http://localhost:4000/api/chat/stream?message=Hi' | head -20 +# Output shows proper streaming with traceId +``` + +### Blockers +None + +### Next Steps +- Monitor production logs to verify both OpenAI and Anthropic models stream correctly +- Consider adding metrics for streaming event counts and latency diff --git a/.claude/role-comm-frontend.md b/.claude/role-comm-frontend.md index 6f21e4c..73c4953 100644 --- a/.claude/role-comm-frontend.md +++ b/.claude/role-comm-frontend.md @@ -29,4 +29,196 @@ task: Brief description ## Communication Log - +## Update 2025-12-29T20:00:00Z +agent: frontend-developer +status: completed +task: Implement Chat UI with React 19.2 patterns + +### Progress +- Created comprehensive Chat UI using React 19.2 best practices +- Implemented streaming and non-streaming chat modes +- Added Zod validation for API responses +- Used skeleton loading states (no spinners) +- All TypeScript checks passing (0 errors) +- Build successful + +### Components Created +1. `/packages/frontend/src/pages/ChatPage.tsx` - Main chat page with React 19 patterns +2. `/packages/frontend/src/components/Chat/Message.tsx` - Individual message component +3. `/packages/frontend/src/components/Chat/MessageList.tsx` - Message list with auto-scroll +4. `/packages/frontend/src/components/Chat/MessageInput.tsx` - Form with useActionState +5. `/packages/frontend/src/components/Chat/SubmitButton.tsx` - Button with useFormStatus +6. `/packages/frontend/src/components/Chat/MessageSkeleton.tsx` - Loading skeleton +7. `/packages/frontend/src/components/Chat/index.ts` - Component exports + +### React 19 Patterns Used +- `useActionState` for form submission (replaces deprecated useFormState) +- `useOptimistic` for instant message feedback +- `useFormStatus` in submit button for pending state +- `ref` as prop (no forwardRef needed) +- Skeleton loading (NOT spinners) +- Zod validation for runtime type safety + +### State Management +- Local state with useState for messages and threadId +- useOptimistic for optimistic UI updates +- useActionState for form submission with error handling +- Streaming state management for SSE responses + +### API Integration +- POST /api/chat - Regular chat with Zod validation +- GET /api/chat/stream - SSE streaming with async generator +- Added ChatResponseSchema validation +- Error handling with user-friendly messages + +### Files Modified +1. `/packages/shared/src/types/index.ts` - Added ChatMessage, ChatRequest, ChatResponse, StreamEvent types +2. `/packages/frontend/src/lib/api.ts` - Added sendChatMessage() and chatStream() methods +3. `/packages/frontend/src/App.tsx` - Added /chat route +4. `/packages/frontend/src/components/Layout.tsx` - Added Chat navigation link + +### Features Implemented +- User/assistant message styling +- Tool call display when AI uses tools +- Timestamp display +- Auto-scroll to latest message +- Streaming/non-streaming toggle +- Clear chat functionality +- Thread ID persistence +- Enter to send (Shift+Enter for new line) +- Loading states with skeletons +- Error handling with user feedback +- Dark mode support + +### Code Quality +- TypeScript: PASSING (0 errors) +- ESLint: PASSING (0 warnings) +- Build: PASSING +- Pattern compliance: 100% (React 19.2 patterns) + +### API Dependencies +All required endpoints exist in backend: +- POST /api/chat - Validated with ChatMessageSchema +- GET /api/chat/stream - SSE streaming with events + +### Blockers +None + +### Next Steps +1. Notify test-engineer to write tests for Chat components +2. Notify visual-qa for visual verification +3. Consider adding: + - Message persistence (save to localStorage or backend) + - Multiple chat threads UI + - Export chat history + - File upload support + +--- + +## Update 2025-12-30T10:00:00Z +agent: frontend-developer +status: completed +task: Implement React 19 components for AI chat message parts with tool invocations + +### Progress +- Created type-safe MessageParts component using discriminated unions +- Implemented TextContent with streaming cursor animation +- Built ToolInvocation with status indicators (pending/running/complete) +- Created ToolResult with success/error states +- Updated Message component for backward compatibility +- All components use design tokens from index.css +- TypeScript: PASSING (0 errors, strict mode with exactOptionalPropertyTypes) +- Build: PASSING + +### Components Created +1. `/packages/frontend/src/components/Chat/MessageParts.tsx` - Main orchestrator for content blocks +2. `/packages/frontend/src/components/Chat/TextContent.tsx` - Text with optional streaming cursor +3. `/packages/frontend/src/components/Chat/ToolInvocation.tsx` - Tool calls with status +4. `/packages/frontend/src/components/Chat/ToolResult.tsx` - Tool results with success/error states +5. `/packages/frontend/src/components/Chat/USAGE.md` - Comprehensive usage documentation + +### Components Updated +1. `/packages/frontend/src/components/Chat/Message.tsx` - Now supports both legacy (content string) and new (parts array) formats +2. `/packages/frontend/src/components/Chat/index.ts` - Added exports for new components + +### TypeScript Types +```typescript +type ContentBlock = + | { type: 'text'; content: string } + | { type: 'tool_use'; toolCallId: string; toolName: string; toolInput: unknown; status: 'pending' | 'running' | 'complete' } + | { type: 'tool_result'; toolCallId: string; result: string; isError?: boolean } + | { type: 'thinking'; content: string }; +``` + +### React 19 Patterns Used +- Discriminated unions with exhaustive type checking (assertNever) +- ref as prop (no forwardRef) +- Proper TypeScript with exactOptionalPropertyTypes +- Conditional prop spreading for optional props +- CSS variables for theme tokens +- Skeleton loading (NOT spinners for content) +- Accessible animations with prefers-reduced-motion support + +### Design Token Integration +All components use CSS variables from `/packages/frontend/src/index.css`: +- Tool colors: `--color-tool-invoke-bg`, `--color-tool-invoke-border`, `--color-tool-invoke-icon`, `--color-tool-invoke-text` +- Result colors: `--color-tool-success-bg`, `--color-tool-error-bg`, etc. +- Spacing: `--spacing-tool-inset`, `--spacing-bubble-padding-x` +- Radius: `--radius-tool`, `--radius-bubble` +- Animations: `--duration-cursor-blink`, `--duration-tool-pulse` +- Font: `--font-family-mono` for code display + +### Features Implemented +- Type-safe content block rendering with discriminated unions +- Streaming text with blinking cursor animation +- Tool invocation status (pending/running/complete) with spinner +- Tool result success/error states with color coding +- Thinking blocks for internal reasoning display +- JSON formatting for tool inputs and results +- Pulse animation for running tools +- Expand animation for tool results +- Dark mode support via CSS variables +- Accessibility features (aria-labels, reduced motion) + +### Backward Compatibility +Message component now supports: +- Legacy: `` +- New: `` +- Both formats work simultaneously + +### Code Quality +- TypeScript: PASSING (0 errors, strict mode) +- ESLint: PASSING (0 warnings) +- Prettier: PASSING (formatted) +- Build: PASSING (Vite production build successful) +- Pattern compliance: 100% (React 19.2 patterns) + +### API Dependencies +Components are ready for backend integration. Expecting ContentBlock[] format from: +- Chat streaming endpoint +- LangGraph agent responses +- Langfuse traced tool calls + +Backend should provide parts in this format: +```typescript +{ + parts: [ + { type: 'text', content: 'Let me search for that...' }, + { type: 'tool_use', toolCallId: 'call_1', toolName: 'search', toolInput: { query: 'test' }, status: 'running' }, + { type: 'tool_result', toolCallId: 'call_1', result: 'Found 5 results', isError: false } + ] +} +``` + +### Blockers +None + +### Next Steps +1. Notify test-engineer to write tests for MessageParts, ToolInvocation, ToolResult +2. Notify visual-qa to verify tool status animations and color states +3. Backend integration: Update chat agent to return ContentBlock[] format +4. Consider adding: + - Collapsible tool invocations for long input/output + - Copy button for tool results + - Syntax highlighting for JSON in tool inputs/results + - Retry button for failed tool calls diff --git a/.env.example b/.env.example index d33f70f..9835cdc 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,9 @@ # ============================================================================= # YG App Node - Environment Variables # ============================================================================= +# Copy this file to .env and fill in your values: +# cp .env.example .env +# ============================================================================= # ----------------------------------------------------------------------------- # Server Configuration @@ -10,30 +13,62 @@ PORT=4000 VERSION=1.0.0 # ----------------------------------------------------------------------------- -# Database (Docker Compose uses port 5433 to avoid conflicts) +# PostgreSQL 17 + pgvector (Docker Compose port 5434) # ----------------------------------------------------------------------------- -DATABASE_URL=postgresql://postgres:postgres@localhost:5433/yg_app_node +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=yg_app_node +DATABASE_URL=postgresql://postgres:postgres@localhost:5434/yg_app_node # ----------------------------------------------------------------------------- -# Redis (Docker Compose uses port 6380 to avoid conflicts) +# Redis 7 (Docker Compose port 6381) # ----------------------------------------------------------------------------- -REDIS_URL=redis://localhost:6380 +REDIS_PASSWORD=redis_password +REDIS_URL=redis://:redis_password@localhost:6381 # ----------------------------------------------------------------------------- -# AI/LLM API Keys +# AI/LLM API Keys (Required for chat/RAG) # ----------------------------------------------------------------------------- +# Get from: https://platform.openai.com/api-keys OPENAI_API_KEY= + +# Get from: https://console.anthropic.com/settings/keys ANTHROPIC_API_KEY= # ----------------------------------------------------------------------------- -# Observability (Langfuse - runs on port 3001) +# Langfuse v3 Observability (Docker Compose port 3002) # ----------------------------------------------------------------------------- +# After starting Langfuse, create a project at http://localhost:3002 +# Then get your keys from Project Settings > API Keys LANGFUSE_PUBLIC_KEY= LANGFUSE_SECRET_KEY= -LANGFUSE_HOST=http://localhost:3001 +LANGFUSE_HOST=http://localhost:3002 + +# Langfuse Web UI Login (for local development) +LANGFUSE_UI_EMAIL=admin@localhost.dev +LANGFUSE_UI_PASSWORD=LangfuseAdmin123! + +# OpenTelemetry configuration for @langfuse/langchain v4.x +# The CallbackHandler uses OTel internally to send traces +# Generate the auth string: echo -n "PUBLIC_KEY:SECRET_KEY" | base64 +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3002/api/public/otel +OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic YOUR_BASE64_ENCODED_PUBLIC_KEY_COLON_SECRET_KEY + +# Langfuse Infrastructure (used by docker-compose) +CLICKHOUSE_USER=langfuse +CLICKHOUSE_PASSWORD=langfuse_clickhouse_password +MINIO_ROOT_USER=minio_admin +MINIO_ROOT_PASSWORD=minio_password_change_me +LANGFUSE_NEXTAUTH_SECRET=my-nextauth-secret-change-in-production-32chars +LANGFUSE_SALT=my-salt-change-in-production +LANGFUSE_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 +LANGFUSE_WORKER_PASSWORD=worker_password_change_me # ----------------------------------------------------------------------------- # Security # ----------------------------------------------------------------------------- JWT_SECRET=change-this-in-production-please-use-at-least-32-chars CORS_ORIGINS=http://localhost:4173,http://localhost:4000 +# Trust proxy headers (X-Forwarded-For, X-Real-IP) - only enable behind reverse proxy +# IMPORTANT: Do NOT enable unless running behind nginx, cloudflare, or similar +TRUST_PROXY=false diff --git a/.gitignore b/.gitignore index 1d2d796..6e8e541 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,12 @@ coverage/ .nyc_output/ playwright-report/ test-results/ +.playwright-mcp/ + +# ----------------------------------------------------------------------------- +# Screenshots & Images (generated artifacts) +# ----------------------------------------------------------------------------- +*.png # ----------------------------------------------------------------------------- # Docker diff --git a/docker-compose.yml b/docker-compose.yml index bd37259..5e04d4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,55 +1,241 @@ -version: "3.8" +# ============================================================================= +# YG App Node - Docker Compose (Updated for Langfuse v3 + PostgreSQL 17) +# ============================================================================= +# +# Services: +# - postgres: PostgreSQL 17 + pgvector (app database) +# - redis: Redis 7 (app caching + Langfuse queue) +# - clickhouse: ClickHouse (Langfuse v3 analytics) +# - minio: MinIO S3 (Langfuse blob storage) +# - langfuse-web: Langfuse web UI +# - langfuse-worker: Langfuse background worker +# +# Ports (unique - won't conflict with skillforge/other projects): +# - 5434: PostgreSQL +# - 6381: Redis +# - 3002: Langfuse UI +# - 9011: MinIO Console +# - 9010: MinIO S3 API +# +# Usage: +# docker compose up -d +# docker compose logs -f langfuse-web +# ============================================================================= services: + # --------------------------------------------------------------------------- + # PostgreSQL 17 + pgvector (Application Database + Langfuse metadata) + # --------------------------------------------------------------------------- postgres: - image: pgvector/pgvector:pg16 + image: pgvector/pgvector:pg17 container_name: yg-app-node-postgres restart: unless-stopped environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: yg_app_node + POSTGRES_USER: ${POSTGRES_USER:-postgres} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} + POSTGRES_DB: ${POSTGRES_DB:-yg_app_node} ports: - - "5433:5432" # Different port to avoid conflicts + - "5434:5432" volumes: - postgres_data:/var/lib/postgresql/data + - ./docker/init-langfuse-db.sql:/docker-entrypoint-initdb.d/init-langfuse-db.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 + networks: + - yg-app-network + # --------------------------------------------------------------------------- + # Redis 7 (Application Cache + Langfuse Queue) + # --------------------------------------------------------------------------- redis: image: redis:7-alpine container_name: yg-app-node-redis restart: unless-stopped + command: redis-server --requirepass ${REDIS_PASSWORD:-redis_password} ports: - - "6380:6379" # Different port to avoid conflicts + - "6381:6379" volumes: - redis_data:/data healthcheck: - test: ["CMD", "redis-cli", "ping"] + test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-redis_password}", "ping"] interval: 10s timeout: 5s retries: 5 + networks: + - yg-app-network - langfuse: - image: langfuse/langfuse:latest - container_name: yg-app-node-langfuse + # --------------------------------------------------------------------------- + # ClickHouse (Langfuse v3 Analytics Storage) + # --------------------------------------------------------------------------- + clickhouse: + image: clickhouse/clickhouse-server:24.3 + container_name: yg-app-node-clickhouse restart: unless-stopped + user: "101:101" environment: - DATABASE_URL: postgresql://postgres:postgres@postgres:5432/langfuse - NEXTAUTH_URL: http://localhost:3001 - NEXTAUTH_SECRET: my-langfuse-secret-change-in-production - SALT: my-langfuse-salt-change-in-production + CLICKHOUSE_DB: langfuse + CLICKHOUSE_USER: ${CLICKHOUSE_USER:-langfuse} + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-langfuse_clickhouse_password} + ports: + - "8125:8123" # HTTP interface + - "9012:9000" # Native interface + volumes: + - clickhouse_data:/var/lib/clickhouse + - clickhouse_logs:/var/log/clickhouse-server + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:8123/ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - yg-app-network + + # --------------------------------------------------------------------------- + # MinIO S3 (Langfuse Blob Storage) + # --------------------------------------------------------------------------- + minio: + image: minio/minio:latest + container_name: yg-app-node-minio + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio_admin} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minio_password_change_me} + ports: + - "9010:9000" # S3 API + - "9011:9001" # MinIO Console + volumes: + - minio_data:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - yg-app-network + + # --------------------------------------------------------------------------- + # Langfuse Web (UI + API) + # --------------------------------------------------------------------------- + langfuse-web: + image: langfuse/langfuse:3 + container_name: yg-app-node-langfuse-web + restart: unless-stopped + ports: + - "3002:3000" + environment: + # Database + DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/langfuse + + # ClickHouse + CLICKHOUSE_URL: http://clickhouse:8123 + CLICKHOUSE_MIGRATION_URL: clickhouse://${CLICKHOUSE_USER:-langfuse}:${CLICKHOUSE_PASSWORD:-langfuse_clickhouse_password}@clickhouse:9000/langfuse + CLICKHOUSE_USER: ${CLICKHOUSE_USER:-langfuse} + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-langfuse_clickhouse_password} + CLICKHOUSE_CLUSTER_ENABLED: "false" + + # Redis + REDIS_CONNECTION_STRING: redis://:${REDIS_PASSWORD:-redis_password}@redis:6379 + + # S3/MinIO + LANGFUSE_S3_EVENT_UPLOAD_ENABLED: "true" + LANGFUSE_S3_EVENT_UPLOAD_BUCKET: langfuse + LANGFUSE_S3_EVENT_UPLOAD_REGION: us-east-1 + LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${MINIO_ROOT_USER:-minio_admin} + LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:-minio_password_change_me} + LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: http://minio:9000 + LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: "true" + + # Auth + NEXTAUTH_URL: http://localhost:3002 + NEXTAUTH_SECRET: ${LANGFUSE_NEXTAUTH_SECRET:-my-nextauth-secret-change-in-production-32chars} + SALT: ${LANGFUSE_SALT:-my-salt-change-in-production} + ENCRYPTION_KEY: ${LANGFUSE_ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000} + + # Telemetry TELEMETRY_ENABLED: "false" NEXT_PUBLIC_SIGN_UP_DISABLED: "false" - ports: - - "3001:3000" # Different port for Langfuse UI + + # Worker coordination + LANGFUSE_WORKER_HOST: http://langfuse-worker:3030 + LANGFUSE_WORKER_PASSWORD: ${LANGFUSE_WORKER_PASSWORD:-worker_password_change_me} depends_on: postgres: condition: service_healthy + redis: + condition: service_healthy + clickhouse: + condition: service_healthy + minio: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "wget --spider -q http://$(hostname -i):3000/api/public/health || exit 1"] + interval: 15s + timeout: 10s + retries: 5 + start_period: 60s + networks: + - yg-app-network + + # --------------------------------------------------------------------------- + # Langfuse Worker (Background Processing) + # --------------------------------------------------------------------------- + langfuse-worker: + image: langfuse/langfuse-worker:3 + container_name: yg-app-node-langfuse-worker + restart: unless-stopped + environment: + # Database + DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/langfuse + + # ClickHouse + CLICKHOUSE_URL: http://clickhouse:8123 + CLICKHOUSE_MIGRATION_URL: clickhouse://${CLICKHOUSE_USER:-langfuse}:${CLICKHOUSE_PASSWORD:-langfuse_clickhouse_password}@clickhouse:9000/langfuse + CLICKHOUSE_USER: ${CLICKHOUSE_USER:-langfuse} + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-langfuse_clickhouse_password} + CLICKHOUSE_CLUSTER_ENABLED: "false" + + # Redis + REDIS_CONNECTION_STRING: redis://:${REDIS_PASSWORD:-redis_password}@redis:6379 + + # S3/MinIO + LANGFUSE_S3_EVENT_UPLOAD_ENABLED: "true" + LANGFUSE_S3_EVENT_UPLOAD_BUCKET: langfuse + LANGFUSE_S3_EVENT_UPLOAD_REGION: us-east-1 + LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${MINIO_ROOT_USER:-minio_admin} + LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:-minio_password_change_me} + LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: http://minio:9000 + LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: "true" + + # Auth + SALT: ${LANGFUSE_SALT:-my-salt-change-in-production} + ENCRYPTION_KEY: ${LANGFUSE_ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000} + + # Worker + LANGFUSE_WORKER_PASSWORD: ${LANGFUSE_WORKER_PASSWORD:-worker_password_change_me} + PORT: 3030 + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + clickhouse: + condition: service_healthy + minio: + condition: service_healthy + networks: + - yg-app-network volumes: postgres_data: redis_data: + clickhouse_data: + clickhouse_logs: + minio_data: + +networks: + yg-app-network: + driver: bridge diff --git a/docker/init-langfuse-db.sql b/docker/init-langfuse-db.sql new file mode 100644 index 0000000..651a937 --- /dev/null +++ b/docker/init-langfuse-db.sql @@ -0,0 +1,5 @@ +-- Create Langfuse database if it doesn't exist +-- This runs on PostgreSQL startup + +SELECT 'CREATE DATABASE langfuse' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'langfuse')\gexec diff --git a/docker/redis.conf b/docker/redis.conf new file mode 100644 index 0000000..7ab29ca --- /dev/null +++ b/docker/redis.conf @@ -0,0 +1,170 @@ +# ============================================================================= +# Redis Configuration for YG Node Starter +# ============================================================================= +# +# Production-ready Redis configuration with: +# - RDB + AOF persistence (hybrid) +# - LRU eviction for cache workloads +# - Memory limits and optimization +# - Security hardening +# +# Usage: Mount this file in docker-compose.yml +# volumes: +# - ./docker/redis.conf:/usr/local/etc/redis/redis.conf:ro +# command: redis-server /usr/local/etc/redis/redis.conf +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Network +# ----------------------------------------------------------------------------- +bind 0.0.0.0 +port 6379 +timeout 0 +tcp-keepalive 300 + +# ----------------------------------------------------------------------------- +# Security +# ----------------------------------------------------------------------------- +# Password authentication (override with REDIS_PASSWORD env var) +# requirepass redis_password + +# Disable dangerous commands in production +# rename-command FLUSHDB "" +# rename-command FLUSHALL "" +# rename-command CONFIG "" +# rename-command SHUTDOWN SHUTDOWN_SECRET_KEY + +# ----------------------------------------------------------------------------- +# Memory Management +# ----------------------------------------------------------------------------- +# Maximum memory (adjust based on available RAM) +maxmemory 2gb + +# Eviction policy for cache workloads +# - allkeys-lru: Evict least recently used keys (recommended for cache) +# - volatile-lru: Evict LRU keys with EXPIRE set +# - allkeys-lfu: Evict least frequently used (Redis 4.0+) +# - volatile-ttl: Evict keys with shortest TTL +# - noeviction: Return errors when memory full (NOT for cache) +maxmemory-policy allkeys-lru + +# Number of samples for LRU/LFU (higher = more accurate, slower) +maxmemory-samples 5 + +# ----------------------------------------------------------------------------- +# Persistence - RDB (Snapshots) +# ----------------------------------------------------------------------------- +# Save database to disk +# Format: save +save 900 1 # After 900s (15 min) if at least 1 key changed +save 300 10 # After 300s (5 min) if at least 10 keys changed +save 60 10000 # After 60s (1 min) if at least 10000 keys changed + +# Stop accepting writes if RDB save fails (protect data integrity) +stop-writes-on-bgsave-error yes + +# Compress RDB files with LZF +rdbcompression yes + +# Checksum RDB files +rdbchecksum yes + +# RDB filename +dbfilename dump.rdb + +# Directory for RDB/AOF files +dir /data + +# ----------------------------------------------------------------------------- +# Persistence - AOF (Append-Only File) +# ----------------------------------------------------------------------------- +# Enable AOF persistence +appendonly yes + +# AOF filename +appendfilename "appendonly.aof" + +# Fsync strategy: +# - always: Fsync after every write (slowest, safest) +# - everysec: Fsync once per second (balanced, recommended) +# - no: Let OS decide when to fsync (fastest, least safe) +appendfsync everysec + +# Don't fsync during BGSAVE/BGREWRITEAOF (prevents latency spikes) +no-appendfsync-on-rewrite no + +# Automatic AOF rewrite (compact the log) +auto-aof-rewrite-percentage 100 # Rewrite when AOF is 100% larger than last rewrite +auto-aof-rewrite-min-size 64mb # Don't rewrite if AOF < 64MB + +# Load truncated AOF on startup (recover from crashes) +aof-load-truncated yes + +# Use RDB-AOF mixed format for faster loading +aof-use-rdb-preamble yes + +# ----------------------------------------------------------------------------- +# Replication (for future read replicas) +# ----------------------------------------------------------------------------- +# Replica serves stale data if master is down +replica-serve-stale-data yes + +# Replica is read-only +replica-read-only yes + +# Disable TCP_NODELAY for replication (save bandwidth) +repl-disable-tcp-nodelay no + +# ----------------------------------------------------------------------------- +# Performance Tuning +# ----------------------------------------------------------------------------- +# Disable slow log (or set threshold in microseconds) +slowlog-log-slower-than 10000 # Log queries slower than 10ms +slowlog-max-len 128 # Keep last 128 slow queries + +# Max number of client connections +maxclients 10000 + +# Hash/List/Set encoding thresholds (memory optimization) +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 +list-max-ziplist-size -2 +set-max-intset-entries 512 +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +# HyperLogLog sparse representation (saves memory) +hll-sparse-max-bytes 3000 + +# ----------------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------------- +# Log level: debug, verbose, notice, warning +loglevel notice + +# Log to stdout (captured by Docker) +logfile "" + +# ----------------------------------------------------------------------------- +# Advanced +# ----------------------------------------------------------------------------- +# Disable active defragmentation (enable if you see fragmentation issues) +# activedefrag yes +# active-defrag-ignore-bytes 100mb +# active-defrag-threshold-lower 10 +# active-defrag-threshold-upper 100 + +# Lazy freeing (async memory reclamation) +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del no +replica-lazy-flush no + +# I/O threads (Redis 6.0+, use for high concurrency) +# io-threads 4 +# io-threads-do-reads yes + +# ----------------------------------------------------------------------------- +# Modules (if needed) +# ----------------------------------------------------------------------------- +# loadmodule /path/to/module.so diff --git a/docs/ui/chat-components.md b/docs/ui/chat-components.md new file mode 100644 index 0000000..2c0ddcb --- /dev/null +++ b/docs/ui/chat-components.md @@ -0,0 +1,479 @@ +# Chat UI Component Specifications + +**Design System**: Tailwind CSS 4 + oklch color space +**File**: `/packages/frontend/src/index.css` +**Stack**: React 19 + LangGraph + Langfuse + +--- + +## Component: Chat Message Bubble + +### User Message +```tsx +
+

+ Your message here +

+
+``` + +### Assistant Message +```tsx +
+

+ AI response here +

+
+``` + +### System Message +```tsx +
+ System notification +
+``` + +--- + +## Component: Tool Invocation + +### Tool Call (LangGraph) +```tsx +
+ {/* Tool Header */} +
+ + {/* Icon */} + + + Calling tool: search_documents + +
+ + {/* Tool Arguments */} +
+    {JSON.stringify(args, null, 2)}
+  
+
+``` + +### Tool Result (Success) +```tsx +
+
+ + {/* Checkmark icon */} + + + Tool executed successfully + +
+ +
+ Found 5 documents +
+
+``` + +### Tool Result (Error) +```tsx +
+
+ + {/* Error icon */} + + + Tool execution failed + +
+ +
+ {error.message} +
+
+``` + +--- + +## Component: Streaming Indicator + +### Typing Cursor +```tsx + +``` + +### Typing Dots +```tsx +
+ + + +
+``` + +--- + +## Component: Code Block + +### Multi-line Code (Langfuse Trace) +```tsx +
+ {/* Header */} +
+ + python + + +
+ + {/* Code Content */} +
{codeContent}
+
+``` + +### Inline Code +```tsx + + variable_name + +``` + +--- + +## Layout: Chat Container + +### Main Chat View +```tsx +
+ {/* Messages Container */} +
+
+ {messages.map(msg => )} +
+
+ + {/* Input Area */} +
+
+ {/* Input component */} +
+
+
+``` + +--- + +## Component: Message Input + +```tsx +
+