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 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-26 17:02:26 +00:00
parent db43dfa08e
commit d8e62a7108

View File

@@ -4,12 +4,16 @@ import { Sparkles, Send, Loader2, Flag, MessageSquare, Paperclip, Terminal, X, R
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { uploadsApi } from '@/api/uploads' import { uploadsApi } from '@/api/uploads'
import type { PendingUpload } from '@/types/upload' import type { PendingUpload } from '@/types/upload'
import type { ForkMetadata, ActionItem, QuestionItem } from '@/types/ai-session'
import { PageMeta } from '@/components/common/PageMeta' import { PageMeta } from '@/components/common/PageMeta'
import { aiSessionsApi } from '@/api/aiSessions' import { aiSessionsApi } from '@/api/aiSessions'
import { useBranching } from '@/hooks/useBranching'
import { BranchMap } from '@/components/session/BranchMap'
import { analytics } from '@/lib/analytics' import { analytics } from '@/lib/analytics'
import { toast } from '@/lib/toast' import { toast } from '@/lib/toast'
import { ChatSidebar } from '@/components/assistant/ChatSidebar' import { ChatSidebar } from '@/components/assistant/ChatSidebar'
import { ChatMessage } from '@/components/assistant/ChatMessage' import { ChatMessage } from '@/components/assistant/ChatMessage'
import { TaskLane } from '@/components/assistant/TaskLane'
import { ConcludeSessionModal } from '@/components/assistant/ConcludeSessionModal' import { ConcludeSessionModal } from '@/components/assistant/ConcludeSessionModal'
import type { ChatListItem, ConclusionOutcome } from '@/types/assistant-chat' import type { ChatListItem, ConclusionOutcome } from '@/types/assistant-chat'
import type { SuggestedFlow } from '@/types/copilot' import type { SuggestedFlow } from '@/types/copilot'
@@ -18,6 +22,9 @@ interface MessageWithMeta {
role: 'user' | 'assistant' role: 'user' | 'assistant'
content: string content: string
suggestedFlows?: SuggestedFlow[] suggestedFlows?: SuggestedFlow[]
fork?: ForkMetadata | null
actions?: ActionItem[] | null
questions?: QuestionItem[] | null
} }
export default function AssistantChatPage() { export default function AssistantChatPage() {
@@ -30,11 +37,15 @@ export default function AssistantChatPage() {
const [input, setInput] = useState('') const [input, setInput] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [showConclude, setShowConclude] = useState(false) const [showConclude, setShowConclude] = useState(false)
const branching = useBranching()
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false) const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false)
const [showLogs, setShowLogs] = useState(false) const [showLogs, setShowLogs] = useState(false)
const [logContent, setLogContent] = useState('') const [logContent, setLogContent] = useState('')
const [pendingUploads, setPendingUploads] = useState<PendingUpload[]>([]) const [pendingUploads, setPendingUploads] = useState<PendingUpload[]>([])
const [isDragOver, setIsDragOver] = useState(false) const [isDragOver, setIsDragOver] = useState(false)
const [activeQuestions, setActiveQuestions] = useState<QuestionItem[]>([])
const [activeActions, setActiveActions] = useState<ActionItem[]>([])
const [showTaskLane, setShowTaskLane] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null) const messagesEndRef = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLTextAreaElement>(null) const inputRef = useRef<HTMLTextAreaElement>(null)
const fileInputRef = useRef<HTMLInputElement>(null) const fileInputRef = useRef<HTMLInputElement>(null)
@@ -88,7 +99,7 @@ export default function AssistantChatPage() {
}) })
setMessages(prev => [ setMessages(prev => [
...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 => setChats(prev =>
prev.map(c => prev.map(c =>
@@ -97,6 +108,17 @@ export default function AssistantChatPage() {
: c : 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 { } catch {
toast.error('Failed to start AI conversation') toast.error('Failed to start AI conversation')
} finally { } finally {
@@ -131,6 +153,10 @@ export default function AssistantChatPage() {
const selectChat = useCallback(async (chatId: string) => { const selectChat = useCallback(async (chatId: string) => {
setActiveChatId(chatId) setActiveChatId(chatId)
// Clear TaskLane when switching chats
setShowTaskLane(false)
setActiveQuestions([])
setActiveActions([])
try { try {
const detail = await aiSessionsApi.getSession(chatId) const detail = await aiSessionsApi.getSession(chatId)
setMessages( setMessages(
@@ -161,6 +187,10 @@ export default function AssistantChatPage() {
setChats(prev => [chatItem, ...prev]) setChats(prev => [chatItem, ...prev])
setActiveChatId(session.session_id) setActiveChatId(session.session_id)
setMessages([]) setMessages([])
// Clear TaskLane from previous session
setShowTaskLane(false)
setActiveQuestions([])
setActiveActions([])
} catch { } catch {
toast.error('Failed to create chat') toast.error('Failed to create chat')
} }
@@ -184,6 +214,19 @@ export default function AssistantChatPage() {
toast.info('Pin feature coming soon') 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 () => { const handleSend = async () => {
if (!input.trim() || !activeChatId || loading) return if (!input.trim() || !activeChatId || loading) return
@@ -204,7 +247,7 @@ export default function AssistantChatPage() {
analytics.aiFeatureUsed({ feature: 'assistant_chat' }) analytics.aiFeatureUsed({ feature: 'assistant_chat' })
setMessages(prev => [ setMessages(prev => [
...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 => setChats(prev =>
prev.map(c => prev.map(c =>
@@ -213,6 +256,18 @@ export default function AssistantChatPage() {
: c : 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 { } catch {
setMessages(prev => [ setMessages(prev => [
...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<string> => { const handleConclude = async (outcome: ConclusionOutcome, _notes: string): Promise<string> => {
if (!activeChatId) throw new Error('No active chat') 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 }) const response = await aiSessionsApi.sendChatMessage(session.session_id, { message: resumePrompt })
setMessages(prev => [ setMessages(prev => [
...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 => setChats(prev =>
prev.map(c => prev.map(c =>
@@ -277,6 +382,17 @@ export default function AssistantChatPage() {
: c : 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 { } catch {
toast.error('Failed to create resume chat') toast.error('Failed to create resume chat')
} finally { } finally {
@@ -392,7 +508,8 @@ export default function AssistantChatPage() {
/> />
</div> </div>
{/* Main chat area */} {/* Main chat area + optional branch sidebar */}
<div className="flex-1 flex min-w-0">
<div className="flex-1 flex flex-col min-w-0"> <div className="flex-1 flex flex-col min-w-0">
{/* Mobile header with chat history toggle */} {/* Mobile header with chat history toggle */}
<div className="sm:hidden flex items-center gap-2 px-3 py-2 border-b border-border shrink-0"> <div className="sm:hidden flex items-center gap-2 px-3 py-2 border-b border-border shrink-0">
@@ -452,7 +569,7 @@ export default function AssistantChatPage() {
</div> </div>
{/* Rich Input */} {/* Rich Input */}
<div className="px-3 sm:px-6 py-3 shrink-0 border-t border-border"> <div className={cn("px-3 sm:px-6 py-3 shrink-0", !showTaskLane && "border-t border-border")}>
<div <div
className="max-w-3xl mx-auto" className="max-w-3xl mx-auto"
onDragOver={handleDragOver} onDragOver={handleDragOver}
@@ -593,6 +710,22 @@ export default function AssistantChatPage() {
)} )}
</div> </div>
{/* Task lane — slides in when AI sends questions or actions */}
{showTaskLane && (activeQuestions.length > 0 || activeActions.length > 0) && (
<TaskLane
questions={activeQuestions}
actions={activeActions}
onSubmit={handleTaskSubmit}
onClose={() => 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. */}
</div>
{/* Conclude Session Modal */} {/* Conclude Session Modal */}
<ConcludeSessionModal <ConcludeSessionModal
isOpen={showConclude} isOpen={showConclude}