diff --git a/demo/package.json b/demo/package.json index 91429aa..411e695 100644 --- a/demo/package.json +++ b/demo/package.json @@ -12,7 +12,7 @@ "start": "node run-demo.js" }, "dependencies": { - "@just-every/demo-ui": "file:../../demo-ui", + "@just-every/demo-ui": "^0.1.1", "d3": "^7.9.0", "dompurify": "^3.2.3", "dotenv": "^17.0.1", diff --git a/demo/run-demo.js b/demo/run-demo.js index 3eddb22..91997a0 100755 --- a/demo/run-demo.js +++ b/demo/run-demo.js @@ -3,6 +3,7 @@ import { spawn, exec } from 'child_process' import { fileURLToPath } from 'url' import { dirname, join } from 'path' import { promisify } from 'util' +import dotenv from 'dotenv' const __dirname = dirname(fileURLToPath(import.meta.url)) const execAsync = promisify(exec) @@ -43,49 +44,60 @@ async function killExistingProcesses(ports) { async function startDemo() { console.log('šŸš€ Starting Task Demo...\n') + dotenv.config({ path: join(__dirname, '..', '.env') }) + const hasKeys = Boolean( + process.env.OPENAI_API_KEY || + process.env.ANTHROPIC_API_KEY || + process.env.GOOGLE_API_KEY || + process.env.XAI_API_KEY || + process.env.DEEPSEEK_API_KEY || + process.env.OPENROUTER_API_KEY || + process.env.BRAVE_API_KEY + ) + // Kill any existing processes on ports 3020 (server) and 3021 (vite) await killExistingProcesses([3020, 3021]) - // Start the WebSocket server - console.log('šŸ“” Starting WebSocket server on port 3020...') - const server = spawn('node', ['server.js'], { + let server + if (hasKeys) { + console.log('šŸ“” Starting WebSocket server on port 3020...') + server = spawn('node', ['server.js'], { + cwd: __dirname, + stdio: 'inherit', + shell: true, + env: { ...process.env, VITE_USE_SERVER: 'true' } + }) + } + + console.log('\n🌐 Starting Vite dev server on port 3021...') + const vite = spawn('npm', ['run', 'dev'], { cwd: __dirname, stdio: 'inherit', - shell: true + shell: true, + env: { ...process.env, VITE_USE_SERVER: hasKeys ? 'true' : 'false' } }) - // Wait a bit for server to start - setTimeout(() => { - console.log('\n🌐 Starting Vite dev server on port 3021...') - const vite = spawn('npm', ['run', 'dev'], { - cwd: __dirname, - stdio: 'inherit', - shell: true - }) + console.log('\nāœ… Task demo is running!') + if (hasKeys) console.log(` • WebSocket server: ws://localhost:3020`) + console.log(` • Web interface: http://localhost:3021`) + console.log('\nPress Ctrl+C to stop all servers\n') - console.log('\nāœ… Task demo is running!') - console.log(` • WebSocket server: ws://localhost:3020`) - console.log(` • Web interface: http://localhost:3021`) - console.log('\nPress Ctrl+C to stop all servers\n') + const cleanup = () => { + console.log('\nšŸ›‘ Shutting down demo...') + if (server) server.kill() + vite.kill() + process.exit(0) + } - // Handle cleanup - const cleanup = () => { - console.log('\nšŸ›‘ Shutting down demo...') - server.kill() - vite.kill() - process.exit(0) - } + process.on('SIGINT', cleanup) + process.on('SIGTERM', cleanup) - process.on('SIGINT', cleanup) - process.on('SIGTERM', cleanup) - - vite.on('exit', cleanup) - server.on('exit', cleanup) - }, 1000) + vite.on('exit', cleanup) + if (server) server.on('exit', cleanup) } // Start the demo startDemo().catch(err => { console.error('Failed to start demo:', err) process.exit(1) -}) \ No newline at end of file +}) diff --git a/demo/server.js b/demo/server.js index c9f7ff3..45e6e11 100644 --- a/demo/server.js +++ b/demo/server.js @@ -16,8 +16,8 @@ import { WebSocketServer } from 'ws'; import { createServer } from 'http'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; -import { runTask } from '../dist/index.js'; -import { Agent, ensembleRequest, createToolFunction, setEnsembleLogger } from '@just-every/ensemble'; +import { setEnsembleLogger } from '@just-every/ensemble'; +import { startDemoTask } from './task-core.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -97,174 +97,6 @@ class TaskDemoLogger { const logger = new TaskDemoLogger(); setEnsembleLogger(logger); -// Generate mock response using mini model -async function generateMockResponse(toolName, args, context) { - try { - const mockAgent = new Agent({ - name: 'MockResponseGenerator', - modelClass: 'mini', - instructions: `You are generating realistic mock data for a tool called "${toolName}". - Generate a brief, realistic response based on the provided arguments. - Keep responses concise but informative. Use realistic data formats.` - }); - - const prompt = `Generate a mock response for ${toolName} with arguments: ${JSON.stringify(args, null, 2)} - Context: ${context} - - Provide a realistic response that would be returned by this tool.`; - - const messages = [{ - type: 'message', - role: 'user', - content: prompt, - id: `mock_${Date.now()}` - }]; - - let response = ''; - for await (const event of ensembleRequest(messages, mockAgent)) { - if (event.type === 'message_delta' && event.content) { - response += event.content; - } - } - - return response || `Mock result for ${toolName}`; - } catch (error) { - // Fallback to simple mock response - return `Mock ${toolName} result: Successfully processed ${JSON.stringify(args)}`; - } -} - -// Create demo tools -function createDemoTools() { - return [ - // Web/Research tools - createToolFunction( - async (query, options = {}) => { - const results = options.max_results || 5; - return await generateMockResponse('web_search', { query, results }, - 'Web search tool that returns relevant search results with titles, URLs, and snippets'); - }, - 'Search the web for information', - { - query: { type: 'string', description: 'Search query' }, - options: { - type: 'object', - properties: { - max_results: { type: 'number', description: 'Maximum number of results (default: 5)' } - } - } - }, - undefined, - 'web_search' - ), - - createToolFunction( - async (url) => { - return await generateMockResponse('fetch_page', { url }, - 'Fetches and extracts content from a web page, returning the main text content'); - }, - 'Fetch and extract content from a web page', - { - url: { type: 'string', description: 'URL to fetch' } - }, - undefined, - 'fetch_page' - ), - - // File/Code tools - createToolFunction( - async (path) => { - return await generateMockResponse('read_file', { path }, - 'Reads a file from the filesystem and returns its contents'); - }, - 'Read a file from the filesystem', - { - path: { type: 'string', description: 'File path to read' } - }, - undefined, - 'read_file' - ), - - createToolFunction( - async (code, language) => { - return await generateMockResponse('analyze_code', { code, language }, - 'Analyzes code for issues, patterns, complexity, and suggestions'); - }, - 'Analyze code for quality, issues, and improvements', - { - code: { type: 'string', description: 'Code to analyze' }, - language: { type: 'string', description: 'Programming language' } - }, - undefined, - 'analyze_code' - ), - - // Travel/Planning tools - createToolFunction( - async (location, date) => { - return await generateMockResponse('check_weather', { location, date }, - 'Gets weather forecast for a location, including temperature, conditions, precipitation'); - }, - 'Check weather forecast for a location', - { - location: { type: 'string', description: 'City or location name' }, - date: { type: 'string', description: 'Date (YYYY-MM-DD) or "today"' } - }, - undefined, - 'check_weather' - ), - - createToolFunction( - async (from, to, date) => { - return await generateMockResponse('search_flights', { from, to, date }, - 'Searches for available flights between cities with prices and times'); - }, - 'Search for flights between cities', - { - from: { type: 'string', description: 'Departure city' }, - to: { type: 'string', description: 'Arrival city' }, - date: { type: 'string', description: 'Travel date (YYYY-MM-DD)' } - }, - undefined, - 'search_flights' - ), - - createToolFunction( - async (city, checkin, checkout) => { - return await generateMockResponse('search_hotels', { city, checkin, checkout }, - 'Searches for available hotels with ratings, prices, and amenities'); - }, - 'Search for hotels in a city', - { - city: { type: 'string', description: 'City name' }, - checkin: { type: 'string', description: 'Check-in date (YYYY-MM-DD)' }, - checkout: { type: 'string', description: 'Check-out date (YYYY-MM-DD)' } - }, - undefined, - 'search_hotels' - ), - - // Math/Calculation tools - createToolFunction( - async (expression) => { - try { - // For simple expressions, actually calculate - const result = eval(expression.replace(/[^0-9+\-*/().\s]/g, '')); - return `Result: ${result}`; - } catch { - return await generateMockResponse('calculate', { expression }, - 'Evaluates mathematical expressions and returns the result'); - } - }, - 'Calculate mathematical expressions', - { - expression: { type: 'string', description: 'Mathematical expression to evaluate' } - }, - undefined, - 'calculate' - ) - ]; -} const app = express(); const server = createServer(app); @@ -293,278 +125,23 @@ wss.on('connection', (ws, req) => { const sessionId = Math.random().toString(36).substr(2, 9); console.log(`šŸ”— New Task demo connection: ${sessionId}`); - let taskGenerator = null; - const threads = new Map(); - const metamemoryThreads = new Map(); - let messageCount = 0; - let thinkingCount = 0; - let toolCallCount = 0; + let session = null; // Set this WebSocket as active for the logger logger.setActiveWebSocket(ws); - const runTaskWithWebSocket = async () => { - try { - ws.send(JSON.stringify({ type: 'status', status: 'running' })); - - // Create agent configuration with demo tools - const demoTools = createDemoTools(); - const agent = new Agent({ - name: 'TaskDemoAgent', - modelClass: 'standard', - instructions: `You are a helpful AI assistant demonstrating the Task framework. You have access to various tools for web search, file operations, data analysis, planning, and more. Use these tools as needed to complete the task thoroughly. Be detailed in your work and use multiple tools when appropriate. When you have fully completed the task, use the task_complete tool with a comprehensive summary.`, - tools: demoTools - }); - - // Track main thread - const mainThreadId = 'main-thread'; - threads.set(mainThreadId, { - id: mainThreadId, - name: 'Main Conversation', - type: 'main', - messages: [] - }); - - // Send initial thread info - ws.send(JSON.stringify({ - type: 'thread', - id: mainThreadId, - name: 'Main Conversation', - threadType: 'main', - messages: [] - })); - - // Add user message - messageCount++; - const userMessage = { - type: 'message', - id: `msg-${messageCount}`, - role: 'user', - content: prompt, - threadId: mainThreadId, - timestamp: Date.now() - }; - - threads.get(mainThreadId).messages.push(userMessage.id); - ws.send(JSON.stringify(userMessage)); - - let lastMessageRole = null; - let lastMessageContent = []; - let currentToolCall = null; - let messageStartTime = Date.now(); - - // Run the task with real AI - taskGenerator = runTask(agent, prompt, { - metamemoryEnabled: true, - processInterval: 2, - windowSize: 10, - metaFrequency: 10 - }); - - for await (const event of taskGenerator) { - // Handle different event types from ensemble - switch (event.type) { - case 'agent_start': - console.log('Agent started:', event.agent?.name); - break; - - case 'message_start': - lastMessageRole = event.message?.role || 'assistant'; - lastMessageContent = []; - messageStartTime = Date.now(); - break; - - case 'message_delta': - if (event.content) { - lastMessageContent.push(event.content); - } - break; - - case 'message_done': - const fullContent = lastMessageContent.join(''); - if (fullContent) { - messageCount++; - const messageId = `msg-${messageCount}`; - - // Add to thread - threads.get(mainThreadId).messages.push(messageId); - - const message = { - type: 'message', - id: messageId, - role: lastMessageRole, - content: fullContent, - threadId: mainThreadId, - timestamp: Date.now() - }; - - ws.send(JSON.stringify(message)); - } - break; - - case 'thinking': - if (event.content) { - thinkingCount++; - ws.send(JSON.stringify({ - type: 'thinking', - content: event.content, - threadId: mainThreadId, - thinkingType: 'reasoning' - })); - } - break; - - case 'tool_start': - if (event.tool_call) { - currentToolCall = { - id: event.tool_call.id || `tool-${++toolCallCount}`, - name: event.tool_call.function?.name, - arguments: event.tool_call.function?.arguments - }; - - ws.send(JSON.stringify({ - type: 'tool_call', - ...currentToolCall, - threadId: mainThreadId - })); - } - break; - - case 'tool_done': - if (currentToolCall && event.result) { - ws.send(JSON.stringify({ - type: 'tool_result', - toolId: currentToolCall.id, - result: event.result.output, - duration: Date.now() - messageStartTime - })); - } - - // Check if task is complete - if (event.tool_call?.function?.name === 'task_complete') { - ws.send(JSON.stringify({ - type: 'status', - status: 'completed', - result: event.result?.output - })); - } else if (event.tool_call?.function?.name === 'task_fatal_error') { - ws.send(JSON.stringify({ - type: 'error', - message: event.result?.output - })); - ws.send(JSON.stringify({ - type: 'status', - status: 'error' - })); - } - break; - - case 'metamemory_processing': - if (event.data) { - const threadId = `metamemory-${event.data.threadClass || 'unknown'}`; - if (!metamemoryThreads.has(threadId)) { - metamemoryThreads.set(threadId, { - id: threadId, - name: event.data.threadClass || 'Metamemory', - size: 0, - overlap: new Set(), - messages: [] - }); - } - - const thread = metamemoryThreads.get(threadId); - thread.size++; - - ws.send(JSON.stringify({ - type: 'thread', - id: threadId, - name: thread.name, - threadType: 'metamemory', - metadata: { - triggerReason: event.data.trigger - } - })); - } - break; - - case 'metacognition_trigger': - if (event.data) { - ws.send(JSON.stringify({ - type: 'thinking', - content: event.data.reasoning, - threadId: 'metacognition', - thinkingType: 'reflection' - })); - - ws.send(JSON.stringify({ - type: 'thread', - id: 'metacognition', - name: 'Metacognition', - threadType: 'metacognition', - metadata: { - triggerReason: event.data.trigger - } - })); - } - break; - - // LLM requests are now handled by the EnsembleLogger - - case 'error': - ws.send(JSON.stringify({ - type: 'error', - message: event.error || 'Unknown error' - })); - break; - } - } - - // Send final analysis - const metaAnalysis = { - threads: Array.from(metamemoryThreads.values()), - metacognition: [], - summary: { - totalThreads: threads.size + metamemoryThreads.size, - totalMessages: messageCount, - avgOverlap: metamemoryThreads.size > 0 ? 0.2 : 0, - mostActiveThread: 'Main Conversation' - } - }; - - ws.send(JSON.stringify({ - type: 'meta_analysis', - analysis: metaAnalysis - })); - - // Mark as completed if not already done - ws.send(JSON.stringify({ - type: 'status', - status: 'completed' - })); - - } catch (error) { - console.error('Task error:', error); - ws.send(JSON.stringify({ - type: 'error', - message: error.message - })); - ws.send(JSON.stringify({ - type: 'status', - status: 'error' - })); - } - }; - - runTaskWithWebSocket(); + session = startDemoTask(prompt, (data) => { + ws.send(JSON.stringify(data)); + }); ws.on('message', (message) => { try { const data = JSON.parse(message.toString()); - if (data.type === 'stop' && taskGenerator) { - // Signal to stop the task - ws.send(JSON.stringify({ - type: 'status', - status: 'stopped' + if (data.type === 'stop' && session) { + session.abort(); + ws.send(JSON.stringify({ + type: 'status', + status: 'stopped' })); ws.close(); } @@ -576,7 +153,9 @@ wss.on('connection', (ws, req) => { ws.on('close', () => { // Cleanup if needed console.log(`šŸ”Œ Task demo connection closed: ${sessionId}`); - + + if (session) session.abort(); + // Clear the active WebSocket from logger logger.setActiveWebSocket(null); }); diff --git a/demo/src/hooks/useTaskRunner.ts b/demo/src/hooks/useTaskRunner.ts index 8d67455..656cf38 100644 --- a/demo/src/hooks/useTaskRunner.ts +++ b/demo/src/hooks/useTaskRunner.ts @@ -1,6 +1,8 @@ import { useState, useCallback, useRef } from 'react' import useWebSocket, { ReadyState } from 'react-use-websocket' import { TaskState, LLMRequest, MetaAnalysis, Thread } from '../types' +// @ts-ignore - JS module +import { startDemoTask } from '../../task-core.js' interface UseTaskRunnerProps { onStateUpdate: (state: TaskState) => void @@ -9,6 +11,7 @@ interface UseTaskRunnerProps { } export function useTaskRunner({ onStateUpdate, onLLMRequest, onMetaAnalysis }: UseTaskRunnerProps) { + const useServer = import.meta.env.VITE_USE_SERVER === 'true' const [socketUrl, setSocketUrl] = useState(null) const currentStateRef = useRef({ messages: [], @@ -18,6 +21,7 @@ export function useTaskRunner({ onStateUpdate, onLLMRequest, onMetaAnalysis }: U status: 'idle' }) const llmRequestsRef = useRef>(new Map()) + const localSessionRef = useRef(null) const { sendMessage, readyState } = useWebSocket(socketUrl, { onMessage: (event) => { @@ -152,7 +156,6 @@ export function useTaskRunner({ onStateUpdate, onLLMRequest, onMetaAnalysis }: U }, [updateState, onLLMRequest, onMetaAnalysis]) const runTask = useCallback(async (prompt: string) => { - // Clear previous state and LLM requests llmRequestsRef.current.clear() currentStateRef.current = { messages: [], @@ -163,17 +166,25 @@ export function useTaskRunner({ onStateUpdate, onLLMRequest, onMetaAnalysis }: U } onStateUpdate(currentStateRef.current) - const wsUrl = `ws://localhost:3020/ws?prompt=${encodeURIComponent(prompt)}` - setSocketUrl(wsUrl) - }, [onStateUpdate]) + if (useServer) { + const wsUrl = `ws://localhost:3020/ws?prompt=${encodeURIComponent(prompt)}` + setSocketUrl(wsUrl) + } else { + localSessionRef.current = startDemoTask(prompt, handleWebSocketMessage) + } + }, [onStateUpdate, useServer, handleWebSocketMessage]) const stopTask = useCallback(() => { - if (readyState === ReadyState.OPEN) { - sendMessage(JSON.stringify({ type: 'stop' })) + if (useServer) { + if (readyState === ReadyState.OPEN) { + sendMessage(JSON.stringify({ type: 'stop' })) + } + setSocketUrl(null) + } else if (localSessionRef.current) { + localSessionRef.current.abort() } - setSocketUrl(null) updateState({ status: 'completed' }) - }, [readyState, sendMessage, updateState]) + }, [readyState, sendMessage, updateState, useServer]) return { runTask, stopTask } -} \ No newline at end of file +} diff --git a/demo/task-core.js b/demo/task-core.js new file mode 100644 index 0000000..82fd7d3 --- /dev/null +++ b/demo/task-core.js @@ -0,0 +1,263 @@ +import { runTask } from '../dist/index.js'; +import { Agent, ensembleRequest, createToolFunction } from '@just-every/ensemble'; + +export async function generateMockResponse(toolName, args, context) { + try { + const mockAgent = new Agent({ + name: 'MockResponseGenerator', + modelClass: 'mini', + instructions: `You are generating realistic mock data for a tool called "${toolName}". + Generate a brief, realistic response based on the provided arguments. + Keep responses concise but informative. Use realistic data formats.` + }); + + const prompt = `Generate a mock response for ${toolName} with arguments: ${JSON.stringify(args, null, 2)}\n Context: ${context}\n\n Provide a realistic response that would be returned by this tool.`; + + const messages = [{ + type: 'message', + role: 'user', + content: prompt, + id: `mock_${Date.now()}` + }]; + + let response = ''; + for await (const event of ensembleRequest(messages, mockAgent)) { + if (event.type === 'message_delta' && event.content) { + response += event.content; + } + } + + return response || `Mock result for ${toolName}`; + } catch { + return `Mock ${toolName} result: Successfully processed ${JSON.stringify(args)}`; + } +} + +export function createDemoTools() { + return [ + createToolFunction( + async (query, options = {}) => { + const results = options.max_results || 5; + return await generateMockResponse('web_search', { query, results }, + 'Web search tool that returns relevant search results with titles, URLs, and snippets'); + }, + 'Search the web for information', + { + query: { type: 'string', description: 'Search query' }, + options: { + type: 'object', + properties: { + max_results: { type: 'number', description: 'Maximum number of results (default: 5)' } + } + } + }, + undefined, + 'web_search' + ), + createToolFunction( + async (url) => { + return await generateMockResponse('fetch_page', { url }, + 'Fetches and extracts content from a web page, returning the main text content'); + }, + 'Fetch and extract content from a web page', + { url: { type: 'string', description: 'URL to fetch' } }, + undefined, + 'fetch_page' + ), + createToolFunction( + async (path) => { + return await generateMockResponse('read_file', { path }, + 'Reads a file from the filesystem and returns its contents'); + }, + 'Read a file from the filesystem', + { path: { type: 'string', description: 'File path to read' } }, + undefined, + 'read_file' + ), + createToolFunction( + async (code, language) => { + return await generateMockResponse('analyze_code', { code, language }, + 'Analyzes code for issues, patterns, complexity, and suggestions'); + }, + 'Analyze code for quality, issues, and improvements', + { + code: { type: 'string', description: 'Code to analyze' }, + language: { type: 'string', description: 'Programming language' } + }, + undefined, + 'analyze_code' + ), + createToolFunction( + async (location, date) => { + return await generateMockResponse('check_weather', { location, date }, + 'Gets weather forecast for a location, including temperature, conditions, precipitation'); + }, + 'Check weather forecast for a location', + { + location: { type: 'string', description: 'City or location name' }, + date: { type: 'string', description: 'Date (YYYY-MM-DD) or "today"' } + }, + undefined, + 'check_weather' + ), + createToolFunction( + async (from, to, date) => { + return await generateMockResponse('search_flights', { from, to, date }, + 'Searches for available flights between cities with prices and times'); + }, + 'Search for flights between cities', + { + from: { type: 'string', description: 'Departure city' }, + to: { type: 'string', description: 'Arrival city' }, + date: { type: 'string', description: 'Travel date (YYYY-MM-DD)' } + }, + undefined, + 'search_flights' + ), + createToolFunction( + async (city, checkin, checkout) => { + return await generateMockResponse('search_hotels', { city, checkin, checkout }, + 'Searches for available hotels with ratings, prices, and amenities'); + }, + 'Search for hotels in a city', + { + city: { type: 'string', description: 'City name' }, + checkin: { type: 'string', description: 'Check-in date (YYYY-MM-DD)' }, + checkout: { type: 'string', description: 'Check-out date (YYYY-MM-DD)' } + }, + undefined, + 'search_hotels' + ), + createToolFunction( + async (expression) => { + try { + const result = eval(expression.replace(/[^0-9+\-*/().\s]/g, '')); + return `Result: ${result}`; + } catch { + return await generateMockResponse('calculate', { expression }, + 'Evaluates mathematical expressions and returns the result'); + } + }, + 'Calculate mathematical expressions', + { expression: { type: 'string', description: 'Mathematical expression to evaluate' } }, + undefined, + 'calculate' + ) + ]; +} + +function createDemoAgent() { + return new Agent({ + name: 'TaskDemoAgent', + modelClass: 'standard', + instructions: `You are a helpful AI assistant demonstrating the Task framework. You have access to various tools for web search, file operations, data analysis, planning, and more. Use these tools as needed to complete the task thoroughly. Be detailed in your work and use multiple tools when appropriate. When you have fully completed the task, use the task_complete tool with a comprehensive summary.`, + tools: createDemoTools() + }); +} + +export function startDemoTask(prompt, send) { + const controller = new AbortController(); + (async () => { + send({ type: 'status', status: 'running' }); + + const agent = createDemoAgent(); + const mainThreadId = 'main-thread'; + const threads = new Map(); + const metamemoryThreads = new Map(); + let messageCount = 0; + let toolCallCount = 0; + + threads.set(mainThreadId, { id: mainThreadId, name: 'Main Conversation', type: 'main', messages: [] }); + send({ type: 'thread', id: mainThreadId, name: 'Main Conversation', threadType: 'main', messages: [] }); + send({ type: 'message', id: `msg-${++messageCount}`, role: 'user', content: prompt, threadId: mainThreadId, timestamp: Date.now() }); + + let lastMessageRole = null; + let lastMessageContent = []; + let currentToolCall = null; + let messageStartTime = Date.now(); + + try { + const taskGenerator = runTask(agent, prompt, { metamemoryEnabled: true, processInterval: 2, windowSize: 10, metaFrequency: 10 }); + for await (const event of taskGenerator) { + if (controller.signal.aborted) break; + switch (event.type) { + case 'message_start': + lastMessageRole = event.message?.role || 'assistant'; + lastMessageContent = []; + messageStartTime = Date.now(); + break; + case 'message_delta': + if (event.content) lastMessageContent.push(event.content); + break; + case 'message_done': + const full = lastMessageContent.join(''); + if (full) { + const id = `msg-${++messageCount}`; + send({ type: 'message', id, role: lastMessageRole, content: full, threadId: mainThreadId, timestamp: Date.now() }); + } + break; + case 'thinking': + if (event.content) { + send({ type: 'thinking', content: event.content, threadId: mainThreadId, thinkingType: 'reasoning' }); + } + break; + case 'tool_start': + if (event.tool_call) { + currentToolCall = { id: event.tool_call.id || `tool-${++toolCallCount}`, name: event.tool_call.function?.name, arguments: event.tool_call.function?.arguments }; + send({ type: 'tool_call', ...currentToolCall, threadId: mainThreadId }); + } + break; + case 'tool_done': + if (currentToolCall && event.result) { + send({ type: 'tool_result', toolId: currentToolCall.id, result: event.result.output, duration: Date.now() - messageStartTime }); + } + if (event.tool_call?.function?.name === 'task_complete') { + send({ type: 'status', status: 'completed', result: event.result?.output }); + } else if (event.tool_call?.function?.name === 'task_fatal_error') { + send({ type: 'error', message: event.result?.output }); + send({ type: 'status', status: 'error' }); + } + break; + case 'metamemory_processing': + if (event.data) { + const tid = `metamemory-${event.data.threadClass || 'unknown'}`; + if (!metamemoryThreads.has(tid)) { + metamemoryThreads.set(tid, { id: tid, name: event.data.threadClass || 'Metamemory', size: 0, overlap: new Set(), messages: [] }); + } + const thread = metamemoryThreads.get(tid); + thread.size++; + send({ type: 'thread', id: tid, name: thread.name, threadType: 'metamemory', metadata: { triggerReason: event.data.trigger } }); + } + break; + case 'metacognition_trigger': + if (event.data) { + send({ type: 'thinking', content: event.data.reasoning, threadId: 'metacognition', thinkingType: 'reflection' }); + send({ type: 'thread', id: 'metacognition', name: 'Metacognition', threadType: 'metacognition', metadata: { triggerReason: event.data.trigger } }); + } + break; + case 'error': + send({ type: 'error', message: event.error || 'Unknown error' }); + break; + } + } + + const metaAnalysis = { + threads: Array.from(metamemoryThreads.values()), + metacognition: [], + summary: { + totalThreads: threads.size + metamemoryThreads.size, + totalMessages: messageCount, + avgOverlap: metamemoryThreads.size > 0 ? 0.2 : 0, + mostActiveThread: 'Main Conversation' + } + }; + send({ type: 'meta_analysis', analysis: metaAnalysis }); + send({ type: 'status', status: 'completed' }); + } catch (err) { + send({ type: 'error', message: err.message }); + send({ type: 'status', status: 'error' }); + } + })(); + return { abort: () => controller.abort() }; +} + diff --git a/demo/vite.config.ts b/demo/vite.config.ts index 511f622..f55aef6 100644 --- a/demo/vite.config.ts +++ b/demo/vite.config.ts @@ -1,16 +1,21 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -export default defineConfig({ - plugins: [react()], - server: { - port: 3021, - proxy: { - '/ws': { - target: 'ws://localhost:3020', - ws: true, - changeOrigin: true - } +export default defineConfig(() => { + const useServer = process.env.VITE_USE_SERVER === 'true' + return { + plugins: [react()], + server: { + port: 3021, + ...(useServer ? { + proxy: { + '/ws': { + target: 'ws://localhost:3020', + ws: true, + changeOrigin: true + } + } + } : {}) } } -}) \ No newline at end of file +})