mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 15:22:55 +01:00
19 KiB
19 KiB
AI-003: Natural Language Editing
Overview
Enable users to modify their projects using natural language commands. Instead of manually finding and configuring nodes, users can say "make this button blue" or "add a loading spinner when fetching data" and have AI make the changes.
Context
Current editing workflow:
- Select node in canvas
- Find property in sidebar
- Understand property options
- Make change
- Repeat for related nodes
Natural language editing:
- Select component or node
- Describe what you want
- AI makes the changes
- Review and accept/modify
This is especially powerful for:
- Styling changes across multiple elements
- Logic modifications that span nodes
- Refactoring component structure
- Complex multi-step changes
Existing AI Foundation
From AiAssistantModel.ts:
// Chat history for AI interactions
class ChatHistory {
messages: ChatMessage[];
add(message: ChatMessage): void;
}
// AI context per node
class AiCopilotContext {
template: AiTemplate;
chatHistory: ChatHistory;
node: NodeGraphNode;
}
Requirements
Functional Requirements
-
Command Input
- Command palette (Cmd+K style)
- Inline text input on selection
- Voice input (optional)
- Recent commands history
-
Command Understanding
- Style changes: "make it red", "add shadow"
- Structure changes: "add a header", "wrap in a card"
- Logic changes: "show loading while fetching"
- Data changes: "sort by date", "filter active items"
-
Change Preview
- Show what will change before applying
- Highlight affected nodes
- Before/after comparison
- Explanation of changes
-
Change Application
- Apply changes atomically
- Support undo/redo
- Handle errors gracefully
- Learn from corrections
-
Scope Selection
- Selected node(s) only
- Current component
- Related components
- Entire project
Non-Functional Requirements
- Response time < 3 seconds
- Changes are reversible
- Works on any component type
- Graceful degradation without API
Technical Approach
1. Natural Language Command Service
// packages/noodl-editor/src/editor/src/services/NaturalLanguageService.ts
interface CommandContext {
selection: NodeGraphNode[];
component: ComponentModel;
project: ProjectModel;
recentCommands: Command[];
}
interface Command {
id: string;
text: string;
timestamp: Date;
result: CommandResult;
}
interface CommandResult {
success: boolean;
changes: Change[];
explanation: string;
undoAction?: () => void;
}
interface Change {
type: 'property' | 'node' | 'connection' | 'structure';
target: string;
before: any;
after: any;
description: string;
}
class NaturalLanguageService {
private static instance: NaturalLanguageService;
// Main API
async parseCommand(text: string, context: CommandContext): Promise<ParsedCommand>;
async previewChanges(command: ParsedCommand): Promise<ChangePreview>;
async applyChanges(preview: ChangePreview): Promise<CommandResult>;
async undoCommand(commandId: string): Promise<void>;
// Command history
getRecentCommands(): Command[];
searchCommands(query: string): Command[];
// Learning
recordCorrection(commandId: string, correction: Change[]): void;
}
2. Command Parser
// packages/noodl-editor/src/editor/src/services/ai/CommandParser.ts
interface ParsedCommand {
intent: CommandIntent;
targets: CommandTarget[];
modifications: Modification[];
confidence: number;
}
enum CommandIntent {
STYLE_CHANGE = 'style_change',
STRUCTURE_CHANGE = 'structure_change',
LOGIC_CHANGE = 'logic_change',
DATA_CHANGE = 'data_change',
CREATE = 'create',
DELETE = 'delete',
CONNECT = 'connect',
UNKNOWN = 'unknown'
}
interface CommandTarget {
type: 'node' | 'component' | 'property' | 'connection';
selector: string; // How to find it
resolved?: any; // Actual reference
}
class CommandParser {
private patterns: CommandPattern[];
async parse(text: string, context: CommandContext): Promise<ParsedCommand> {
// 1. Try local pattern matching first (fast)
const localMatch = this.matchLocalPatterns(text);
if (localMatch.confidence > 0.9) {
return localMatch;
}
// 2. Use AI for complex commands
const aiParsed = await this.aiParse(text, context);
// 3. Merge and validate
return this.mergeAndValidate(localMatch, aiParsed, context);
}
private matchLocalPatterns(text: string): ParsedCommand {
// Pattern: "make [target] [color]"
// Pattern: "add [element] to [target]"
// Pattern: "connect [source] to [destination]"
// etc.
}
private async aiParse(text: string, context: CommandContext): Promise<ParsedCommand> {
const prompt = `Parse this Noodl editing command:
Command: "${text}"
Current selection: ${context.selection.map(n => n.type.localName).join(', ')}
Component: ${context.component.name}
Output JSON with: intent, targets, modifications`;
const response = await this.anthropicClient.complete(prompt);
return JSON.parse(response);
}
}
3. Change Generator
// packages/noodl-editor/src/editor/src/services/ai/ChangeGenerator.ts
class ChangeGenerator {
// Generate actual changes from parsed command
async generateChanges(command: ParsedCommand, context: CommandContext): Promise<Change[]> {
const changes: Change[] = [];
switch (command.intent) {
case CommandIntent.STYLE_CHANGE:
changes.push(...this.generateStyleChanges(command, context));
break;
case CommandIntent.STRUCTURE_CHANGE:
changes.push(...await this.generateStructureChanges(command, context));
break;
case CommandIntent.LOGIC_CHANGE:
changes.push(...await this.generateLogicChanges(command, context));
break;
// ...
}
return changes;
}
private generateStyleChanges(command: ParsedCommand, context: CommandContext): Change[] {
const changes: Change[] = [];
for (const target of command.targets) {
const node = this.resolveTarget(target, context);
for (const mod of command.modifications) {
const propertyName = this.mapToNoodlProperty(mod.property);
const newValue = this.parseValue(mod.value, propertyName);
changes.push({
type: 'property',
target: node.id,
before: node.parameters[propertyName],
after: newValue,
description: `Change ${propertyName} to ${newValue}`
});
}
}
return changes;
}
private async generateStructureChanges(
command: ParsedCommand,
context: CommandContext
): Promise<Change[]> {
// Use AI to generate node structure
const prompt = `Generate Noodl node structure for:
"${command.modifications.map(m => m.description).join(', ')}"
Current context: ${JSON.stringify(context.selection.map(n => n.type.localName))}
Output JSON array of nodes to create and connections`;
const response = await this.anthropicClient.complete(prompt);
return this.parseStructureResponse(response);
}
}
4. UI Components
Command Palette
┌─────────────────────────────────────────────────────────────────────┐
│ 🔮 What do you want to do? [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Make the button larger and add a hover effect │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Selected: Button, Text │
│ │
│ Recent: │
│ • "Add loading spinner to the form" │
│ • "Make all headers blue" │
│ • "Connect the submit button to the API" │
│ │
│ Examples: │
│ • "Wrap this in a card with shadow" │
│ • "Add validation to all inputs" │
│ • "Show error message when API fails" │
│ │
│ [Preview Changes] │
└─────────────────────────────────────────────────────────────────────┘
Change Preview
┌─────────────────────────────────────────────────────────────────────┐
│ Preview Changes [×] │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Command: "Make the button larger and add a hover effect" │
│ │
│ Changes to apply: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ✓ Button │ │
│ │ • width: 100px → 150px │ │
│ │ • height: 40px → 50px │ │
│ │ • fontSize: 14px → 16px │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ + New: HoverState │ │
│ │ • scale: 1.05 │ │
│ │ • transition: 200ms │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 🤖 "I'll increase the button size by 50% and add a subtle scale │
│ effect on hover with a smooth transition." │
│ │
│ [Cancel] [Modify] [Apply Changes] │
└─────────────────────────────────────────────────────────────────────┘
Inline Command
┌─────────────────────────────────────────────────────────────────────┐
│ Canvas │
│ │
│ ┌─────────────────┐ │
│ │ [Button] │ ← Selected │
│ │ Click me │ │
│ └─────────────────┘ │
│ │ │
│ ┌──────┴──────────────────────────────────────────────┐ │
│ │ 🔮 Make it red with rounded corners [Enter] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
5. Command Patterns
// packages/noodl-editor/src/editor/src/services/ai/CommandPatterns.ts
const COMMAND_PATTERNS: CommandPattern[] = [
// Style patterns
{
pattern: /make (?:it |this |the )?(.+?) (red|blue|green|...)/i,
intent: CommandIntent.STYLE_CHANGE,
extract: (match) => ({
target: match[1] || 'selection',
property: 'backgroundColor',
value: match[2]
})
},
{
pattern: /(?:set |change )?(?:the )?(.+?) (?:to |=) (.+)/i,
intent: CommandIntent.STYLE_CHANGE,
extract: (match) => ({
property: match[1],
value: match[2]
})
},
// Structure patterns
{
pattern: /add (?:a |an )?(.+?) (?:to |inside |in) (.+)/i,
intent: CommandIntent.STRUCTURE_CHANGE,
extract: (match) => ({
nodeType: match[1],
target: match[2]
})
},
{
pattern: /wrap (?:it |this |selection )?in (?:a |an )?(.+)/i,
intent: CommandIntent.STRUCTURE_CHANGE,
extract: (match) => ({
action: 'wrap',
wrapper: match[1]
})
},
// Logic patterns
{
pattern: /show (.+?) when (.+)/i,
intent: CommandIntent.LOGIC_CHANGE,
extract: (match) => ({
action: 'conditional_show',
target: match[1],
condition: match[2]
})
},
{
pattern: /connect (.+?) to (.+)/i,
intent: CommandIntent.CONNECT,
extract: (match) => ({
source: match[1],
destination: match[2]
})
}
];
Files to Create
packages/noodl-editor/src/editor/src/services/NaturalLanguageService.tspackages/noodl-editor/src/editor/src/services/ai/CommandParser.tspackages/noodl-editor/src/editor/src/services/ai/ChangeGenerator.tspackages/noodl-editor/src/editor/src/services/ai/CommandPatterns.tspackages/noodl-core-ui/src/components/ai/CommandPalette/CommandPalette.tsxpackages/noodl-core-ui/src/components/ai/ChangePreview/ChangePreview.tsxpackages/noodl-core-ui/src/components/ai/InlineCommand/InlineCommand.tsx
Files to Modify
-
packages/noodl-editor/src/editor/src/views/nodegrapheditor.js- Add command palette trigger (Cmd+K)
- Add inline command on selection
- Handle change application
-
packages/noodl-editor/src/editor/src/pages/EditorPage/EditorPage.tsx- Add keyboard shortcut handler
- Mount command palette
-
packages/noodl-editor/src/editor/src/models/NodeGraphModel.ts- Add atomic change application
- Support undo for AI changes
Implementation Steps
Phase 1: Command Infrastructure
- Create NaturalLanguageService
- Implement command history
- Set up undo/redo support
Phase 2: Command Parser
- Create CommandParser
- Define local patterns
- Implement AI parsing
- Test parsing accuracy
Phase 3: Change Generator
- Create ChangeGenerator
- Implement style changes
- Implement structure changes
- Implement logic changes
Phase 4: UI - Command Palette
- Create CommandPalette component
- Add keyboard shortcut
- Show recent/examples
- Handle input
Phase 5: UI - Change Preview
- Create ChangePreview component
- Show before/after
- Add explanation
- Handle apply/cancel
Phase 6: UI - Inline Command
- Create InlineCommand component
- Position near selection
- Handle quick commands
Phase 7: Learning & Improvement
- Track command success
- Record corrections
- Improve pattern matching
Testing Checklist
- Style commands work correctly
- Structure commands create nodes
- Logic commands set up conditions
- Preview shows accurate changes
- Apply actually makes changes
- Undo reverts changes
- Keyboard shortcuts work
- Recent commands saved
- Error handling graceful
- Complex commands work
- Multi-target commands work
Dependencies
- AI-001 (AI Project Scaffolding) - for AnthropicClient
- AI-002 (Component Suggestions) - for context analysis
Blocked By
- AI-001
Blocks
- AI-004 (AI Design Assistance)
Estimated Effort
- Command service: 4-5 hours
- Command parser: 5-6 hours
- Change generator: 6-8 hours
- UI command palette: 4-5 hours
- UI change preview: 4-5 hours
- UI inline command: 3-4 hours
- Testing & refinement: 4-5 hours
- Total: 30-38 hours
Success Criteria
- Natural language commands understood
- Preview shows accurate changes
- Changes applied correctly
- Undo/redo works
- < 3 second response time
- 80%+ command success rate
Command Examples
Style Changes
- "Make it red"
- "Add a shadow"
- "Increase font size to 18"
- "Round the corners"
- "Make all buttons blue"
Structure Changes
- "Add a header"
- "Wrap in a card"
- "Add a loading spinner"
- "Create a sidebar"
- "Split into two columns"
Logic Changes
- "Show loading while fetching"
- "Hide when empty"
- "Disable until form is valid"
- "Navigate to home on click"
- "Show error message on failure"
Data Changes
- "Sort by date"
- "Filter completed items"
- "Group by category"
- "Limit to 10 items"
Future Enhancements
- Voice input
- Multi-language support
- Command macros
- Batch changes
- Command sharing
- Context-aware autocomplete