2026-04-15 20:03:47 +02:00
import { createSignal , Show , For , onMount } from "solid-js" ;
import { MessageCircle , X , Send , Bot , User , Loader } from "lucide-solid" ;
2026-07-17 05:48:15 +05:30
const API = "/api" ;
2026-04-15 20:03:47 +02:00
interface ChatMessage {
role : "user" | "assistant" ;
content : string ;
intent? : string ;
2026-06-15 17:04:02 +05:30
status? : string ;
suggestedAction? : string ;
2026-08-14 18:06:07 +02:00
/** 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 ;
2026-04-15 20:03:47 +02:00
}
interface ChatResponse {
2026-06-14 18:04:47 +02:00
reply : string ;
2026-04-15 20:03:47 +02:00
conversation_id : string ;
intent : string ;
confidence : number ;
2026-06-15 06:19:05 +05:30
remaining_credits? : number ;
remaining_daily_actions? : number ;
credits_charged? : number ;
2026-06-15 06:50:09 +05:30
message? : string ;
2026-06-15 17:04:02 +05:30
status? : string ;
suggested_action? : string ;
2026-08-14 18:06:07 +02:00
action_type? : string ;
2026-06-15 17:04:02 +05:30
}
function statusLabel ( status? : string ) : string | null {
switch ( status ) {
case "ticket_created" :
return "Support ticket created" ;
case "kb_results" :
return "Help articles found" ;
case "usage_summary" :
return "AI usage summary" ;
default :
return null ;
}
2026-06-15 06:19:05 +05:30
}
2026-08-14 18:06:07 +02:00
/** 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 "" ;
}
}
2026-06-15 06:19:05 +05:30
interface UsageStatus {
remaining_credits : number ;
remaining_daily_actions : number ;
daily_action_limit : number ;
plan_code : string ;
plan_name : string ;
monthly_credits_total : number ;
monthly_credits_used : number ;
2026-04-15 20:03:47 +02:00
}
export function AiChatWidget() {
const [ isOpen , setIsOpen ] = createSignal ( false ) ;
const [ messages , setMessages ] = createSignal < ChatMessage [ ] > ( [
{
role : "assistant" ,
content :
2026-06-14 18:04:47 +02:00
"Hi! I'm Ask Ash, your Nxtgauge assistant. I can help you:\n• Search help articles & KB\n• Create support tickets\n• Explain your AI plan & usage\n• Answer questions about the platform" ,
2026-04-15 20:03:47 +02:00
} ,
] ) ;
const [ input , setInput ] = createSignal ( "" ) ;
const [ isLoading , setIsLoading ] = createSignal ( false ) ;
const [ conversationId , setConversationId ] = createSignal ( "" ) ;
2026-06-15 06:19:05 +05:30
const [ usage , setUsage ] = createSignal < UsageStatus | null > ( null ) ;
onMount ( ( ) = > {
2026-07-17 05:48:15 +05:30
const hasToken = typeof window !== "undefined" && ! ! sessionStorage . getItem ( "nxtgauge_access_token" ) ;
if ( hasToken ) fetchUsage ( ) ;
2026-06-15 06:19:05 +05:30
} ) ;
const fetchUsage = async ( ) = > {
try {
2026-07-17 05:48:15 +05:30
const res = await fetch ( ` ${ API } /ai/usage/summary ` , {
headers : {
Authorization : ` Bearer ${ sessionStorage . getItem ( "nxtgauge_access_token" ) || "" } ` ,
} ,
credentials : "include" ,
} ) ;
2026-06-15 06:19:05 +05:30
if ( ! res . ok ) return ;
const data = await res . json ( ) ;
2026-06-15 09:39:26 +05:30
const plan = data . plan_details || data . plan || { } ;
2026-06-15 06:19:05 +05:30
setUsage ( {
2026-06-15 09:39:26 +05:30
remaining_credits : plan.remaining_credits ? ? data . monthly_remaining ? ? 0 ,
remaining_daily_actions : plan.remaining_daily_actions ? ? data . daily_remaining ? ? 0 ,
daily_action_limit : plan.daily_action_limit ? ? data . daily_limit ? ? 0 ,
plan_code : plan.plan_code ? ? data . plan_code ? ? "free" ,
plan_name : plan.plan_name ? ? data . plan ? ? "Free" ,
monthly_credits_total : plan.monthly_credits_total ? ? data . monthly_limit ? ? 0 ,
monthly_credits_used : plan.monthly_credits_used ? ? data . monthly_used ? ? 0 ,
2026-06-15 06:19:05 +05:30
} ) ;
} catch ( err ) {
console . error ( "Failed to fetch AI usage" , err ) ;
}
} ;
2026-04-15 20:03:47 +02:00
const toggleChat = ( ) = > setIsOpen ( ( v ) = > ! v ) ;
const sendMessage = async ( ) = > {
const text = input ( ) . trim ( ) ;
if ( ! text || isLoading ( ) ) return ;
setIsLoading ( true ) ;
const userMessage : ChatMessage = { role : "user" , content : text } ;
setMessages ( ( prev ) = > [ . . . prev , userMessage ] ) ;
setInput ( "" ) ;
try {
2026-07-17 05:48:15 +05:30
let res = await fetch ( ` ${ API } /ai/chat/ask ` , {
2026-04-15 20:03:47 +02:00
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON.stringify ( {
message : text ,
conversation_id : conversationId ( ) || undefined ,
} ) ,
} ) ;
2026-06-15 06:50:09 +05:30
if ( res . status === 401 || res . status === 403 || res . status === 404 ) {
2026-07-17 05:48:15 +05:30
res = await fetch ( ` ${ API } /ai/chat/message ` , {
2026-06-15 06:50:09 +05:30
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON.stringify ( {
message : text ,
conversation_id : conversationId ( ) || undefined ,
} ) ,
} ) ;
}
2026-04-15 20:03:47 +02:00
if ( ! res . ok ) throw new Error ( "AI request failed" ) ;
const data : ChatResponse = await res . json ( ) ;
if ( data . conversation_id && ! conversationId ( ) ) {
setConversationId ( data . conversation_id ) ;
}
2026-06-15 06:19:05 +05:30
// Update usage state from response if available
if ( typeof data . remaining_credits === "number" ) {
setUsage ( ( prev ) = >
prev
? {
. . . prev ,
remaining_credits : data.remaining_credits ! ,
remaining_daily_actions : data.remaining_daily_actions ? ? prev . remaining_daily_actions ,
}
: null
) ;
}
2026-04-15 20:03:47 +02:00
const assistantMessage : ChatMessage = {
role : "assistant" ,
2026-06-15 06:50:09 +05:30
content : data.message || data . reply ,
2026-04-15 20:03:47 +02:00
intent : data.intent ,
2026-06-15 17:04:02 +05:30
status : data.status ,
suggestedAction : data.suggested_action ,
2026-08-14 18:06:07 +02:00
actionType : data.action_type ,
userQuery : text ,
confirmed : false ,
2026-04-15 20:03:47 +02:00
} ;
setMessages ( ( prev ) = > [ . . . prev , assistantMessage ] ) ;
} catch ( err ) {
setMessages ( ( prev ) = > [
. . . prev ,
{
role : "assistant" ,
content :
"I'm having trouble connecting right now. Please try again or contact support@nxtgauge.com." ,
} ,
] ) ;
} finally {
setIsLoading ( false ) ;
}
} ;
const handleKeyDown = ( e : KeyboardEvent ) = > {
if ( e . key === "Enter" && ! e . shiftKey ) {
e . preventDefault ( ) ;
sendMessage ( ) ;
}
} ;
2026-08-14 18:06:07 +02:00
/** 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 ) ;
}
} ;
2026-04-15 20:03:47 +02:00
return (
< >
{ /* Floating button */ }
< button
onClick = { toggleChat }
style = { {
position : "fixed" ,
bottom : "24px" ,
right : "24px" ,
width : "56px" ,
height : "56px" ,
"border-radius" : "50%" ,
background : "#FF5E13" ,
border : "none" ,
cursor : "pointer" ,
display : "flex" ,
"align-items" : "center" ,
"justify-content" : "center" ,
"box-shadow" : "0 4px 16px rgba(255, 90, 19, 0.35)" ,
"z-index" : "9999" ,
transition : "transform 0.2s" ,
} }
title = "AI Assistant"
2026-07-17 05:48:15 +05:30
aria - label = { isOpen ( ) ? "Close AI Assistant" : "Open AI Assistant" }
aria - expanded = { isOpen ( ) }
2026-04-15 20:03:47 +02:00
>
< Show when = { isOpen ( ) } fallback = { < MessageCircle size = { 24 } color = "#fff" / > } >
< X size = { 24 } color = "#fff" / >
< / Show >
< / button >
{ /* Chat window */ }
< Show when = { isOpen ( ) } >
< div
2026-05-01 02:54:25 +02:00
role = "dialog"
aria - label = "AI Assistant chat"
aria - modal = "true"
2026-04-15 20:03:47 +02:00
style = { {
position : "fixed" ,
bottom : "96px" ,
right : "24px" ,
width : "380px" ,
height : "520px" ,
background : "#fff" ,
"border-radius" : "16px" ,
"box-shadow" : "0 8px 40px rgba(0,0,0,0.15)" ,
display : "flex" ,
"flex-direction" : "column" ,
overflow : "hidden" ,
2026-05-01 02:54:25 +02:00
"z-index" : "9998" ,
2026-04-15 20:03:47 +02:00
} }
>
{ /* Header */ }
< div
style = { {
background : "linear-gradient(135deg, #FF5E13 0%, #E5470F 100%)" ,
padding : "16px 20px" ,
display : "flex" ,
"align-items" : "center" ,
"justify-content" : "space-between" ,
} }
>
< div style = { { display : "flex" , "align-items" : "center" , gap : "10px" } } >
2026-05-05 20:14:56 +02:00
< img
src = "/ai-assistant-logo.png"
alt = "AI Assistant"
2026-06-14 18:04:47 +02:00
style = { { width : "26px" , height : "26px" , "border-radius" : "6px" , "object-fit" : "contain" , "background" : "transparent" } }
onError = { ( e ) = > { e . currentTarget . style . display = 'none' ; } }
2026-05-05 20:14:56 +02:00
/ >
2026-04-15 20:03:47 +02:00
< div >
< p style = { { margin : 0 , color : "#fff" , "font-weight" : "700" , "font-size" : "15px" } } >
AI Assistant
< / p >
2026-06-15 06:19:05 +05:30
< Show when = { usage ( ) } >
{ ( u ) = > (
< p style = { { margin : "2px 0 0" , color : "rgba(255,255,255,0.85)" , "font-size" : "11px" } } >
{ u ( ) . plan_name } · { u ( ) . remaining_credits } credits · { u ( ) . remaining_daily_actions } / { u ( ) . daily_action_limit } today
< / p >
) }
< / Show >
2026-04-15 20:03:47 +02:00
< / div >
< / div >
< button
onClick = { toggleChat }
2026-05-01 02:54:25 +02:00
aria - label = "Close chat"
2026-04-15 20:03:47 +02:00
style = { {
background : "none" ,
border : "none" ,
cursor : "pointer" ,
padding : "4px" ,
} }
>
< X size = { 20 } color = "#fff" / >
< / button >
< / div >
{ /* Quick actions */ }
< div
style = { {
padding : "10px 16px" ,
"border-bottom" : "1px solid #E5E7EB" ,
display : "flex" ,
gap : "8px" ,
"flex-wrap" : "wrap" ,
} }
>
fix: ESLint, SolidJS reactivity bugs, role workflow bug fixes
ESLint:
- Downgrade eslint v10 → v8.57.1 (compatible with @typescript-eslint v7 + eslint-plugin-solid)
- Fix .eslintrc.cjs: remove invalid require() calls, update to valid rule set
- Add npm run lint script
SolidJS solid/prefer-for (18 fixes across 3 files):
- OpportunityGraph.tsx: EDGES.map() → <For> with reactive visible/drawing signals
- PortfolioPage.tsx: services.map() and experience.map() → <For>
- DashboardDesignPreview.tsx: 15 .map() calls → <For> (stats, packages, timeline,
testimonials, quick actions, step tabs, pills, form fields, status lists, buttons,
counters, filter tabs)
Role workflow bug fixes (from full role audit):
- cover_letter → cover_note in job applications (field name canonical fix)
- applicant_user_id field name fix in shortlisted candidates
- CompanyApplicationsPage: GET → POST for contact unlock endpoint
- CreditsPage: /payments/history → /payments/invoices; response key data.data
- CreditsPage: holds response key data.data (not data.holds)
- ProfessionalResponsesPage: requirement_id → lead_id, decision_at → resolved_at
- PortfolioPage: data.items → data.data; fix vacuous-truth in form completion check;
saveProfessionalForm: check res.ok before showing success
- CustomerBrowseProfessionalsPage: professional_role_code → profession_key
- MyDashboardPage: professional prefix split('_')[0] not replace('_','')
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-12 13:38:38 +02:00
< For each = { [
2026-06-14 18:04:47 +02:00
{ label : "Support Ticket" , text : "I need help with " } ,
{ label : "Search KB" , text : "How do I " } ,
{ label : "AI Plan" , text : "Explain my AI plan" } ,
{ label : "Check Balance" , text : "Check my AI balance" } ,
fix: ESLint, SolidJS reactivity bugs, role workflow bug fixes
ESLint:
- Downgrade eslint v10 → v8.57.1 (compatible with @typescript-eslint v7 + eslint-plugin-solid)
- Fix .eslintrc.cjs: remove invalid require() calls, update to valid rule set
- Add npm run lint script
SolidJS solid/prefer-for (18 fixes across 3 files):
- OpportunityGraph.tsx: EDGES.map() → <For> with reactive visible/drawing signals
- PortfolioPage.tsx: services.map() and experience.map() → <For>
- DashboardDesignPreview.tsx: 15 .map() calls → <For> (stats, packages, timeline,
testimonials, quick actions, step tabs, pills, form fields, status lists, buttons,
counters, filter tabs)
Role workflow bug fixes (from full role audit):
- cover_letter → cover_note in job applications (field name canonical fix)
- applicant_user_id field name fix in shortlisted candidates
- CompanyApplicationsPage: GET → POST for contact unlock endpoint
- CreditsPage: /payments/history → /payments/invoices; response key data.data
- CreditsPage: holds response key data.data (not data.holds)
- ProfessionalResponsesPage: requirement_id → lead_id, decision_at → resolved_at
- PortfolioPage: data.items → data.data; fix vacuous-truth in form completion check;
saveProfessionalForm: check res.ok before showing success
- CustomerBrowseProfessionalsPage: professional_role_code → profession_key
- MyDashboardPage: professional prefix split('_')[0] not replace('_','')
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-12 13:38:38 +02:00
] } > { ( action ) = > (
2026-04-15 20:03:47 +02:00
< button
2026-06-14 18:04:47 +02:00
aria - label = { ` Quick action: ${ action . label } ` }
2026-04-15 20:03:47 +02:00
onClick = { ( ) = > {
2026-06-14 18:04:47 +02:00
setInput ( action . text ) ;
2026-04-15 20:03:47 +02:00
} }
style = { {
padding : "4px 10px" ,
"border-radius" : "20px" ,
border : "1px solid #E5E7EB" ,
background : "#F9FAFB" ,
"font-size" : "11px" ,
cursor : "pointer" ,
color : "#374151" ,
} }
>
2026-06-14 18:04:47 +02:00
{ action . label }
2026-04-15 20:03:47 +02:00
< / button >
fix: ESLint, SolidJS reactivity bugs, role workflow bug fixes
ESLint:
- Downgrade eslint v10 → v8.57.1 (compatible with @typescript-eslint v7 + eslint-plugin-solid)
- Fix .eslintrc.cjs: remove invalid require() calls, update to valid rule set
- Add npm run lint script
SolidJS solid/prefer-for (18 fixes across 3 files):
- OpportunityGraph.tsx: EDGES.map() → <For> with reactive visible/drawing signals
- PortfolioPage.tsx: services.map() and experience.map() → <For>
- DashboardDesignPreview.tsx: 15 .map() calls → <For> (stats, packages, timeline,
testimonials, quick actions, step tabs, pills, form fields, status lists, buttons,
counters, filter tabs)
Role workflow bug fixes (from full role audit):
- cover_letter → cover_note in job applications (field name canonical fix)
- applicant_user_id field name fix in shortlisted candidates
- CompanyApplicationsPage: GET → POST for contact unlock endpoint
- CreditsPage: /payments/history → /payments/invoices; response key data.data
- CreditsPage: holds response key data.data (not data.holds)
- ProfessionalResponsesPage: requirement_id → lead_id, decision_at → resolved_at
- PortfolioPage: data.items → data.data; fix vacuous-truth in form completion check;
saveProfessionalForm: check res.ok before showing success
- CustomerBrowseProfessionalsPage: professional_role_code → profession_key
- MyDashboardPage: professional prefix split('_')[0] not replace('_','')
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-12 13:38:38 +02:00
) } < / For >
2026-04-15 20:03:47 +02:00
< / div >
{ /* Messages */ }
< div
style = { {
flex : 1 ,
overflow : "auto" ,
padding : "16px" ,
display : "flex" ,
"flex-direction" : "column" ,
gap : "12px" ,
} }
>
< For each = { messages ( ) } >
2026-08-14 18:06:07 +02:00
{ ( msg , idx ) = > (
< >
2026-04-15 20:03:47 +02:00
< div
style = { {
display : "flex" ,
2026-08-14 18:06:07 +02:00
"align-items" : "flex-start" ,
gap : "8px" ,
"flex-direction" : msg . role === "user" ? "row-reverse" : "row" ,
2026-04-15 20:03:47 +02:00
} }
>
2026-08-14 18:06:07 +02:00
< div
style = { {
width : "28px" ,
height : "28px" ,
"border-radius" : "50%" ,
background : msg.role === "user" ? "#FF5E13" : "#E5E7EB" ,
display : "flex" ,
"align-items" : "center" ,
"justify-content" : "center" ,
"flex-shrink" : 0 ,
} }
>
< Show when = { msg . role === "user" } fallback = { < Bot size = { 14 } color = "#6B7280" / > } >
< User size = { 14 } color = "#fff" / >
< / Show >
< / div >
< div
style = { {
"max-width" : "75%" ,
padding : "10px 14px" ,
"border-radius" : "14px" ,
background : msg.role === "user" ? "#FF5E13" : "#F3F4F6" ,
color : msg.role === "user" ? "#fff" : "#111827" ,
"font-size" : "13px" ,
"line-height" : "1.5" ,
} }
>
< p style = { { margin : 0 , "white-space" : "pre-wrap" } } > { msg . content } < / p >
< Show when = { msg . role === "assistant" && statusLabel ( msg . status ) } >
{ ( label ) = > (
< p
style = { {
margin : "6px 0 0" ,
"font-size" : "10px" ,
color : "#6B7280" ,
"font-weight" : "600" ,
} }
>
{ label ( ) }
< / p >
) }
< / Show >
< / div >
2026-04-15 20:03:47 +02:00
< / div >
2026-08-14 18:06:07 +02:00
{ /* 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 ) }
2026-06-15 17:04:02 +05:30
style = { {
2026-08-14 18:06:07 +02:00
display : "inline-flex" ,
"align-items" : "center" ,
padding : "5px 12px" ,
background : "#FF5E13" ,
border : "none" ,
"border-radius" : "20px" ,
color : "#fff" ,
"font-size" : "11px" ,
2026-06-15 17:04:02 +05:30
"font-weight" : "600" ,
2026-08-14 18:06:07 +02:00
cursor : "pointer" ,
2026-06-15 17:04:02 +05:30
} }
>
2026-08-14 18:06:07 +02:00
Create Support Ticket
< / button >
< / Show >
< / div >
< / Show >
< / >
2026-04-15 20:03:47 +02:00
) }
< / For >
< Show when = { isLoading ( ) } >
< div
style = { {
display : "flex" ,
"align-items" : "center" ,
gap : "8px" ,
color : "#9CA3AF" ,
"font-size" : "13px" ,
} }
>
< Loader size = { 14 } style = { { animation : "spin 1s linear infinite" } } / >
Thinking . . .
< / div >
< / Show >
< / div >
{ /* Input */ }
< div
style = { {
padding : "12px 16px" ,
"border-top" : "1px solid #E5E7EB" ,
display : "flex" ,
gap : "8px" ,
} }
>
< input
type = "text"
value = { input ( ) }
onInput = { ( e ) = > setInput ( e . currentTarget . value ) }
onKeyDown = { handleKeyDown }
placeholder = "Ask me anything..."
2026-05-01 02:54:25 +02:00
aria - label = "Chat message input"
2026-04-15 20:03:47 +02:00
style = { {
flex : 1 ,
height : "40px" ,
"border-radius" : "20px" ,
border : "1px solid #E5E7EB" ,
padding : "0 16px" ,
"font-size" : "13px" ,
outline : "none" ,
} }
/ >
< button
onClick = { sendMessage }
disabled = { isLoading ( ) || ! input ( ) . trim ( ) }
2026-05-01 02:54:25 +02:00
aria - label = "Send message"
2026-04-15 20:03:47 +02:00
style = { {
width : "40px" ,
height : "40px" ,
"border-radius" : "50%" ,
background : isLoading ( ) ? "#E5E7EB" : "#FF5E13" ,
border : "none" ,
cursor : isLoading ( ) ? "default" : "pointer" ,
display : "flex" ,
"align-items" : "center" ,
"justify-content" : "center" ,
} }
>
< Send size = { 16 } color = "#fff" / >
< / button >
< / div >
< / div >
< / Show >
< style > { `
@keyframes spin {
from { transform : rotate ( 0 deg ) ; }
to { transform : rotate ( 360 deg ) ; }
}
` }</style>
< / >
) ;
}