feat(ai-chat): wire action buttons + ticket confirm flow in AiChatWidget
All checks were successful
build-and-release / build (push) Successful in 2m4s
All checks were successful
build-and-release / build (push) Successful in 2m4s
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 <noreply@anthropic.com>
This commit is contained in:
parent
55c832b620
commit
614b0ed43a
1 changed files with 210 additions and 43 deletions
|
|
@ -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,7 +422,8 @@ export function AiChatWidget() {
|
|||
}}
|
||||
>
|
||||
<For each={messages()}>
|
||||
{(msg) => (
|
||||
{(msg, idx) => (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
|
|
@ -369,6 +476,66 @@ export function AiChatWidget() {
|
|||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action buttons — only for unconfirmed assistant messages with a suggested action */}
|
||||
<Show when={msg.role === "assistant" && msg.suggestedAction && !msg.confirmed}>
|
||||
<div
|
||||
style={{
|
||||
"padding-left": "36px",
|
||||
"margin-top": "-4px",
|
||||
display: "flex",
|
||||
gap: "6px",
|
||||
"flex-wrap": "wrap",
|
||||
}}
|
||||
>
|
||||
{/* Navigation button — links to the relevant dashboard section */}
|
||||
<Show when={msg.suggestedAction !== "create_ticket" && navUrl(msg.suggestedAction)}>
|
||||
{(url) => (
|
||||
<a
|
||||
href={url()}
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
"align-items": "center",
|
||||
padding: "5px 12px",
|
||||
background: "#FFF5F0",
|
||||
border: "1px solid #FF5E13",
|
||||
"border-radius": "20px",
|
||||
color: "#FF5E13",
|
||||
"font-size": "11px",
|
||||
"font-weight": "600",
|
||||
"text-decoration": "none",
|
||||
cursor: "pointer",
|
||||
transition: "background 0.15s",
|
||||
}}
|
||||
>
|
||||
{actionLabel(msg.suggestedAction)}
|
||||
</a>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
{/* Confirm-create button — for ticket_pending messages */}
|
||||
<Show when={msg.suggestedAction === "create_ticket" || msg.actionType === "ticket_pending"}>
|
||||
<button
|
||||
onClick={() => confirmCreateTicket(idx(), msg.userQuery || msg.content)}
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
"align-items": "center",
|
||||
padding: "5px 12px",
|
||||
background: "#FF5E13",
|
||||
border: "none",
|
||||
"border-radius": "20px",
|
||||
color: "#fff",
|
||||
"font-size": "11px",
|
||||
"font-weight": "600",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Create Support Ticket
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
<Show when={isLoading()}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue