diff --git a/frontend/src/pages/AssistantChatPage.tsx b/frontend/src/pages/AssistantChatPage.tsx index e7381287..2b0892d7 100644 --- a/frontend/src/pages/AssistantChatPage.tsx +++ b/frontend/src/pages/AssistantChatPage.tsx @@ -1,12 +1,13 @@ import { useState, useEffect, useRef, useCallback } from 'react' import { useLocation, useNavigate, useParams } from 'react-router-dom' -import { Sparkles, Send, Loader2, MessageSquare, Paperclip, Terminal, X, RotateCcw, ImagePlus, ListChecks, FileText, CheckCircle2, ArrowUpRight, MoreHorizontal, Pause } from 'lucide-react' +import { Sparkles, Send, Loader2, MessageSquare, Paperclip, Terminal, X, RotateCcw, ImagePlus, ListChecks, FileText, CheckCircle2, ArrowUpRight, MoreHorizontal, Pause, Plus } from 'lucide-react' 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 { integrationsApi } from '@/api/integrations' import { useBranching } from '@/hooks/useBranching' import { analytics } from '@/lib/analytics' import { toast } from '@/lib/toast' @@ -15,8 +16,10 @@ import { ChatMessage } from '@/components/assistant/ChatMessage' import { TaskLane, clearTaskState } from '@/components/assistant/TaskLane' import { ConcludeSessionModal } from '@/components/assistant/ConcludeSessionModal' import { StatusUpdateModal } from '@/components/flowpilot/StatusUpdateModal' +import { NewTicketModal } from '@/components/tickets/NewTicketModal' import type { ChatListItem, ConclusionOutcome } from '@/types/assistant-chat' import type { SuggestedFlow } from '@/types/copilot' +import type { PSATicketInfo } from '@/types/integrations' interface MessageWithMeta { role: 'user' | 'assistant' @@ -74,6 +77,9 @@ export default function AssistantChatPage() { ) const [activeSessionStatus, setActiveSessionStatus] = useState(null) const [activePsaTicketId, setActivePsaTicketId] = useState(null) + const [linkedTicket, setLinkedTicket] = useState(null) + const [showNewTicket, setShowNewTicket] = useState(false) + const [spinOffHint, setSpinOffHint] = useState(undefined) const [showOverflow, setShowOverflow] = useState(false) const toggleSidebarCollapse = () => { const next = !sidebarCollapsed @@ -239,6 +245,13 @@ export default function AssistantChatPage() { if (currentChatRef.current !== chatId) return setActiveSessionStatus(detail.status) setActivePsaTicketId(detail.psa_ticket_id) + if (detail.psa_ticket_id) { + integrationsApi.getTicket(detail.psa_ticket_id) + .then(setLinkedTicket) + .catch(() => {}) + } else { + setLinkedTicket(null) + } setMessages( (detail.conversation_messages || []).map(m => ({ role: m.role as 'user' | 'assistant', @@ -387,9 +400,17 @@ export default function AssistantChatPage() { } } - const handleTaskSubmit = async (responses: Array<{ type: string; state: string; value: string; text?: string; label?: string }>) => { + const handleTaskSubmit = async (responses: Array<{ type: string; state: string; value: string; text?: string; label?: string; command?: string | null }>) => { if (!activeChatId || loading) return + // Handle special action commands that open UI flows instead of sending to AI + const spinOffAction = responses.find(r => r.type === 'action' && r.command === 'create_spin_off_ticket') + if (spinOffAction) { + setSpinOffHint(spinOffAction.label || spinOffAction.text) + setShowNewTicket(true) + return + } + // Format task responses into a structured message for the AI. // Pending tasks are included so the AI knows they weren't completed yet. const parts: string[] = [] @@ -708,6 +729,14 @@ export default function AssistantChatPage() { {/* Desktop actions — shown when session is active and has messages */}
+ {activePsaTicketId && ( + + )} {isActive && ( <>
)