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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user