From 614b0ed43aa3ee77fc004d941c7584757831ebbe Mon Sep 17 00:00:00 2001 From: Tracewebstudio Dev Date: Fri, 14 Aug 2026 18:06:07 +0200 Subject: [PATCH] feat(ai-chat): wire action buttons + ticket confirm flow in AiChatWidget ChatMessage gains actionType, userQuery, confirmed fields. ChatResponse gains action_type from backend. Action buttons now render below assistant messages: - Navigation actions (open_help_search, open_billing, etc.) show a link chip to the correct dashboard section via navUrl() - ticket_pending / create_ticket action shows a 'Create Support Ticket' button that calls POST /api/ai/chat/confirm, marks the message confirmed (hides the button), and appends the ticket result as a new assistant message Helper functions navUrl() and actionLabel() map suggested_action keys to dashboard URLs and human-readable labels respectively. Co-Authored-By: Claude Sonnet 4.6 --- src/components/AiChatWidget.tsx | 253 ++++++++++++++++++++++++++------ 1 file changed, 210 insertions(+), 43 deletions(-) diff --git a/src/components/AiChatWidget.tsx b/src/components/AiChatWidget.tsx index 2dc20c9..8cec2b7 100644 --- a/src/components/AiChatWidget.tsx +++ b/src/components/AiChatWidget.tsx @@ -9,6 +9,12 @@ interface ChatMessage { intent?: string; status?: string; suggestedAction?: string; + /** Semantic category: ticket_created | ticket_pending | kb_results | usage_info | navigation */ + actionType?: string; + /** Original user text — stored so ticket confirm has a description to send */ + userQuery?: string; + /** True once the user has clicked the confirm/action button on this message */ + confirmed?: boolean; } interface ChatResponse { @@ -22,6 +28,7 @@ interface ChatResponse { message?: string; status?: string; suggested_action?: string; + action_type?: string; } function statusLabel(status?: string): string | null { @@ -37,6 +44,43 @@ function statusLabel(status?: string): string | null { } } +/** Returns the dashboard URL to navigate to for a given suggested_action key. */ +function navUrl(action: string | undefined): string | null { + switch (action) { + case "open_support_ticket": return "/dashboard?nav=support"; + case "open_help_search": return "/help-center"; + case "open_account_settings": return "/dashboard?nav=settings"; + case "open_billing": + case "show_usage_modal": return "/dashboard?nav=credits"; + case "open_jd_generator": return "/dashboard?nav=job_descriptions"; + case "open_cover_letter": return "/dashboard?nav=cover_letter"; + case "open_resume_tailor": return "/dashboard?nav=resume"; + case "open_lead_unlock": return "/dashboard?nav=leads"; + case "open_auto_apply": return "/dashboard?nav=auto_apply"; + case "open_form_extract": return "/dashboard?nav=form_extract"; + default: return null; + } +} + +/** Human-readable label for an action button. */ +function actionLabel(action: string | undefined): string { + switch (action) { + case "open_support_ticket": return "View Tickets →"; + case "open_help_search": return "Browse Help Center →"; + case "open_account_settings": return "Account Settings →"; + case "open_billing": + case "show_usage_modal": return "AI Credits & Billing →"; + case "open_jd_generator": return "Job Description Generator →"; + case "open_cover_letter": return "Cover Letter Generator →"; + case "open_resume_tailor": return "Resume Tailor →"; + case "open_lead_unlock": return "Lead Requests →"; + case "open_auto_apply": return "Auto Apply →"; + case "open_form_extract": return "Form Assistant →"; + case "create_ticket": return "Create Support Ticket"; + default: return ""; + } +} + interface UsageStatus { remaining_credits: number; remaining_daily_actions: number; @@ -149,6 +193,9 @@ export function AiChatWidget() { intent: data.intent, status: data.status, suggestedAction: data.suggested_action, + actionType: data.action_type, + userQuery: text, + confirmed: false, }; setMessages((prev) => [...prev, assistantMessage]); } catch (err) { @@ -172,6 +219,65 @@ export function AiChatWidget() { } }; + /** Called when user clicks "Create Support Ticket" on a ticket_pending message. */ + const confirmCreateTicket = async (msgIdx: number, userQuery: string) => { + // Mark the originating message as confirmed so the button disappears immediately + setMessages((prev) => prev.map((m, i) => (i === msgIdx ? { ...m, confirmed: true } : m))); + setIsLoading(true); + try { + const res = await fetch(`${API}/ai/chat/confirm`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${sessionStorage.getItem("nxtgauge_access_token") || ""}`, + }, + credentials: "include", + body: JSON.stringify({ + action: "create_ticket", + conversation_id: conversationId() || undefined, + fields: { + subject: userQuery.slice(0, 80), + description: userQuery, + category: "ai_assisted", + priority: "medium", + }, + }), + }); + const data = await res.json().catch(() => ({})); + if (res.ok && data.success) { + setMessages((prev) => [ + ...prev, + { + role: "assistant", + content: data.message || `Support ticket created. Our team will get back to you shortly.`, + status: "ticket_created", + suggestedAction: "open_support_ticket", + actionType: "ticket_created", + confirmed: false, + }, + ]); + } else { + setMessages((prev) => [ + ...prev, + { + role: "assistant", + content: data.error || "Couldn't create the ticket right now. Please try again or email support@nxtgauge.com.", + }, + ]); + } + } catch { + setMessages((prev) => [ + ...prev, + { + role: "assistant", + content: "Couldn't connect to create the ticket. Please email support@nxtgauge.com directly.", + }, + ]); + } finally { + setIsLoading(false); + } + }; + return ( <> {/* Floating button */} @@ -316,59 +422,120 @@ export function AiChatWidget() { }} > - {(msg) => ( -
+ {(msg, idx) => ( + <>
- }> - - +
+ }> + + +
+
+

{msg.content}

+ + {(label) => ( +

+ {label()} +

+ )} +
+
-
-

{msg.content}

- - {(label) => ( -

+

+ {/* Navigation button — links to the relevant dashboard section */} + + {(url) => ( + + {actionLabel(msg.suggestedAction)} + + )} + + + {/* Confirm-create button — for ticket_pending messages */} + +
-
+ Create Support Ticket + + +
+ + )}