/** * useIntentParser - Parse Claude messages for canvas updates * * Detects structured intents in chat messages and applies them to canvas */ import { useCallback } from 'react'; import { useCanvasStore, OptimizationConfig } from './useCanvasStore'; import { OptimizationIntent } from '../lib/canvas/intent'; import { Message } from '../components/chat/ChatMessage'; interface ParsedUpdate { type: 'intent' | 'config' | 'suggestion' | 'none'; data?: OptimizationIntent | OptimizationConfig; suggestions?: string[]; } export function useIntentParser() { const { loadFromIntent, loadFromConfig, updateNodeData, nodes } = useCanvasStore(); /** * Parse a message for structured intent JSON */ const parseIntent = useCallback((content: string): ParsedUpdate => { // Look for JSON blocks in the message const jsonBlockRegex = /```(?:json)?\s*\n?([\s\S]*?)\n?```/g; const matches = [...content.matchAll(jsonBlockRegex)]; for (const match of matches) { try { const parsed = JSON.parse(match[1]); // Check if it's an OptimizationIntent if (parsed.version && parsed.source === 'canvas') { return { type: 'intent', data: parsed as OptimizationIntent }; } // Check if it's an OptimizationConfig (has design_variables or objectives) if (parsed.design_variables || parsed.objectives || parsed.model?.path) { return { type: 'config', data: parsed as OptimizationConfig }; } } catch { // Not valid JSON, continue } } // Look for inline JSON (without code blocks) const inlineJsonRegex = /\{[\s\S]*?"(?:version|design_variables|objectives)"[\s\S]*?\}/g; const inlineMatches = content.match(inlineJsonRegex); if (inlineMatches) { for (const jsonStr of inlineMatches) { try { const parsed = JSON.parse(jsonStr); if (parsed.version && parsed.source === 'canvas') { return { type: 'intent', data: parsed as OptimizationIntent }; } if (parsed.design_variables || parsed.objectives) { return { type: 'config', data: parsed as OptimizationConfig }; } } catch { // Not valid JSON } } } // Look for suggestions in the message const suggestions = extractSuggestions(content); if (suggestions.length > 0) { return { type: 'suggestion', suggestions }; } return { type: 'none' }; }, []); /** * Extract actionable suggestions from Claude's response */ const extractSuggestions = (content: string): string[] => { const suggestions: string[] = []; // Common suggestion patterns const patterns = [ /suggest(?:ing)?\s+(?:to\s+)?(?:add|include|use)\s+([^.!?\n]+)/gi, /recommend(?:ing)?\s+([^.!?\n]+)/gi, /consider\s+(?:adding|using|including)\s+([^.!?\n]+)/gi, /you\s+(?:should|could|might)\s+(?:add|include|use)\s+([^.!?\n]+)/gi, ]; for (const pattern of patterns) { const matches = content.matchAll(pattern); for (const match of matches) { if (match[1]) { suggestions.push(match[1].trim()); } } } return suggestions; }; /** * Apply a parsed update to the canvas */ const applyUpdate = useCallback((update: ParsedUpdate): boolean => { switch (update.type) { case 'intent': if (update.data && 'version' in update.data) { loadFromIntent(update.data as OptimizationIntent); return true; } break; case 'config': if (update.data && !('version' in update.data)) { loadFromConfig(update.data as OptimizationConfig); return true; } break; case 'suggestion': // Suggestions are returned but not auto-applied return false; } return false; }, [loadFromIntent, loadFromConfig]); /** * Process a message and optionally apply updates */ const processMessage = useCallback((message: Message, autoApply = false): ParsedUpdate => { const update = parseIntent(message.content); if (autoApply && (update.type === 'intent' || update.type === 'config')) { applyUpdate(update); } return update; }, [parseIntent, applyUpdate]); /** * Check if a message contains canvas-related content */ const hasCanvasContent = useCallback((content: string): boolean => { const keywords = [ 'design variable', 'objective', 'constraint', 'extractor', 'optimization', 'algorithm', 'surrogate', 'model', 'solver', 'canvas', 'workflow', 'node' ]; const lowerContent = content.toLowerCase(); return keywords.some(kw => lowerContent.includes(kw)); }, []); /** * Apply parameter updates to specific nodes */ const applyParameterUpdates = useCallback((updates: Record>) => { for (const [nodeId, data] of Object.entries(updates)) { const node = nodes.find(n => n.id === nodeId); if (node) { updateNodeData(nodeId, data as Record); } } }, [nodes, updateNodeData]); return { parseIntent, applyUpdate, processMessage, hasCanvasContent, applyParameterUpdates, extractSuggestions, }; }