feat: flow export/import + procedural Flow Assist (#96)
* feat: add flow export/import backend (migration, endpoints, schemas)
Add .rfflow file export/import support:
- Migration 050: import_metadata JSONB column on trees
- GET /trees/{id}/export?format=json|xml endpoint
- POST /trees/import endpoint (creates draft, resolves categories/tags)
- FlowExportEnvelope, FlowImportRequest/Response schemas
- import_metadata field on TreeResponse
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add flow export/import frontend + backend tests
Frontend:
- ExportFlowModal with JSON/XML format selection + download
- ImportFlowModal with drag-drop file picker + preview step
- rfflowParser for client-side JSON/XML .rfflow parsing
- Export buttons on editor toolbar and library action menus
- Import button on library page next to Create New
- Provenance display for imported flows in editor
- flowTransfer API client + types
Backend:
- Fix regex->pattern deprecation in export endpoint
- 12 integration tests covering export, import, round-trip,
access control, tag/category creation, version validation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove XML export, JSON-only for .rfflow files
- Remove XML builder, format query param, and XML tests
- Simplify ExportFlowModal (no format picker)
- Simplify rfflowParser (JSON-only)
- Remove format field from schemas and types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: match Flow Assist chat input to AI Assistant styling + strengthen one-question prompt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add procedural flow support to AI chat builder (Flow Assist)
- Add procedural-specific system prompts (schema, interview protocol, response format)
- Dispatch prompts by flow_type: procedural/maintenance use flat steps schema, troubleshooting uses decision tree schema
- Parse [STEPS_UPDATE] and [INTAKE_FORM] markers in AI responses
- Add validate_generated_procedural_steps() validator
- Handle intake form extraction in AI chat import endpoint
- Add StaticStepsPreview component for procedural flow preview
- Update store and page to render correct preview by flow type
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add flow type selection to Flow Assist entry points
- CreateFlowDropdown now shows "Build with Flow Assist" under each flow type
- Library page "Flow Assist" button respects current type filter
- Clean up unused AIFlowBuilderModal references
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md with AI chat builder and intake form learnings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: refine assistant chat prompt for concise answers and focused questions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: switch AI provider to Claude Sonnet 4.6 + add shift+enter hint to chat inputs
- Default AI_PROVIDER changed from gemini to anthropic
- AI_MODEL and AI_MODEL_ANTHROPIC updated to claude-sonnet-4-6
- Added "Shift + Enter for a new line" hint below all chat textareas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update CLAUDE.md with AI provider and chat input learnings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add editor-embedded Flow Assist design document
Design for replacing the standalone /ai/chat page with context-aware
AI side panels embedded in each editor (Troubleshooting + Procedural).
Covers ghost node suggestion system, output-based thresholds,
config-driven model routing, knowledge integration, and per-flow
chat persistence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add editor-embedded Flow Assist implementation plan
25-task plan across 9 phases covering backend foundation, frontend
infrastructure, tree/procedural editor integration, AI-assisted create,
old code removal, action-type dispatch, suggestion audit trail, and
build verification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use actual root node ID in orphan validation check
AI-generated trees use descriptive IDs (e.g., "verify-account-exists")
instead of "root", causing the root node to be falsely flagged as orphaned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add config-driven AI model tier routing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: extend AI chat session with tree_id and archived_at
Add tree_id FK (CASCADE) for editor-embedded sessions and archived_at
timestamp column to ai_chat_sessions table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add AI suggestion audit trail table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add action_type and focal_node_id to AI chat message API
- Add VALID_ACTION_TYPES literal and action_type/focal_node_id fields to
AIChatMessageRequest schema
- Add tree_id field to AIChatStartRequest schema for editor-embedded sessions
- Update send_message() signature with action_type and focal_node_id params
- Update start_chat_session() signature with tree_id param
- Pass new params through endpoints to service functions
- All new params have defaults so existing behavior is unchanged
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: route AI model selection through action-type config
Update get_ai_provider() to accept an optional model override parameter
(applied only to AnthropicProvider; Gemini always uses its own model).
Thread action_type-based model resolution through send_message() and
generate_final_tree() in the AI chat service.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add TypeScript types for editor-embedded AI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add shared ContextMenu component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add useEditorAI hook and editorAI API client
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add EditorAIPanel component with Chat and Suggestions tabs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: integrate AI panel, context menu, and ghost nodes in tree editor
- Add AI Assist panel toggle button to tree editor toolbar
- Wire EditorAIPanel alongside TreeEditorLayout with single-panel rule
- Thread onNodeContextMenu through TreeEditorLayout → FlowCanvas → FlowCanvasNode
- Add right-click context menu with Generate Branch, Explain Node, Delete actions
- Add ghost node detection (_suggestion flag) with dashed border + opacity styling
- Add Accept/Dismiss overlay buttons on ghost nodes for future suggestion handling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: integrate AI panel, context menu, and ghost steps in procedural editor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add AI prompt dialog and wire into CreateFlowDropdown
Replace navigation to /ai/chat with an inline AIPromptDialog modal
that collects a single prompt, generates a flow via the editor AI API,
imports it, and navigates to the editor with the AI panel open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add glassmorphism to AI prompt dialog + maintenance Flow Assist button
- Use .glass-card-static on AIPromptDialog card for consistent design system
- Add "Build with Flow Assist" button to maintenance section in CreateFlowDropdown
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: remove standalone Flow Assist page and old AI chat components
Remove the old /ai/chat page, AI wizard modal, and all associated
components/stores/types now replaced by the editor-embedded AI panel.
Deleted:
- AIChatBuilderPage, ai-chat/ components, aiChatStore, aiChat API, ai-chat types
- AIFlowBuilderModal, ai-builder/ components, aiFlowBuilderStore
Cleaned up:
- Router (removed /ai/chat route)
- Sidebar (removed Flow Assist nav item)
- MyTreesPage (removed AI builder modal and button)
- TreeLibraryPage (removed Flow Assist button)
- API and type barrel exports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add delta response parsing and action-type prompt dispatch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add AI suggestion audit trail endpoints
Create/list/resolve endpoints for tracking AI-applied changes to flows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add APScheduler task to auto-archive stale AI chat sessions
Archives AI chat sessions with no activity for 30 days, runs daily at 3 AM.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update project status for editor-embedded Flow Assist
- Add Editor-Embedded Flow Assist to CURRENT-STATE.md in-progress items
- Update CLAUDE.md: fix stale lessons (#41, #46), add new patterns (#47 editor AI architecture, #48 orphan validation)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use correct model alias in AI_MODEL_TIERS standard tier
The dated model ID `claude-sonnet-4-6-20250514` was causing 502 errors.
Use the alias `claude-sonnet-4-6` which matches AI_MODEL_ANTHROPIC.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: send live flow context to AI Assist for full editor awareness
The AI panel now sends the current tree structure (troubleshooting) or
steps + intake form (procedural/maintenance) with each message. This
gives the AI full visibility into node details, questions, descriptions,
options, and intake form fields — not just the node ID.
- Backend: add flow_context param to schema, endpoint, and service
- Frontend: add getFlowContext callback to useEditorAI hook
- TreeEditorPage: passes treeStructure as flow context
- ProceduralEditorPage: passes steps + intakeForm as flow context
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: include flow name and description in AI Assist context
Both editors now send name and description alongside the flow structure,
so the AI can reference what the flow is about when responding.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: increase AI timeout to 120s and limit retries to 1
The 45s timeout was too short for generation tasks with full flow
context in the system prompt. The Anthropic SDK's default 2 retries
caused requests to hang for ~136s before failing. Now: 120s timeout
with max 1 retry = faster failure if it does timeout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wire AI-generated flow structures into editor stores
The useEditorAI hook was ignoring result.working_tree from AI responses,
so generated steps/trees never appeared in the editor. Now:
- useEditorAI calls onFlowUpdate when working_tree is present in response
- ProceduralEditorPage handles steps + intake form updates via replaceSteps
- TreeEditorPage handles tree structure updates via replaceTreeStructure
- Both stores have new bulk-replace methods for AI integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add lessons learned for full-stack integration, Anthropic retries, model tiers
#49 Always verify frontend consumes backend response fields
#50 Anthropic SDK max_retries=1 to avoid 3× timeout
#51 AI model tier routing via settings.get_model_for_action()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: move AI Assist panel to full-height side layout in both editors
The AI panel was nested inside the content area, only spanning the
step list / canvas section. Now it sits at the outermost flex level,
spanning the full page height alongside all content (toolbar,
collapsible sections, steps/canvas). This prevents the panel from
overlapping content and lets the editor area properly shrink.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: AI Assist panel as fixed right drawer (matching Copilot/Scratchpad)
Convert EditorAIPanel from in-flow flex child to fixed right-side drawer
overlay, same pattern as CopilotPanel and ScratchpadSidebar. The panel
is fixed at right:0 spanning full viewport height, and editor pages add
pr-[380px] padding when open so content shifts left without overlap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: AI Assist panel sits below topbar with slide-in animation
- Panel now uses top:56px to sit below the app shell topbar instead of
covering it (matches the main-content grid cell area)
- Added slideInRight CSS animation for smooth drawer entrance
- Editor pages use dynamic paddingRight via PANEL_WIDTH constant
- ChatTab upgraded: markdown rendering, CopilotPanel-style message
bubbles, auto-focus input, Shift+Enter hint
- All borders use --glass-border for consistent glassmorphism
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: AI Assist panel as in-flow flex sibling (not fixed/overlay)
Replace fixed positioning with in-flow flex layout. The outermost div
is now a horizontal flex row: content column (flex-1 min-w-0) + panel
(w-[380px] shrink-0). When the panel opens, the content column
automatically shrinks — no padding hacks or z-index stacking needed.
This guarantees the content shifts left and stays fully visible.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: AI Copilot panel as in-flow flex sibling in session navigation pages
Changed CopilotPanel from fixed overlay to flex layout sibling so it
pushes main content instead of covering it during active sessions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: remove duplicate CLAUDE.md lessons #47-48
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #96.
This commit is contained in:
361
docs/plans/2026-03-06-editor-embedded-flow-assist-design.md
Normal file
361
docs/plans/2026-03-06-editor-embedded-flow-assist-design.md
Normal file
@@ -0,0 +1,361 @@
|
||||
# Editor-Embedded Flow Assist - Design Document
|
||||
|
||||
> **Date:** 2026-03-06
|
||||
> **Status:** Approved
|
||||
> **Replaces:** Standalone AI Chat Builder (`/ai/chat`)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Replace the standalone `/ai/chat` page with a context-aware AI side panel embedded directly in each editor (Troubleshooting + Procedural). The panel knows which node/step is focused, supports targeted and open-ended actions, and applies changes via a tiered suggestion system. Knowledge integration and variable inference are phased features built on the same panel architecture.
|
||||
|
||||
**Key Principles:**
|
||||
- Context-aware: panel knows the full tree/step structure + focal node
|
||||
- Targeted actions auto-apply; open-ended suggestions require acceptance
|
||||
- Output-based thresholds determine suggestion UX
|
||||
- Model routing is config-driven, not hardcoded
|
||||
- Chat history persists per-flow, per-user
|
||||
|
||||
---
|
||||
|
||||
## Panel Layout & Behavior
|
||||
|
||||
### Dimensions & Styling
|
||||
- **Width:** 320px fixed, right side
|
||||
- **Styling:** Glassmorphism (`.glass-card-static` bg, backdrop blur, `border-l border-border`)
|
||||
- **Z-index:** Same layer as node editor panel (not overlay)
|
||||
|
||||
### Single-Panel Rule
|
||||
- **Tree editor:** AI panel occupies the right panel slot, closing the node editor panel. When AI panel closes, if a node was previously being edited, the node editor panel reopens for that node.
|
||||
- **Procedural editor:** AI panel slides in from right, narrowing the step list (step list takes `flex-1`). No existing panel to replace.
|
||||
|
||||
### Top Section: Context Summary
|
||||
- **Node/step selected:** Read-only summary showing type, title, question/description of the focused item.
|
||||
- **No selection:** Flow summary showing name, node/step count, flow type.
|
||||
- Switching selection updates the summary live.
|
||||
|
||||
### Tabs
|
||||
- **Chat** — conversation + inline suggestions
|
||||
- **Suggestions** — audit trail of all AI-applied changes to this flow (accepted, dismissed, pending)
|
||||
|
||||
### Visibility
|
||||
- Hidden by default
|
||||
- Auto-opens on: AI-assisted flow creation, right-click AI action, toolbar toggle
|
||||
- Auto-contextual: opens with focal node already set when triggered via context menu
|
||||
|
||||
---
|
||||
|
||||
## Entry Points
|
||||
|
||||
### 1. Create Flow Dropdown (AI-Assisted)
|
||||
- "Blank" or "AI-assisted" option per flow type (Troubleshooting, Project, Maintenance)
|
||||
- **AI-assisted** shows a simple prompt dialog modal:
|
||||
- Text area: "Describe the flow you want to build"
|
||||
- Flow type already known from dropdown selection
|
||||
- Loading state during generation
|
||||
- On failure: error message + retry button (stays in dialog)
|
||||
- On success: creates tree via API, navigates to editor with AI panel auto-opened and generation chat history loaded
|
||||
- No multi-phase interview, no preview — just prompt and go
|
||||
|
||||
### 2. Right-Click Context Menu
|
||||
- New `<ContextMenu>` component (no existing context menus in either editor)
|
||||
- Positioned absolutely at right-click point
|
||||
- Closes on click-away, Escape, or action selection
|
||||
- **Tree editor items:** Generate branch, Add decision/action/solution, Explain node, Find known fixes, Delete
|
||||
- **Procedural editor items:** Generate steps after, Add verification step, Expand step, Generate section, Delete
|
||||
- Selecting an AI action sets the focal node/step and opens the AI panel
|
||||
|
||||
### 3. Toolbar Toggle
|
||||
- "AI Assist" button in editor toolbar to manually open/close the panel
|
||||
|
||||
### 4. Existing Flows
|
||||
- AI panel works on any flow — new or existing, AI-created or manually built
|
||||
- No restriction to AI-created flows
|
||||
|
||||
---
|
||||
|
||||
## Suggestion & Apply System
|
||||
|
||||
### Ghost Node/Step Mechanics
|
||||
|
||||
Ghost nodes/steps are added to `treeStructure`/steps array with a `_suggestion: true` flag:
|
||||
- Canvas/step list renders them normally (auto-layout works) but with **dashed borders + reduced opacity**
|
||||
- Zundo temporal store **paused** while suggestions are pending
|
||||
- On **accept**: remove `_suggestion` flag, unpause zundo (creates one clean undo point)
|
||||
- On **dismiss**: remove ghost nodes from structure, unpause zundo (no undo point created)
|
||||
- Ghost nodes participate in auto-layout and connection drawing but are visually distinct
|
||||
|
||||
### Addition vs Modification
|
||||
|
||||
| Change Type | Visual Treatment |
|
||||
|---|---|
|
||||
| **New nodes/steps** | Ghost nodes: dashed borders, reduced opacity |
|
||||
| **Modified existing nodes** | Subtle highlight + badge showing what changed |
|
||||
| **Modified selected node** | Before/after shown in chat message with Apply button (not inline ghost) |
|
||||
|
||||
### Output-Based Threshold
|
||||
|
||||
| Output Size | Behavior |
|
||||
|---|---|
|
||||
| **1 node/step** | Auto-apply + toast notification with undo link |
|
||||
| **2-4 nodes/steps** | Individual ghost suggestions + "Accept All" shortcut button |
|
||||
| **5+ nodes/steps** | Ghost suggestions grouped by branch (tree) or section (procedural) with "Accept Branch"/"Accept Section" and "Accept All" controls + summary card in panel |
|
||||
|
||||
All changes (accepted or dismissed) logged in the Suggestions tab as an audit trail.
|
||||
|
||||
---
|
||||
|
||||
## Backend Action Types
|
||||
|
||||
Each message to the AI includes an `action_type` that determines prompt construction, response schema, and model routing:
|
||||
|
||||
| Action Type | Description | Model Tier | Response Format |
|
||||
|---|---|---|---|
|
||||
| `generate_full` | Initial skeleton from prompt dialog | standard | Full tree structure or step array |
|
||||
| `generate_branch` | Generate children for a specific node | standard | Subtree delta (node + children) |
|
||||
| `modify_node` | Update a specific node's content | fast | Single node delta (before/after) |
|
||||
| `add_steps` | Add steps after a specific step | standard | Step array delta |
|
||||
| `quick_action` | Single-node operations (explain, expand) | fast | Single node delta or text response |
|
||||
| `open_chat` | General conversation about the flow | standard | Text + optional delta |
|
||||
| `variable_inference` | Detect implicit variables in step content | fast | Variable suggestions |
|
||||
|
||||
### Prompt Construction
|
||||
|
||||
Each action type gets a tailored system prompt:
|
||||
- **Full tree context** always included (so AI understands the complete flow)
|
||||
- **Focal node** highlighted when present (the specific node/step being acted on)
|
||||
- **Action instruction** describes what the AI should return
|
||||
- **Response schema** constrains output format (full tree, subtree delta, single node, text)
|
||||
|
||||
### Delta Response Format
|
||||
|
||||
For partial updates, the AI returns a delta object:
|
||||
```json
|
||||
{
|
||||
"action": "add" | "modify" | "delete",
|
||||
"target_node_id": "node-to-modify-or-insert-after",
|
||||
"nodes": [{ /* node objects */ }],
|
||||
"explanation": "What was changed and why"
|
||||
}
|
||||
```
|
||||
|
||||
The frontend applies the delta to the tree structure and renders ghost nodes as appropriate.
|
||||
|
||||
---
|
||||
|
||||
## Model Routing (Config-Driven)
|
||||
|
||||
### Configuration
|
||||
|
||||
```python
|
||||
# backend/app/core/config.py
|
||||
AI_MODEL_TIERS = {
|
||||
"fast": "claude-haiku-4-5-20251001",
|
||||
"standard": "claude-sonnet-4-6-20250514",
|
||||
}
|
||||
|
||||
ACTION_MODEL_MAP = {
|
||||
"generate_full": "standard",
|
||||
"generate_branch": "standard",
|
||||
"modify_node": "fast",
|
||||
"add_steps": "standard",
|
||||
"quick_action": "fast",
|
||||
"open_chat": "standard",
|
||||
"variable_inference": "fast",
|
||||
}
|
||||
```
|
||||
|
||||
### Routing Logic
|
||||
|
||||
1. Message endpoint receives `action_type` parameter
|
||||
2. Look up tier from `ACTION_MODEL_MAP`
|
||||
3. Resolve model name from `AI_MODEL_TIERS`
|
||||
4. Pass to Anthropic API call
|
||||
|
||||
Both tiers can map to the same model initially. Changing model assignment is a config change, not a code change.
|
||||
|
||||
---
|
||||
|
||||
## Knowledge Integration (Phased)
|
||||
|
||||
### Phase 1 (Initial Release)
|
||||
- Uses existing Microsoft Learn MCP server
|
||||
- AI can cite KB articles, known issues, and official fix procedures in chat responses
|
||||
- Citations rendered inline as collapsible cards with source URL and title
|
||||
- AI response marker: `[KNOWLEDGE]{"title": "...", "url": "...", "excerpt": "..."}[/KNOWLEDGE]`
|
||||
|
||||
### Phase 2 (Future)
|
||||
- Additional vendor documentation sources
|
||||
- Community knowledge bases
|
||||
- Proactive suggestions ("Microsoft released KB5034441 addressing this scenario")
|
||||
|
||||
---
|
||||
|
||||
## Chat Persistence
|
||||
|
||||
### Session Model
|
||||
- `ai_chat_session` model extended with:
|
||||
- `tree_id` FK (which flow this session belongs to)
|
||||
- `archived_at` timestamp (null = active)
|
||||
- Per-flow, per-user sessions: multiple engineers on the same flow get separate chat histories
|
||||
- Session loads on panel open if one exists for this flow + user
|
||||
|
||||
### Suggestions Audit Trail
|
||||
New `ai_suggestion` table:
|
||||
|
||||
| Column | Type | Description |
|
||||
|---|---|---|
|
||||
| `id` | UUID | Primary key |
|
||||
| `tree_id` | UUID FK | Which flow |
|
||||
| `user_id` | UUID FK | Who triggered |
|
||||
| `session_id` | UUID FK | Which chat session |
|
||||
| `action_type` | String | Action that generated this suggestion |
|
||||
| `target_node_id` | String | Node/step acted on (nullable) |
|
||||
| `changes_json` | JSONB | Before/after snapshot |
|
||||
| `status` | Enum | `pending`, `accepted`, `dismissed` |
|
||||
| `created_at` | DateTime(tz) | When suggested |
|
||||
| `resolved_at` | DateTime(tz) | When accepted/dismissed (nullable) |
|
||||
|
||||
### Auto-Archive
|
||||
- APScheduler task runs daily
|
||||
- Archives sessions with no activity for 30 days (`archived_at = now()`)
|
||||
- Archived sessions viewable in Suggestions tab but not resumable for chat
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Editor Integration
|
||||
|
||||
### Panel Context
|
||||
- Full tree structure included in AI context
|
||||
- Focal node (when selected/right-clicked) highlighted in context
|
||||
- Node summary at panel top shows: type icon, node ID, question/title, option count
|
||||
|
||||
### Context Menu Actions
|
||||
| Action | Description | Model Tier |
|
||||
|---|---|---|
|
||||
| Generate branch | Create child nodes from this decision | standard |
|
||||
| Add decision node | Add a decision child | fast |
|
||||
| Add action node | Add an action child | fast |
|
||||
| Add solution node | Add a solution child | fast |
|
||||
| Explain node | AI explains what this node does | fast |
|
||||
| Find known fixes | Search knowledge sources for this scenario | standard |
|
||||
|
||||
### Ghost Node Rendering
|
||||
- Dashed `border-dashed border-primary/40` borders
|
||||
- `opacity-60` on the node card
|
||||
- Connection lines drawn with dashed stroke
|
||||
- Accept/dismiss buttons overlaid on each ghost node
|
||||
- "Accept All" button in the panel when 2+ ghost nodes
|
||||
|
||||
---
|
||||
|
||||
## Procedural Editor Integration
|
||||
|
||||
### Panel Context
|
||||
- Full step list included in AI context
|
||||
- Focal step (when selected/right-clicked) highlighted in context
|
||||
- Step summary at panel top shows: step number, type badge, title, content type
|
||||
|
||||
### Context Menu Actions
|
||||
| Action | Description | Model Tier |
|
||||
|---|---|---|
|
||||
| Generate steps after | Add steps following this one | standard |
|
||||
| Add verification step | Insert a verification step | fast |
|
||||
| Expand step | Break this step into substeps | standard |
|
||||
| Generate section | Create a section header + steps | standard |
|
||||
|
||||
### Ghost Step Rendering
|
||||
- Dashed left border (`border-l-2 border-dashed border-primary/40`)
|
||||
- `opacity-60` background
|
||||
- Accept/dismiss buttons on each ghost step
|
||||
- Grouped by section when 5+ suggestions
|
||||
|
||||
### Intake Variable Detection (Three Tiers)
|
||||
|
||||
| Tier | Trigger | Timing | Model Tier |
|
||||
|---|---|---|---|
|
||||
| **Explicit** | `[VAR:name]` syntax in step content | Immediate on content save | None (regex match) |
|
||||
| **Inference** | Natural language suggests variable ("check the customer's server") | Debounced on step save/blur | fast |
|
||||
| **Cross-step** | Same implicit variable in 2+ steps | On panel open + when steps modified | fast |
|
||||
|
||||
**Behavior:**
|
||||
- Explicit: immediate inline suggestion card in panel ("Add `server_name` to intake form?")
|
||||
- Inference: non-blocking suggestion in panel, lower confidence indicator
|
||||
- Cross-step: promoted suggestion with gap flag ("Variable `server_name` used in steps 3, 7, 12 but not captured in intake form")
|
||||
- Results cached per-session until step content changes
|
||||
|
||||
---
|
||||
|
||||
## What Gets Removed
|
||||
|
||||
| Item | Location |
|
||||
|---|---|
|
||||
| `AIChatBuilderPage.tsx` | `frontend/src/pages/` |
|
||||
| `aiChatStore.ts` | `frontend/src/store/` |
|
||||
| `ai-chat/` component directory | `frontend/src/components/` |
|
||||
| `AIFlowBuilderModal` | `frontend/src/components/` |
|
||||
| `/ai/chat` route | `frontend/src/router.tsx` |
|
||||
| Flow type selection routing | URL params `?type=...` |
|
||||
|
||||
---
|
||||
|
||||
## What Gets Repurposed
|
||||
|
||||
| Item | Changes |
|
||||
|---|---|
|
||||
| `ai_chat_service.py` | Action-type dispatch, partial generation prompts, model routing, focal node context |
|
||||
| `ai_tree_validator.py` | Validates AI-generated fragments (subtree, step batch) in addition to full trees |
|
||||
| `ai_chat_session` model | Extended with `tree_id` FK, `archived_at` timestamp |
|
||||
| AI chat endpoints | Tree-scoped sessions, `action_type` parameter, model tier routing |
|
||||
|
||||
---
|
||||
|
||||
## What Gets Built (New)
|
||||
|
||||
| Item | Description |
|
||||
|---|---|
|
||||
| `EditorAIPanel` component | Shared panel with Chat + Suggestions tabs, node summary, input |
|
||||
| `ContextMenu` component | Shared right-click menu for nodes and steps |
|
||||
| `useEditorAI` hook | Panel state, focal node, suggestion management, ghost node lifecycle |
|
||||
| Prompt dialog modal | Simple "describe your flow" modal for AI-assisted create |
|
||||
| `ai_suggestion` DB model | Audit trail table + Alembic migration |
|
||||
| Ghost node CSS | Dashed borders, reduced opacity, accept/dismiss overlays |
|
||||
| Model tier config | `AI_MODEL_TIERS` + `ACTION_MODEL_MAP` in `config.py` |
|
||||
| APScheduler archive task | Daily job to archive stale sessions |
|
||||
|
||||
---
|
||||
|
||||
## What Gets Modified
|
||||
|
||||
| Item | Changes |
|
||||
|---|---|
|
||||
| `TreeEditorPage` | Right panel slot for AI, context menu handler, ghost node support |
|
||||
| `TreeCanvas` / `TreeCanvasNode` | Ghost node rendering (dashed borders, overlays) |
|
||||
| `ProceduralEditorPage` | Flex layout for AI panel, context menu on steps |
|
||||
| `StepList` / `StepEditor` | Ghost step rendering |
|
||||
| `treeEditorStore` | Ghost node state slice, zundo pause/resume, orphan bug fix |
|
||||
| `proceduralEditorStore` | Ghost step state slice |
|
||||
| `ai_chat_service.py` | Action-type dispatch, delta response format, model routing |
|
||||
| `ai_chat_session` model | `tree_id` FK, `archived_at` |
|
||||
| `config.py` | Model tier configuration |
|
||||
| `CreateFlowDropdown` | AI-assisted option + prompt dialog trigger |
|
||||
| `router.tsx` | Remove `/ai/chat` route |
|
||||
|
||||
---
|
||||
|
||||
## Bug Fix (Included)
|
||||
|
||||
**File:** `frontend/src/store/treeEditorStore.ts` line 858
|
||||
|
||||
**Current code:**
|
||||
```typescript
|
||||
if (id !== 'root' && !referencedIds.has(id)) {
|
||||
```
|
||||
|
||||
**Fixed code:**
|
||||
```typescript
|
||||
if (id !== state.treeStructure?.id && !referencedIds.has(id)) {
|
||||
```
|
||||
|
||||
**Root cause:** Orphan check hardcodes `'root'` as the expected root node ID. AI-generated trees use descriptive IDs (e.g., `"verify-account-exists"`). Since the root is never referenced by any other node's `next_node_id`, it gets flagged as orphaned. This is a false positive.
|
||||
2802
docs/plans/2026-03-06-editor-embedded-flow-assist-plan.md
Normal file
2802
docs/plans/2026-03-06-editor-embedded-flow-assist-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
835
docs/plans/2026-03-06-procedural-flow-assist.md
Normal file
835
docs/plans/2026-03-06-procedural-flow-assist.md
Normal file
@@ -0,0 +1,835 @@
|
||||
# Procedural Flow Assist — AI Chat Builder Support
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Make the Flow Assist AI chat builder correctly generate procedural/maintenance flows using the flat steps-array schema instead of the troubleshooting decision-tree schema.
|
||||
|
||||
**Architecture:** The AI chat service (`ai_chat_service.py`) currently has hardcoded troubleshooting-specific prompts (schema, interview protocol, response format). We add parallel procedural versions and dispatch based on `flow_type`. The AI validator gets a procedural counterpart. The frontend gets a procedural steps preview component and the store/page handle the different data shape.
|
||||
|
||||
**Tech Stack:** Python/FastAPI (backend), React/TypeScript (frontend), Zustand (state), Tailwind CSS (styling)
|
||||
|
||||
---
|
||||
|
||||
## Context: Procedural vs Troubleshooting Structure
|
||||
|
||||
**Troubleshooting** flows use a recursive tree: `{ id, type: "decision", question, options, children: [...] }` — branching paths ending in solution nodes.
|
||||
|
||||
**Procedural** flows use a flat ordered array: `{ steps: [{ id, type, title, ... }, ...] }` — sequential steps with a `procedure_end` as the final step.
|
||||
|
||||
### Procedural Step Schema
|
||||
```json
|
||||
{
|
||||
"id": "unique-slug",
|
||||
"type": "procedure_step | procedure_end | section_header",
|
||||
"title": "Step title",
|
||||
"description": "Detailed instructions (supports [VAR:variable_name] interpolation)",
|
||||
"content_type": "action | informational | verification | warning",
|
||||
"estimated_minutes": 5,
|
||||
"commands": [{ "code": "Get-Service ...", "label": "Check service", "language": "powershell" }],
|
||||
"expected_outcome": "What success looks like",
|
||||
"verification_prompt": "Confirm the service is running",
|
||||
"verification_type": "checkbox | text_input",
|
||||
"warning_text": "Caution text for warning content_type",
|
||||
"notes_enabled": true,
|
||||
"reference_url": "https://docs.microsoft.com/...",
|
||||
"section_header": "Optional section label"
|
||||
}
|
||||
```
|
||||
|
||||
### Structural Rules
|
||||
- `steps` array must be non-empty
|
||||
- Each step needs `id`, `type`, `title`
|
||||
- Valid types: `procedure_step`, `procedure_end`, `section_header`
|
||||
- Exactly ONE `procedure_end` as the LAST step
|
||||
- No duplicate step IDs
|
||||
- `content_type` if present must be: `action`, `informational`, `verification`, `warning`
|
||||
- Commands can be a string or array of `{ code, label?, language? }`
|
||||
|
||||
### Intake Form (Optional)
|
||||
Procedural flows can have an intake form that captures variables before execution. Fields use `variable_name` (e.g., `server_name`) referenced in step descriptions as `[VAR:server_name]`.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Add Procedural System Prompts to AI Chat Service
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/core/ai_chat_service.py`
|
||||
|
||||
**Step 1: Add procedural schema context constant**
|
||||
|
||||
After the existing `SCHEMA_CONTEXT` constant (~line 78), add:
|
||||
|
||||
```python
|
||||
PROCEDURAL_SCHEMA_CONTEXT = """
|
||||
PROCEDURAL STEP SCHEMA — This is what you are building:
|
||||
|
||||
Procedural flows are a FLAT ORDERED ARRAY of steps (NOT a branching tree). The structure is:
|
||||
{"steps": [step1, step2, ..., end_step]}
|
||||
|
||||
Each step has a "type" field:
|
||||
|
||||
1. procedure_step — A task the engineer performs
|
||||
Required: id (string), type ("procedure_step"), title (string)
|
||||
Optional: description (string — detailed instructions, supports [VAR:variable_name] interpolation),
|
||||
content_type ("action" | "informational" | "verification" | "warning"),
|
||||
estimated_minutes (integer),
|
||||
commands (array of {code: string, label?: string, language?: string}),
|
||||
expected_outcome (string),
|
||||
verification_prompt (string — question to confirm step completion),
|
||||
verification_type ("checkbox" | "text_input"),
|
||||
warning_text (string — caution text, used with content_type "warning"),
|
||||
notes_enabled (boolean, default true),
|
||||
reference_url (string — documentation link)
|
||||
|
||||
2. section_header — A visual divider to organize steps into phases
|
||||
Required: id (string), type ("section_header"), title (string)
|
||||
Optional: description (string)
|
||||
|
||||
3. procedure_end — The final completion marker (exactly ONE, always LAST)
|
||||
Required: id (string), type ("procedure_end"), title (string)
|
||||
Optional: description (string — completion summary text)
|
||||
|
||||
CONTENT TYPES for procedure_step:
|
||||
- "action" (default): Executable task with commands — shows terminal icon
|
||||
- "informational": Read-only context or reference info — shows info icon
|
||||
- "verification": Requires engineer confirmation before proceeding — shows checkmark icon
|
||||
- "warning": Highlighted caution/danger step — shows alert icon
|
||||
|
||||
STRUCTURAL RULES:
|
||||
- Steps are executed in array order — position determines sequence
|
||||
- All IDs must be unique strings (use descriptive slugs like "install-ad-ds-role")
|
||||
- The LAST step MUST be type "procedure_end"
|
||||
- Section headers group related steps visually but don't affect execution order
|
||||
- Use [VAR:variable_name] in descriptions to reference intake form variables (e.g., "Configure IP on [VAR:server_name]")
|
||||
|
||||
COMMAND FORMAT:
|
||||
Commands are arrays of objects, each with:
|
||||
- code (required): The exact command syntax (PowerShell, CMD, bash, etc.)
|
||||
- label (optional): Short description of what the command does
|
||||
- language (optional): "powershell", "cmd", "bash", etc.
|
||||
"""
|
||||
|
||||
PROCEDURAL_INTERVIEW_PROTOCOL = """
|
||||
INTERVIEW PHASES — Follow this progression:
|
||||
|
||||
PHASE 1 - SCOPING (current_phase: scoping):
|
||||
Understand what procedure this flow covers:
|
||||
- What process is this flow for? (e.g., "new domain controller build", "Exchange migration", "firewall replacement")
|
||||
- What is the target environment? (on-prem, hybrid, cloud, specific vendors?)
|
||||
- Who will execute this? (Tier level, experience assumptions)
|
||||
- What information will the engineer need before starting? (This becomes the intake form — server name, IP, domain, credentials, etc.)
|
||||
Demonstrate expertise: "For a DC build, we'd typically need server name, IP, subnet, gateway, domain name, DSRM password, and whether this is the first DC or joining an existing domain."
|
||||
DO NOT emit [STEPS_UPDATE] during scoping.
|
||||
|
||||
PHASE 2 - DISCOVERY (current_phase: discovery):
|
||||
Build out the procedure step by step:
|
||||
- Establish the major phases (these become section_headers)
|
||||
- For each phase, work through the steps in execution order
|
||||
- Capture specific commands with exact syntax
|
||||
- Add verification steps where the engineer should confirm something before proceeding
|
||||
- Add warning steps for anything destructive or irreversible
|
||||
EMIT [STEPS_UPDATE] when you and the user have agreed on concrete steps. Include ALL steps discussed so far.
|
||||
|
||||
PHASE 3 - ENRICHMENT (current_phase: enrichment):
|
||||
Circle back to improve existing steps:
|
||||
- Add exact PowerShell/CLI commands with syntax
|
||||
- Add expected_outcome for action steps
|
||||
- Add verification prompts for critical checkpoints
|
||||
- Add estimated_minutes for time-sensitive procedures
|
||||
- Add reference_url links to relevant documentation
|
||||
- Add warning_text for dangerous operations
|
||||
- Suggest intake form variables for values that change per execution
|
||||
EMIT [STEPS_UPDATE] when enriching steps.
|
||||
|
||||
PHASE 4 - REVIEW (current_phase: review):
|
||||
Present a summary:
|
||||
- Total step count by content_type
|
||||
- Section-by-section outline
|
||||
- Estimated total time
|
||||
- List of intake form variables suggested
|
||||
- Flag any gaps or areas needing more detail
|
||||
EMIT [STEPS_UPDATE] only if the user requests changes.
|
||||
|
||||
TRANSITION between phases by emitting [PHASE:phase_name] when the conversation naturally moves to the next stage.
|
||||
"""
|
||||
|
||||
PROCEDURAL_RESPONSE_FORMAT = """
|
||||
RESPONSE FORMAT:
|
||||
|
||||
Your response is natural conversational text. When the step structure changes, include structured markers that will be parsed by the system (the user will NOT see these markers):
|
||||
|
||||
1. Steps update (only when structure changes — see phase rules above):
|
||||
[STEPS_UPDATE]
|
||||
{"steps": [...valid steps array...]}
|
||||
[/STEPS_UPDATE]
|
||||
|
||||
2. Phase transition (when moving to next phase):
|
||||
[PHASE:discovery]
|
||||
|
||||
3. Metadata capture (when you learn the flow's name, description, or tags):
|
||||
[METADATA]
|
||||
{"name": "...", "description": "...", "tags": ["..."]}
|
||||
[/METADATA]
|
||||
|
||||
4. Intake form suggestion (when you identify variables the engineer will need):
|
||||
[INTAKE_FORM]
|
||||
[{"variable_name": "server_name", "label": "Server Name", "field_type": "text", "required": true, "placeholder": "e.g., DC01", "group_name": "Server Details", "display_order": 1}]
|
||||
[/INTAKE_FORM]
|
||||
|
||||
IMPORTANT:
|
||||
- Include [STEPS_UPDATE] sparingly. Only when concrete steps are established or modified.
|
||||
- The steps update should be the COMPLETE working steps array, not a diff.
|
||||
- Always include conversational text OUTSIDE the markers — never respond with only markers.
|
||||
- The last step in the array MUST always be type "procedure_end".
|
||||
"""
|
||||
```
|
||||
|
||||
**Step 2: Update `_build_system_prompt` to dispatch by flow_type**
|
||||
|
||||
Replace the existing `_build_system_prompt` function:
|
||||
|
||||
```python
|
||||
def _build_system_prompt(flow_type: str) -> str:
|
||||
"""Assemble the full system prompt for the chat builder."""
|
||||
if flow_type in ("procedural", "maintenance"):
|
||||
flow_context = (
|
||||
f"The user wants to build a {'MAINTENANCE' if flow_type == 'maintenance' else 'PROCEDURAL'} flow — "
|
||||
"a step-by-step process guide that walks engineers through a procedure in sequence. "
|
||||
"Steps are executed in order, not branching paths."
|
||||
)
|
||||
return f"{ROLE_PERSONA}\n\n{flow_context}\n\n{PROCEDURAL_SCHEMA_CONTEXT}\n\n{PROCEDURAL_INTERVIEW_PROTOCOL}\n\n{PROCEDURAL_RESPONSE_FORMAT}"
|
||||
else:
|
||||
flow_context = (
|
||||
"The user wants to build a TROUBLESHOOTING flow — a diagnostic decision tree "
|
||||
"that guides engineers through symptom identification, diagnostic checks, and "
|
||||
"resolution steps."
|
||||
)
|
||||
return f"{ROLE_PERSONA}\n\n{flow_context}\n\n{SCHEMA_CONTEXT}\n\n{INTERVIEW_PROTOCOL}\n\n{RESPONSE_FORMAT}"
|
||||
```
|
||||
|
||||
**Step 3: Update `_parse_ai_response` to handle `[STEPS_UPDATE]` and `[INTAKE_FORM]`**
|
||||
|
||||
Add extraction for the new markers. After the `[METADATA]` extraction block:
|
||||
|
||||
```python
|
||||
# Extract [STEPS_UPDATE]...[/STEPS_UPDATE]
|
||||
steps_match = re.search(
|
||||
r"\[STEPS_UPDATE\]\s*([\s\S]*?)\s*\[/STEPS_UPDATE\]", result["content"]
|
||||
)
|
||||
if steps_match:
|
||||
try:
|
||||
raw_json = _strip_markdown_fences(steps_match.group(1))
|
||||
result["tree_update"] = json.loads(raw_json)
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logger.warning("Failed to parse steps update JSON: %s", e)
|
||||
result["content"] = result["content"][: steps_match.start()] + result["content"][steps_match.end() :]
|
||||
else:
|
||||
truncated_steps = re.search(r"\[STEPS_UPDATE\][\s\S]*$", result["content"])
|
||||
if truncated_steps:
|
||||
logger.warning("Truncated [STEPS_UPDATE] block detected — stripping from display")
|
||||
result["content"] = result["content"][: truncated_steps.start()]
|
||||
|
||||
# Extract [INTAKE_FORM]...[/INTAKE_FORM]
|
||||
intake_match = re.search(
|
||||
r"\[INTAKE_FORM\]\s*([\s\S]*?)\s*\[/INTAKE_FORM\]", result["content"]
|
||||
)
|
||||
if intake_match:
|
||||
try:
|
||||
raw_json = _strip_markdown_fences(intake_match.group(1))
|
||||
result["intake_form"] = json.loads(raw_json)
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logger.warning("Failed to parse intake form JSON: %s", e)
|
||||
result["content"] = result["content"][: intake_match.start()] + result["content"][intake_match.end() :]
|
||||
else:
|
||||
truncated_intake = re.search(r"\[INTAKE_FORM\][\s\S]*$", result["content"])
|
||||
if truncated_intake:
|
||||
logger.warning("Truncated [INTAKE_FORM] block detected — stripping from display")
|
||||
result["content"] = result["content"][: truncated_intake.start()]
|
||||
```
|
||||
|
||||
Also add `"intake_form": None` to the initial `result` dict.
|
||||
|
||||
**Step 4: Update `send_message` to validate procedural structure**
|
||||
|
||||
In `send_message()`, replace the tree_update validation block (~line 320-326):
|
||||
|
||||
```python
|
||||
# Validate tree update if present
|
||||
tree_update = parsed["tree_update"]
|
||||
if tree_update:
|
||||
if session.flow_type in ("procedural", "maintenance"):
|
||||
# Procedural: must have a steps array
|
||||
if not isinstance(tree_update, dict) or not isinstance(tree_update.get("steps"), list):
|
||||
logger.warning("AI steps update rejected: must have a steps array")
|
||||
tree_update = None
|
||||
else:
|
||||
# Troubleshooting: root must be a decision node
|
||||
if not isinstance(tree_update, dict) or tree_update.get("type") != "decision":
|
||||
logger.warning("AI tree update rejected: root must be a decision node")
|
||||
tree_update = None
|
||||
elif not tree_update.get("id"):
|
||||
logger.warning("AI tree update rejected: root node missing id")
|
||||
tree_update = None
|
||||
```
|
||||
|
||||
Also handle intake_form persistence after the metadata block:
|
||||
|
||||
```python
|
||||
if parsed.get("intake_form"):
|
||||
session.intake_form_draft = parsed["intake_form"]
|
||||
```
|
||||
|
||||
Wait — `AIChatSession` may not have an `intake_form_draft` field. We'll store it in `tree_metadata` instead:
|
||||
|
||||
```python
|
||||
if parsed.get("intake_form"):
|
||||
merged = dict(session.tree_metadata) if session.tree_metadata else {}
|
||||
merged["intake_form"] = parsed["intake_form"]
|
||||
session.tree_metadata = merged
|
||||
```
|
||||
|
||||
**Step 5: Update `generate_final_tree` for procedural flows**
|
||||
|
||||
Replace the `generation_instruction` string with flow-type-aware instructions:
|
||||
|
||||
```python
|
||||
if session.flow_type in ("procedural", "maintenance"):
|
||||
generation_instruction = """Based on our entire conversation, generate the COMPLETE and FINAL procedural steps JSON for this flow.
|
||||
|
||||
Requirements:
|
||||
- Include ALL steps we discussed, organized into sections
|
||||
- Use descriptive step IDs (slugs, not UUIDs)
|
||||
- Each step needs: id, type, title, description
|
||||
- Include commands with exact syntax where discussed
|
||||
- Include content_type for each step (action, informational, verification, warning)
|
||||
- Include estimated_minutes where discussed
|
||||
- Include verification_prompt for verification steps
|
||||
- Include warning_text for warning steps
|
||||
- The LAST step MUST be type "procedure_end"
|
||||
- Respond with ONLY the JSON — no conversational text, no markdown fences
|
||||
|
||||
Format: {"steps": [step1, step2, ..., end_step]}
|
||||
|
||||
Also provide metadata as a separate JSON object after the steps:
|
||||
[METADATA]
|
||||
{"name": "...", "description": "...", "tags": ["..."]}
|
||||
[/METADATA]
|
||||
|
||||
If we discussed intake form variables, include them:
|
||||
[INTAKE_FORM]
|
||||
[{"variable_name": "...", "label": "...", "field_type": "text", "required": true, "display_order": 1}]
|
||||
[/INTAKE_FORM]"""
|
||||
else:
|
||||
generation_instruction = """Based on our entire conversation, generate the COMPLETE and FINAL TreeStructure JSON for this flow.
|
||||
...existing troubleshooting instruction..."""
|
||||
```
|
||||
|
||||
Update the validation inside the generation loop to handle procedural:
|
||||
|
||||
```python
|
||||
if not tree:
|
||||
# ... existing retry logic ...
|
||||
|
||||
if session.flow_type in ("procedural", "maintenance"):
|
||||
# Validate procedural structure
|
||||
p_errors = validate_generated_procedural_steps(tree)
|
||||
if p_errors:
|
||||
if attempt == 0:
|
||||
# ... retry with correction ...
|
||||
continue
|
||||
raise ValueError(f"Generated steps failed validation: {'; '.join(p_errors)}")
|
||||
else:
|
||||
errors = validate_generated_tree(tree)
|
||||
if errors:
|
||||
# ... existing retry logic ...
|
||||
```
|
||||
|
||||
**Step 6: Run backend tests**
|
||||
|
||||
Run: `cd /home/michaelchihlas/dev/patherly/backend && python -m pytest tests/test_ai_chat.py -v --override-ini="addopts="`
|
||||
Expected: All existing tests pass (they test troubleshooting flow).
|
||||
|
||||
**Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/core/ai_chat_service.py
|
||||
git commit -m "feat: add procedural flow prompts to AI chat builder"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Add Procedural Validation to AI Tree Validator
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/core/ai_tree_validator.py`
|
||||
|
||||
**Step 1: Add `validate_generated_procedural_steps` function**
|
||||
|
||||
Add after the existing `count_tree_stats` function:
|
||||
|
||||
```python
|
||||
VALID_PROCEDURAL_STEP_TYPES = {"procedure_step", "procedure_end", "section_header"}
|
||||
VALID_CONTENT_TYPES = {"action", "informational", "verification", "warning"}
|
||||
|
||||
|
||||
def validate_generated_procedural_steps(tree: dict[str, Any]) -> list[str]:
|
||||
"""Validate an AI-generated procedural steps structure.
|
||||
|
||||
Returns a list of error strings. Empty list means valid.
|
||||
"""
|
||||
errors: list[str] = []
|
||||
|
||||
if not isinstance(tree, dict):
|
||||
return ["Steps structure must be a JSON object"]
|
||||
|
||||
steps = tree.get("steps")
|
||||
if not isinstance(steps, list) or len(steps) == 0:
|
||||
return ["Must have a non-empty 'steps' array"]
|
||||
|
||||
seen_ids: set[str] = set()
|
||||
end_count = 0
|
||||
step_count = 0
|
||||
|
||||
for i, step in enumerate(steps):
|
||||
if not isinstance(step, dict):
|
||||
errors.append(f"Step at index {i} is not an object")
|
||||
continue
|
||||
|
||||
step_id = step.get("id")
|
||||
step_type = step.get("type")
|
||||
|
||||
# Check ID
|
||||
if not step_id:
|
||||
errors.append(f"Step at index {i} missing 'id'")
|
||||
elif step_id in seen_ids:
|
||||
errors.append(f"Duplicate step ID: '{step_id}'")
|
||||
else:
|
||||
seen_ids.add(step_id)
|
||||
|
||||
# Check type
|
||||
if step_type not in VALID_PROCEDURAL_STEP_TYPES:
|
||||
errors.append(
|
||||
f"Step '{step_id or i}' has invalid type '{step_type}'. "
|
||||
f"Must be one of: {', '.join(sorted(VALID_PROCEDURAL_STEP_TYPES))}"
|
||||
)
|
||||
continue
|
||||
|
||||
# Check title
|
||||
if not step.get("title"):
|
||||
errors.append(f"Step '{step_id}' missing 'title'")
|
||||
|
||||
# Content type validation
|
||||
content_type = step.get("content_type")
|
||||
if content_type and content_type not in VALID_CONTENT_TYPES:
|
||||
errors.append(
|
||||
f"Step '{step_id}' has invalid content_type '{content_type}'. "
|
||||
f"Must be one of: {', '.join(sorted(VALID_CONTENT_TYPES))}"
|
||||
)
|
||||
|
||||
if step_type == "procedure_step":
|
||||
step_count += 1
|
||||
elif step_type == "procedure_end":
|
||||
end_count += 1
|
||||
|
||||
# Structural checks
|
||||
if end_count == 0:
|
||||
errors.append("Must have exactly one 'procedure_end' step as the last step")
|
||||
elif end_count > 1:
|
||||
errors.append(f"Found {end_count} procedure_end steps, must have exactly 1")
|
||||
|
||||
if end_count == 1 and steps[-1].get("type") != "procedure_end":
|
||||
errors.append("The procedure_end step must be the last step in the array")
|
||||
|
||||
if step_count < 2:
|
||||
errors.append(
|
||||
f"Flow has only {step_count} procedure steps. "
|
||||
"Need at least 2 for a useful procedure."
|
||||
)
|
||||
|
||||
if len(steps) > 100:
|
||||
errors.append(f"Flow has {len(steps)} steps. Maximum 100 allowed.")
|
||||
|
||||
return errors
|
||||
```
|
||||
|
||||
**Step 2: Run tests**
|
||||
|
||||
Run: `cd /home/michaelchihlas/dev/patherly/backend && python -m pytest tests/ -k "procedural" -v --override-ini="addopts="`
|
||||
Expected: Existing procedural tests pass.
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/core/ai_tree_validator.py
|
||||
git commit -m "feat: add procedural steps validator for AI-generated flows"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Handle Intake Form + Procedural Import in AI Chat Endpoint
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/api/endpoints/ai_chat.py`
|
||||
|
||||
**Step 1: Update the `import_tree` endpoint to handle intake form from metadata**
|
||||
|
||||
In the `import_tree` function (~line 393), after building the Tree object, check for intake form:
|
||||
|
||||
```python
|
||||
# Extract intake form from metadata if present
|
||||
intake_form = None
|
||||
if metadata.get("intake_form"):
|
||||
intake_form = metadata.pop("intake_form")
|
||||
|
||||
tree = Tree(
|
||||
name=data.name or metadata.get("name", "AI-Generated Flow"),
|
||||
description=data.description or metadata.get("description", ""),
|
||||
tree_type=session.flow_type,
|
||||
tree_structure=session.working_tree,
|
||||
intake_form=intake_form,
|
||||
author_id=current_user.id,
|
||||
account_id=current_user.account_id,
|
||||
category_id=data.category_id,
|
||||
is_public=False,
|
||||
)
|
||||
```
|
||||
|
||||
**Step 2: Run tests**
|
||||
|
||||
Run: `cd /home/michaelchihlas/dev/patherly/backend && python -m pytest tests/test_ai_chat.py -v --override-ini="addopts="`
|
||||
Expected: All tests pass.
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/api/endpoints/ai_chat.py
|
||||
git commit -m "feat: handle intake form in AI chat procedural import"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Add Procedural Steps Preview Component (Frontend)
|
||||
|
||||
**Files:**
|
||||
- Create: `frontend/src/components/ai-chat/StaticStepsPreview.tsx`
|
||||
|
||||
**Step 1: Create the procedural steps preview component**
|
||||
|
||||
```tsx
|
||||
import type { ProceduralStep } from '@/types'
|
||||
import { Terminal, Info, CheckSquare, AlertTriangle, LayoutList } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface StaticStepsPreviewProps {
|
||||
steps: ProceduralStep[]
|
||||
name?: string
|
||||
}
|
||||
|
||||
const CONTENT_TYPE_ICONS: Record<string, typeof Terminal> = {
|
||||
action: Terminal,
|
||||
informational: Info,
|
||||
verification: CheckSquare,
|
||||
warning: AlertTriangle,
|
||||
}
|
||||
|
||||
export function StaticStepsPreview({ steps, name }: StaticStepsPreviewProps) {
|
||||
let stepNumber = 0
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b border-border px-4 py-2">
|
||||
<h3 className="text-sm font-semibold text-foreground">
|
||||
Preview: {name || 'Untitled Flow'}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{steps.filter((s) => s.type === 'procedure_step').length} steps
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto p-4">
|
||||
<div className="space-y-1.5">
|
||||
{steps.map((step) => {
|
||||
if (step.type === 'section_header') {
|
||||
return (
|
||||
<div key={step.id} className="pt-3 pb-1 first:pt-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<LayoutList className="h-3.5 w-3.5 text-primary" />
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-primary">
|
||||
{step.title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (step.type === 'procedure_end') {
|
||||
return (
|
||||
<div
|
||||
key={step.id}
|
||||
className="mt-2 rounded-lg border border-emerald-500/20 bg-emerald-500/5 px-3 py-2"
|
||||
>
|
||||
<span className="text-xs font-medium text-emerald-400">
|
||||
{step.title || 'Procedure Complete'}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
stepNumber++
|
||||
const contentType = step.content_type || 'action'
|
||||
const Icon = CONTENT_TYPE_ICONS[contentType] || Terminal
|
||||
|
||||
return (
|
||||
<div
|
||||
key={step.id}
|
||||
className={cn(
|
||||
'rounded-lg border px-3 py-2 text-xs',
|
||||
contentType === 'warning'
|
||||
? 'border-amber-500/20 bg-amber-500/5'
|
||||
: 'border-border bg-card'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded bg-primary/10 font-label text-[0.5rem] text-primary">
|
||||
{stepNumber}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Icon className={cn(
|
||||
'h-3 w-3 shrink-0',
|
||||
contentType === 'warning' ? 'text-amber-400' : 'text-muted-foreground'
|
||||
)} />
|
||||
<span className={cn(
|
||||
'font-medium truncate',
|
||||
contentType === 'warning' ? 'text-amber-400' : 'text-foreground'
|
||||
)}>
|
||||
{step.title}
|
||||
</span>
|
||||
</div>
|
||||
{step.commands && (
|
||||
<div className="mt-1 flex items-center gap-1 text-muted-foreground">
|
||||
<Terminal className="h-2.5 w-2.5" />
|
||||
<span className="font-label text-[0.5rem]">
|
||||
{Array.isArray(step.commands) ? step.commands.length : 1} command{(Array.isArray(step.commands) ? step.commands.length : 1) !== 1 ? 's' : ''}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{step.estimated_minutes && (
|
||||
<span className="shrink-0 font-label text-[0.5rem] text-muted-foreground">
|
||||
~{step.estimated_minutes}m
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run build**
|
||||
|
||||
Run: `cd /home/michaelchihlas/dev/patherly/frontend && npm run build`
|
||||
Expected: Build passes.
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/components/ai-chat/StaticStepsPreview.tsx
|
||||
git commit -m "feat: add procedural steps preview component for AI chat builder"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Update AI Chat Store + Page for Procedural Flows
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/store/aiChatStore.ts`
|
||||
- Modify: `frontend/src/pages/AIChatBuilderPage.tsx`
|
||||
|
||||
**Step 1: Update `AIChatState` interface and `sendMessage` handler in store**
|
||||
|
||||
In `aiChatStore.ts`, update the `workingTree` type to also accept procedural structure:
|
||||
|
||||
```typescript
|
||||
// Change line 29:
|
||||
workingTree: TreeStructure | { steps: ProceduralStep[] } | null
|
||||
// Change line 33:
|
||||
generatedTree: TreeStructure | { steps: ProceduralStep[] } | null
|
||||
```
|
||||
|
||||
Add `ProceduralStep` to the imports:
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
ChatMessage,
|
||||
InterviewPhase,
|
||||
TreeStructure,
|
||||
ProceduralStep,
|
||||
} from '@/types'
|
||||
```
|
||||
|
||||
Update `sendMessage` (~line 121-127) — the response handling already works because `working_tree` is stored as-is from the API. The cast just needs updating:
|
||||
|
||||
```typescript
|
||||
workingTree: (response.working_tree as TreeStructure | { steps: ProceduralStep[] } | null) ?? state.workingTree,
|
||||
```
|
||||
|
||||
And in `generateTree` (~line 142-143):
|
||||
|
||||
```typescript
|
||||
generatedTree: response.tree_structure as unknown as TreeStructure | { steps: ProceduralStep[] },
|
||||
workingTree: response.tree_structure as unknown as TreeStructure | { steps: ProceduralStep[] },
|
||||
```
|
||||
|
||||
And in `resumeSession` (~line 185-187):
|
||||
|
||||
```typescript
|
||||
workingTree: session.working_tree as TreeStructure | { steps: ProceduralStep[] } | null,
|
||||
generatedTree: session.generated_tree as TreeStructure | { steps: ProceduralStep[] } | null,
|
||||
```
|
||||
|
||||
**Step 2: Update `AIChatBuilderPage.tsx` to render correct preview**
|
||||
|
||||
Add import for `StaticStepsPreview` and `ProceduralStep`:
|
||||
|
||||
```typescript
|
||||
import { StaticStepsPreview } from '@/components/ai-chat/StaticStepsPreview'
|
||||
import type { ProceduralStep } from '@/types'
|
||||
```
|
||||
|
||||
Replace the preview tree logic (~line 116) and preview render (~line 143-151):
|
||||
|
||||
```typescript
|
||||
const previewData = generatedTree || workingTree
|
||||
|
||||
// Determine if this is a procedural preview
|
||||
const isProceduralPreview = previewData && 'steps' in previewData
|
||||
|
||||
// ... in the JSX:
|
||||
{previewData ? (
|
||||
isProceduralPreview ? (
|
||||
<StaticStepsPreview
|
||||
steps={(previewData as { steps: ProceduralStep[] }).steps}
|
||||
name={treeMetadata?.name}
|
||||
/>
|
||||
) : (
|
||||
<StaticTreePreview
|
||||
tree={previewData as TreeStructure}
|
||||
name={treeMetadata?.name}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<EmptyPreview />
|
||||
)}
|
||||
```
|
||||
|
||||
Remove the now-unused `const previewTree = (generatedTree || workingTree) as TreeStructure | null` line.
|
||||
|
||||
**Step 3: Run build**
|
||||
|
||||
Run: `cd /home/michaelchihlas/dev/patherly/frontend && npm run build`
|
||||
Expected: Build passes.
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/store/aiChatStore.ts frontend/src/pages/AIChatBuilderPage.tsx
|
||||
git commit -m "feat: wire procedural steps preview into AI chat builder page"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Update `generate_final_tree` Generation + Validation Wiring
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/core/ai_chat_service.py`
|
||||
|
||||
This task ensures the full `generate_final_tree` function properly handles the procedural path end-to-end, including the retry loop and validation import.
|
||||
|
||||
**Step 1: Add import for the new validator**
|
||||
|
||||
```python
|
||||
from app.core.ai_tree_validator import validate_generated_tree, validate_generated_procedural_steps
|
||||
```
|
||||
|
||||
**Step 2: Update the validation block in `generate_final_tree`**
|
||||
|
||||
Inside the `for attempt in range(2)` loop, after tree is extracted, replace the validation block:
|
||||
|
||||
```python
|
||||
if session.flow_type in ("procedural", "maintenance"):
|
||||
val_errors = validate_generated_procedural_steps(tree)
|
||||
else:
|
||||
val_errors = validate_generated_tree(tree)
|
||||
|
||||
if val_errors:
|
||||
if attempt == 0:
|
||||
provider_messages.append({"role": "assistant", "content": response_text})
|
||||
correction = (
|
||||
f"The generated structure has validation errors: {'; '.join(val_errors)}. "
|
||||
"Please fix these issues and respond with the corrected JSON only."
|
||||
)
|
||||
provider_messages.append({"role": "user", "content": correction})
|
||||
continue
|
||||
raise ValueError(f"Generated structure failed validation: {'; '.join(val_errors)}")
|
||||
```
|
||||
|
||||
**Step 3: Handle intake form from final generation**
|
||||
|
||||
After the `# Success` comment, before returning:
|
||||
|
||||
```python
|
||||
# Extract intake form from metadata if present
|
||||
if parsed.get("intake_form") and isinstance(parsed["intake_form"], list):
|
||||
metadata["intake_form"] = parsed["intake_form"]
|
||||
```
|
||||
|
||||
**Step 4: Run backend tests**
|
||||
|
||||
Run: `cd /home/michaelchihlas/dev/patherly/backend && python -m pytest tests/test_ai_chat.py -v --override-ini="addopts="`
|
||||
Expected: All tests pass.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/core/ai_chat_service.py
|
||||
git commit -m "feat: wire procedural validation into AI chat generate flow"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Final Integration Test + Build Verification
|
||||
|
||||
**Step 1: Run full backend test suite**
|
||||
|
||||
Run: `cd /home/michaelchihlas/dev/patherly/backend && python -m pytest tests/ --override-ini="addopts=" -v`
|
||||
Expected: All tests pass.
|
||||
|
||||
**Step 2: Run frontend build**
|
||||
|
||||
Run: `cd /home/michaelchihlas/dev/patherly/frontend && npm run build`
|
||||
Expected: Build passes with zero errors.
|
||||
|
||||
**Step 3: Final commit (if any remaining changes)**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore: final cleanup for procedural Flow Assist support"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `backend/app/core/ai_chat_service.py` | Add procedural schema/protocol/format prompts, dispatch by flow_type, parse `[STEPS_UPDATE]` + `[INTAKE_FORM]`, validate procedural structure, procedural generation instruction |
|
||||
| `backend/app/core/ai_tree_validator.py` | Add `validate_generated_procedural_steps()` function |
|
||||
| `backend/app/api/endpoints/ai_chat.py` | Handle intake form in import endpoint |
|
||||
| `frontend/src/components/ai-chat/StaticStepsPreview.tsx` | New procedural steps preview component |
|
||||
| `frontend/src/store/aiChatStore.ts` | Widen `workingTree`/`generatedTree` types for procedural |
|
||||
| `frontend/src/pages/AIChatBuilderPage.tsx` | Render `StaticStepsPreview` for procedural flows |
|
||||
Reference in New Issue
Block a user