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 { 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<PendingUpload[]>([])
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 inputRef = useRef<HTMLTextAreaElement>(null)
const fileInputRef = useRef<HTMLInputElement>(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<string> => {
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() {
/>
</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">
{/* 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">
@@ -452,7 +569,7 @@ export default function AssistantChatPage() {
</div>
{/* 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
className="max-w-3xl mx-auto"
onDragOver={handleDragOver}
@@ -593,6 +710,22 @@ export default function AssistantChatPage() {
)}
</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 */}
<ConcludeSessionModal
isOpen={showConclude}