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>
This commit is contained in:
@@ -172,6 +172,7 @@ async def post_message(
|
|||||||
session, data.content, db,
|
session, data.content, db,
|
||||||
action_type=data.action_type or "open_chat",
|
action_type=data.action_type or "open_chat",
|
||||||
focal_node_id=data.focal_node_id,
|
focal_node_id=data.focal_node_id,
|
||||||
|
flow_context=data.flow_context,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("AI chat message failed: %s", e)
|
logger.exception("AI chat message failed: %s", e)
|
||||||
|
|||||||
@@ -533,13 +533,33 @@ async def send_message(
|
|||||||
db: AsyncSession,
|
db: AsyncSession,
|
||||||
action_type: str = "open_chat",
|
action_type: str = "open_chat",
|
||||||
focal_node_id: str | None = None,
|
focal_node_id: str | None = None,
|
||||||
|
flow_context: dict | None = None,
|
||||||
) -> tuple[str, Optional[dict], Optional[str], Optional[dict]]:
|
) -> tuple[str, Optional[dict], Optional[str], Optional[dict]]:
|
||||||
"""Send a user message and get AI response.
|
"""Send a user message and get AI response.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flow_context: Live flow structure from the editor. Contains the current
|
||||||
|
tree_structure (troubleshooting) or steps + intake_form (procedural).
|
||||||
|
This gives the AI full awareness of the flow being edited.
|
||||||
|
|
||||||
Returns (ai_content, working_tree_update, new_phase, metadata_update).
|
Returns (ai_content, working_tree_update, new_phase, metadata_update).
|
||||||
"""
|
"""
|
||||||
system_prompt = _build_system_prompt(session.flow_type)
|
system_prompt = _build_system_prompt(session.flow_type)
|
||||||
|
|
||||||
|
# Inject live flow context so the AI can see current editor state
|
||||||
|
if flow_context:
|
||||||
|
context_json = json.dumps(flow_context, indent=2)
|
||||||
|
system_prompt += (
|
||||||
|
f"\n\nCURRENT FLOW STATE (live from editor):\n{context_json}"
|
||||||
|
)
|
||||||
|
if focal_node_id:
|
||||||
|
focal_node = _find_node_by_id(flow_context, focal_node_id)
|
||||||
|
if focal_node:
|
||||||
|
system_prompt += (
|
||||||
|
f"\n\nFOCAL NODE/STEP (the item being acted on):\n"
|
||||||
|
f"{json.dumps(focal_node, indent=2)}"
|
||||||
|
)
|
||||||
|
|
||||||
# Build messages array from conversation history
|
# Build messages array from conversation history
|
||||||
now_iso = datetime.now(timezone.utc).isoformat()
|
now_iso = datetime.now(timezone.utc).isoformat()
|
||||||
history = list(session.conversation_history)
|
history = list(session.conversation_history)
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ class AIChatMessageRequest(BaseModel):
|
|||||||
default=None,
|
default=None,
|
||||||
description="ID of the node/step being acted on",
|
description="ID of the node/step being acted on",
|
||||||
)
|
)
|
||||||
|
flow_context: Optional[dict[str, Any]] = Field(
|
||||||
|
default=None,
|
||||||
|
description="Live flow structure from the editor (tree structure, steps, intake form)",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AIChatImportRequest(BaseModel):
|
class AIChatImportRequest(BaseModel):
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export interface SendMessageParams {
|
|||||||
content: string
|
content: string
|
||||||
actionType?: AIActionType
|
actionType?: AIActionType
|
||||||
focalNodeId?: string | null
|
focalNodeId?: string | null
|
||||||
|
flowContext?: Record<string, unknown> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const editorAIApi = {
|
export const editorAIApi = {
|
||||||
@@ -17,11 +18,12 @@ export const editorAIApi = {
|
|||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
|
|
||||||
sendMessage: async ({ sessionId, content, actionType, focalNodeId }: SendMessageParams) => {
|
sendMessage: async ({ sessionId, content, actionType, focalNodeId, flowContext }: SendMessageParams) => {
|
||||||
const { data } = await apiClient.post(`/ai/chat/sessions/${sessionId}/messages`, {
|
const { data } = await apiClient.post(`/ai/chat/sessions/${sessionId}/messages`, {
|
||||||
content,
|
content,
|
||||||
action_type: actionType || 'open_chat',
|
action_type: actionType || 'open_chat',
|
||||||
focal_node_id: focalNodeId,
|
focal_node_id: focalNodeId,
|
||||||
|
flow_context: flowContext || undefined,
|
||||||
})
|
})
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import type {
|
|||||||
interface UseEditorAIOptions {
|
interface UseEditorAIOptions {
|
||||||
flowType: 'troubleshooting' | 'procedural'
|
flowType: 'troubleshooting' | 'procedural'
|
||||||
treeId?: string | null
|
treeId?: string | null
|
||||||
|
/** Returns the live flow structure from the editor for AI context */
|
||||||
|
getFlowContext?: () => Record<string, unknown> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEditorAI({ flowType, treeId }: UseEditorAIOptions) {
|
export function useEditorAI({ flowType, treeId, getFlowContext }: UseEditorAIOptions) {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [focalNodeId, setFocalNodeId] = useState<string | null>(null)
|
const [focalNodeId, setFocalNodeId] = useState<string | null>(null)
|
||||||
const [contextMenu, setContextMenu] = useState<{
|
const [contextMenu, setContextMenu] = useState<{
|
||||||
@@ -95,6 +97,7 @@ export function useEditorAI({ flowType, treeId }: UseEditorAIOptions) {
|
|||||||
content: currentInput,
|
content: currentInput,
|
||||||
actionType: currentAction,
|
actionType: currentAction,
|
||||||
focalNodeId: currentFocalNodeId,
|
focalNodeId: currentFocalNodeId,
|
||||||
|
flowContext: getFlowContext?.() || null,
|
||||||
})
|
})
|
||||||
|
|
||||||
setMessages((prev) => [
|
setMessages((prev) => [
|
||||||
@@ -118,7 +121,7 @@ export function useEditorAI({ flowType, treeId }: UseEditorAIOptions) {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
pendingActionRef.current = 'open_chat'
|
pendingActionRef.current = 'open_chat'
|
||||||
}
|
}
|
||||||
}, [input, isLoading, ensureSession, focalNodeId])
|
}, [input, isLoading, ensureSession, focalNodeId, getFlowContext])
|
||||||
|
|
||||||
const triggerAction = useCallback(
|
const triggerAction = useCallback(
|
||||||
(nodeId: string, actionType: AIActionType, prompt: string) => {
|
(nodeId: string, actionType: AIActionType, prompt: string) => {
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ export function ProceduralEditorPage() {
|
|||||||
const editorAI = useEditorAI({
|
const editorAI = useEditorAI({
|
||||||
flowType: 'procedural',
|
flowType: 'procedural',
|
||||||
treeId: id,
|
treeId: id,
|
||||||
|
getFlowContext: useCallback(() => {
|
||||||
|
return {
|
||||||
|
steps: steps as unknown as Record<string, unknown>[],
|
||||||
|
intake_form: intakeForm,
|
||||||
|
}
|
||||||
|
}, [steps, intakeForm]),
|
||||||
})
|
})
|
||||||
|
|
||||||
const isMaintenance = treeType === 'maintenance'
|
const isMaintenance = treeType === 'maintenance'
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ export function TreeEditorPage() {
|
|||||||
const editorAI = useEditorAI({
|
const editorAI = useEditorAI({
|
||||||
flowType: 'troubleshooting',
|
flowType: 'troubleshooting',
|
||||||
treeId: id,
|
treeId: id,
|
||||||
|
getFlowContext: useCallback(() => {
|
||||||
|
if (!treeStructure) return null
|
||||||
|
return treeStructure as unknown as Record<string, unknown>
|
||||||
|
}, [treeStructure]),
|
||||||
})
|
})
|
||||||
|
|
||||||
const previousEditingNodeRef = useRef<string | null>(null)
|
const previousEditingNodeRef = useRef<string | null>(null)
|
||||||
|
|||||||
Reference in New Issue
Block a user