feat: add useEditorAI hook and editorAI API client
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
42
frontend/src/api/editorAI.ts
Normal file
42
frontend/src/api/editorAI.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { apiClient } from './client'
|
||||
import type { AIActionType } from '@/types'
|
||||
|
||||
export interface SendMessageParams {
|
||||
sessionId: string
|
||||
content: string
|
||||
actionType?: AIActionType
|
||||
focalNodeId?: string | null
|
||||
}
|
||||
|
||||
export const editorAIApi = {
|
||||
startSession: async (flowType: 'troubleshooting' | 'procedural', treeId?: string) => {
|
||||
const { data } = await apiClient.post('/ai/chat/sessions', {
|
||||
flow_type: flowType,
|
||||
tree_id: treeId,
|
||||
})
|
||||
return data
|
||||
},
|
||||
|
||||
sendMessage: async ({ sessionId, content, actionType, focalNodeId }: SendMessageParams) => {
|
||||
const { data } = await apiClient.post(`/ai/chat/sessions/${sessionId}/messages`, {
|
||||
content,
|
||||
action_type: actionType || 'open_chat',
|
||||
focal_node_id: focalNodeId,
|
||||
})
|
||||
return data
|
||||
},
|
||||
|
||||
getSession: async (sessionId: string) => {
|
||||
const { data } = await apiClient.get(`/ai/chat/sessions/${sessionId}`)
|
||||
return data
|
||||
},
|
||||
|
||||
generateFull: async (sessionId: string) => {
|
||||
const { data } = await apiClient.post(`/ai/chat/sessions/${sessionId}/generate`)
|
||||
return data
|
||||
},
|
||||
|
||||
abandonSession: async (sessionId: string) => {
|
||||
await apiClient.delete(`/ai/chat/sessions/${sessionId}`)
|
||||
},
|
||||
}
|
||||
151
frontend/src/hooks/useEditorAI.ts
Normal file
151
frontend/src/hooks/useEditorAI.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { useState, useCallback, useRef } from 'react'
|
||||
import { editorAIApi } from '@/api/editorAI'
|
||||
import type {
|
||||
AIActionType,
|
||||
EditorAIChatMessage,
|
||||
AISuggestion,
|
||||
ContextMenuPosition,
|
||||
} from '@/types'
|
||||
|
||||
interface UseEditorAIOptions {
|
||||
flowType: 'troubleshooting' | 'procedural'
|
||||
treeId?: string | null
|
||||
}
|
||||
|
||||
export function useEditorAI({ flowType, treeId }: UseEditorAIOptions) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [focalNodeId, setFocalNodeId] = useState<string | null>(null)
|
||||
const [contextMenu, setContextMenu] = useState<{
|
||||
position: ContextMenuPosition
|
||||
nodeId: string
|
||||
} | null>(null)
|
||||
const [sessionId, setSessionId] = useState<string | null>(null)
|
||||
const [messages, setMessages] = useState<EditorAIChatMessage[]>([])
|
||||
const [input, setInput] = useState('')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [suggestions, setSuggestions] = useState<AISuggestion[]>([])
|
||||
|
||||
const pendingActionRef = useRef<AIActionType>('open_chat')
|
||||
|
||||
const ensureSession = useCallback(async () => {
|
||||
if (sessionId) return sessionId
|
||||
try {
|
||||
const result = await editorAIApi.startSession(flowType, treeId || undefined)
|
||||
setSessionId(result.session_id)
|
||||
if (result.greeting) {
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
role: 'assistant' as const,
|
||||
content: result.greeting,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
])
|
||||
}
|
||||
return result.session_id
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}, [sessionId, flowType, treeId])
|
||||
|
||||
const openPanel = useCallback((nodeId?: string, actionType?: AIActionType) => {
|
||||
setIsOpen(true)
|
||||
if (nodeId) setFocalNodeId(nodeId)
|
||||
if (actionType) pendingActionRef.current = actionType
|
||||
}, [])
|
||||
|
||||
const closePanel = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
setContextMenu(null)
|
||||
}, [])
|
||||
|
||||
const openContextMenu = useCallback((e: React.MouseEvent, nodeId: string) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setContextMenu({ position: { x: e.clientX, y: e.clientY }, nodeId })
|
||||
}, [])
|
||||
|
||||
const closeContextMenu = useCallback(() => {
|
||||
setContextMenu(null)
|
||||
}, [])
|
||||
|
||||
const sendMessage = useCallback(async () => {
|
||||
if (!input.trim() || isLoading) return
|
||||
|
||||
const currentInput = input
|
||||
const currentAction = pendingActionRef.current
|
||||
const currentFocalNodeId = focalNodeId
|
||||
|
||||
const userMessage: EditorAIChatMessage = {
|
||||
role: 'user',
|
||||
content: currentInput,
|
||||
timestamp: new Date().toISOString(),
|
||||
action_type: currentAction,
|
||||
}
|
||||
setMessages((prev) => [...prev, userMessage])
|
||||
setInput('')
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const sid = await ensureSession()
|
||||
if (!sid) return
|
||||
|
||||
const result = await editorAIApi.sendMessage({
|
||||
sessionId: sid,
|
||||
content: currentInput,
|
||||
actionType: currentAction,
|
||||
focalNodeId: currentFocalNodeId,
|
||||
})
|
||||
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
role: 'assistant',
|
||||
content: result.content,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
])
|
||||
} catch {
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'Sorry, something went wrong. Please try again.',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
])
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
pendingActionRef.current = 'open_chat'
|
||||
}
|
||||
}, [input, isLoading, ensureSession, focalNodeId])
|
||||
|
||||
const triggerAction = useCallback(
|
||||
(nodeId: string, actionType: AIActionType, prompt: string) => {
|
||||
setFocalNodeId(nodeId)
|
||||
pendingActionRef.current = actionType
|
||||
setInput(prompt)
|
||||
setIsOpen(true)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
openPanel,
|
||||
closePanel,
|
||||
focalNodeId,
|
||||
setFocalNodeId,
|
||||
contextMenu,
|
||||
openContextMenu,
|
||||
closeContextMenu,
|
||||
messages,
|
||||
input,
|
||||
setInput,
|
||||
sendMessage,
|
||||
isLoading,
|
||||
suggestions,
|
||||
setSuggestions,
|
||||
triggerAction,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user