Files
Atomizer/atomizer-dashboard/frontend/src/hooks/useIntentParser.ts
Anto01 5bd142780f feat(canvas): Add Phase 3+4 - Bidirectional sync, templates, and visual polish
Phase 3 - Bidirectional Sync:
- Add loadFromIntent and loadFromConfig to canvas store
- Create useIntentParser hook for parsing Claude messages
- Create ConfigImporter component (file upload, paste JSON, load study)
- Add import/clear buttons to CanvasView header

Phase 4 - Templates & Polish:
- Create template library with 5 presets:
  - Mass Minimization (single-objective)
  - Multi-Objective Pareto (NSGA-II)
  - Turbo Mode (with MLP surrogate)
  - Mirror Zernike (optical optimization)
  - Frequency Optimization (modal)
- Create TemplateSelector component with category filters
- Enhanced BaseNode with animations, glow effects, status indicators
- Add colorBg to all node types for visual distinction
- Add notification toast system
- Update all exports

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 20:30:28 -05:00

173 lines
5.2 KiB
TypeScript

/**
* 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<string, Partial<unknown>>) => {
for (const [nodeId, data] of Object.entries(updates)) {
const node = nodes.find(n => n.id === nodeId);
if (node) {
updateNodeData(nodeId, data as Record<string, unknown>);
}
}
}, [nodes, updateNodeData]);
return {
parseIntent,
applyUpdate,
processMessage,
hasCanvasContent,
applyParameterUpdates,
extractSuggestions,
};
}