diff --git a/frontend/src/pages/FlowPilotPage.tsx b/frontend/src/pages/FlowPilotPage.tsx new file mode 100644 index 00000000..6ff76b53 --- /dev/null +++ b/frontend/src/pages/FlowPilotPage.tsx @@ -0,0 +1,368 @@ +import { useEffect } from 'react' +import { Sparkles, Send, Loader2, Flag, MessageSquare, Paperclip, Terminal, X, RotateCcw, ImagePlus, ListChecks, FileText } from 'lucide-react' +import { cn } from '@/lib/utils' +import { PageMeta } from '@/components/common/PageMeta' +import { aiSessionsApi } from '@/api/aiSessions' +import { ChatSidebar, ChatSidebarCollapsedBar } from '@/components/assistant/ChatSidebar' +import { ChatMessage } from '@/components/assistant/ChatMessage' +import { TaskLane } from '@/components/assistant/TaskLane' +import { ConcludeSessionModal } from '@/components/assistant/ConcludeSessionModal' +import { StatusUpdateModal } from '@/components/flowpilot/StatusUpdateModal' +// TODO: uncomment after Task 4 +// import { ViewToggle } from '@/components/assistant/ViewToggle' +import { useAssistantSession } from '@/hooks/useAssistantSession' + +export default function FlowPilotPage() { + const session = useAssistantSession() + + // Handle prefill from dashboard / command palette + useEffect(() => { + session.handlePrefill('/assistant') + }, []) // eslint-disable-line react-hooks/exhaustive-deps + + const handleTaskSubmit = async (responses: Array<{ type: string; state: string; value: string; text?: string; label?: string }>) => { + if (!session.activeChatId || session.loading) return + + const parts: string[] = [] + for (const r of responses) { + const name = r.type === 'question' ? `Q: ${r.text}` : r.label || 'Check' + if (r.state === 'done' && r.value.trim()) { + parts.push(`**${name}:**\n\`\`\`\n${r.value.trim()}\n\`\`\``) + } else if (r.state === 'skipped') { + parts.push(`**${name}:** _(skipped)_`) + } + } + const userMessage = parts.join('\n\n') + const sendChatId = session.activeChatId + + session.setInput('') + session.setShowTaskLane(false) + session.setActiveQuestions([]) + session.setActiveActions([]) + + try { + const response = await aiSessionsApi.sendChatMessage(sendChatId, { message: userMessage }) + if (session.currentChatRef.current !== sendChatId) return + session.processResponse(response, sendChatId) + + const hasQuestions = response.questions && response.questions.length > 0 + const hasActions = response.actions && response.actions.length > 0 + if (!hasQuestions && !hasActions) { + session.setShowTaskLane(false) + session.setActiveQuestions([]) + session.setActiveActions([]) + } + } catch { + // Error handled by processResponse guard + } + } + + return ( + <> + +
+ {/* Chat Sidebar — desktop */} + {!session.sidebarCollapsed && ( +
+ +
+ )} + {/* Chat Sidebar — mobile */} +
+ session.setMobileSidebarOpen(false)} + /> +
+ + {/* Main area */} +
+ {/* Collapsed sidebar bar */} + {session.sidebarCollapsed && ( +
+ +
+ )} + + {/* Chat content row */} +
+
+ {/* Mobile header */} +
+ +
+ {/* TODO: uncomment after Task 4 */} + {/* {session.activeChatId && ( + + )} */} + +
+ + {session.activeChatId ? ( + <> + {/* Desktop view toggle bar */} +
+ {/* TODO: uncomment after Task 4 */} + {/* */} +
+ + {/* Messages */} +
+ {session.messages.length === 0 && !session.loading && ( +
+
+ +
+

+ FlowPilot +

+

+ Ask me anything about IT infrastructure, networking, Active Directory, + cloud platforms, or troubleshooting. I'll also suggest relevant flows from your team's library. +

+
+ )} + {session.messages.map((msg, i) => ( + + ))} + {session.loading && ( +
+
+ +
+
+ +
+
+ )} +
+
+ + {/* Rich Input */} +
+
+
+ {session.isDragOver && ( +
+
+ + Drop files to attach +
+
+ )} + +