From d8e62a71084f45eb56232d13d697ba3eb570c7bc Mon Sep 17 00:00:00 2001 From: chihlasm Date: Thu, 26 Mar 2026 17:02:26 +0000 Subject: [PATCH] fix: clear TaskLane on chat switch and remove double border - Reset TaskLane state in handleNewChat and selectChat - Conditionally remove chat input top border when TaskLane is open Co-Authored-By: Claude Opus 4.6 --- frontend/src/pages/AssistantChatPage.tsx | 143 ++++++++++++++++++++++- 1 file changed, 138 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/AssistantChatPage.tsx b/frontend/src/pages/AssistantChatPage.tsx index 2cb0058c..28f6c159 100644 --- a/frontend/src/pages/AssistantChatPage.tsx +++ b/frontend/src/pages/AssistantChatPage.tsx @@ -4,12 +4,16 @@ import { Sparkles, Send, Loader2, Flag, MessageSquare, Paperclip, Terminal, X, R import { cn } from '@/lib/utils' import { uploadsApi } from '@/api/uploads' import type { PendingUpload } from '@/types/upload' +import type { ForkMetadata, ActionItem, QuestionItem } from '@/types/ai-session' import { PageMeta } from '@/components/common/PageMeta' import { aiSessionsApi } from '@/api/aiSessions' +import { useBranching } from '@/hooks/useBranching' +import { BranchMap } from '@/components/session/BranchMap' import { analytics } from '@/lib/analytics' import { toast } from '@/lib/toast' import { ChatSidebar } from '@/components/assistant/ChatSidebar' import { ChatMessage } from '@/components/assistant/ChatMessage' +import { TaskLane } from '@/components/assistant/TaskLane' import { ConcludeSessionModal } from '@/components/assistant/ConcludeSessionModal' import type { ChatListItem, ConclusionOutcome } from '@/types/assistant-chat' import type { SuggestedFlow } from '@/types/copilot' @@ -18,6 +22,9 @@ interface MessageWithMeta { role: 'user' | 'assistant' content: string suggestedFlows?: SuggestedFlow[] + fork?: ForkMetadata | null + actions?: ActionItem[] | null + questions?: QuestionItem[] | null } export default function AssistantChatPage() { @@ -30,11 +37,15 @@ export default function AssistantChatPage() { const [input, setInput] = useState('') const [loading, setLoading] = useState(false) const [showConclude, setShowConclude] = useState(false) + const branching = useBranching() const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false) const [showLogs, setShowLogs] = useState(false) const [logContent, setLogContent] = useState('') const [pendingUploads, setPendingUploads] = useState([]) const [isDragOver, setIsDragOver] = useState(false) + const [activeQuestions, setActiveQuestions] = useState([]) + const [activeActions, setActiveActions] = useState([]) + const [showTaskLane, setShowTaskLane] = useState(false) const messagesEndRef = useRef(null) const inputRef = useRef(null) const fileInputRef = useRef(null) @@ -88,7 +99,7 @@ export default function AssistantChatPage() { }) setMessages(prev => [ ...prev, - { role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows }, + { role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows, fork: response.fork, actions: response.actions, questions: response.questions }, ]) setChats(prev => prev.map(c => @@ -97,6 +108,17 @@ export default function AssistantChatPage() { : c ) ) + // Show task lane if AI sent questions or actions + if (response.fork && session.session_id) { + branching.loadBranches(session.session_id) + } + const hasQuestions = response.questions && response.questions.length > 0 + const hasActions = response.actions && response.actions.length > 0 + if (hasQuestions || hasActions) { + setActiveQuestions(response.questions || []) + setActiveActions(response.actions || []) + setShowTaskLane(true) + } } catch { toast.error('Failed to start AI conversation') } finally { @@ -131,6 +153,10 @@ export default function AssistantChatPage() { const selectChat = useCallback(async (chatId: string) => { setActiveChatId(chatId) + // Clear TaskLane when switching chats + setShowTaskLane(false) + setActiveQuestions([]) + setActiveActions([]) try { const detail = await aiSessionsApi.getSession(chatId) setMessages( @@ -161,6 +187,10 @@ export default function AssistantChatPage() { setChats(prev => [chatItem, ...prev]) setActiveChatId(session.session_id) setMessages([]) + // Clear TaskLane from previous session + setShowTaskLane(false) + setActiveQuestions([]) + setActiveActions([]) } catch { toast.error('Failed to create chat') } @@ -184,6 +214,19 @@ export default function AssistantChatPage() { toast.info('Pin feature coming soon') } + const handleBranchSwitch = async (branchId: string) => { + if (!activeChatId || loading) return + const result = await branching.switchBranch(activeChatId, branchId) + if (!result) return + + // Load the branch's conversation into the chat + const branchMessages: MessageWithMeta[] = (result.conversation_messages || []).map(m => ({ + role: m.role as 'user' | 'assistant', + content: m.content, + })) + setMessages(branchMessages) + } + const handleSend = async () => { if (!input.trim() || !activeChatId || loading) return @@ -204,7 +247,7 @@ export default function AssistantChatPage() { analytics.aiFeatureUsed({ feature: 'assistant_chat' }) setMessages(prev => [ ...prev, - { role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows }, + { role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows, fork: response.fork, actions: response.actions, questions: response.questions }, ]) setChats(prev => prev.map(c => @@ -213,6 +256,18 @@ export default function AssistantChatPage() { : c ) ) + // Load branches if fork was created + if (response.fork && activeChatId) { + branching.loadBranches(activeChatId) + } + // Show task lane if AI sent questions or actions + const hasQuestions = response.questions && response.questions.length > 0 + const hasActions = response.actions && response.actions.length > 0 + if (hasQuestions || hasActions) { + setActiveQuestions(response.questions || []) + setActiveActions(response.actions || []) + setShowTaskLane(true) + } } catch { setMessages(prev => [ ...prev, @@ -224,6 +279,56 @@ export default function AssistantChatPage() { } } + const handleTaskSubmit = async (responses: Array<{ type: string; state: string; value: string; text?: string; label?: string }>) => { + if (!activeChatId || loading) return + + // Format task responses into a structured message for the AI + 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') + + // Close the task lane + setShowTaskLane(false) + setActiveQuestions([]) + setActiveActions([]) + + setMessages(prev => [...prev, { role: 'user', content: userMessage }]) + setLoading(true) + + try { + const response = await aiSessionsApi.sendChatMessage(activeChatId, { message: userMessage }) + setMessages(prev => [ + ...prev, + { role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows, fork: response.fork, actions: response.actions, questions: response.questions }, + ]) + if (response.fork && activeChatId) { + branching.loadBranches(activeChatId) + } + // Show task lane again if AI sends more tasks + const hasQuestions = response.questions && response.questions.length > 0 + const hasActions = response.actions && response.actions.length > 0 + if (hasQuestions || hasActions) { + setActiveQuestions(response.questions || []) + setActiveActions(response.actions || []) + setShowTaskLane(true) + } + } catch { + setMessages(prev => [ + ...prev, + { role: 'assistant', content: 'Sorry, something went wrong processing your responses. Please try again.' }, + ]) + } finally { + setLoading(false) + } + } + const handleConclude = async (outcome: ConclusionOutcome, _notes: string): Promise => { if (!activeChatId) throw new Error('No active chat') @@ -268,7 +373,7 @@ export default function AssistantChatPage() { const response = await aiSessionsApi.sendChatMessage(session.session_id, { message: resumePrompt }) setMessages(prev => [ ...prev, - { role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows }, + { role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows, fork: response.fork, actions: response.actions, questions: response.questions }, ]) setChats(prev => prev.map(c => @@ -277,6 +382,17 @@ export default function AssistantChatPage() { : c ) ) + // Show task lane if AI sent questions or actions + if (response.fork && session.session_id) { + branching.loadBranches(session.session_id) + } + const hasQuestions = response.questions && response.questions.length > 0 + const hasActions = response.actions && response.actions.length > 0 + if (hasQuestions || hasActions) { + setActiveQuestions(response.questions || []) + setActiveActions(response.actions || []) + setShowTaskLane(true) + } } catch { toast.error('Failed to create resume chat') } finally { @@ -392,7 +508,8 @@ export default function AssistantChatPage() { /> - {/* Main chat area */} + {/* Main chat area + optional branch sidebar */} +
{/* Mobile header with chat history toggle */}
@@ -452,7 +569,7 @@ export default function AssistantChatPage() {
{/* Rich Input */} -
+
+ {/* Task lane — slides in when AI sends questions or actions */} + {showTaskLane && (activeQuestions.length > 0 || activeActions.length > 0) && ( + setShowTaskLane(false)} + loading={loading} + /> + )} + + {/* Branch map hidden — branching is now silent/background only. + Branches are tracked in the DB but not shown to the user. + The AI manages branch context internally. */} +
+ {/* Conclude Session Modal */}