-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add AI Council Proxy UI and Docker configuration #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
358a375
f7085ab
d9155ff
6fbf08e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # Build stage | ||
| FROM node:18-alpine AS builder | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # Copy package files | ||
| COPY package*.json ./ | ||
| COPY tsconfig.json ./ | ||
|
|
||
| # Install dependencies | ||
| RUN npm ci | ||
|
|
||
| # Copy source code | ||
| COPY src ./src | ||
|
|
||
| # Build TypeScript | ||
| RUN npm run build | ||
|
|
||
| # Production stage | ||
| FROM node:18-alpine | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # Install curl for healthcheck | ||
| RUN apk add --no-cache curl | ||
|
|
||
| # Copy package files | ||
| COPY package*.json ./ | ||
|
|
||
| # Install production dependencies only | ||
| RUN npm ci --only=production | ||
|
|
||
| # Copy built application from builder | ||
| COPY --from=builder /app/dist ./dist | ||
|
|
||
| # Copy database schema for reference | ||
| COPY database ./database | ||
|
|
||
| # Create non-root user | ||
| RUN addgroup -g 1001 -S nodejs && \ | ||
| adduser -S nodejs -u 1001 | ||
|
|
||
| USER nodejs | ||
|
|
||
| EXPOSE 8080 | ||
|
|
||
| # Health check | ||
| HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ | ||
| CMD curl -f http://localhost:8080/ || exit 1 | ||
|
|
||
| CMD ["node", "dist/ui-server.js"] | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,6 +99,52 @@ services: | |
| retries: 3 | ||
| start_period: 40s | ||
|
|
||
| # AI Council Proxy UI | ||
| ui: | ||
| build: | ||
| context: . | ||
| dockerfile: Dockerfile.ui | ||
| container_name: council-ui | ||
| environment: | ||
| NODE_ENV: ${NODE_ENV:-production} | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| # Database | ||
| DATABASE_HOST: postgres | ||
| DATABASE_PORT: 5432 | ||
| DATABASE_NAME: ${DATABASE_NAME:-ai_council_proxy} | ||
| DATABASE_USER: ${DATABASE_USER:-postgres} | ||
| DATABASE_PASSWORD: ${DATABASE_PASSWORD:-postgres} | ||
| DATABASE_URL: postgresql://${DATABASE_USER:-postgres}:${DATABASE_PASSWORD:-postgres}@postgres:5432/${DATABASE_NAME:-ai_council_proxy} | ||
|
|
||
| # Redis | ||
| REDIS_HOST: redis | ||
| REDIS_PORT: 6379 | ||
| REDIS_URL: redis://redis:6379 | ||
|
|
||
| # UI Configuration | ||
| UI_PORT: ${UI_PORT:-8080} | ||
| # API_BASE_URL defaults to http://localhost:3000 in code (for browser access) | ||
| # Override if needed for different deployment scenarios | ||
| API_BASE_URL: ${API_BASE_URL} | ||
| ports: | ||
| - "${UI_PORT:-8080}:8080" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Port configuration mismatch in UI serviceThe Additional Locations (1) |
||
| depends_on: | ||
| postgres: | ||
| condition: service_healthy | ||
| redis: | ||
| condition: service_healthy | ||
| api: | ||
| condition: service_healthy | ||
| restart: unless-stopped | ||
| networks: | ||
| - council-network | ||
| healthcheck: | ||
| test: ["CMD", "curl", "-f", "http://localhost:8080/"] | ||
| interval: 30s | ||
| timeout: 10s | ||
| retries: 3 | ||
| start_period: 40s | ||
|
|
||
| volumes: | ||
| postgres_data: | ||
| driver: local | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| /** | ||
| * UI Server Entry Point | ||
| * Starts the User Interface web server | ||
| */ | ||
|
|
||
| import { Pool } from 'pg'; | ||
| import { createClient } from 'redis'; | ||
| import { UserInterface } from './ui/interface'; | ||
| import { ConfigurationManager } from './config/manager'; | ||
|
|
||
| async function startUIServer() { | ||
| console.log('Starting AI Council Proxy UI...'); | ||
|
|
||
| // Initialize database connection | ||
| const pool = new Pool({ | ||
| host: process.env.DATABASE_HOST || 'localhost', | ||
| port: parseInt(process.env.DATABASE_PORT || '5432'), | ||
| database: process.env.DATABASE_NAME || 'ai_council_proxy', | ||
| user: process.env.DATABASE_USER || 'postgres', | ||
| password: process.env.DATABASE_PASSWORD || 'postgres' | ||
| }); | ||
|
|
||
| console.log('Connecting to PostgreSQL...'); | ||
| try { | ||
| await pool.query('SELECT 1'); | ||
| console.log('✓ PostgreSQL connected'); | ||
| } catch (error) { | ||
| console.error('Failed to connect to PostgreSQL:', error); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // Initialize Redis client | ||
| const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379'; | ||
| console.log('Connecting to Redis...'); | ||
| const redis = createClient({ url: redisUrl }); | ||
|
|
||
| redis.on('error', (err) => { | ||
| console.error('Redis error:', err); | ||
| }); | ||
|
|
||
| try { | ||
| await redis.connect(); | ||
| console.log('✓ Redis connected'); | ||
| } catch (error) { | ||
| console.error('Failed to connect to Redis:', error); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // Initialize ConfigurationManager | ||
| const configManager = new ConfigurationManager(pool, redis as any); | ||
|
|
||
| // Get API base URL from environment or default to localhost (for browser access) | ||
| // Note: In Docker, this should be set via API_BASE_URL env var to http://localhost:3000 | ||
| // so the browser (running on host) can access the API | ||
| const apiBaseUrl = process.env.API_BASE_URL || 'http://localhost:3000'; | ||
| const uiPort = parseInt(process.env.UI_PORT || '8080'); | ||
|
|
||
| // Start User Interface | ||
| const ui = new UserInterface(configManager, apiBaseUrl); | ||
|
|
||
| console.log(`Starting UI server on port ${uiPort}...`); | ||
| await ui.start(uiPort); | ||
| console.log(`✓ UI server running on http://0.0.0.0:${uiPort}`); | ||
| console.log(`✓ API Gateway URL: ${apiBaseUrl}`); | ||
|
|
||
| // Handle graceful shutdown | ||
| const shutdown = async (signal: string, isError: boolean = false) => { | ||
| if (isError) { | ||
| console.error(`\n${signal} received. Shutting down due to error...`); | ||
| } else { | ||
| console.log(`\n${signal} received. Shutting down gracefully...`); | ||
| } | ||
| try { | ||
| await ui.stop(); | ||
| console.log('✓ UI server stopped'); | ||
| await redis.disconnect(); | ||
| console.log('✓ Redis disconnected'); | ||
| await pool.end(); | ||
| console.log('✓ PostgreSQL disconnected'); | ||
| console.log('Shutdown complete'); | ||
| process.exit(isError ? 1 : 0); | ||
| } catch (error) { | ||
| console.error('Error during shutdown:', error); | ||
| process.exit(1); | ||
| } | ||
| }; | ||
|
alias8818 marked this conversation as resolved.
|
||
|
|
||
| process.on('SIGTERM', () => void shutdown('SIGTERM', false)); | ||
| process.on('SIGINT', () => void shutdown('SIGINT', false)); | ||
|
|
||
| // Handle uncaught errors | ||
| process.on('uncaughtException', (error) => { | ||
| console.error('Uncaught exception:', error); | ||
| void shutdown('UNCAUGHT_EXCEPTION', true); | ||
| }); | ||
|
|
||
| process.on('unhandledRejection', (reason, promise) => { | ||
| console.error('Unhandled rejection at:', promise, 'reason:', reason); | ||
| void shutdown('UNHANDLED_REJECTION', true); | ||
| }); | ||
| } | ||
|
|
||
| // Start the UI server | ||
| startUIServer().catch((error) => { | ||
| console.error('Failed to start UI server:', error); | ||
| process.exit(1); | ||
| }); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.