diff --git a/src/components/WorkflowList.tsx b/src/components/WorkflowList.tsx index a4bfb0a..ecb1f12 100644 --- a/src/components/WorkflowList.tsx +++ b/src/components/WorkflowList.tsx @@ -17,6 +17,12 @@ import { ArrowRight, Clock, HelpCircle, + Database, + MessageSquare, + Mail, + FileText, + Search, + GitBranch, } from "lucide-react"; interface WorkflowListProps { @@ -24,6 +30,88 @@ interface WorkflowListProps { onCreateWorkflow: (workflowName?: string) => void; } +// Helper function to get node icon +const getNodeIconComponent = (type: string) => { + switch (type) { + case "dataInput": + return ArrowRight; + case "dataOutput": + return ArrowRight; + case "webScraping": + return Globe; + case "llmTask": + return Brain; + case "embeddingGenerator": + return Sparkles; + case "similaritySearch": + return Search; + case "structuredOutput": + return FileText; + case "database": + return Database; + case "slack": + return MessageSquare; + case "discord": + return MessageSquare; + case "gmail": + return Mail; + default: + return Zap; + } +}; + +// Helper function to get node color +const getNodeColorClass = (type: string) => { + switch (type) { + case "dataInput": + return "bg-blue-100 text-blue-700 border-blue-200"; + case "dataOutput": + return "bg-orange-100 text-orange-700 border-orange-200"; + case "webScraping": + return "bg-green-100 text-green-700 border-green-200"; + case "llmTask": + return "bg-purple-100 text-purple-700 border-purple-200"; + case "embeddingGenerator": + return "bg-pink-100 text-pink-700 border-pink-200"; + case "similaritySearch": + return "bg-indigo-100 text-indigo-700 border-indigo-200"; + case "structuredOutput": + return "bg-yellow-100 text-yellow-700 border-yellow-200"; + case "database": + return "bg-cyan-100 text-cyan-700 border-cyan-200"; + case "slack": + return "bg-violet-100 text-violet-700 border-violet-200"; + case "discord": + return "bg-indigo-100 text-indigo-700 border-indigo-200"; + case "gmail": + return "bg-red-100 text-red-700 border-red-200"; + default: + return "bg-gray-100 text-gray-700 border-gray-200"; + } +}; + +// Helper function to format node type name +const formatNodeTypeName = (type: string): string => { + const formatted = type.replace(/([A-Z])/g, " $1").trim(); + return formatted.charAt(0).toUpperCase() + formatted.slice(1); +}; + +// Helper function to get node type summary with counts +const getNodeTypeSummary = (nodes: any[]) => { + const typeCounts = new Map(); + + nodes.forEach((node) => { + const count = typeCounts.get(node.type) || 0; + typeCounts.set(node.type, count + 1); + }); + + // Sort by count (descending) and then alphabetically + return Array.from(typeCounts.entries()).sort((a, b) => { + if (b[1] !== a[1]) return b[1] - a[1]; // Sort by count first + return a[0].localeCompare(b[0]); // Then alphabetically + }); +}; + export const WorkflowList: React.FC = ({ onOpenWorkflow, onCreateWorkflow, @@ -397,56 +485,57 @@ export const WorkflowList: React.FC = ({ - {/* Node Type Indicators */} -
- {workflow.nodes.slice(0, 3).map((node, index) => { - const getNodeIcon = (type: string) => { - switch (type) { - case "dataInput": - return ; - case "webScraping": - return ; - case "llmTask": - return ; - case "dataOutput": - return ; - default: - return ; - } - }; - const getNodeColor = (type: string) => { - switch (type) { - case "dataInput": - return "bg-blue-100 text-blue-600"; - case "webScraping": - return "bg-green-100 text-green-600"; - case "llmTask": - return "bg-purple-100 text-purple-600"; - case "dataOutput": - return "bg-orange-100 text-orange-600"; - default: - return "bg-gray-100 text-gray-600"; - } - }; - return ( -
- {getNodeIcon(node.type)} - - {node.type.replace(/([A-Z])/g, " $1").trim()} + {/* Node Type Summary with Counts */} +
+ {/* Workflow Stats */} +
+
+
+ + + {workflow.nodes.length} + nodes +
+
+ + + {workflow.edges.length} + + connections
- ); - })} - {workflow.nodes.length > 3 && ( -
- +{workflow.nodes.length - 3} more
- )} +
+ {getNodeTypeSummary(workflow.nodes).length}{" "} + {getNodeTypeSummary(workflow.nodes).length === 1 + ? "type" + : "types"} +
+
+ + {/* Node Type Breakdown */} +
+ {getNodeTypeSummary(workflow.nodes).map(([type, count]) => { + const IconComponent = getNodeIconComponent(type); + return ( +
1 ? "s" : "" + }`} + > + + {count} + + {formatNodeTypeName(type)} + +
+ ); + })} +
{/* Action Button */} diff --git a/src/services/ROBUST_JSON_PARSING_SOLUTION.md b/src/services/ROBUST_JSON_PARSING_SOLUTION.md deleted file mode 100644 index bcfc873..0000000 --- a/src/services/ROBUST_JSON_PARSING_SOLUTION.md +++ /dev/null @@ -1,436 +0,0 @@ -# Robust JSON Parsing for LLM Outputs - -## Overview - -LLM outputs can occasionally generate malformed JSON due to: - -- Code block wrappers (`json ... `) -- Trailing commas in objects/arrays -- Extra whitespace or newlines -- Markdown formatting -- Incomplete JSON (truncated responses) -- Single quotes instead of double quotes -- Missing quotes around property names -- Comments in JSON - -This solution provides a multi-strategy JSON parser that can recover from these common issues. - -## Architecture - -### Core Components - -1. **`parseRobustJson(input, options)`** - - - Main parsing function with multiple fallback strategies - - Returns a `ParseResult` with success status and data/error - -2. **`parseAndValidate(input, requiredFields, options)`** - - - Combines parsing with structure validation - - Ensures parsed JSON has expected fields and types - -3. **Parsing Strategies** (applied in order): - - **Direct Parse**: Fast path for valid JSON - - **Cleaned Parse**: Remove code blocks, markdown, BOM - - **Advanced Recovery**: Fix trailing commas, quotes, comments - - **Relaxed Parse**: Extract JSON from partial/broken input - -### Parsing Strategy Details - -#### Strategy 1: Direct Parse - -```typescript -try { - return JSON.parse(input); -} catch { - /* fallback */ -} -``` - -- Fastest path for valid JSON -- No modifications to input - -#### Strategy 2: Cleaned Parse - -````typescript -const cleaned = cleanJsonString(input); -// Removes: -// - Code blocks: ```json ... ``` -// - Markdown formatting -// - BOM characters -// - Extra whitespace -return JSON.parse(cleaned); -```` - -#### Strategy 3: Advanced Recovery - -```typescript -const recovered = advancedJsonRecovery(input); -// Fixes: -// - Trailing commas: ,} → } -// - Missing quotes: {name: "val"} → {"name": "val"} -// - Single quotes: 'value' → "value" -// - Comments: // ... or /* ... */ -return JSON.parse(recovered); -``` - -#### Strategy 4: Relaxed Parse - -```typescript -// Extract JSON boundaries -// Manually parse key-value pairs -// Return best-effort object -``` - -## Integration Points - -### Updated Files - -1. **`src/services/naturalLanguageToWorkflowService.ts`** - - `classifyIntent()`: Line ~145 - - `extractEntities()`: Line ~238 - - `generateWorkflowStructure()`: Line ~416 - -### Before (Fragile) - -```typescript -try { - const response = await callOpenAI(prompt, config); - const result = JSON.parse(response.content); // ❌ Can crash - return result; -} catch (error) { - return fallback(); -} -``` - -### After (Robust) - -```typescript -try { - const response = await callOpenAI(prompt, config); - - // ✅ Robust parsing with validation - const parseResult = parseAndValidate( - response.content, - [ - { field: "intent", type: "string" }, - { field: "confidence", type: "number" }, - ], - { logAttempts: true } - ); - - if (!parseResult.success) { - console.error("Parse failed:", parseResult.error); - throw new Error(parseResult.error); - } - - return parseResult.data; -} catch (error) { - return fallback(); -} -``` - -## API Reference - -### `parseRobustJson(input, options)` - -**Parameters:** - -- `input: string` - JSON string to parse (possibly malformed) -- `options`: - - `strictMode?: boolean` - Skip relaxed parsing (default: false) - - `fallbackValue?: T` - Return value if all strategies fail - - `logAttempts?: boolean` - Log parsing attempts (default: false) - -**Returns:** `ParseResult` - -```typescript -interface ParseResult { - success: boolean; - data?: T; - error?: string; - strategy?: string; // "direct" | "cleaned" | "recovered" | "relaxed" | "fallback" - cleanedJson?: string; -} -``` - -**Example:** - -```typescript -const result = parseRobustJson(llmOutput, { - logAttempts: true, - fallbackValue: { default: "structure" }, -}); - -if (result.success) { - console.log("Parsed:", result.data); - console.log("Strategy used:", result.strategy); -} else { - console.error("Failed:", result.error); -} -``` - -### `parseAndValidate(input, requiredFields, options)` - -**Parameters:** - -- `input: string` - JSON string to parse -- `requiredFields: Array<{ field: string; type: string }>` - Expected structure -- `options`: Same as `parseRobustJson` - -**Returns:** `ParseResult & { validationErrors?: string[] }` - -**Example:** - -```typescript -const result = parseAndValidate( - llmOutput, - [ - { field: "intent", type: "string" }, - { field: "confidence", type: "number" }, - { field: "nodes", type: "object" }, - ], - { - logAttempts: true, - } -); - -if (!result.success) { - console.error("Validation errors:", result.validationErrors); -} -``` - -### `validateJsonStructure(data, requiredFields, options)` - -**Parameters:** - -- `data: any` - Parsed JSON data -- `requiredFields: Array<{ field: string; type: string }>` -- `options`: - - `allowExtra?: boolean` - Allow extra fields not in schema - -**Returns:** `{ isValid: boolean; errors: string[] }` - -## Examples - -### Example 1: Handling Code Blocks - -```typescript -const llmOutput = ` -\`\`\`json -{ - "intent": "web_scraping", - "confidence": 0.95 -} -\`\`\` -`; - -const result = parseRobustJson(llmOutput); -// ✅ Success: { intent: "web_scraping", confidence: 0.95 } -``` - -### Example 2: Handling Trailing Commas - -```typescript -const llmOutput = `{ - "intent": "data_analysis", - "confidence": 0.87, - "nodes": ["input", "process", "output",], -}`; - -const result = parseRobustJson(llmOutput); -// ✅ Success: Trailing commas fixed automatically -``` - -### Example 3: Handling Missing Quotes - -```typescript -const llmOutput = `{ - intent: "workflow_generation", - confidence: 0.92 -}`; - -const result = parseRobustJson(llmOutput); -// ✅ Success: Property names quoted automatically -``` - -### Example 4: Validation with Required Fields - -```typescript -const result = parseAndValidate(llmOutput, [ - { field: "intent", type: "string" }, - { field: "confidence", type: "number" }, -]); - -if (!result.success) { - console.error("Missing required fields:", result.validationErrors); - // e.g., ["Missing required field: intent"] -} -``` - -## Benefits - -### 1. Increased Reliability - -- **Before**: ~5-10% failure rate due to malformed JSON -- **After**: <1% failure rate with multi-strategy parsing - -### 2. Better Error Messages - -- **Before**: Generic "Unexpected token" errors -- **After**: Clear indication of which parsing strategy was used/failed - -### 3. Graceful Degradation - -- Falls back through multiple strategies before giving up -- Can return fallback values for non-critical failures - -### 4. User Trust - -- Fewer "something went wrong" errors -- More reliable AI-powered features -- Better developer experience - -## Testing - -### Unit Tests - -````typescript -// Test code block removal -expect(parseRobustJson('```json\n{"key":"value"}\n```')).toMatchObject({ - success: true, - strategy: "cleaned", - data: { key: "value" }, -}); - -// Test trailing comma fix -expect(parseRobustJson('{"key":"value",}')).toMatchObject({ - success: true, - strategy: "recovered", - data: { key: "value" }, -}); - -// Test validation -expect( - parseAndValidate('{"key":"value"}', [{ field: "key", type: "string" }]) -).toMatchObject({ - success: true, - validationErrors: undefined, -}); -```` - -### Integration Tests - -- Test with actual LLM outputs from DeepSeek/OpenAI -- Verify fallback behavior on complete failures -- Ensure performance is acceptable (< 10ms per parse) - -## Performance - -### Benchmarks - -- **Direct parse** (valid JSON): ~0.1ms -- **Cleaned parse**: ~0.5ms -- **Advanced recovery**: ~2ms -- **Relaxed parse**: ~5ms - -### Optimization Tips - -1. Use `strictMode: true` when JSON is likely to be valid -2. Cache parsing results for repeated inputs -3. Set appropriate `maxTokens` to prevent large responses -4. Use `logAttempts: false` in production for better performance - -## Future Enhancements - -### Potential Improvements - -1. **Schema-based validation**: Support JSON Schema for complex validation -2. **Custom recovery rules**: Allow users to define custom cleanup patterns -3. **Streaming parser**: Handle large JSON streams incrementally -4. **Type inference**: Automatically infer TypeScript types from parsed data -5. **Metrics collection**: Track parsing success rates and strategies used - -### LLM Prompt Engineering - -To reduce malformed JSON from LLMs: - -1. Explicitly request "valid JSON without code blocks" -2. Use structured output formats (OpenAI's JSON mode) -3. Add validation examples in prompts -4. Set lower temperature for more deterministic output -5. Use system messages to enforce JSON format - -## Migration Guide - -### For Existing Code - -1. **Import the parser**: - -```typescript -import { parseRobustJson, parseAndValidate } from "./robustJsonParser"; -``` - -2. **Replace JSON.parse()**: - -```typescript -// Before -const data = JSON.parse(llmResponse); - -// After -const result = parseRobustJson(llmResponse); -if (result.success) { - const data = result.data; -} -``` - -3. **Add validation** (optional but recommended): - -```typescript -const result = parseAndValidate(llmResponse, [ - { field: "requiredField", type: "string" }, -]); -``` - -4. **Handle errors gracefully**: - -```typescript -if (!result.success) { - console.error("Parse error:", result.error); - // Use fallback or retry -} -``` - -## Troubleshooting - -### Common Issues - -**Issue**: Parser returns fallback value - -- **Cause**: Input is severely malformed -- **Solution**: Check LLM prompt, increase temperature, or use structured output - -**Issue**: Validation fails on correct JSON - -- **Cause**: Type mismatch (e.g., array vs object) -- **Solution**: Review required fields, adjust types or make fields optional - -**Issue**: Performance degradation - -- **Cause**: Large inputs triggering multiple strategies -- **Solution**: Enable `strictMode`, reduce `maxTokens`, or cache results - -## Support - -For issues or questions: - -1. Check console logs with `logAttempts: true` -2. Review the parsed/cleaned JSON in error messages -3. Test with `parseRobustJson` in isolation -4. Consider prompt engineering to improve LLM JSON quality - -## References - -- JSON Specification: [RFC 8259](https://tools.ietf.org/html/rfc8259) -- LLM Best Practices: [OpenAI JSON Mode](https://platform.openai.com/docs/guides/text-generation/json-mode) -- Error Handling: [Error Handling Best Practices](https://nodejs.org/en/docs/guides/error-handling/)