tin-spa 20.14.113 → 20.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/index.d.ts +80 -10
- package/fesm2022/{tin-spa-accounting.module-Cg4vWaJE.mjs → tin-spa-accounting.module-CamqAeW_.mjs} +71 -83
- package/fesm2022/tin-spa-accounting.module-CamqAeW_.mjs.map +1 -0
- package/fesm2022/{tin-spa-agent-page.component-DJvMPjCQ.mjs → tin-spa-agent-page.component-DTYn54p0.mjs} +2 -2
- package/fesm2022/{tin-spa-agent-page.component-DJvMPjCQ.mjs.map → tin-spa-agent-page.component-DTYn54p0.mjs.map} +1 -1
- package/fesm2022/{tin-spa-agent.component-D0CGGnGZ.mjs → tin-spa-agent.component-CZKMWl3r.mjs} +2 -2
- package/fesm2022/{tin-spa-agent.component-D0CGGnGZ.mjs.map → tin-spa-agent.component-CZKMWl3r.mjs.map} +1 -1
- package/fesm2022/{tin-spa-assets.module-qIWNwJ2E.mjs → tin-spa-assets.module-BcOjlhfm.mjs} +16 -11
- package/fesm2022/tin-spa-assets.module-BcOjlhfm.mjs.map +1 -0
- package/fesm2022/tin-spa-core.mjs +426 -112
- package/fesm2022/tin-spa-core.mjs.map +1 -1
- package/fesm2022/{tin-spa-general-erp.module-CoYxceLj.mjs → tin-spa-general-erp.module-b6SjnWUM.mjs} +2 -2
- package/fesm2022/{tin-spa-general-erp.module-CoYxceLj.mjs.map → tin-spa-general-erp.module-b6SjnWUM.mjs.map} +1 -1
- package/fesm2022/{tin-spa-hr.module-CKPZ9ZOj.mjs → tin-spa-hr.module-DkGVK2lX.mjs} +2 -2
- package/fesm2022/{tin-spa-hr.module-CKPZ9ZOj.mjs.map → tin-spa-hr.module-DkGVK2lX.mjs.map} +1 -1
- package/fesm2022/tin-spa-http.mjs +32 -1
- package/fesm2022/tin-spa-http.mjs.map +1 -1
- package/fesm2022/{tin-spa-inventory.module-DRKAcNau.mjs → tin-spa-inventory.module-CYWBqSCd.mjs} +16 -11
- package/fesm2022/tin-spa-inventory.module-CYWBqSCd.mjs.map +1 -0
- package/fesm2022/{tin-spa-loans.module-BTDmgrWo.mjs → tin-spa-loans.module-BDh-T-Ng.mjs} +2 -2
- package/fesm2022/{tin-spa-loans.module-BTDmgrWo.mjs.map → tin-spa-loans.module-BDh-T-Ng.mjs.map} +1 -1
- package/fesm2022/{tin-spa-manufacturing.module-CHta6oV_.mjs → tin-spa-manufacturing.module-9Ti-BFb2.mjs} +2 -2
- package/fesm2022/{tin-spa-manufacturing.module-CHta6oV_.mjs.map → tin-spa-manufacturing.module-9Ti-BFb2.mjs.map} +1 -1
- package/fesm2022/{tin-spa-payroll.module-CZ_GO1jF.mjs → tin-spa-payroll.module-54Efumv_.mjs} +9 -6
- package/fesm2022/{tin-spa-payroll.module-CZ_GO1jF.mjs.map → tin-spa-payroll.module-54Efumv_.mjs.map} +1 -1
- package/fesm2022/{tin-spa-purchasing.module-NCdwquaF.mjs → tin-spa-purchasing.module-Bq8JnsyM.mjs} +16 -11
- package/fesm2022/tin-spa-purchasing.module-Bq8JnsyM.mjs.map +1 -0
- package/fesm2022/{tin-spa-sales.module-D0uEGJW7.mjs → tin-spa-sales.module-nyrJulzD.mjs} +40 -17
- package/fesm2022/tin-spa-sales.module-nyrJulzD.mjs.map +1 -0
- package/fesm2022/{tin-spa-tenancy-erp.module-GwIv8Wbc.mjs → tin-spa-tenancy-erp.module-DGATA-p_.mjs} +2 -2
- package/fesm2022/{tin-spa-tenancy-erp.module-GwIv8Wbc.mjs.map → tin-spa-tenancy-erp.module-DGATA-p_.mjs.map} +1 -1
- package/fesm2022/{tin-spa-tin-spa-Cqe5hYTr.mjs → tin-spa-tin-spa-BMy59BVn.mjs} +1428 -219
- package/fesm2022/tin-spa-tin-spa-BMy59BVn.mjs.map +1 -0
- package/fesm2022/tin-spa.mjs +1 -1
- package/http/index.d.ts +3 -1
- package/index.d.ts +144 -24
- package/package.json +1 -1
- package/src/tin-styles.css +27 -0
- package/fesm2022/tin-spa-accounting.module-Cg4vWaJE.mjs.map +0 -1
- package/fesm2022/tin-spa-assets.module-qIWNwJ2E.mjs.map +0 -1
- package/fesm2022/tin-spa-inventory.module-DRKAcNau.mjs.map +0 -1
- package/fesm2022/tin-spa-purchasing.module-NCdwquaF.mjs.map +0 -1
- package/fesm2022/tin-spa-sales.module-D0uEGJW7.mjs.map +0 -1
- package/fesm2022/tin-spa-tin-spa-Cqe5hYTr.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tin-spa-agent.component-D0CGGnGZ.mjs","sources":["../../../projects/tin-spa/src/lib/pipes/agent-markdown.pipe.ts","../../../projects/tin-spa/src/lib/services/agent.service.ts","../../../projects/tin-spa/src/lib/components/agent/agent.component.ts","../../../projects/tin-spa/src/lib/components/agent/agent.component.html"],"sourcesContent":["// Renders the agent's replies as the markdown they are actually written in.\n//\n// The agent writes **bold**, lists, headings and menu paths, and the widget printed all of it verbatim — so a\n// reply the guide carefully formatted arrived as a wall of asterisks. Telegram and WhatsApp now convert to\n// their own markup; this is the web's half of the same job.\n//\n// Deliberately a small hand-rolled subset rather than a markdown library: the input is not arbitrary user\n// markdown, it is the agent's own output, which uses bold, italic, inline code, headings, bullets and\n// numbered steps and nothing else. A library would add a dependency and a much larger escaping surface for\n// no gain here.\n//\n// SECURITY: every character is HTML-escaped BEFORE any formatting is applied, so nothing in a reply can\n// become markup. That matters more than usual for this component — an agent reply can contain data a\n// different user typed, so it must be treated as hostile text, not as trusted output.\nimport { Pipe, PipeTransform } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\n\n// Changed (nav-menu-standalone): standalone so the standalone AgentComponent can import the pipe by path.\n// TinSpaModule now IMPORTS it; it was never exported, so no consumer-facing surface changes.\n@Pipe({ name: 'agentMarkdown', standalone: true })\nexport class AgentMarkdownPipe implements PipeTransform {\n\n constructor(private sanitizer: DomSanitizer) { }\n\n transform(value: string | null | undefined): SafeHtml {\n if (!value) return '';\n\n let html = this.escape(value);\n\n // Headings first: they are line-anchored, and converting them after inline rules would let a ** on the\n // same line interfere\n html = html.replace(/^#{1,6}\\s*(.+)$/gm, '<strong class=\"md-heading\">$1</strong>');\n\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/(?<![\\w*])\\*(?!\\s)(.+?)(?<!\\s)\\*(?![\\w*])/g, '<em>$1</em>');\n html = html.replace(/`([^`]+)`/g, '<code>$1</code>');\n\n // Bullets and numbered steps keep their marker — turning them into <ul>/<ol> would need block parsing,\n // and the agent's lists are already readable as lines\n html = html.replace(/^(\\s*)[-•*]\\s+(.+)$/gm, '$1<span class=\"md-bullet\">•</span> $2');\n\n html = html.replace(/\\n/g, '<br>');\n\n return this.sanitizer.bypassSecurityTrustHtml(html);\n }\n\n // Escaping BEFORE formatting is what makes bypassSecurityTrustHtml safe here: by the time anything is\n // trusted, the only tags present are the ones this pipe put there itself.\n private escape(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n}\n","// Frontend service for Agent chat — state management, API calls, and SignalR subscriptions (renamed from AssistantService)\nimport { Injectable } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\nimport { DataServiceLib } from 'tin-spa/core';\nimport { SignalRService } from 'tin-spa/core';\nimport { ApiErrorService } from 'tin-spa/core'; // Added: the two chat calls a user actually asks for report; the widget's own startup loads deliberately do not (WS-6 Phase 4)\n\n// Message interface for UI rendering (renamed from AssistantMessage)\nexport interface AgentMessage {\n conversationId?: number;\n role: 'user' | 'assistant';\n content: string;\n intent?: string;\n createdDate: Date;\n pending?: boolean; // Changed: voice/photo placeholder shown until the backend returns what it heard or saw\n divider?: boolean; // Changed: not a message — a rendered boundary between conversations\n}\n\n// Conversation summary for list view\nexport interface ConversationSummary {\n conversationID: number;\n title: string;\n appName: string;\n createdDate: Date;\n lastActivityDate?: Date; // Changed: real recency, so the switcher orders by when a thread was last used\n}\n\n// Changed: the structured half of a reply. The agent has always known it was holding a half-built Customer\n// with two fields missing; until now that only existed as prose inside the message text, so the UI could not\n// show it. Everything here is optional — a plain answer carries no session state at all.\nexport interface AgentSessionInfo {\n sessionType?: string;\n modelName?: string;\n missingFields?: string[];\n conversationID?: number;\n newConversation?: boolean;\n notice?: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class AgentService { // Changed: Renamed from AssistantService\n\n private messages = new BehaviorSubject<AgentMessage[]>([]);\n private conversations = new BehaviorSubject<ConversationSummary[]>([]);\n private typing = new BehaviorSubject<boolean>(false);\n private isOpen = new BehaviorSubject<boolean>(false);\n private currentConversationId = new BehaviorSubject<number | null>(null);\n private greeting = new BehaviorSubject<string>('');\n private suggestedQuestions = new BehaviorSubject<string[]>([]);\n private sessionInfo = new BehaviorSubject<AgentSessionInfo | null>(null); // Changed: what the agent is in the middle of\n\n messages$ = this.messages.asObservable();\n conversations$ = this.conversations.asObservable();\n typing$ = this.typing.asObservable();\n isOpen$ = this.isOpen.asObservable();\n currentConversationId$ = this.currentConversationId.asObservable();\n greeting$ = this.greeting.asObservable();\n suggestedQuestions$ = this.suggestedQuestions.asObservable();\n sessionInfo$ = this.sessionInfo.asObservable();\n\n constructor(\n private dataService: DataServiceLib,\n private signalRService: SignalRService,\n private apiErrorService: ApiErrorService // Changed: injected for loadMessages and deleteConversation only\n ) {\n this.initListeners();\n }\n\n // App name derived from the existing appConfig\n get appName(): string {\n return this.dataService.appConfig.appName || 'App';\n }\n\n // Agent name derived from appConfig (changed from assistantName)\n get agentName(): string {\n return `${this.appName} Agent`;\n }\n\n // Subscribe to SignalR events for real-time message delivery\n private initListeners() {\n // Changed: SignalR is the SECOND path to a reply, not the only one. It still matters — a turn started on\n // the phone should appear on the desktop — but it now goes through the same deduping append as the HTTP\n // answer, so the common case (both arrive) shows one bubble rather than two.\n this.signalRService.agentMessage$.subscribe(msg => { // Changed: was assistantMessage$\n if (msg && msg.content) {\n this.addAssistantMessage(msg.content);\n }\n });\n\n this.signalRService.agentTyping$.subscribe(typing => { // Changed: was assistantTyping$\n this.typing.next(typing);\n });\n }\n\n // Load agent config (greeting + suggested questions) from backend\n loadConfig() {\n this.dataService.CallApi({ url: `agent/config/${this.appName}` }).subscribe(res => { // Changed: was assistant/config\n // DELIBERATELY SILENT (WS-6 Phase 4 triage). Greeting text and suggested-question chips are decoration\n // on a chat window that works perfectly without them — the user can still type. Nothing they can see is\n // wrong, so there is nothing truthful to tell them.\n if (res.success && res.data) {\n this.greeting.next(res.data.greeting || '');\n this.suggestedQuestions.next(res.data.suggestedQuestions || []);\n }\n });\n }\n\n // Load user's conversation list\n loadConversations() {\n this.dataService.CallApi({ url: 'agent/conversations' }).subscribe(res => { // Changed: was assistant/conversations\n // DELIBERATELY SILENT (WS-6 Phase 4 triage), and this was the closest call in the sweep — an empty\n // thread list IS a false statement about the user's own history. It stays silent because of WHEN it\n // fires: agent.component.ts:212 calls it from the widget's ngOnInit, so it runs on every app-shell load\n // whether or not anyone opens the chat. Converting would put a dialog in front of every user of the app\n // for a list none of them had asked to see. loadMessages() below — which only ever runs because someone\n // clicked a specific thread — is the one that reports, and it covers the same outage the moment the\n // list is actually used.\n if (res.success) {\n this.conversations.next(res.data || []);\n }\n });\n }\n\n // Load messages for a specific conversation\n loadMessages(conversationId: number) {\n this.currentConversationId.next(conversationId);\n this.dataService.CallApi({ url: `agent/conversations/${conversationId}/messages` }).subscribe(res => { // Changed: was assistant/conversations\n // CONVERTED (WS-6 Phase 4 triage). Unlike everything else in this service, this only runs because the\n // user clicked a named conversation in the switcher. Failing left the thread pane holding the PREVIOUS\n // conversation's messages under the new conversation's id — so the next thing they typed would have\n // been sent into a thread they were not looking at.\n if (!res.success) this.apiErrorService.presentAppFailure(res, 'load', `agent/conversations/${conversationId}/messages`);\n if (res.success) {\n this.messages.next((res.data || []).map((m: any) => ({\n conversationId: m.conversationID,\n role: m.role,\n content: m.content,\n intent: m.intent,\n createdDate: new Date(m.createdDate)\n })));\n }\n });\n }\n\n // Send a message — optimistically add user message, then POST to backend\n sendMessage(text: string) {\n if (!text.trim()) return;\n\n // Optimistic: add user message immediately\n const current = this.messages.value;\n this.messages.next([...current, { role: 'user', content: text, createdDate: new Date() }]);\n\n // Changed: the HTTP response is now the AUTHORITATIVE reply. It used to be discarded entirely and the\n // answer was expected to arrive over SignalR — so with the hub down, or real-time switched off, the\n // server answered and the user saw nothing at all. The request that asked the question is the one that\n // carries the answer; SignalR is now only a cross-device echo, deduped on arrival.\n this.dataService.CallApi(\n { url: 'agent/message', method: 'post' }, // Changed: was assistant/message\n { text, appName: this.appName }\n ).subscribe({\n next: (res: any) => {\n this.typing.next(false);\n\n const reply = res?.data?.message;\n if (reply) this.addAssistantMessage(reply);\n\n this.applySessionInfo(res?.data?.sessionInfo);\n },\n error: () => {\n this.typing.next(false);\n this.addAssistantMessage('Sorry, that could not be sent. Please check your connection and try again.');\n }\n });\n }\n\n // Changed: one place that appends an assistant reply, so the HTTP answer and the SignalR echo of the same\n // answer cannot both land. Matching on content against the last few messages rather than an id because the\n // two paths carry no shared identifier — and the failure being prevented (the same answer shown twice) is\n // more annoying than the rare case this suppresses (a genuine identical repeat).\n private addAssistantMessage(content: string) {\n const recent = this.messages.value.slice(-4);\n if (recent.some(m => m.role === 'assistant' && m.content === content)) return;\n\n this.messages.next([...this.messages.value, {\n role: 'assistant',\n content,\n createdDate: new Date()\n }]);\n }\n\n // Changed: keeps the widget's idea of \"what am I in the middle of\" in step with the server's, and draws the\n // boundary when a conversation rolls over so the user can see why the agent stopped referring to earlier\n // messages. Without a visible line, a rollover looks like the agent simply forgetting.\n private applySessionInfo(info: AgentSessionInfo | null | undefined) {\n if (!info) {\n this.sessionInfo.next(null);\n return;\n }\n\n if (info.newConversation) {\n if (info.conversationID) this.currentConversationId.next(info.conversationID);\n this.messages.next([...this.messages.value, {\n role: 'assistant',\n content: 'New conversation',\n createdDate: new Date(),\n divider: true\n }]);\n this.loadConversations();\n }\n\n // A finished operation reports no session type — that is the signal to clear the banner\n this.sessionInfo.next(info.sessionType ? info : null);\n }\n\n // Transcribe a recording WITHOUT invoking the agent — backs the composer's Stop button, so the user can\n // review and edit what was heard before deciding to send.\n transcribeOnly(audio: Blob, fileName: string) {\n const form = new FormData();\n form.append('audio', audio, fileName);\n return this.dataService.CallApi({ url: 'agent/transcribe', method: 'post', isFormData: true }, form);\n }\n\n // One hands-free spoken turn: audio in, spoken answer out, in a single round trip. The reply comes back\n // twice over — replyText verbatim for the transcript panel, spokenText as actually voiced — because a long\n // list is summarised before it is spoken.\n voiceTurn(audio: Blob, fileName: string) {\n const form = new FormData();\n form.append('audio', audio, fileName);\n form.append('appName', this.appName);\n return this.dataService.CallApi({ url: 'agent/voice-turn', method: 'post', isFormData: true }, form);\n }\n\n // Puts what was heard into the conversation thread, so a spoken turn reads like a typed one. Only the user\n // side is added here — the agent's reply arrives over SignalR exactly as it does for a typed message, and\n // adding it here as well would show it twice.\n appendSpokenMessage(text: string) {\n if (!text || !text.trim()) return;\n this.messages.next([...this.messages.value, { role: 'user', content: text.trim(), createdDate: new Date() }]);\n }\n\n // Send a composed message — any combination of a staged photo, a voice clip and typed text, in one go.\n // A placeholder user bubble goes up immediately and is swapped for the derived text when the backend\n // replies, which also keeps it above the assistant's answer (that arrives over SignalR mid-request).\n sendComposed(photo: Blob | null, photoName: string, audio: Blob | null, audioName: string, caption: string = '') {\n const hasPhoto = !!(photo && photo.size);\n const hasAudio = !!(audio && audio.size);\n if (!hasPhoto && !hasAudio && !caption.trim()) return;\n\n const placeholder: AgentMessage = {\n role: 'user',\n content: caption.trim() || (hasAudio ? 'Voice note…' : 'Photo…'),\n createdDate: new Date(),\n pending: true\n };\n\n this.messages.next([...this.messages.value, placeholder]);\n this.typing.next(true);\n\n const form = new FormData();\n if (hasPhoto) form.append('photo', photo!, photoName);\n if (hasAudio) form.append('audio', audio!, audioName);\n form.append('text', caption.trim());\n form.append('appName', this.appName);\n\n this.dataService.CallApi({ url: 'agent/media', method: 'post', isFormData: true }, form).subscribe({\n next: (res: any) => {\n this.typing.next(false);\n\n if (res?.success && res.data?.inputText) {\n this.replaceMessage(placeholder, { ...placeholder, content: res.data.inputText, pending: false });\n return;\n }\n\n // Nothing usable came back — drop the placeholder and say why\n this.removeMessage(placeholder);\n this.messages.next([...this.messages.value, {\n role: 'assistant',\n content: res?.message || 'Sorry, I could not process that. Please try again.',\n createdDate: new Date()\n }]);\n },\n error: () => {\n this.typing.next(false);\n this.removeMessage(placeholder);\n this.messages.next([...this.messages.value, {\n role: 'assistant',\n content: 'Sorry, that could not be sent. Please check your connection and try again.',\n createdDate: new Date()\n }]);\n }\n });\n }\n\n // Changed: kept so existing callers of the single-attachment API keep working\n sendMedia(file: Blob, fileName: string, kind: 'voice' | 'photo', caption: string = '') {\n if (kind === 'voice') this.sendComposed(null, '', file, fileName, caption);\n else this.sendComposed(file, fileName, null, '', caption);\n }\n\n // Swap a message in place so its position in the thread is preserved\n private replaceMessage(target: AgentMessage, updated: AgentMessage) {\n const msgs = this.messages.value.slice();\n const index = msgs.indexOf(target);\n if (index < 0) return;\n msgs[index] = updated;\n this.messages.next(msgs);\n }\n\n private removeMessage(target: AgentMessage) {\n this.messages.next(this.messages.value.filter(m => m !== target));\n }\n\n // Start a new conversation — only when user explicitly clicks \"New conversation\"\n newConversation() {\n this.messages.next([]);\n this.currentConversationId.next(null);\n this.dataService.CallApi(\n { url: 'agent/conversations', method: 'post' },\n { appName: this.appName }\n ).subscribe(res => {\n // DELIBERATELY SILENT (WS-6 Phase 4 triage). The UI has already done the thing the user asked for —\n // messages cleared, id nulled, a blank thread on screen — before this call is even made. All the server\n // round trip adds is a pre-allocated id, and sendMessage() creates one anyway when there is none. So a\n // failure here changes nothing the user can see or do.\n if (res.success && res.data) {\n this.currentConversationId.next(res.data.conversationID);\n }\n });\n }\n\n // Delete a conversation\n deleteConversation(id: number) {\n this.dataService.CallApi({ url: `agent/conversations/${id}/delete`, method: 'post' }).subscribe(res => { // Changed: was agent/conversations/${id}, which is DELETE-only on the API — this POST hit a 405 every time and the conversation was never deleted\n // CONVERTED (WS-6 Phase 4 triage). A delete the user pressed. Failing left the conversation sitting in\n // the list exactly as it was, which reads as \"the button did nothing\" — and the answer to a button that\n // did nothing is to press it again. Note the comment on the URL below: this endpoint has already been\n // wrong once (a 405 on every call), and it was silence that let that survive.\n if (!res.success) this.apiErrorService.presentAppFailure(res, 'action', `agent/conversations/${id}/delete`);\n if (res.success) {\n this.loadConversations();\n if (this.currentConversationId.value === id) {\n this.messages.next([]);\n this.currentConversationId.next(null);\n }\n }\n });\n }\n\n // Changed: Load the most recent conversation's messages on first open\n private hasLoadedInitial = false;\n\n // Toggle chat window open/closed\n toggle() {\n const opening = !this.isOpen.value;\n this.isOpen.next(opening);\n if (opening && !this.hasLoadedInitial) {\n this.hasLoadedInitial = true;\n this.restoreLastConversation(); // Changed: Load previous chat on first open\n }\n }\n open() {\n this.isOpen.next(true);\n if (!this.hasLoadedInitial) {\n this.hasLoadedInitial = true;\n this.restoreLastConversation();\n }\n }\n close() { this.isOpen.next(false); }\n\n // Changed: Fetch conversation list and load the most recent one's messages\n private restoreLastConversation() {\n this.dataService.CallApi({ url: 'agent/conversations' }).subscribe(res => {\n if (res.success && res.data?.length > 0) {\n this.conversations.next(res.data);\n const latest = res.data[0]; // Backend returns ordered by CreatedDate desc\n this.currentConversationId.next(latest.conversationID);\n this.loadMessages(latest.conversationID);\n }\n });\n }\n}\n","// Floating chat widget component for in-app Agent (renamed from AssistantComponent)\nimport { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked, HostListener, NgZone, Input } from '@angular/core'; // Changed: Input added for pageMode\nimport { Subscription } from 'rxjs';\nimport { filter } from 'rxjs/operators'; // Changed: NavigationEnd-only router stream for the agent-route check\nimport { Router, NavigationEnd } from '@angular/router'; // Changed: the floating widget stands down on the chat page's own route\nimport { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; // Changed: the bottom tab bar replaces the FAB on mobile\nimport { AgentService, AgentMessage, ConversationSummary, AgentSessionInfo } from '../../services/agent.service';\nimport { SignalRService } from 'tin-spa/core';\nimport { trigger, transition, style, animate } from '@angular/animations';\nimport { SpaMatModule } from 'tin-spa/core'; // Added (nav-menu-standalone): the declaration-free Material re-export shell — same surface TinSpaModule provided\nimport { CamelToWordsPipe } from '../../pipes/camelToWords.pipe'; // Added (nav-menu-standalone): used at agent.component.html:64 — a TinSpaModule declaration, so a standalone host must import it by path\nimport { AgentMarkdownPipe } from '../../pipes/agent-markdown.pipe'; // Added (nav-menu-standalone): used at agent.component.html:125 — same reason\n\n@Component({\n selector: 'spa-agent', // Changed: was spa-assistant\n templateUrl: './agent.component.html',\n styleUrls: ['./agent.component.css'],\n // Changed (nav-menu-standalone): standalone so the standalone NavMenuComponent can import <spa-agent> by\n // path (N-5). TinSpaModule now IMPORTS it and still EXPORTS it (A-AMD-2 shape), so every consumer that\n // renders <spa-agent> with only TinSpaModule imported keeps working unchanged.\n standalone: true,\n imports: [SpaMatModule, CamelToWordsPipe, AgentMarkdownPipe], // Added: Material/Common surface plus the two custom pipes the template names\n animations: [\n trigger('slideUp', [\n // Changed: page mode enters in normal flow and must not slide up — a page that animates in like a\n // dialog still reads as a dialog. Both empty transitions are declared BEFORE :enter/:leave because the\n // first matching transition wins, so 'none' opts the page instance out of the floating animation.\n transition('void => none', []), // Changed: page mode — no enter animation\n transition('none => void', []), // Changed: page mode — no leave animation\n transition(':enter', [\n style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }),\n animate('250ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 1, transform: 'translateY(0) scale(1)' }))\n ]),\n transition(':leave', [\n animate('200ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }))\n ])\n ])\n ]\n})\nexport class AgentComponent implements OnInit, OnDestroy, AfterViewChecked { // Changed: was AssistantComponent\n\n @ViewChild('messageContainer') messageContainer!: ElementRef;\n @ViewChild('messageInput') messageInput!: ElementRef;\n @ViewChild('cameraInput') cameraInput!: ElementRef<HTMLInputElement>; // Changed: hands off to the phone camera\n @ViewChild('galleryInput') galleryInput!: ElementRef<HTMLInputElement>; // Changed: gallery / file picker\n\n // Changed: one component, two presentations. false = the floating widget (FAB + fixed window, unchanged);\n // true = the routed chat page (home/agent) — no FAB, window always rendered, no outside-click close, no\n // minimize. Only presentation differs; all chat behaviour stays shared through AgentService.\n @Input() pageMode = false;\n\n // Changed: mobile retires the floating widget entirely — the bottom tab bar's Chat tab is the entry point\n // and home/agent is the surface. Both flags only ever gate the FLOATING presentation; page mode ignores them.\n smallScreen = false; // Changed: same (max-width: 600px) breakpoint nav-menu uses — one source of truth\n onAgentRoute = false; // Changed: true while the routed chat page owns the screen\n\n // Changed: the floating FAB is suppressed in page mode (Phase 1), on mobile, and on the chat page's own\n // route at any width — otherwise it hovers over the very page it would open.\n get showFab(): boolean {\n return !this.pageMode && !this.smallScreen && !this.onAgentRoute;\n }\n\n // Changed: the floating window follows the FAB. Page mode renders regardless of isOpen; the floating\n // instance additionally refuses to render wherever its FAB is gone, so there are never two chat surfaces.\n get showWindow(): boolean {\n return this.pageMode || (this.isOpen && !this.smallScreen && !this.onAgentRoute);\n }\n\n messages: AgentMessage[] = [];\n conversations: ConversationSummary[] = []; // Changed: switcher list\n currentConversationId: number | null = null;\n showConversations = false; // Changed: the switcher is a dropdown, not a permanent panel — the window is small\n sessionInfo: AgentSessionInfo | null = null; // Changed: drives the \"you are creating a Customer\" banner\n isOpen = false;\n isTyping = false;\n inputText = '';\n greeting = '';\n suggestedQuestions: string[] = [];\n isOnline = false;\n\n isRecording = false; // Changed: voice note capture state\n recordSeconds = 0;\n mediaError = ''; // Changed: shown inline when the mic is blocked or unavailable\n pendingPhoto: File | null = null; // Changed: photo is staged, not sent — the user adds a caption first\n isTranscribing = false; // Changed: Stop pressed, waiting for the transcript to land in the text box\n\n private subs: Subscription[] = [];\n private shouldScroll = false;\n private viewportHandler: (() => void) | null = null; // Changed: visualViewport resize handler reference\n\n // Changed: voice note recording internals\n private recorder: MediaRecorder | null = null;\n private mediaStream: MediaStream | null = null;\n private chunks: Blob[] = [];\n private recordTimer: any = null;\n private stopMode: 'send' | 'transcribe' | 'cancel' = 'send'; // Changed: what to do with the clip once stopped\n\n private static readonly MAX_RECORD_SECONDS = 120; // Auto-stop so a forgotten recording can't run away\n\n // ---------------------------------------------------------------------------------------------------\n // Voice mode — hands-free. Enter once, then speak and listen in a loop until you exit. Built for someone\n // driving or with their hands full, so nothing between entry and exit requires touch or sight.\n //\n // The session is deliberately HALF-DUPLEX: the microphone is closed whenever audio is playing, and\n // playback finishes before it reopens. That single property removes echo cancellation, acoustic feedback\n // and interrupt arbitration entirely. Preserve it — barge-in would be a re-architecture, not an increment.\n // ---------------------------------------------------------------------------------------------------\n voiceMode = false;\n // 'starting' is the room-calibration window. It exists because showing \"Listening\" during it was a lie —\n // the microphone is open but the detector is still measuring the noise floor, so anything said then is\n // measured AS noise and raises the bar against the speaker's own voice.\n voiceState: 'idle' | 'starting' | 'listening' | 'capturing' | 'thinking' | 'speaking' = 'idle';\n voiceTranscript = ''; // what it heard on the last turn\n voiceReply = ''; // what it answered, verbatim (the spoken version is shortened)\n voiceError = '';\n voiceLevel = 0; // 0..1, drives the level meter so the user can see the mic is live\n\n // Tunable because a vehicle cabin is not a desk. These are fields rather than constants so they can be\n // adjusted against a real environment without a rebuild.\n voiceThreshold = 0.01; // absolute floor — the room can raise this, never lower it\n noiseMultiplier = 3; // speech must be this many times the measured room noise\n maxThreshold = 0.08; // ...but never so high that a normal voice cannot clear it\n onsetMs = 250; // sustained loudness needed before it counts as speech at all\n silenceMs = 2200; // quiet needed to call the utterance finished\n minSpeechMs = 400; // anything shorter than this was a noise, not a sentence\n\n // What calibration actually settled on, exposed so it can be inspected while running\n noiseFloor = 0;\n activeThreshold = 0.03;\n\n private static readonly SETTLE_MS = 450; // let the speakers finish before trusting the mic again\n private static readonly CALIBRATION_MS = 600; // listen to the room before listening to the person\n private static readonly CONTINUE_RATIO = 0.55; // hysteresis: it takes less to STAY in speech than to enter it\n private static readonly MAX_UTTERANCE_MS = 30000; // a noisy cab must not record forever\n private static readonly IDLE_MS = 12000; // no speech this long → don't upload silence, listen again\n private static readonly MAX_SILENT_MS = 180000; // ...but give up after three minutes of nothing at all\n\n private calibrationEndsAt = 0;\n private calibrationSamples: number[] = [];\n private loudSince = 0;\n\n private voiceRecorder: MediaRecorder | null = null;\n private voiceStream: MediaStream | null = null;\n private voiceChunks: Blob[] = [];\n private audioContext: AudioContext | null = null;\n private analyser: AnalyserNode | null = null;\n private vadTimer: any = null;\n private player: HTMLAudioElement | null = null;\n private speechStartedAt = 0;\n private lastLoudAt = 0;\n private listenStartedAt = 0;\n private silentSince = 0;\n private abandonUtterance = false; // true when the clip should be dropped rather than uploaded\n\n constructor(public agentService: AgentService, private signalRService: SignalRService,\n private zone: NgZone, private router: Router, private breakpointObserver: BreakpointObserver) {} // Changed: Router + BreakpointObserver gate the floating presentation\n\n // Every voice state change goes through here. The states are set from audio-element events, a MediaRecorder\n // callback and a sampling timer, and those did not reliably trigger a repaint — the panel was showing\n // \"Listening\" while the machine was already capturing, thinking, or speaking aloud. That is worse than no\n // indicator at all: someone who cannot look at the screen is being told the wrong thing about whose turn it\n // is. Re-entering the Angular zone forces the view to catch up immediately.\n private setVoiceState(state: 'idle' | 'starting' | 'listening' | 'capturing' | 'thinking' | 'speaking') {\n if (this.voiceState === state) return;\n this.zone.run(() => { this.voiceState = state; });\n }\n\n get agentName(): string { // Changed: was assistantName\n return this.agentService.agentName;\n }\n\n ngOnInit(): void {\n this.agentService.loadConfig();\n this.initMobileViewport(); // Changed: Listen for keyboard resize on mobile\n\n this.subs.push(\n this.agentService.messages$.subscribe(msgs => {\n this.messages = msgs;\n this.shouldScroll = true;\n }),\n this.agentService.isOpen$.subscribe(open => {\n this.isOpen = open;\n if (open) this.focusInput();\n // Changed: the widget can be closed by an outside click while a voice session is running. The\n // component survives that (only the window is *ngIf'd away), so without this the microphone would\n // stay live behind a hidden window with no way to reach the exit button.\n // Changed: page mode ignores isOpen for visibility, so a close() from the floating window elsewhere\n // must not tear down a voice session that is still on screen on the page.\n else if (this.voiceMode && !this.pageMode) this.stopVoiceMode();\n }),\n this.agentService.typing$.subscribe(typing => {\n this.isTyping = typing;\n this.shouldScroll = true;\n }),\n this.agentService.greeting$.subscribe(g => this.greeting = g),\n this.agentService.suggestedQuestions$.subscribe(q => this.suggestedQuestions = q),\n // Changed: the conversation switcher and the session banner both come from the service\n this.agentService.conversations$.subscribe(list => this.conversations = list),\n this.agentService.currentConversationId$.subscribe(id => this.currentConversationId = id),\n this.agentService.sessionInfo$.subscribe(info => this.sessionInfo = info),\n this.signalRService.dataHubConnected$.subscribe(connected => this.isOnline = connected),\n // Changed: crossing into mobile must not strand an open floating window behind the new tab bar. Only\n // the FLOATING instance closes it — the page instance never mutates the shared isOpen (see fsd Risk 2).\n this.breakpointObserver.observe(['(max-width: 600px)']).subscribe((result: BreakpointState) => {\n this.smallScreen = result.matches;\n if (result.matches && !this.pageMode && this.isOpen) this.agentService.close();\n }),\n // Changed: the routed chat page renders its own instance, so the floating one steps aside on that URL\n this.router.events.pipe(\n filter(event => event instanceof NavigationEnd)\n ).subscribe((event: NavigationEnd) => {\n this.onAgentRoute = this.isAgentRoute(event.urlAfterRedirects || event.url);\n if (this.onAgentRoute && !this.pageMode && this.isOpen) this.agentService.close();\n })\n );\n\n this.onAgentRoute = this.isAgentRoute(this.router.url); // Changed: NavigationEnd has usually already fired by now\n\n this.agentService.loadConversations(); // Changed: the switcher needs the list before it is opened\n }\n\n // Changed: 'home/agent' is mounted through the shared home routing module, so the reliable test is the\n // trailing segment rather than a whole-URL match that would miss a consumer app's own route prefix.\n private isAgentRoute(url: string): boolean {\n const path = (url || '').split('?')[0].split(';')[0].split('#')[0].replace(/\\/+$/, '');\n return path.endsWith('/agent');\n }\n\n // Reads as \"creating a Customer\" rather than \"Create a Customer\" in the banner\n get sessionVerb(): string {\n switch (this.sessionInfo?.sessionType) {\n case 'Create': return 'Creating';\n case 'Edit': return 'Editing';\n case 'Delete': return 'Deleting';\n case 'BatchPost': return 'Batch posting';\n default: return 'Working on';\n }\n }\n\n // Changed: switching threads. The agent has always kept several conversations per user, but the widget\n // could only ever show the newest one — earlier threads existed and were unreachable.\n selectConversation(id: number) {\n if (id === this.currentConversationId) return;\n this.showConversations = false;\n this.agentService.loadMessages(id);\n }\n\n toggleConversationList() {\n this.showConversations = !this.showConversations;\n if (this.showConversations) this.agentService.loadConversations();\n }\n\n ngAfterViewChecked(): void {\n if (this.shouldScroll) {\n this.scrollToBottom();\n this.shouldScroll = false;\n }\n }\n\n toggleChat() {\n this.agentService.toggle();\n }\n\n // Changed: Close chat when clicking outside the chat window and FAB button\n @HostListener('document:click', ['$event'])\n onDocumentClick(event: MouseEvent) {\n if (this.pageMode) return; // Changed: a page does not close when you click the page — outside-click close is float-only\n if (!this.isOpen) return;\n\n // Changed: match on composedPath() instead of closest(). A control that re-renders the widget removes\n // itself from the DOM before this handler runs — the mic button lives inside *ngIf=\"!isRecording\", so\n // starting a recording detaches it. closest() on a detached node finds nothing, and the widget used to\n // close itself the instant recording began, leaving the mic live behind a hidden window.\n //\n // cdk-overlay-container is checked for the same reason: Material renders menus, tooltips and dialogs in\n // an overlay attached to <body>, OUTSIDE .agent-window. Without this, picking an item from the attach\n // menu closed the whole chat. Any overlay open on top of the widget counts as being inside it.\n const path = (event.composedPath ? event.composedPath() : []) as HTMLElement[];\n if (path.some(el => el?.classList?.contains('agent-window')\n || el?.classList?.contains('agent-fab')\n || el?.classList?.contains('cdk-overlay-container'))) return;\n\n // Fallback for browsers without composedPath, and for targets still attached\n const target = event.target as HTMLElement;\n if (target?.isConnected && (target.closest('.agent-window') || target.closest('.agent-fab'))) return;\n\n // A detached target means the click came from a control that just re-rendered — i.e. inside the widget\n if (target && !target.isConnected) return;\n\n this.agentService.close();\n }\n\n // Changed: sends whatever is staged — typed text, an attached photo, or both. A recording in progress is\n // stopped and folded into the same send rather than being lost.\n sendMessage() {\n if (this.isRecording) { this.stopMode = 'send'; this.recorder?.stop(); return; }\n\n const caption = this.inputText.trim();\n if (!caption && !this.pendingPhoto) return;\n\n if (this.pendingPhoto) {\n this.agentService.sendComposed(this.pendingPhoto, this.pendingPhoto.name || 'photo.jpg', null, '', caption);\n this.pendingPhoto = null;\n } else {\n this.agentService.sendMessage(caption);\n }\n\n this.inputText = '';\n }\n\n // Changed: true when there is anything worth sending\n get canSend(): boolean {\n return !!this.inputText.trim() || !!this.pendingPhoto;\n }\n\n clearPhoto() {\n this.pendingPhoto = null;\n }\n\n sendSuggested(question: string) {\n this.agentService.sendMessage(question);\n }\n\n onKeydown(event: KeyboardEvent) {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n this.sendMessage();\n }\n }\n\n newConversation() {\n this.agentService.newConversation();\n }\n\n // Changed: mm:ss shown while recording\n get recordDisplay(): string {\n const mins = Math.floor(this.recordSeconds / 60);\n const secs = this.recordSeconds % 60;\n return `${mins}:${secs < 10 ? '0' : ''}${secs}`;\n }\n\n // Changed: voice note capture — the clip goes straight to the backend, which transcribes it and feeds the\n // text into the agent exactly as Telegram and WhatsApp voice notes are handled\n async startRecording() {\n if (this.isRecording) return;\n this.mediaError = '';\n\n if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === 'undefined') {\n this.mediaError = 'Voice notes are not supported in this browser.';\n return;\n }\n\n try {\n this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch {\n this.mediaError = 'Microphone access was blocked. Allow it in your browser settings to send voice notes.';\n return;\n }\n\n this.chunks = [];\n this.stopMode = 'send';\n\n const mimeType = this.pickAudioMimeType();\n this.recorder = mimeType ? new MediaRecorder(this.mediaStream, { mimeType }) : new MediaRecorder(this.mediaStream);\n this.recorder.ondataavailable = e => { if (e.data && e.data.size > 0) this.chunks.push(e.data); };\n this.recorder.onstop = () => this.finishRecording();\n this.recorder.start();\n\n this.isRecording = true;\n this.recordSeconds = 0;\n this.recordTimer = setInterval(() => {\n this.recordSeconds++;\n if (this.recordSeconds >= AgentComponent.MAX_RECORD_SECONDS) this.stopRecording();\n }, 1000);\n }\n\n // Changed: send immediately — the clip goes with whatever else is staged, no review step\n stopRecording() {\n if (!this.isRecording || !this.recorder) return;\n this.stopMode = 'send';\n this.recorder.stop(); // finishRecording() runs from onstop\n }\n\n // Changed: stop and transcribe INTO the text box so the user can read it back, edit it, and send when ready\n stopAndTranscribe() {\n if (!this.isRecording || !this.recorder) return;\n this.stopMode = 'transcribe';\n this.recorder.stop();\n }\n\n cancelRecording() {\n if (!this.isRecording || !this.recorder) return;\n this.stopMode = 'cancel';\n this.recorder.stop();\n }\n\n // ---------------------------------------------------------------------------------------------------\n // Voice mode\n // ---------------------------------------------------------------------------------------------------\n\n // Tap versus hold on the single mic button. The composer row is full — attach, text, mic, send — so voice\n // mode gets a gesture rather than a fifth control that would squeeze the text field on a phone. Same idea\n // as holding the mic in WhatsApp: the button keeps its obvious job, the second mode hides behind the hold.\n micHeld = false;\n private micHoldTimer: any = null;\n private micHoldFired = false;\n\n private static readonly MIC_HOLD_MS = 500; // long enough not to fire on a normal tap\n\n onMicPressStart() {\n if (this.voiceMode || this.isRecording) return;\n\n this.micHeld = true;\n this.micHoldFired = false;\n\n this.micHoldTimer = setTimeout(() => {\n this.micHeld = false;\n this.micHoldFired = true; // swallows the click that follows the release\n if (navigator.vibrate) navigator.vibrate(30); // confirms the mode switch without needing to look\n this.startVoiceMode();\n }, AgentComponent.MIC_HOLD_MS);\n }\n\n onMicPressEnd() {\n this.micHeld = false;\n if (this.micHoldTimer) { clearTimeout(this.micHoldTimer); this.micHoldTimer = null; }\n }\n\n // Runs after pointerup. A hold has already acted, so the click that follows it must do nothing.\n onMicClick() {\n if (this.micHoldFired) { this.micHoldFired = false; return; }\n this.startRecording();\n }\n\n // Entering is the ONLY user gesture in the whole session, so everything that needs one happens here: iOS\n // will not let audio play later unless an AudioContext was created and resumed inside a real tap.\n async startVoiceMode() {\n if (this.voiceMode) return;\n\n this.voiceError = '';\n this.voiceTranscript = '';\n this.voiceReply = '';\n\n if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === 'undefined') {\n this.voiceError = 'Voice mode is not supported in this browser.';\n return;\n }\n\n if (this.isRecording) this.cancelRecording(); // never run both capture paths at once\n\n try {\n const Ctx = (window as any).AudioContext || (window as any).webkitAudioContext;\n this.audioContext = new Ctx();\n if (this.audioContext!.state === 'suspended') await this.audioContext!.resume();\n } catch {\n this.voiceError = 'Audio could not be started in this browser.';\n return;\n }\n\n this.voiceMode = true;\n this.silentSince = Date.now();\n await this.beginListening();\n }\n\n // One tap out, from anywhere in the loop. Everything is torn down: recorder, microphone, playback, timers.\n stopVoiceMode() {\n this.voiceMode = false;\n this.setVoiceState('idle');\n this.voiceLevel = 0;\n\n this.abandonUtterance = true;\n this.stopVoiceCapture();\n this.stopPlayback();\n\n if (this.audioContext) {\n try { this.audioContext.close(); } catch {}\n this.audioContext = null;\n }\n }\n\n // Opens the mic and waits. The clip records from the moment the mic opens, so the first syllable is never\n // clipped — the detector decides afterwards whether anything worth sending was actually said.\n private async beginListening() {\n if (!this.voiceMode) return;\n\n try {\n // These three constraints were all settled by measurement on a real machine, not by reasoning:\n //\n // echoCancellation ON — without it the microphone hears the agent's OWN reply from the speakers and\n // answers itself; a spoken reply came back as the next command.\n // noiseSuppression OFF — tried ON to fight fan noise and it crushed the voice instead: a normal\n // speaking voice fell from 0.15 to 0.026 RMS, barely grazing the threshold, and\n // no utterance was ever captured. Steady noise is already handled better by the\n // per-cycle noise-floor calibration and the sustained-onset rule below, neither\n // of which costs any signal.\n // autoGainControl ON — also tried OFF (to keep levels stable for calibration) and that made it worse\n // still, 0.008 RMS. A drifting floor beats a microphone that cannot hear you.\n this.voiceStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: false,\n autoGainControl: true\n }\n });\n } catch {\n this.voiceError = 'Microphone access was blocked. Allow it in your browser settings to use voice mode.';\n this.stopVoiceMode();\n return;\n }\n\n if (!this.voiceMode) { // exited while the permission prompt was up\n this.voiceStream.getTracks().forEach(t => t.stop());\n this.voiceStream = null;\n return;\n }\n\n this.voiceChunks = [];\n this.abandonUtterance = false;\n this.speechStartedAt = 0;\n this.listenStartedAt = Date.now();\n this.lastLoudAt = 0;\n this.loudSince = 0;\n\n // Re-measure the room on every cycle: an engine starts, a window opens, the air-con changes note\n this.calibrationSamples = [];\n this.calibrationEndsAt = Date.now() + AgentComponent.CALIBRATION_MS;\n\n const mimeType = this.pickAudioMimeType();\n this.voiceRecorder = mimeType\n ? new MediaRecorder(this.voiceStream, { mimeType })\n : new MediaRecorder(this.voiceStream);\n this.voiceRecorder.ondataavailable = e => { if (e.data && e.data.size > 0) this.voiceChunks.push(e.data); };\n this.voiceRecorder.onstop = () => this.onUtteranceCaptured();\n this.voiceRecorder.start();\n\n // NOT 'listening' yet — the room is being measured first. The \"your turn\" tone and the Listening label\n // both wait for calibration to finish, so the tone always means the same thing: start talking now.\n this.setVoiceState('starting');\n this.startVad();\n }\n\n // Voice activity detection: RMS over a short window, no dependencies. Speech starts when it goes loud, ends\n // after a run of quiet — which is what makes the turn hands-free, since nothing has to be pressed to stop.\n private startVad() {\n if (!this.audioContext || !this.voiceStream) return;\n\n const source = this.audioContext.createMediaStreamSource(this.voiceStream);\n this.analyser = this.audioContext.createAnalyser();\n this.analyser.fftSize = 1024;\n source.connect(this.analyser);\n\n const samples = new Uint8Array(this.analyser.fftSize);\n\n this.vadTimer = setInterval(() => {\n if (!this.analyser) return;\n\n this.analyser.getByteTimeDomainData(samples);\n\n let sum = 0;\n for (let i = 0; i < samples.length; i++) {\n const deviation = (samples[i] - 128) / 128;\n sum += deviation * deviation;\n }\n const rms = Math.sqrt(sum / samples.length);\n\n this.voiceLevel = Math.min(1, rms / 0.15); // meter only — the decision uses rms directly\n this.evaluateVad(rms);\n }, 50);\n }\n\n private evaluateVad(rms: number) {\n const now = Date.now();\n\n // Phase 0 — listen to the ROOM before listening to the person. A fixed threshold cannot know whether it\n // is sitting in a quiet office or next to a fan, so the first moments of every listening cycle measure the\n // ambient level and the speech threshold is set above it. This is what stops a fan being heard as speech.\n if (this.calibrationEndsAt) {\n if (now < this.calibrationEndsAt) { this.calibrationSamples.push(rms); return; }\n\n this.applyCalibration();\n\n // Only NOW is it actually listening to the person, so this is where the label flips and the \"your turn\"\n // tone plays. The listening clock restarts here too — the calibration window must not count against the\n // idle timeout, or a slow starter loses part of their thinking time.\n this.setVoiceState('listening');\n this.listenStartedAt = now;\n this.earcon('ready');\n }\n\n const enterThreshold = this.activeThreshold;\n // Hysteresis: once someone is speaking it takes LESS to keep them speaking. Without this, the natural dip\n // between words reads as the end of the sentence.\n const stayThreshold = enterThreshold * AgentComponent.CONTINUE_RATIO;\n\n if (!this.speechStartedAt) {\n // Not speaking yet. Loudness must be SUSTAINED to count — a door, a keyboard, a fan surge is loud for\n // an instant, and a single loud sample used to be enough to start capturing.\n if (rms >= enterThreshold) {\n if (!this.loudSince) this.loudSince = now;\n\n if (now - this.loudSince >= this.onsetMs) {\n this.speechStartedAt = this.loudSince; // backdated, so the first word is not clipped\n this.lastLoudAt = now;\n this.setVoiceState('capturing');\n this.silentSince = 0; // they are talking, so the give-up clock stops\n this.earcon('gotyou'); // \"I can hear you\" — answers the question the old single beep left open\n }\n } else {\n this.loudSince = 0; // a blip, not a sentence\n }\n } else {\n // Mid-sentence. Pauses and breaths are expected — only a long quiet ends the turn.\n if (rms >= stayThreshold) {\n this.lastLoudAt = now;\n } else if (now - this.lastLoudAt >= this.silenceMs) {\n // Too short to be a sentence? Then it was a noise — throw it away instead of asking the model to\n // find words in it, which is where \"Hallo!\" and other invented transcripts come from.\n const spoken = this.lastLoudAt - this.speechStartedAt;\n this.endUtterance(spoken < this.minSpeechMs);\n return;\n }\n\n if (now - this.speechStartedAt >= AgentComponent.MAX_UTTERANCE_MS) this.endUtterance(false);\n return;\n }\n\n // Nothing said yet — don't upload silence. Reopen the mic instead of ending the session, because someone\n // driving may simply not be ready to talk yet.\n // The speechStartedAt guard matters: the branch above can START speech on this very sample, and without\n // it the idle check below would immediately throw that fresh utterance away.\n if (!this.speechStartedAt && now - this.listenStartedAt >= AgentComponent.IDLE_MS) {\n if (!this.silentSince) this.silentSince = now;\n\n if (now - this.silentSince >= AgentComponent.MAX_SILENT_MS) {\n this.voiceError = 'Voice mode ended after a long silence.';\n this.stopVoiceMode();\n return;\n }\n\n this.endUtterance(true); // drop this clip and listen again\n }\n }\n\n // Turns the room measurement into a working threshold. Uses a high percentile rather than the average so a\n // steady hum sets the floor without one loud knock dragging it up, then requires speech to be several times\n // that. The configured voiceThreshold acts as a floor only — a noisy room can raise the bar, never lower it.\n private applyCalibration() {\n this.calibrationEndsAt = 0;\n\n const samples = this.calibrationSamples.slice().sort((a, b) => a - b);\n this.calibrationSamples = [];\n\n if (!samples.length) { this.activeThreshold = this.voiceThreshold; return; }\n\n const percentile = samples[Math.min(samples.length - 1, Math.floor(samples.length * 0.9))];\n this.noiseFloor = percentile;\n\n // Clamped at BOTH ends. The upper clamp is not theoretical: calibrating while someone was still talking\n // measured a \"room\" of 0.17 and set the bar at 0.52 — around three times louder than any normal voice, so\n // the microphone was open and permanently deaf. A ceiling means the worst case is over-sensitivity, which\n // the onset rule and minimum-utterance check still catch, rather than total silence.\n const scaled = percentile * this.noiseMultiplier;\n this.activeThreshold = Math.min(this.maxThreshold, Math.max(this.voiceThreshold, scaled));\n }\n\n // Ends the current capture. `abandon` means the clip is thrown away rather than sent.\n private endUtterance(abandon: boolean) {\n this.abandonUtterance = abandon;\n\n if (this.vadTimer) { clearInterval(this.vadTimer); this.vadTimer = null; }\n this.voiceLevel = 0;\n\n if (this.voiceRecorder && this.voiceRecorder.state !== 'inactive') {\n this.voiceRecorder.stop(); // onUtteranceCaptured() runs from onstop\n } else {\n this.onUtteranceCaptured();\n }\n }\n\n // The microphone is released HERE, before anything is sent — so it is provably shut for the whole of\n // thinking and speaking, which is what keeps the session half-duplex.\n private onUtteranceCaptured() {\n const chunks = this.voiceChunks;\n const mimeType = this.voiceRecorder?.mimeType || 'audio/webm';\n\n this.releaseMic();\n\n if (!this.voiceMode) return;\n\n if (this.abandonUtterance || !chunks.length) {\n this.voiceChunks = [];\n this.beginListening(); // nothing worth sending — straight back to listening\n return;\n }\n\n this.voiceChunks = [];\n this.setVoiceState('thinking');\n\n const blob = new Blob(chunks, { type: mimeType });\n const fileName = `voice-turn.${this.extensionFor(mimeType)}`;\n\n this.agentService.voiceTurn(blob, fileName).subscribe({\n next: (res: any) => this.onTurnAnswered(res),\n error: () => this.speakThenListen('', 'Sorry, that did not get through. Please try again.')\n });\n }\n\n private onTurnAnswered(res: any) {\n if (!this.voiceMode) return;\n\n const data = res?.data || {};\n\n if (data.transcript) {\n this.voiceTranscript = data.transcript;\n this.agentService.appendSpokenMessage(data.transcript); // the spoken turn joins the normal thread\n }\n\n const reply = data.replyText || res?.message || 'Sorry, something went wrong.';\n this.speakThenListen(data.audioBase64, reply, data.audioMimeType);\n }\n\n // Plays the answer, then reopens the microphone STRICTLY on 'ended'. Never before — that ordering is the\n // half-duplex guarantee, and it is why no echo cancellation is needed anywhere in this feature.\n private speakThenListen(audioBase64: string, replyText: string, mimeType: string = 'audio/mpeg') {\n this.voiceReply = replyText;\n this.setVoiceState('speaking');\n\n if (!audioBase64) {\n // No audio came back (a network failure, or speech synthesis was unavailable server-side). Say it with\n // the browser's own voice rather than leaving someone who cannot look at the screen in silence.\n this.speakFallback(replyText);\n return;\n }\n\n this.stopPlayback();\n\n this.player = new Audio(`data:${mimeType || 'audio/mpeg'};base64,${audioBase64}`);\n // Keep the label honest: 'speaking' is re-asserted when sound actually starts, not merely when it was\n // requested, so the indicator never claims to be doing something the ear cannot hear yet.\n this.player.onplaying = () => { if (this.voiceMode) this.setVoiceState('speaking'); };\n this.player.onended = () => this.afterSpeaking();\n this.player.onerror = () => this.afterSpeaking();\n\n this.player.play().catch(() => this.afterSpeaking()); // autoplay refused → don't hang the loop\n }\n\n // Browser speech synthesis, used only when the server could not produce audio.\n // Chrome's utterance.onend is unreliable for long text — it can fire early or not at all, which is what made\n // the panel say \"Listening\" while a voice was still talking. So the end of speech is confirmed by POLLING\n // speechSynthesis.speaking rather than by trusting the event.\n private speakFallback(text: string) {\n const synth = (window as any).speechSynthesis;\n\n if (!synth || !text) { this.afterSpeaking(); return; }\n\n try {\n const utterance = new (window as any).SpeechSynthesisUtterance(text);\n synth.speak(utterance);\n\n const started = Date.now();\n const watch = setInterval(() => {\n const stillTalking = synth.speaking || synth.pending;\n const tooLong = Date.now() - started > 60000; // never wedge the loop on a stuck engine\n\n if (!this.voiceMode) { clearInterval(watch); return; }\n if (stillTalking && !tooLong) { this.setVoiceState('speaking'); return; }\n\n clearInterval(watch);\n this.afterSpeaking();\n }, 200);\n } catch {\n this.afterSpeaking();\n }\n }\n\n // 'ended' means the audio element finished, NOT that the room is quiet: the last of it is still leaving the\n // speakers and reverberating. Reopening instantly meant the mic caught the tail of the agent's own sentence\n // and, worse, calibrated the noise floor against it. A short settle keeps the loop honest.\n private afterSpeaking() {\n this.player = null;\n if (!this.voiceMode) return;\n\n setTimeout(() => { if (this.voiceMode) this.beginListening(); }, AgentComponent.SETTLE_MS);\n }\n\n // Three tones, each meaning exactly ONE thing, because \"a beep happened\" is not information:\n //\n // ready — rising two-note, plays when it is ready FOR YOU. \"Start talking now.\"\n // gotyou — single soft note, plays the moment your speech is \"I can hear you, keep going.\"\n // detected, so you know you were picked up\n // taken — falling two-note, plays when the mic closes and it \"I have it, I'm working on it.\"\n // starts working\n //\n // Rising always means the floor is yours; falling always means it has been taken away. That direction is\n // the whole cue — it can be recognised without looking and without remembering pitches.\n private earcon(kind: 'ready' | 'gotyou' | 'taken') {\n if (!this.audioContext) return;\n\n const notes = kind === 'ready' ? [660, 990]\n : kind === 'taken' ? [660, 440]\n : [880];\n\n try {\n const start = this.audioContext.currentTime;\n\n notes.forEach((frequency, index) => {\n const oscillator = this.audioContext!.createOscillator();\n const gain = this.audioContext!.createGain();\n\n oscillator.type = 'sine';\n oscillator.frequency.value = frequency;\n\n const at = start + index * 0.1;\n const peak = kind === 'gotyou' ? 0.06 : 0.12; // the mid-speech cue is deliberately quieter\n\n gain.gain.setValueAtTime(0.0001, at);\n gain.gain.exponentialRampToValueAtTime(peak, at + 0.01);\n gain.gain.exponentialRampToValueAtTime(0.0001, at + 0.09);\n\n oscillator.connect(gain);\n gain.connect(this.audioContext!.destination);\n oscillator.start(at);\n oscillator.stop(at + 0.1);\n });\n } catch {}\n }\n\n private releaseMic() {\n if (this.vadTimer) { clearInterval(this.vadTimer); this.vadTimer = null; }\n\n this.analyser = null;\n this.voiceRecorder = null;\n this.voiceLevel = 0;\n\n if (this.voiceStream) {\n this.voiceStream.getTracks().forEach(t => t.stop());\n this.voiceStream = null;\n this.earcon('taken'); // \"I have it\" — the floor is no longer yours\n }\n }\n\n private stopVoiceCapture() {\n if (this.voiceRecorder && this.voiceRecorder.state !== 'inactive') {\n try { this.voiceRecorder.stop(); } catch {}\n }\n this.releaseMic();\n this.voiceChunks = [];\n }\n\n private stopPlayback() {\n if (!this.player) return;\n try { this.player.pause(); this.player.currentTime = 0; } catch {}\n this.player.onended = null;\n this.player.onerror = null;\n this.player = null;\n\n const synth = (window as any).speechSynthesis;\n if (synth) { try { synth.cancel(); } catch {} }\n }\n\n // What the state indicator reads. Plain words, because it is glanced at rather than read.\n get voiceStateLabel(): string {\n switch (this.voiceState) {\n // Changed: shown during room calibration. \"Wait\" rather than \"Listening\", because talking during this\n // window is actively harmful — the words get measured as background noise.\n case 'starting': return 'Getting ready';\n case 'listening': return 'Listening';\n // Changed: capturing is now shown. It tells the user it has locked onto their voice and is WAITING for\n // them to finish — without it, a long pause feels like the thing has stopped paying attention.\n case 'capturing': return 'Hearing you';\n case 'thinking': return 'Thinking';\n case 'speaking': return 'Speaking';\n default: return '';\n }\n }\n\n get voiceStateIcon(): string {\n switch (this.voiceState) {\n case 'starting': return 'graphic_eq'; // measuring the room, not the person\n case 'listening':\n case 'capturing': return 'mic';\n case 'thinking': return 'hourglass_top';\n case 'speaking': return 'volume_up';\n default: return 'mic_off';\n }\n }\n\n // Changed: \"Take photo\" — hands off to the device camera. On a phone this opens the camera app on the rear\n // lens and returns the shot; no in-page camera permission is involved because the OS handles it. Desktop\n // browsers ignore `capture` and fall back to the file picker.\n takePhoto() {\n this.cameraInput?.nativeElement.click();\n }\n\n // Changed: \"Choose photo\" — same input minus `capture`, so it opens the gallery / file picker\n choosePhoto() {\n this.galleryInput?.nativeElement.click();\n }\n\n // Changed: photo capture now STAGES the file instead of sending it. Nothing leaves until the user presses\n // send, so they can add a typed or spoken caption first — that caption is the actual command, the photo is\n // the evidence it refers to. `capture` opens the camera directly on mobile, the file picker on desktop.\n onPhotoSelected(event: Event) {\n const input = event.target as HTMLInputElement;\n const file = input.files && input.files.length ? input.files[0] : null;\n input.value = ''; // Reset so picking the same file twice still fires a change event\n\n if (!file) return;\n\n this.pendingPhoto = file;\n this.focusInput();\n }\n\n // Picks a container the browser can record AND the transcription endpoint accepts. Chrome/Edge/Firefox give\n // webm/opus, Safari gives mp4 — both are supported server-side, so no transcoding is needed anywhere.\n private pickAudioMimeType(): string {\n const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg;codecs=opus', 'audio/ogg'];\n return candidates.find(t => MediaRecorder.isTypeSupported(t)) || '';\n }\n\n private finishRecording() {\n const mode = this.stopMode;\n this.clearRecording();\n\n if (mode === 'cancel' || !this.chunks.length) {\n this.chunks = [];\n return;\n }\n\n const type = this.recorder?.mimeType || 'audio/webm';\n const blob = new Blob(this.chunks, { type });\n const fileName = `voice-note.${this.extensionFor(type)}`;\n this.chunks = [];\n\n // Stop → transcribe into the text box for review; the photo (if any) stays staged\n if (mode === 'transcribe') {\n this.isTranscribing = true;\n this.agentService.transcribeOnly(blob, fileName).subscribe({\n next: (res: any) => {\n this.isTranscribing = false;\n if (res?.success && res.data?.text) {\n const existing = this.inputText.trim();\n this.inputText = existing ? `${existing} ${res.data.text}` : res.data.text;\n this.focusInput();\n } else {\n this.mediaError = res?.message || 'Sorry, that recording could not be transcribed.';\n }\n },\n error: () => {\n this.isTranscribing = false;\n this.mediaError = 'Sorry, that recording could not be transcribed.';\n }\n });\n return;\n }\n\n // Send → the clip goes with the staged photo and whatever is typed, all in one message\n this.agentService.sendComposed(\n this.pendingPhoto, this.pendingPhoto?.name || 'photo.jpg',\n blob, fileName,\n this.inputText.trim()\n );\n\n this.pendingPhoto = null;\n this.inputText = '';\n }\n\n private extensionFor(mimeType: string): string {\n const type = (mimeType || '').toLowerCase();\n if (type.indexOf('mp4') >= 0) return 'mp4';\n if (type.indexOf('ogg') >= 0) return 'ogg';\n return 'webm';\n }\n\n // Releases the mic — without this the browser keeps showing the recording indicator\n private clearRecording() {\n this.isRecording = false;\n this.recordSeconds = 0;\n\n if (this.recordTimer) {\n clearInterval(this.recordTimer);\n this.recordTimer = null;\n }\n\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach(t => t.stop());\n this.mediaStream = null;\n }\n }\n\n private scrollToBottom() {\n try {\n if (this.messageContainer) {\n this.messageContainer.nativeElement.scrollTop = this.messageContainer.nativeElement.scrollHeight;\n }\n } catch (e) {}\n }\n\n private focusInput() {\n setTimeout(() => {\n if (this.messageInput) {\n this.messageInput.nativeElement.focus();\n }\n }, 300);\n }\n\n trackMessage(index: number, msg: AgentMessage): string {\n return `${index}-${msg.role}-${msg.createdDate}`;\n }\n\n // Changed: Set --agent-vh CSS variable from visualViewport so the chat resizes when the mobile keyboard opens\n private initMobileViewport() {\n if (!window.visualViewport) return;\n this.viewportHandler = () => {\n const vh = window.visualViewport!.height;\n document.documentElement.style.setProperty('--agent-vh', `${vh}px`);\n // Changed: one signal, a second consumer. When the on-screen keyboard opens the visual viewport\n // collapses well below the layout viewport; the fixed bottom tab bar has to get out of the way or it\n // sits between the keyboard and the field being typed into. Published as a class on <html> so the tab\n // bar reacts in CSS alone — deliberately NOT a second visualViewport listener of its own.\n // 0.75 is the usual heuristic: real keyboards take 30-50% of the height, browser chrome far less.\n document.documentElement.classList.toggle('keyboard-open', vh < window.innerHeight * 0.75);\n };\n window.visualViewport.addEventListener('resize', this.viewportHandler);\n this.viewportHandler(); // Set initial value\n }\n\n ngOnDestroy(): void {\n this.subs.forEach(s => s.unsubscribe());\n if (this.isRecording) this.cancelRecording(); // Changed: never leave the mic open behind a destroyed widget\n if (this.voiceMode) this.stopVoiceMode(); // Changed: same for a voice session — no orphaned mic\n this.onMicPressEnd(); // Changed: a held mic must not fire after teardown\n this.clearRecording();\n if (this.viewportHandler && window.visualViewport) {\n window.visualViewport.removeEventListener('resize', this.viewportHandler);\n document.documentElement.classList.remove('keyboard-open'); // Changed: never leave the tab bar hidden by a destroyed widget\n }\n }\n}\n","<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\n\n<!-- FAB toggle button -->\n<!-- Changed: the page presentation has no FAB — the page IS the chat, so a button to open it is nonsense.\n Changed: showFab also stands the FAB down on mobile (the bottom tab bar's Chat tab replaces it) and on\n the home/agent route at any width, where it would otherwise hover over the page it opens. -->\n<button mat-fab class=\"agent-fab\" *ngIf=\"showFab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\n</button>\n\n<!-- Chat window -->\n<!-- Changed: rendered unconditionally in page mode (isOpen only governs the floating window); .page-mode\n swaps the fixed overlay for in-flow layout and 'none' opts out of the slide-up animation -->\n<!-- Changed: showWindow keeps the floating window off wherever its FAB is off, so mobile and the chat route\n can never show two chat surfaces at once -->\n<div class=\"agent-window\" [class.page-mode]=\"pageMode\" *ngIf=\"showWindow\" [@slideUp]=\"pageMode ? 'none' : 'float'\">\n\n <!-- Header with online status -->\n <div class=\"agent-header\">\n <div class=\"header-info\">\n <mat-icon class=\"header-icon\">smart_toy</mat-icon>\n <div class=\"header-text\">\n <span class=\"header-title\">{{ agentName }}</span>\n <span class=\"status-badge\" [class.online]=\"isOnline\" [class.offline]=\"!isOnline\">\n <span class=\"status-dot\"></span>\n {{ isOnline ? 'Online' : 'Offline' }}\n </span>\n </div>\n </div>\n <div class=\"header-actions\">\n <!-- Changed: earlier conversations existed but were unreachable — the widget only ever showed the newest -->\n <button mat-icon-button matTooltip=\"Conversations\" (click)=\"toggleConversationList()\">\n <mat-icon>forum</mat-icon>\n </button>\n <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\n <mat-icon>add_comment</mat-icon>\n </button>\n <!-- Changed: minimise is float-only — there is nothing to minimise a routed page into -->\n <button mat-icon-button matTooltip=\"Close\" *ngIf=\"!pageMode\" (click)=\"toggleChat()\">\n <mat-icon>remove</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Conversation switcher. A dropdown rather than a panel: the window is small and this is a jump list,\n not somewhere the user spends time. -->\n <div class=\"agent-conversations\" *ngIf=\"showConversations\">\n <div class=\"conversation-row\" *ngFor=\"let c of conversations\"\n [class.current]=\"c.conversationID === currentConversationId\"\n (click)=\"selectConversation(c.conversationID)\">\n <mat-icon class=\"conversation-icon\">chat</mat-icon>\n <span class=\"conversation-title\">{{ c.title }}</span>\n </div>\n <div class=\"conversation-empty\" *ngIf=\"conversations.length === 0\">No earlier conversations</div>\n </div>\n\n <!-- Changed: the session banner. The agent has always known it was holding a half-built record; until the\n reply carried structured state, that fact only existed as prose the user had to read carefully. -->\n <div class=\"agent-session-banner\" *ngIf=\"sessionInfo?.sessionType\">\n <mat-icon class=\"session-icon\">edit_note</mat-icon>\n <span class=\"session-text\">{{ sessionVerb }} a {{ sessionInfo?.modelName }}</span>\n <!-- Changed: chips rather than a sentence. What is still required is the one thing the user has to act\n on, and a chip per field is scannable where a comma-separated list inside a sentence is not. -->\n <span class=\"session-chip\" *ngFor=\"let field of sessionInfo?.missingFields\">{{ field | camelToWords }}</span>\n </div>\n\n <!-- Messages area -->\n <div class=\"agent-messages\" #messageContainer>\n\n <!-- Welcome experience (show when no messages) -->\n <div class=\"agent-greeting\" *ngIf=\"messages.length === 0\">\n <mat-icon class=\"greeting-icon\">smart_toy</mat-icon>\n <p class=\"greeting-text\">{{ greeting }}</p>\n\n <!-- Three capability pillars -->\n <div class=\"capability-pillars\">\n\n <!-- Pillar 1: Navigate & Inquire -->\n <div class=\"pillar\" (click)=\"sendSuggested('How do I post a transaction?')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">explore</mat-icon>\n <span class=\"pillar-title\">Ask & Navigate</span>\n </div>\n <p class=\"pillar-desc\">Ask how to do things or find features in the app</p>\n <span class=\"pillar-example\">\"How do I post a transaction?\"</span>\n </div>\n\n <!-- Pillar 2: Create & Modify -->\n <div class=\"pillar\" (click)=\"sendSuggested('Create a new customer')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">edit_note</mat-icon>\n <span class=\"pillar-title\">Create & Modify</span>\n </div>\n <p class=\"pillar-desc\">Add, edit, or delete records using natural language</p>\n <span class=\"pillar-example\">\"Create a new customer called Acme Ltd\"</span>\n </div>\n\n <!-- Pillar 3: Get Information -->\n <div class=\"pillar\" (click)=\"sendSuggested('List all accounts')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">search</mat-icon>\n <span class=\"pillar-title\">Get Information</span>\n </div>\n <p class=\"pillar-desc\">Retrieve details, list records, or get summaries</p>\n <span class=\"pillar-example\">\"Show me the details of Customer A\"</span>\n </div>\n\n </div>\n </div>\n\n <!-- Message bubbles -->\n <ng-container *ngFor=\"let msg of messages; trackBy: trackMessage\">\n\n <!-- Changed: a conversation boundary is drawn, not spoken. Without a visible line, the agent starting a\n fresh thread after a long gap just looks like it forgetting what was said. -->\n <div class=\"conversation-divider\" *ngIf=\"msg.divider\">\n <span class=\"divider-label\">{{ msg.content }}</span>\n </div>\n\n <div *ngIf=\"!msg.divider\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\n <!-- Changed: agent replies are markdown and were being printed verbatim, so a carefully formatted\n answer arrived as a wall of asterisks. User messages stay plain text — they are not markdown,\n and interpreting them as such would be putting words in the user's mouth. -->\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\" [class.pending-bubble]=\"msg.pending\">\n <span *ngIf=\"msg.role === 'assistant'\" [innerHTML]=\"msg.content | agentMarkdown\"></span>\n <span *ngIf=\"msg.role !== 'assistant'\">{{ msg.content }}</span>\n </div>\n </div>\n\n </ng-container>\n\n <!-- Typing indicator -->\n <div class=\"message-row agent-row\" *ngIf=\"isTyping\">\n <div class=\"message-bubble agent-bubble typing-bubble\">\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n </div>\n </div>\n\n </div>\n\n <!-- Media error (mic blocked / unsupported browser) -->\n <div class=\"agent-media-error\" *ngIf=\"mediaError\">\n <mat-icon class=\"media-error-icon\">error_outline</mat-icon>\n <span>{{ mediaError }}</span>\n </div>\n\n <!-- Staged attachment: a photo waits here until the user adds a caption and presses send -->\n <div class=\"agent-attachment\" *ngIf=\"pendingPhoto\">\n <mat-icon class=\"attach-icon\">image</mat-icon>\n <span class=\"attach-label\">Photo attached</span>\n <button mat-icon-button class=\"attach-remove\" matTooltip=\"Remove photo\" (click)=\"clearPhoto()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <!-- Transcribing indicator for the Stop button -->\n <div class=\"agent-transcribing\" *ngIf=\"isTranscribing\">\n <mat-icon class=\"transcribing-icon\">graphic_eq</mat-icon>\n <span>Transcribing…</span>\n </div>\n\n <!-- Voice mode: replaces the composer entirely. Everything here is sized to be used without looking —\n a big state indicator, the last exchange, and a thumb-sized exit. -->\n <div class=\"agent-voice\" *ngIf=\"voiceMode\">\n\n <div class=\"voice-state\" [class.is-starting]=\"voiceState === 'starting'\"\n [class.is-listening]=\"voiceState === 'listening' || voiceState === 'capturing'\"\n [class.is-thinking]=\"voiceState === 'thinking'\" [class.is-speaking]=\"voiceState === 'speaking'\">\n <mat-icon class=\"voice-state-icon\">{{ voiceStateIcon }}</mat-icon>\n <span class=\"voice-state-label\">{{ voiceStateLabel }}</span>\n </div>\n\n <!-- Live level, so the mic being open is visible as well as audible. Not shown while calibrating: the\n meter would invite exactly the talking that ruins the measurement. -->\n <div class=\"voice-meter\" *ngIf=\"voiceState === 'listening' || voiceState === 'capturing'\">\n <div class=\"voice-meter-fill\" [style.width.%]=\"voiceLevel * 100\"></div>\n </div>\n\n <!-- Deliberately NO transcript/reply repeated here: both already appear in the chat thread directly above,\n and duplicating them pushed the state indicator — the one thing that must be readable at a glance —\n down the panel. The state, the meter and the exit are all this needs. -->\n\n <!-- Says explicitly that pauses are fine, because the natural worry is that stopping to think will send -->\n <p class=\"voice-hint\" *ngIf=\"voiceState === 'starting'\">Checking the background noise — hold on…</p>\n <p class=\"voice-hint\" *ngIf=\"voiceState === 'capturing'\">Take your time — it waits until you're done.</p>\n <p class=\"voice-hint\" *ngIf=\"voiceState === 'listening'\">Just speak — it sends when you finish.</p>\n\n <button mat-flat-button color=\"warn\" class=\"voice-exit\" (click)=\"stopVoiceMode()\">\n <mat-icon>close</mat-icon>\n <span>Exit voice mode</span>\n </button>\n </div>\n\n <!-- Input area -->\n <div class=\"agent-input\" [class.recording]=\"isRecording\" *ngIf=\"!voiceMode\">\n\n <!-- Normal input: attach, text, voice note, hands-free, send -->\n <ng-container *ngIf=\"!isRecording\">\n <button mat-icon-button class=\"media-btn\" matTooltip=\"Attach\" [matMenuTriggerFor]=\"attachMenu\">\n <mat-icon>add</mat-icon>\n </button>\n <mat-form-field appearance=\"outline\" class=\"input-field\">\n <input matInput #messageInput placeholder=\"Type, speak or snap...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\n </mat-form-field>\n <!-- Tap records a voice note; HOLD enters hands-free voice mode. Deliberately no separate button for\n voice mode: the row is already at capacity, and a fifth control would crowd out the text field on a\n phone. The gesture is the accelerator, the \"+\" menu below is the discoverable path to the same\n thing — a gesture nobody can see is a feature nobody finds. -->\n <button mat-icon-button class=\"media-btn mic-btn\" [class.arming]=\"micHeld\"\n matTooltip=\"Tap to record · hold for hands-free\"\n (click)=\"onMicClick()\"\n (pointerdown)=\"onMicPressStart()\" (pointerup)=\"onMicPressEnd()\"\n (pointerleave)=\"onMicPressEnd()\" (pointercancel)=\"onMicPressEnd()\">\n <mat-icon>mic</mat-icon>\n </button>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!canSend\">\n <mat-icon>send</mat-icon>\n </button>\n </ng-container>\n\n <!-- Recording bar: replaces the input while a voice note is being captured.\n Three exits — discard it, stop and read it back as text, or send it straight away. -->\n <ng-container *ngIf=\"isRecording\">\n <button mat-icon-button class=\"media-btn cancel-btn\" matTooltip=\"Discard\" (click)=\"cancelRecording()\">\n <mat-icon>delete</mat-icon>\n </button>\n <div class=\"recording-status\">\n <span class=\"rec-dot\"></span>\n <span class=\"rec-label\">Recording</span>\n <span class=\"rec-time\">{{ recordDisplay }}</span>\n </div>\n <button mat-icon-button class=\"media-btn stop-btn\" matTooltip=\"Stop and add as text\" (click)=\"stopAndTranscribe()\">\n <mat-icon>stop_circle</mat-icon>\n </button>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" matTooltip=\"Send now\" (click)=\"stopRecording()\">\n <mat-icon>send</mat-icon>\n </button>\n </ng-container>\n\n <!-- Attach menu — keeps the row compact: one \"+\" instead of a button per source -->\n <mat-menu #attachMenu=\"matMenu\">\n <button mat-menu-item (click)=\"takePhoto()\">\n <mat-icon>photo_camera</mat-icon>\n <span>Take photo</span>\n </button>\n <button mat-menu-item (click)=\"choosePhoto()\">\n <mat-icon>photo_library</mat-icon>\n <span>Choose photo</span>\n </button>\n <!-- The discoverable way into voice mode. The mic long-press does the same thing faster, but only\n once you know it exists — this is how you find out. -->\n <button mat-menu-item (click)=\"startVoiceMode()\">\n <mat-icon>record_voice_over</mat-icon>\n <span>Hands-free voice mode</span>\n </button>\n </mat-menu>\n\n <!-- Two separate pickers on purpose. `capture=\"environment\"` hands off to the phone's camera app and\n opens the rear lens; WITHOUT it the same input opens the gallery/file picker. One input cannot do\n both, which is why the old single button always went to the camera on mobile. -->\n <input type=\"file\" #cameraInput accept=\"image/*\" capture=\"environment\" hidden (change)=\"onPhotoSelected($event)\" />\n <input type=\"file\" #galleryInput accept=\"image/*\" hidden (change)=\"onPhotoSelected($event)\" />\n </div>\n\n</div>\n"],"names":["i1","i1.AgentService","i2","i3","i4"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tin-spa-agent.component-CZKMWl3r.mjs","sources":["../../../projects/tin-spa/src/lib/pipes/agent-markdown.pipe.ts","../../../projects/tin-spa/src/lib/services/agent.service.ts","../../../projects/tin-spa/src/lib/components/agent/agent.component.ts","../../../projects/tin-spa/src/lib/components/agent/agent.component.html"],"sourcesContent":["// Renders the agent's replies as the markdown they are actually written in.\n//\n// The agent writes **bold**, lists, headings and menu paths, and the widget printed all of it verbatim — so a\n// reply the guide carefully formatted arrived as a wall of asterisks. Telegram and WhatsApp now convert to\n// their own markup; this is the web's half of the same job.\n//\n// Deliberately a small hand-rolled subset rather than a markdown library: the input is not arbitrary user\n// markdown, it is the agent's own output, which uses bold, italic, inline code, headings, bullets and\n// numbered steps and nothing else. A library would add a dependency and a much larger escaping surface for\n// no gain here.\n//\n// SECURITY: every character is HTML-escaped BEFORE any formatting is applied, so nothing in a reply can\n// become markup. That matters more than usual for this component — an agent reply can contain data a\n// different user typed, so it must be treated as hostile text, not as trusted output.\nimport { Pipe, PipeTransform } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\n\n// Changed (nav-menu-standalone): standalone so the standalone AgentComponent can import the pipe by path.\n// TinSpaModule now IMPORTS it; it was never exported, so no consumer-facing surface changes.\n@Pipe({ name: 'agentMarkdown', standalone: true })\nexport class AgentMarkdownPipe implements PipeTransform {\n\n constructor(private sanitizer: DomSanitizer) { }\n\n transform(value: string | null | undefined): SafeHtml {\n if (!value) return '';\n\n let html = this.escape(value);\n\n // Headings first: they are line-anchored, and converting them after inline rules would let a ** on the\n // same line interfere\n html = html.replace(/^#{1,6}\\s*(.+)$/gm, '<strong class=\"md-heading\">$1</strong>');\n\n html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/(?<![\\w*])\\*(?!\\s)(.+?)(?<!\\s)\\*(?![\\w*])/g, '<em>$1</em>');\n html = html.replace(/`([^`]+)`/g, '<code>$1</code>');\n\n // Bullets and numbered steps keep their marker — turning them into <ul>/<ol> would need block parsing,\n // and the agent's lists are already readable as lines\n html = html.replace(/^(\\s*)[-•*]\\s+(.+)$/gm, '$1<span class=\"md-bullet\">•</span> $2');\n\n html = html.replace(/\\n/g, '<br>');\n\n return this.sanitizer.bypassSecurityTrustHtml(html);\n }\n\n // Escaping BEFORE formatting is what makes bypassSecurityTrustHtml safe here: by the time anything is\n // trusted, the only tags present are the ones this pipe put there itself.\n private escape(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n }\n}\n","// Frontend service for Agent chat — state management, API calls, and SignalR subscriptions (renamed from AssistantService)\nimport { Injectable } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\nimport { DataServiceLib } from 'tin-spa/core';\nimport { SignalRService } from 'tin-spa/core';\nimport { ApiErrorService } from 'tin-spa/core'; // Added: the two chat calls a user actually asks for report; the widget's own startup loads deliberately do not (WS-6 Phase 4)\n\n// Message interface for UI rendering (renamed from AssistantMessage)\nexport interface AgentMessage {\n conversationId?: number;\n role: 'user' | 'assistant';\n content: string;\n intent?: string;\n createdDate: Date;\n pending?: boolean; // Changed: voice/photo placeholder shown until the backend returns what it heard or saw\n divider?: boolean; // Changed: not a message — a rendered boundary between conversations\n}\n\n// Conversation summary for list view\nexport interface ConversationSummary {\n conversationID: number;\n title: string;\n appName: string;\n createdDate: Date;\n lastActivityDate?: Date; // Changed: real recency, so the switcher orders by when a thread was last used\n}\n\n// Changed: the structured half of a reply. The agent has always known it was holding a half-built Customer\n// with two fields missing; until now that only existed as prose inside the message text, so the UI could not\n// show it. Everything here is optional — a plain answer carries no session state at all.\nexport interface AgentSessionInfo {\n sessionType?: string;\n modelName?: string;\n missingFields?: string[];\n conversationID?: number;\n newConversation?: boolean;\n notice?: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class AgentService { // Changed: Renamed from AssistantService\n\n private messages = new BehaviorSubject<AgentMessage[]>([]);\n private conversations = new BehaviorSubject<ConversationSummary[]>([]);\n private typing = new BehaviorSubject<boolean>(false);\n private isOpen = new BehaviorSubject<boolean>(false);\n private currentConversationId = new BehaviorSubject<number | null>(null);\n private greeting = new BehaviorSubject<string>('');\n private suggestedQuestions = new BehaviorSubject<string[]>([]);\n private sessionInfo = new BehaviorSubject<AgentSessionInfo | null>(null); // Changed: what the agent is in the middle of\n\n messages$ = this.messages.asObservable();\n conversations$ = this.conversations.asObservable();\n typing$ = this.typing.asObservable();\n isOpen$ = this.isOpen.asObservable();\n currentConversationId$ = this.currentConversationId.asObservable();\n greeting$ = this.greeting.asObservable();\n suggestedQuestions$ = this.suggestedQuestions.asObservable();\n sessionInfo$ = this.sessionInfo.asObservable();\n\n constructor(\n private dataService: DataServiceLib,\n private signalRService: SignalRService,\n private apiErrorService: ApiErrorService // Changed: injected for loadMessages and deleteConversation only\n ) {\n this.initListeners();\n }\n\n // App name derived from the existing appConfig\n get appName(): string {\n return this.dataService.appConfig.appName || 'App';\n }\n\n // Agent name derived from appConfig (changed from assistantName)\n get agentName(): string {\n return `${this.appName} Agent`;\n }\n\n // Subscribe to SignalR events for real-time message delivery\n private initListeners() {\n // Changed: SignalR is the SECOND path to a reply, not the only one. It still matters — a turn started on\n // the phone should appear on the desktop — but it now goes through the same deduping append as the HTTP\n // answer, so the common case (both arrive) shows one bubble rather than two.\n this.signalRService.agentMessage$.subscribe(msg => { // Changed: was assistantMessage$\n if (msg && msg.content) {\n this.addAssistantMessage(msg.content);\n }\n });\n\n this.signalRService.agentTyping$.subscribe(typing => { // Changed: was assistantTyping$\n this.typing.next(typing);\n });\n }\n\n // Load agent config (greeting + suggested questions) from backend\n loadConfig() {\n this.dataService.CallApi({ url: `agent/config/${this.appName}` }).subscribe(res => { // Changed: was assistant/config\n // DELIBERATELY SILENT (WS-6 Phase 4 triage). Greeting text and suggested-question chips are decoration\n // on a chat window that works perfectly without them — the user can still type. Nothing they can see is\n // wrong, so there is nothing truthful to tell them.\n if (res.success && res.data) {\n this.greeting.next(res.data.greeting || '');\n this.suggestedQuestions.next(res.data.suggestedQuestions || []);\n }\n });\n }\n\n // Load user's conversation list\n loadConversations() {\n this.dataService.CallApi({ url: 'agent/conversations' }).subscribe(res => { // Changed: was assistant/conversations\n // DELIBERATELY SILENT (WS-6 Phase 4 triage), and this was the closest call in the sweep — an empty\n // thread list IS a false statement about the user's own history. It stays silent because of WHEN it\n // fires: agent.component.ts:212 calls it from the widget's ngOnInit, so it runs on every app-shell load\n // whether or not anyone opens the chat. Converting would put a dialog in front of every user of the app\n // for a list none of them had asked to see. loadMessages() below — which only ever runs because someone\n // clicked a specific thread — is the one that reports, and it covers the same outage the moment the\n // list is actually used.\n if (res.success) {\n this.conversations.next(res.data || []);\n }\n });\n }\n\n // Load messages for a specific conversation\n loadMessages(conversationId: number) {\n this.currentConversationId.next(conversationId);\n this.dataService.CallApi({ url: `agent/conversations/${conversationId}/messages` }).subscribe(res => { // Changed: was assistant/conversations\n // CONVERTED (WS-6 Phase 4 triage). Unlike everything else in this service, this only runs because the\n // user clicked a named conversation in the switcher. Failing left the thread pane holding the PREVIOUS\n // conversation's messages under the new conversation's id — so the next thing they typed would have\n // been sent into a thread they were not looking at.\n if (!res.success) this.apiErrorService.presentAppFailure(res, 'load', `agent/conversations/${conversationId}/messages`);\n if (res.success) {\n this.messages.next((res.data || []).map((m: any) => ({\n conversationId: m.conversationID,\n role: m.role,\n content: m.content,\n intent: m.intent,\n createdDate: new Date(m.createdDate)\n })));\n }\n });\n }\n\n // Send a message — optimistically add user message, then POST to backend\n sendMessage(text: string) {\n if (!text.trim()) return;\n\n // Optimistic: add user message immediately\n const current = this.messages.value;\n this.messages.next([...current, { role: 'user', content: text, createdDate: new Date() }]);\n\n // Changed: the HTTP response is now the AUTHORITATIVE reply. It used to be discarded entirely and the\n // answer was expected to arrive over SignalR — so with the hub down, or real-time switched off, the\n // server answered and the user saw nothing at all. The request that asked the question is the one that\n // carries the answer; SignalR is now only a cross-device echo, deduped on arrival.\n this.dataService.CallApi(\n { url: 'agent/message', method: 'post' }, // Changed: was assistant/message\n { text, appName: this.appName }\n ).subscribe({\n next: (res: any) => {\n this.typing.next(false);\n\n const reply = res?.data?.message;\n if (reply) this.addAssistantMessage(reply);\n\n this.applySessionInfo(res?.data?.sessionInfo);\n },\n error: () => {\n this.typing.next(false);\n this.addAssistantMessage('Sorry, that could not be sent. Please check your connection and try again.');\n }\n });\n }\n\n // Changed: one place that appends an assistant reply, so the HTTP answer and the SignalR echo of the same\n // answer cannot both land. Matching on content against the last few messages rather than an id because the\n // two paths carry no shared identifier — and the failure being prevented (the same answer shown twice) is\n // more annoying than the rare case this suppresses (a genuine identical repeat).\n private addAssistantMessage(content: string) {\n const recent = this.messages.value.slice(-4);\n if (recent.some(m => m.role === 'assistant' && m.content === content)) return;\n\n this.messages.next([...this.messages.value, {\n role: 'assistant',\n content,\n createdDate: new Date()\n }]);\n }\n\n // Changed: keeps the widget's idea of \"what am I in the middle of\" in step with the server's, and draws the\n // boundary when a conversation rolls over so the user can see why the agent stopped referring to earlier\n // messages. Without a visible line, a rollover looks like the agent simply forgetting.\n private applySessionInfo(info: AgentSessionInfo | null | undefined) {\n if (!info) {\n this.sessionInfo.next(null);\n return;\n }\n\n if (info.newConversation) {\n if (info.conversationID) this.currentConversationId.next(info.conversationID);\n this.messages.next([...this.messages.value, {\n role: 'assistant',\n content: 'New conversation',\n createdDate: new Date(),\n divider: true\n }]);\n this.loadConversations();\n }\n\n // A finished operation reports no session type — that is the signal to clear the banner\n this.sessionInfo.next(info.sessionType ? info : null);\n }\n\n // Transcribe a recording WITHOUT invoking the agent — backs the composer's Stop button, so the user can\n // review and edit what was heard before deciding to send.\n transcribeOnly(audio: Blob, fileName: string) {\n const form = new FormData();\n form.append('audio', audio, fileName);\n return this.dataService.CallApi({ url: 'agent/transcribe', method: 'post', isFormData: true }, form);\n }\n\n // One hands-free spoken turn: audio in, spoken answer out, in a single round trip. The reply comes back\n // twice over — replyText verbatim for the transcript panel, spokenText as actually voiced — because a long\n // list is summarised before it is spoken.\n voiceTurn(audio: Blob, fileName: string) {\n const form = new FormData();\n form.append('audio', audio, fileName);\n form.append('appName', this.appName);\n return this.dataService.CallApi({ url: 'agent/voice-turn', method: 'post', isFormData: true }, form);\n }\n\n // Puts what was heard into the conversation thread, so a spoken turn reads like a typed one. Only the user\n // side is added here — the agent's reply arrives over SignalR exactly as it does for a typed message, and\n // adding it here as well would show it twice.\n appendSpokenMessage(text: string) {\n if (!text || !text.trim()) return;\n this.messages.next([...this.messages.value, { role: 'user', content: text.trim(), createdDate: new Date() }]);\n }\n\n // Send a composed message — any combination of a staged photo, a voice clip and typed text, in one go.\n // A placeholder user bubble goes up immediately and is swapped for the derived text when the backend\n // replies, which also keeps it above the assistant's answer (that arrives over SignalR mid-request).\n sendComposed(photo: Blob | null, photoName: string, audio: Blob | null, audioName: string, caption: string = '') {\n const hasPhoto = !!(photo && photo.size);\n const hasAudio = !!(audio && audio.size);\n if (!hasPhoto && !hasAudio && !caption.trim()) return;\n\n const placeholder: AgentMessage = {\n role: 'user',\n content: caption.trim() || (hasAudio ? 'Voice note…' : 'Photo…'),\n createdDate: new Date(),\n pending: true\n };\n\n this.messages.next([...this.messages.value, placeholder]);\n this.typing.next(true);\n\n const form = new FormData();\n if (hasPhoto) form.append('photo', photo!, photoName);\n if (hasAudio) form.append('audio', audio!, audioName);\n form.append('text', caption.trim());\n form.append('appName', this.appName);\n\n this.dataService.CallApi({ url: 'agent/media', method: 'post', isFormData: true }, form).subscribe({\n next: (res: any) => {\n this.typing.next(false);\n\n if (res?.success && res.data?.inputText) {\n this.replaceMessage(placeholder, { ...placeholder, content: res.data.inputText, pending: false });\n return;\n }\n\n // Nothing usable came back — drop the placeholder and say why\n this.removeMessage(placeholder);\n this.messages.next([...this.messages.value, {\n role: 'assistant',\n content: res?.message || 'Sorry, I could not process that. Please try again.',\n createdDate: new Date()\n }]);\n },\n error: () => {\n this.typing.next(false);\n this.removeMessage(placeholder);\n this.messages.next([...this.messages.value, {\n role: 'assistant',\n content: 'Sorry, that could not be sent. Please check your connection and try again.',\n createdDate: new Date()\n }]);\n }\n });\n }\n\n // Changed: kept so existing callers of the single-attachment API keep working\n sendMedia(file: Blob, fileName: string, kind: 'voice' | 'photo', caption: string = '') {\n if (kind === 'voice') this.sendComposed(null, '', file, fileName, caption);\n else this.sendComposed(file, fileName, null, '', caption);\n }\n\n // Swap a message in place so its position in the thread is preserved\n private replaceMessage(target: AgentMessage, updated: AgentMessage) {\n const msgs = this.messages.value.slice();\n const index = msgs.indexOf(target);\n if (index < 0) return;\n msgs[index] = updated;\n this.messages.next(msgs);\n }\n\n private removeMessage(target: AgentMessage) {\n this.messages.next(this.messages.value.filter(m => m !== target));\n }\n\n // Start a new conversation — only when user explicitly clicks \"New conversation\"\n newConversation() {\n this.messages.next([]);\n this.currentConversationId.next(null);\n this.dataService.CallApi(\n { url: 'agent/conversations', method: 'post' },\n { appName: this.appName }\n ).subscribe(res => {\n // DELIBERATELY SILENT (WS-6 Phase 4 triage). The UI has already done the thing the user asked for —\n // messages cleared, id nulled, a blank thread on screen — before this call is even made. All the server\n // round trip adds is a pre-allocated id, and sendMessage() creates one anyway when there is none. So a\n // failure here changes nothing the user can see or do.\n if (res.success && res.data) {\n this.currentConversationId.next(res.data.conversationID);\n }\n });\n }\n\n // Delete a conversation\n deleteConversation(id: number) {\n this.dataService.CallApi({ url: `agent/conversations/${id}/delete`, method: 'post' }).subscribe(res => { // Changed: was agent/conversations/${id}, which is DELETE-only on the API — this POST hit a 405 every time and the conversation was never deleted\n // CONVERTED (WS-6 Phase 4 triage). A delete the user pressed. Failing left the conversation sitting in\n // the list exactly as it was, which reads as \"the button did nothing\" — and the answer to a button that\n // did nothing is to press it again. Note the comment on the URL below: this endpoint has already been\n // wrong once (a 405 on every call), and it was silence that let that survive.\n if (!res.success) this.apiErrorService.presentAppFailure(res, 'action', `agent/conversations/${id}/delete`);\n if (res.success) {\n this.loadConversations();\n if (this.currentConversationId.value === id) {\n this.messages.next([]);\n this.currentConversationId.next(null);\n }\n }\n });\n }\n\n // Changed: Load the most recent conversation's messages on first open\n private hasLoadedInitial = false;\n\n // Toggle chat window open/closed\n toggle() {\n const opening = !this.isOpen.value;\n this.isOpen.next(opening);\n if (opening && !this.hasLoadedInitial) {\n this.hasLoadedInitial = true;\n this.restoreLastConversation(); // Changed: Load previous chat on first open\n }\n }\n open() {\n this.isOpen.next(true);\n if (!this.hasLoadedInitial) {\n this.hasLoadedInitial = true;\n this.restoreLastConversation();\n }\n }\n close() { this.isOpen.next(false); }\n\n // Changed: Fetch conversation list and load the most recent one's messages\n private restoreLastConversation() {\n this.dataService.CallApi({ url: 'agent/conversations' }).subscribe(res => {\n if (res.success && res.data?.length > 0) {\n this.conversations.next(res.data);\n const latest = res.data[0]; // Backend returns ordered by CreatedDate desc\n this.currentConversationId.next(latest.conversationID);\n this.loadMessages(latest.conversationID);\n }\n });\n }\n}\n","// Floating chat widget component for in-app Agent (renamed from AssistantComponent)\nimport { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked, HostListener, NgZone, Input } from '@angular/core'; // Changed: Input added for pageMode\nimport { Subscription } from 'rxjs';\nimport { filter } from 'rxjs/operators'; // Changed: NavigationEnd-only router stream for the agent-route check\nimport { Router, NavigationEnd } from '@angular/router'; // Changed: the floating widget stands down on the chat page's own route\nimport { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; // Changed: the bottom tab bar replaces the FAB on mobile\nimport { AgentService, AgentMessage, ConversationSummary, AgentSessionInfo } from '../../services/agent.service';\nimport { SignalRService } from 'tin-spa/core';\nimport { trigger, transition, style, animate } from '@angular/animations';\nimport { SpaMatModule } from 'tin-spa/core'; // Added (nav-menu-standalone): the declaration-free Material re-export shell — same surface TinSpaModule provided\nimport { CamelToWordsPipe } from '../../pipes/camelToWords.pipe'; // Added (nav-menu-standalone): used at agent.component.html:64 — a TinSpaModule declaration, so a standalone host must import it by path\nimport { AgentMarkdownPipe } from '../../pipes/agent-markdown.pipe'; // Added (nav-menu-standalone): used at agent.component.html:125 — same reason\n\n@Component({\n selector: 'spa-agent', // Changed: was spa-assistant\n templateUrl: './agent.component.html',\n styleUrls: ['./agent.component.css'],\n // Changed (nav-menu-standalone): standalone so the standalone NavMenuComponent can import <spa-agent> by\n // path (N-5). TinSpaModule now IMPORTS it and still EXPORTS it (A-AMD-2 shape), so every consumer that\n // renders <spa-agent> with only TinSpaModule imported keeps working unchanged.\n standalone: true,\n imports: [SpaMatModule, CamelToWordsPipe, AgentMarkdownPipe], // Added: Material/Common surface plus the two custom pipes the template names\n animations: [\n trigger('slideUp', [\n // Changed: page mode enters in normal flow and must not slide up — a page that animates in like a\n // dialog still reads as a dialog. Both empty transitions are declared BEFORE :enter/:leave because the\n // first matching transition wins, so 'none' opts the page instance out of the floating animation.\n transition('void => none', []), // Changed: page mode — no enter animation\n transition('none => void', []), // Changed: page mode — no leave animation\n transition(':enter', [\n style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }),\n animate('250ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 1, transform: 'translateY(0) scale(1)' }))\n ]),\n transition(':leave', [\n animate('200ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }))\n ])\n ])\n ]\n})\nexport class AgentComponent implements OnInit, OnDestroy, AfterViewChecked { // Changed: was AssistantComponent\n\n @ViewChild('messageContainer') messageContainer!: ElementRef;\n @ViewChild('messageInput') messageInput!: ElementRef;\n @ViewChild('cameraInput') cameraInput!: ElementRef<HTMLInputElement>; // Changed: hands off to the phone camera\n @ViewChild('galleryInput') galleryInput!: ElementRef<HTMLInputElement>; // Changed: gallery / file picker\n\n // Changed: one component, two presentations. false = the floating widget (FAB + fixed window, unchanged);\n // true = the routed chat page (home/agent) — no FAB, window always rendered, no outside-click close, no\n // minimize. Only presentation differs; all chat behaviour stays shared through AgentService.\n @Input() pageMode = false;\n\n // Changed: mobile retires the floating widget entirely — the bottom tab bar's Chat tab is the entry point\n // and home/agent is the surface. Both flags only ever gate the FLOATING presentation; page mode ignores them.\n smallScreen = false; // Changed: same (max-width: 600px) breakpoint nav-menu uses — one source of truth\n onAgentRoute = false; // Changed: true while the routed chat page owns the screen\n\n // Changed: the floating FAB is suppressed in page mode (Phase 1), on mobile, and on the chat page's own\n // route at any width — otherwise it hovers over the very page it would open.\n get showFab(): boolean {\n return !this.pageMode && !this.smallScreen && !this.onAgentRoute;\n }\n\n // Changed: the floating window follows the FAB. Page mode renders regardless of isOpen; the floating\n // instance additionally refuses to render wherever its FAB is gone, so there are never two chat surfaces.\n get showWindow(): boolean {\n return this.pageMode || (this.isOpen && !this.smallScreen && !this.onAgentRoute);\n }\n\n messages: AgentMessage[] = [];\n conversations: ConversationSummary[] = []; // Changed: switcher list\n currentConversationId: number | null = null;\n showConversations = false; // Changed: the switcher is a dropdown, not a permanent panel — the window is small\n sessionInfo: AgentSessionInfo | null = null; // Changed: drives the \"you are creating a Customer\" banner\n isOpen = false;\n isTyping = false;\n inputText = '';\n greeting = '';\n suggestedQuestions: string[] = [];\n isOnline = false;\n\n isRecording = false; // Changed: voice note capture state\n recordSeconds = 0;\n mediaError = ''; // Changed: shown inline when the mic is blocked or unavailable\n pendingPhoto: File | null = null; // Changed: photo is staged, not sent — the user adds a caption first\n isTranscribing = false; // Changed: Stop pressed, waiting for the transcript to land in the text box\n\n private subs: Subscription[] = [];\n private shouldScroll = false;\n private viewportHandler: (() => void) | null = null; // Changed: visualViewport resize handler reference\n\n // Changed: voice note recording internals\n private recorder: MediaRecorder | null = null;\n private mediaStream: MediaStream | null = null;\n private chunks: Blob[] = [];\n private recordTimer: any = null;\n private stopMode: 'send' | 'transcribe' | 'cancel' = 'send'; // Changed: what to do with the clip once stopped\n\n private static readonly MAX_RECORD_SECONDS = 120; // Auto-stop so a forgotten recording can't run away\n\n // ---------------------------------------------------------------------------------------------------\n // Voice mode — hands-free. Enter once, then speak and listen in a loop until you exit. Built for someone\n // driving or with their hands full, so nothing between entry and exit requires touch or sight.\n //\n // The session is deliberately HALF-DUPLEX: the microphone is closed whenever audio is playing, and\n // playback finishes before it reopens. That single property removes echo cancellation, acoustic feedback\n // and interrupt arbitration entirely. Preserve it — barge-in would be a re-architecture, not an increment.\n // ---------------------------------------------------------------------------------------------------\n voiceMode = false;\n // 'starting' is the room-calibration window. It exists because showing \"Listening\" during it was a lie —\n // the microphone is open but the detector is still measuring the noise floor, so anything said then is\n // measured AS noise and raises the bar against the speaker's own voice.\n voiceState: 'idle' | 'starting' | 'listening' | 'capturing' | 'thinking' | 'speaking' = 'idle';\n voiceTranscript = ''; // what it heard on the last turn\n voiceReply = ''; // what it answered, verbatim (the spoken version is shortened)\n voiceError = '';\n voiceLevel = 0; // 0..1, drives the level meter so the user can see the mic is live\n\n // Tunable because a vehicle cabin is not a desk. These are fields rather than constants so they can be\n // adjusted against a real environment without a rebuild.\n voiceThreshold = 0.01; // absolute floor — the room can raise this, never lower it\n noiseMultiplier = 3; // speech must be this many times the measured room noise\n maxThreshold = 0.08; // ...but never so high that a normal voice cannot clear it\n onsetMs = 250; // sustained loudness needed before it counts as speech at all\n silenceMs = 2200; // quiet needed to call the utterance finished\n minSpeechMs = 400; // anything shorter than this was a noise, not a sentence\n\n // What calibration actually settled on, exposed so it can be inspected while running\n noiseFloor = 0;\n activeThreshold = 0.03;\n\n private static readonly SETTLE_MS = 450; // let the speakers finish before trusting the mic again\n private static readonly CALIBRATION_MS = 600; // listen to the room before listening to the person\n private static readonly CONTINUE_RATIO = 0.55; // hysteresis: it takes less to STAY in speech than to enter it\n private static readonly MAX_UTTERANCE_MS = 30000; // a noisy cab must not record forever\n private static readonly IDLE_MS = 12000; // no speech this long → don't upload silence, listen again\n private static readonly MAX_SILENT_MS = 180000; // ...but give up after three minutes of nothing at all\n\n private calibrationEndsAt = 0;\n private calibrationSamples: number[] = [];\n private loudSince = 0;\n\n private voiceRecorder: MediaRecorder | null = null;\n private voiceStream: MediaStream | null = null;\n private voiceChunks: Blob[] = [];\n private audioContext: AudioContext | null = null;\n private analyser: AnalyserNode | null = null;\n private vadTimer: any = null;\n private player: HTMLAudioElement | null = null;\n private speechStartedAt = 0;\n private lastLoudAt = 0;\n private listenStartedAt = 0;\n private silentSince = 0;\n private abandonUtterance = false; // true when the clip should be dropped rather than uploaded\n\n constructor(public agentService: AgentService, private signalRService: SignalRService,\n private zone: NgZone, private router: Router, private breakpointObserver: BreakpointObserver) {} // Changed: Router + BreakpointObserver gate the floating presentation\n\n // Every voice state change goes through here. The states are set from audio-element events, a MediaRecorder\n // callback and a sampling timer, and those did not reliably trigger a repaint — the panel was showing\n // \"Listening\" while the machine was already capturing, thinking, or speaking aloud. That is worse than no\n // indicator at all: someone who cannot look at the screen is being told the wrong thing about whose turn it\n // is. Re-entering the Angular zone forces the view to catch up immediately.\n private setVoiceState(state: 'idle' | 'starting' | 'listening' | 'capturing' | 'thinking' | 'speaking') {\n if (this.voiceState === state) return;\n this.zone.run(() => { this.voiceState = state; });\n }\n\n get agentName(): string { // Changed: was assistantName\n return this.agentService.agentName;\n }\n\n ngOnInit(): void {\n this.agentService.loadConfig();\n this.initMobileViewport(); // Changed: Listen for keyboard resize on mobile\n\n this.subs.push(\n this.agentService.messages$.subscribe(msgs => {\n this.messages = msgs;\n this.shouldScroll = true;\n }),\n this.agentService.isOpen$.subscribe(open => {\n this.isOpen = open;\n if (open) this.focusInput();\n // Changed: the widget can be closed by an outside click while a voice session is running. The\n // component survives that (only the window is *ngIf'd away), so without this the microphone would\n // stay live behind a hidden window with no way to reach the exit button.\n // Changed: page mode ignores isOpen for visibility, so a close() from the floating window elsewhere\n // must not tear down a voice session that is still on screen on the page.\n else if (this.voiceMode && !this.pageMode) this.stopVoiceMode();\n }),\n this.agentService.typing$.subscribe(typing => {\n this.isTyping = typing;\n this.shouldScroll = true;\n }),\n this.agentService.greeting$.subscribe(g => this.greeting = g),\n this.agentService.suggestedQuestions$.subscribe(q => this.suggestedQuestions = q),\n // Changed: the conversation switcher and the session banner both come from the service\n this.agentService.conversations$.subscribe(list => this.conversations = list),\n this.agentService.currentConversationId$.subscribe(id => this.currentConversationId = id),\n this.agentService.sessionInfo$.subscribe(info => this.sessionInfo = info),\n this.signalRService.dataHubConnected$.subscribe(connected => this.isOnline = connected),\n // Changed: crossing into mobile must not strand an open floating window behind the new tab bar. Only\n // the FLOATING instance closes it — the page instance never mutates the shared isOpen (see fsd Risk 2).\n this.breakpointObserver.observe(['(max-width: 600px)']).subscribe((result: BreakpointState) => {\n this.smallScreen = result.matches;\n if (result.matches && !this.pageMode && this.isOpen) this.agentService.close();\n }),\n // Changed: the routed chat page renders its own instance, so the floating one steps aside on that URL\n this.router.events.pipe(\n filter(event => event instanceof NavigationEnd)\n ).subscribe((event: NavigationEnd) => {\n this.onAgentRoute = this.isAgentRoute(event.urlAfterRedirects || event.url);\n if (this.onAgentRoute && !this.pageMode && this.isOpen) this.agentService.close();\n })\n );\n\n this.onAgentRoute = this.isAgentRoute(this.router.url); // Changed: NavigationEnd has usually already fired by now\n\n this.agentService.loadConversations(); // Changed: the switcher needs the list before it is opened\n }\n\n // Changed: 'home/agent' is mounted through the shared home routing module, so the reliable test is the\n // trailing segment rather than a whole-URL match that would miss a consumer app's own route prefix.\n private isAgentRoute(url: string): boolean {\n const path = (url || '').split('?')[0].split(';')[0].split('#')[0].replace(/\\/+$/, '');\n return path.endsWith('/agent');\n }\n\n // Reads as \"creating a Customer\" rather than \"Create a Customer\" in the banner\n get sessionVerb(): string {\n switch (this.sessionInfo?.sessionType) {\n case 'Create': return 'Creating';\n case 'Edit': return 'Editing';\n case 'Delete': return 'Deleting';\n case 'BatchPost': return 'Batch posting';\n default: return 'Working on';\n }\n }\n\n // Changed: switching threads. The agent has always kept several conversations per user, but the widget\n // could only ever show the newest one — earlier threads existed and were unreachable.\n selectConversation(id: number) {\n if (id === this.currentConversationId) return;\n this.showConversations = false;\n this.agentService.loadMessages(id);\n }\n\n toggleConversationList() {\n this.showConversations = !this.showConversations;\n if (this.showConversations) this.agentService.loadConversations();\n }\n\n ngAfterViewChecked(): void {\n if (this.shouldScroll) {\n this.scrollToBottom();\n this.shouldScroll = false;\n }\n }\n\n toggleChat() {\n this.agentService.toggle();\n }\n\n // Changed: Close chat when clicking outside the chat window and FAB button\n @HostListener('document:click', ['$event'])\n onDocumentClick(event: MouseEvent) {\n if (this.pageMode) return; // Changed: a page does not close when you click the page — outside-click close is float-only\n if (!this.isOpen) return;\n\n // Changed: match on composedPath() instead of closest(). A control that re-renders the widget removes\n // itself from the DOM before this handler runs — the mic button lives inside *ngIf=\"!isRecording\", so\n // starting a recording detaches it. closest() on a detached node finds nothing, and the widget used to\n // close itself the instant recording began, leaving the mic live behind a hidden window.\n //\n // cdk-overlay-container is checked for the same reason: Material renders menus, tooltips and dialogs in\n // an overlay attached to <body>, OUTSIDE .agent-window. Without this, picking an item from the attach\n // menu closed the whole chat. Any overlay open on top of the widget counts as being inside it.\n const path = (event.composedPath ? event.composedPath() : []) as HTMLElement[];\n if (path.some(el => el?.classList?.contains('agent-window')\n || el?.classList?.contains('agent-fab')\n || el?.classList?.contains('cdk-overlay-container'))) return;\n\n // Fallback for browsers without composedPath, and for targets still attached\n const target = event.target as HTMLElement;\n if (target?.isConnected && (target.closest('.agent-window') || target.closest('.agent-fab'))) return;\n\n // A detached target means the click came from a control that just re-rendered — i.e. inside the widget\n if (target && !target.isConnected) return;\n\n this.agentService.close();\n }\n\n // Changed: sends whatever is staged — typed text, an attached photo, or both. A recording in progress is\n // stopped and folded into the same send rather than being lost.\n sendMessage() {\n if (this.isRecording) { this.stopMode = 'send'; this.recorder?.stop(); return; }\n\n const caption = this.inputText.trim();\n if (!caption && !this.pendingPhoto) return;\n\n if (this.pendingPhoto) {\n this.agentService.sendComposed(this.pendingPhoto, this.pendingPhoto.name || 'photo.jpg', null, '', caption);\n this.pendingPhoto = null;\n } else {\n this.agentService.sendMessage(caption);\n }\n\n this.inputText = '';\n }\n\n // Changed: true when there is anything worth sending\n get canSend(): boolean {\n return !!this.inputText.trim() || !!this.pendingPhoto;\n }\n\n clearPhoto() {\n this.pendingPhoto = null;\n }\n\n sendSuggested(question: string) {\n this.agentService.sendMessage(question);\n }\n\n onKeydown(event: KeyboardEvent) {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n this.sendMessage();\n }\n }\n\n newConversation() {\n this.agentService.newConversation();\n }\n\n // Changed: mm:ss shown while recording\n get recordDisplay(): string {\n const mins = Math.floor(this.recordSeconds / 60);\n const secs = this.recordSeconds % 60;\n return `${mins}:${secs < 10 ? '0' : ''}${secs}`;\n }\n\n // Changed: voice note capture — the clip goes straight to the backend, which transcribes it and feeds the\n // text into the agent exactly as Telegram and WhatsApp voice notes are handled\n async startRecording() {\n if (this.isRecording) return;\n this.mediaError = '';\n\n if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === 'undefined') {\n this.mediaError = 'Voice notes are not supported in this browser.';\n return;\n }\n\n try {\n this.mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch {\n this.mediaError = 'Microphone access was blocked. Allow it in your browser settings to send voice notes.';\n return;\n }\n\n this.chunks = [];\n this.stopMode = 'send';\n\n const mimeType = this.pickAudioMimeType();\n this.recorder = mimeType ? new MediaRecorder(this.mediaStream, { mimeType }) : new MediaRecorder(this.mediaStream);\n this.recorder.ondataavailable = e => { if (e.data && e.data.size > 0) this.chunks.push(e.data); };\n this.recorder.onstop = () => this.finishRecording();\n this.recorder.start();\n\n this.isRecording = true;\n this.recordSeconds = 0;\n this.recordTimer = setInterval(() => {\n this.recordSeconds++;\n if (this.recordSeconds >= AgentComponent.MAX_RECORD_SECONDS) this.stopRecording();\n }, 1000);\n }\n\n // Changed: send immediately — the clip goes with whatever else is staged, no review step\n stopRecording() {\n if (!this.isRecording || !this.recorder) return;\n this.stopMode = 'send';\n this.recorder.stop(); // finishRecording() runs from onstop\n }\n\n // Changed: stop and transcribe INTO the text box so the user can read it back, edit it, and send when ready\n stopAndTranscribe() {\n if (!this.isRecording || !this.recorder) return;\n this.stopMode = 'transcribe';\n this.recorder.stop();\n }\n\n cancelRecording() {\n if (!this.isRecording || !this.recorder) return;\n this.stopMode = 'cancel';\n this.recorder.stop();\n }\n\n // ---------------------------------------------------------------------------------------------------\n // Voice mode\n // ---------------------------------------------------------------------------------------------------\n\n // Tap versus hold on the single mic button. The composer row is full — attach, text, mic, send — so voice\n // mode gets a gesture rather than a fifth control that would squeeze the text field on a phone. Same idea\n // as holding the mic in WhatsApp: the button keeps its obvious job, the second mode hides behind the hold.\n micHeld = false;\n private micHoldTimer: any = null;\n private micHoldFired = false;\n\n private static readonly MIC_HOLD_MS = 500; // long enough not to fire on a normal tap\n\n onMicPressStart() {\n if (this.voiceMode || this.isRecording) return;\n\n this.micHeld = true;\n this.micHoldFired = false;\n\n this.micHoldTimer = setTimeout(() => {\n this.micHeld = false;\n this.micHoldFired = true; // swallows the click that follows the release\n if (navigator.vibrate) navigator.vibrate(30); // confirms the mode switch without needing to look\n this.startVoiceMode();\n }, AgentComponent.MIC_HOLD_MS);\n }\n\n onMicPressEnd() {\n this.micHeld = false;\n if (this.micHoldTimer) { clearTimeout(this.micHoldTimer); this.micHoldTimer = null; }\n }\n\n // Runs after pointerup. A hold has already acted, so the click that follows it must do nothing.\n onMicClick() {\n if (this.micHoldFired) { this.micHoldFired = false; return; }\n this.startRecording();\n }\n\n // Entering is the ONLY user gesture in the whole session, so everything that needs one happens here: iOS\n // will not let audio play later unless an AudioContext was created and resumed inside a real tap.\n async startVoiceMode() {\n if (this.voiceMode) return;\n\n this.voiceError = '';\n this.voiceTranscript = '';\n this.voiceReply = '';\n\n if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === 'undefined') {\n this.voiceError = 'Voice mode is not supported in this browser.';\n return;\n }\n\n if (this.isRecording) this.cancelRecording(); // never run both capture paths at once\n\n try {\n const Ctx = (window as any).AudioContext || (window as any).webkitAudioContext;\n this.audioContext = new Ctx();\n if (this.audioContext!.state === 'suspended') await this.audioContext!.resume();\n } catch {\n this.voiceError = 'Audio could not be started in this browser.';\n return;\n }\n\n this.voiceMode = true;\n this.silentSince = Date.now();\n await this.beginListening();\n }\n\n // One tap out, from anywhere in the loop. Everything is torn down: recorder, microphone, playback, timers.\n stopVoiceMode() {\n this.voiceMode = false;\n this.setVoiceState('idle');\n this.voiceLevel = 0;\n\n this.abandonUtterance = true;\n this.stopVoiceCapture();\n this.stopPlayback();\n\n if (this.audioContext) {\n try { this.audioContext.close(); } catch {}\n this.audioContext = null;\n }\n }\n\n // Opens the mic and waits. The clip records from the moment the mic opens, so the first syllable is never\n // clipped — the detector decides afterwards whether anything worth sending was actually said.\n private async beginListening() {\n if (!this.voiceMode) return;\n\n try {\n // These three constraints were all settled by measurement on a real machine, not by reasoning:\n //\n // echoCancellation ON — without it the microphone hears the agent's OWN reply from the speakers and\n // answers itself; a spoken reply came back as the next command.\n // noiseSuppression OFF — tried ON to fight fan noise and it crushed the voice instead: a normal\n // speaking voice fell from 0.15 to 0.026 RMS, barely grazing the threshold, and\n // no utterance was ever captured. Steady noise is already handled better by the\n // per-cycle noise-floor calibration and the sustained-onset rule below, neither\n // of which costs any signal.\n // autoGainControl ON — also tried OFF (to keep levels stable for calibration) and that made it worse\n // still, 0.008 RMS. A drifting floor beats a microphone that cannot hear you.\n this.voiceStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: false,\n autoGainControl: true\n }\n });\n } catch {\n this.voiceError = 'Microphone access was blocked. Allow it in your browser settings to use voice mode.';\n this.stopVoiceMode();\n return;\n }\n\n if (!this.voiceMode) { // exited while the permission prompt was up\n this.voiceStream.getTracks().forEach(t => t.stop());\n this.voiceStream = null;\n return;\n }\n\n this.voiceChunks = [];\n this.abandonUtterance = false;\n this.speechStartedAt = 0;\n this.listenStartedAt = Date.now();\n this.lastLoudAt = 0;\n this.loudSince = 0;\n\n // Re-measure the room on every cycle: an engine starts, a window opens, the air-con changes note\n this.calibrationSamples = [];\n this.calibrationEndsAt = Date.now() + AgentComponent.CALIBRATION_MS;\n\n const mimeType = this.pickAudioMimeType();\n this.voiceRecorder = mimeType\n ? new MediaRecorder(this.voiceStream, { mimeType })\n : new MediaRecorder(this.voiceStream);\n this.voiceRecorder.ondataavailable = e => { if (e.data && e.data.size > 0) this.voiceChunks.push(e.data); };\n this.voiceRecorder.onstop = () => this.onUtteranceCaptured();\n this.voiceRecorder.start();\n\n // NOT 'listening' yet — the room is being measured first. The \"your turn\" tone and the Listening label\n // both wait for calibration to finish, so the tone always means the same thing: start talking now.\n this.setVoiceState('starting');\n this.startVad();\n }\n\n // Voice activity detection: RMS over a short window, no dependencies. Speech starts when it goes loud, ends\n // after a run of quiet — which is what makes the turn hands-free, since nothing has to be pressed to stop.\n private startVad() {\n if (!this.audioContext || !this.voiceStream) return;\n\n const source = this.audioContext.createMediaStreamSource(this.voiceStream);\n this.analyser = this.audioContext.createAnalyser();\n this.analyser.fftSize = 1024;\n source.connect(this.analyser);\n\n const samples = new Uint8Array(this.analyser.fftSize);\n\n this.vadTimer = setInterval(() => {\n if (!this.analyser) return;\n\n this.analyser.getByteTimeDomainData(samples);\n\n let sum = 0;\n for (let i = 0; i < samples.length; i++) {\n const deviation = (samples[i] - 128) / 128;\n sum += deviation * deviation;\n }\n const rms = Math.sqrt(sum / samples.length);\n\n this.voiceLevel = Math.min(1, rms / 0.15); // meter only — the decision uses rms directly\n this.evaluateVad(rms);\n }, 50);\n }\n\n private evaluateVad(rms: number) {\n const now = Date.now();\n\n // Phase 0 — listen to the ROOM before listening to the person. A fixed threshold cannot know whether it\n // is sitting in a quiet office or next to a fan, so the first moments of every listening cycle measure the\n // ambient level and the speech threshold is set above it. This is what stops a fan being heard as speech.\n if (this.calibrationEndsAt) {\n if (now < this.calibrationEndsAt) { this.calibrationSamples.push(rms); return; }\n\n this.applyCalibration();\n\n // Only NOW is it actually listening to the person, so this is where the label flips and the \"your turn\"\n // tone plays. The listening clock restarts here too — the calibration window must not count against the\n // idle timeout, or a slow starter loses part of their thinking time.\n this.setVoiceState('listening');\n this.listenStartedAt = now;\n this.earcon('ready');\n }\n\n const enterThreshold = this.activeThreshold;\n // Hysteresis: once someone is speaking it takes LESS to keep them speaking. Without this, the natural dip\n // between words reads as the end of the sentence.\n const stayThreshold = enterThreshold * AgentComponent.CONTINUE_RATIO;\n\n if (!this.speechStartedAt) {\n // Not speaking yet. Loudness must be SUSTAINED to count — a door, a keyboard, a fan surge is loud for\n // an instant, and a single loud sample used to be enough to start capturing.\n if (rms >= enterThreshold) {\n if (!this.loudSince) this.loudSince = now;\n\n if (now - this.loudSince >= this.onsetMs) {\n this.speechStartedAt = this.loudSince; // backdated, so the first word is not clipped\n this.lastLoudAt = now;\n this.setVoiceState('capturing');\n this.silentSince = 0; // they are talking, so the give-up clock stops\n this.earcon('gotyou'); // \"I can hear you\" — answers the question the old single beep left open\n }\n } else {\n this.loudSince = 0; // a blip, not a sentence\n }\n } else {\n // Mid-sentence. Pauses and breaths are expected — only a long quiet ends the turn.\n if (rms >= stayThreshold) {\n this.lastLoudAt = now;\n } else if (now - this.lastLoudAt >= this.silenceMs) {\n // Too short to be a sentence? Then it was a noise — throw it away instead of asking the model to\n // find words in it, which is where \"Hallo!\" and other invented transcripts come from.\n const spoken = this.lastLoudAt - this.speechStartedAt;\n this.endUtterance(spoken < this.minSpeechMs);\n return;\n }\n\n if (now - this.speechStartedAt >= AgentComponent.MAX_UTTERANCE_MS) this.endUtterance(false);\n return;\n }\n\n // Nothing said yet — don't upload silence. Reopen the mic instead of ending the session, because someone\n // driving may simply not be ready to talk yet.\n // The speechStartedAt guard matters: the branch above can START speech on this very sample, and without\n // it the idle check below would immediately throw that fresh utterance away.\n if (!this.speechStartedAt && now - this.listenStartedAt >= AgentComponent.IDLE_MS) {\n if (!this.silentSince) this.silentSince = now;\n\n if (now - this.silentSince >= AgentComponent.MAX_SILENT_MS) {\n this.voiceError = 'Voice mode ended after a long silence.';\n this.stopVoiceMode();\n return;\n }\n\n this.endUtterance(true); // drop this clip and listen again\n }\n }\n\n // Turns the room measurement into a working threshold. Uses a high percentile rather than the average so a\n // steady hum sets the floor without one loud knock dragging it up, then requires speech to be several times\n // that. The configured voiceThreshold acts as a floor only — a noisy room can raise the bar, never lower it.\n private applyCalibration() {\n this.calibrationEndsAt = 0;\n\n const samples = this.calibrationSamples.slice().sort((a, b) => a - b);\n this.calibrationSamples = [];\n\n if (!samples.length) { this.activeThreshold = this.voiceThreshold; return; }\n\n const percentile = samples[Math.min(samples.length - 1, Math.floor(samples.length * 0.9))];\n this.noiseFloor = percentile;\n\n // Clamped at BOTH ends. The upper clamp is not theoretical: calibrating while someone was still talking\n // measured a \"room\" of 0.17 and set the bar at 0.52 — around three times louder than any normal voice, so\n // the microphone was open and permanently deaf. A ceiling means the worst case is over-sensitivity, which\n // the onset rule and minimum-utterance check still catch, rather than total silence.\n const scaled = percentile * this.noiseMultiplier;\n this.activeThreshold = Math.min(this.maxThreshold, Math.max(this.voiceThreshold, scaled));\n }\n\n // Ends the current capture. `abandon` means the clip is thrown away rather than sent.\n private endUtterance(abandon: boolean) {\n this.abandonUtterance = abandon;\n\n if (this.vadTimer) { clearInterval(this.vadTimer); this.vadTimer = null; }\n this.voiceLevel = 0;\n\n if (this.voiceRecorder && this.voiceRecorder.state !== 'inactive') {\n this.voiceRecorder.stop(); // onUtteranceCaptured() runs from onstop\n } else {\n this.onUtteranceCaptured();\n }\n }\n\n // The microphone is released HERE, before anything is sent — so it is provably shut for the whole of\n // thinking and speaking, which is what keeps the session half-duplex.\n private onUtteranceCaptured() {\n const chunks = this.voiceChunks;\n const mimeType = this.voiceRecorder?.mimeType || 'audio/webm';\n\n this.releaseMic();\n\n if (!this.voiceMode) return;\n\n if (this.abandonUtterance || !chunks.length) {\n this.voiceChunks = [];\n this.beginListening(); // nothing worth sending — straight back to listening\n return;\n }\n\n this.voiceChunks = [];\n this.setVoiceState('thinking');\n\n const blob = new Blob(chunks, { type: mimeType });\n const fileName = `voice-turn.${this.extensionFor(mimeType)}`;\n\n this.agentService.voiceTurn(blob, fileName).subscribe({\n next: (res: any) => this.onTurnAnswered(res),\n error: () => this.speakThenListen('', 'Sorry, that did not get through. Please try again.')\n });\n }\n\n private onTurnAnswered(res: any) {\n if (!this.voiceMode) return;\n\n const data = res?.data || {};\n\n if (data.transcript) {\n this.voiceTranscript = data.transcript;\n this.agentService.appendSpokenMessage(data.transcript); // the spoken turn joins the normal thread\n }\n\n const reply = data.replyText || res?.message || 'Sorry, something went wrong.';\n this.speakThenListen(data.audioBase64, reply, data.audioMimeType);\n }\n\n // Plays the answer, then reopens the microphone STRICTLY on 'ended'. Never before — that ordering is the\n // half-duplex guarantee, and it is why no echo cancellation is needed anywhere in this feature.\n private speakThenListen(audioBase64: string, replyText: string, mimeType: string = 'audio/mpeg') {\n this.voiceReply = replyText;\n this.setVoiceState('speaking');\n\n if (!audioBase64) {\n // No audio came back (a network failure, or speech synthesis was unavailable server-side). Say it with\n // the browser's own voice rather than leaving someone who cannot look at the screen in silence.\n this.speakFallback(replyText);\n return;\n }\n\n this.stopPlayback();\n\n this.player = new Audio(`data:${mimeType || 'audio/mpeg'};base64,${audioBase64}`);\n // Keep the label honest: 'speaking' is re-asserted when sound actually starts, not merely when it was\n // requested, so the indicator never claims to be doing something the ear cannot hear yet.\n this.player.onplaying = () => { if (this.voiceMode) this.setVoiceState('speaking'); };\n this.player.onended = () => this.afterSpeaking();\n this.player.onerror = () => this.afterSpeaking();\n\n this.player.play().catch(() => this.afterSpeaking()); // autoplay refused → don't hang the loop\n }\n\n // Browser speech synthesis, used only when the server could not produce audio.\n // Chrome's utterance.onend is unreliable for long text — it can fire early or not at all, which is what made\n // the panel say \"Listening\" while a voice was still talking. So the end of speech is confirmed by POLLING\n // speechSynthesis.speaking rather than by trusting the event.\n private speakFallback(text: string) {\n const synth = (window as any).speechSynthesis;\n\n if (!synth || !text) { this.afterSpeaking(); return; }\n\n try {\n const utterance = new (window as any).SpeechSynthesisUtterance(text);\n synth.speak(utterance);\n\n const started = Date.now();\n const watch = setInterval(() => {\n const stillTalking = synth.speaking || synth.pending;\n const tooLong = Date.now() - started > 60000; // never wedge the loop on a stuck engine\n\n if (!this.voiceMode) { clearInterval(watch); return; }\n if (stillTalking && !tooLong) { this.setVoiceState('speaking'); return; }\n\n clearInterval(watch);\n this.afterSpeaking();\n }, 200);\n } catch {\n this.afterSpeaking();\n }\n }\n\n // 'ended' means the audio element finished, NOT that the room is quiet: the last of it is still leaving the\n // speakers and reverberating. Reopening instantly meant the mic caught the tail of the agent's own sentence\n // and, worse, calibrated the noise floor against it. A short settle keeps the loop honest.\n private afterSpeaking() {\n this.player = null;\n if (!this.voiceMode) return;\n\n setTimeout(() => { if (this.voiceMode) this.beginListening(); }, AgentComponent.SETTLE_MS);\n }\n\n // Three tones, each meaning exactly ONE thing, because \"a beep happened\" is not information:\n //\n // ready — rising two-note, plays when it is ready FOR YOU. \"Start talking now.\"\n // gotyou — single soft note, plays the moment your speech is \"I can hear you, keep going.\"\n // detected, so you know you were picked up\n // taken — falling two-note, plays when the mic closes and it \"I have it, I'm working on it.\"\n // starts working\n //\n // Rising always means the floor is yours; falling always means it has been taken away. That direction is\n // the whole cue — it can be recognised without looking and without remembering pitches.\n private earcon(kind: 'ready' | 'gotyou' | 'taken') {\n if (!this.audioContext) return;\n\n const notes = kind === 'ready' ? [660, 990]\n : kind === 'taken' ? [660, 440]\n : [880];\n\n try {\n const start = this.audioContext.currentTime;\n\n notes.forEach((frequency, index) => {\n const oscillator = this.audioContext!.createOscillator();\n const gain = this.audioContext!.createGain();\n\n oscillator.type = 'sine';\n oscillator.frequency.value = frequency;\n\n const at = start + index * 0.1;\n const peak = kind === 'gotyou' ? 0.06 : 0.12; // the mid-speech cue is deliberately quieter\n\n gain.gain.setValueAtTime(0.0001, at);\n gain.gain.exponentialRampToValueAtTime(peak, at + 0.01);\n gain.gain.exponentialRampToValueAtTime(0.0001, at + 0.09);\n\n oscillator.connect(gain);\n gain.connect(this.audioContext!.destination);\n oscillator.start(at);\n oscillator.stop(at + 0.1);\n });\n } catch {}\n }\n\n private releaseMic() {\n if (this.vadTimer) { clearInterval(this.vadTimer); this.vadTimer = null; }\n\n this.analyser = null;\n this.voiceRecorder = null;\n this.voiceLevel = 0;\n\n if (this.voiceStream) {\n this.voiceStream.getTracks().forEach(t => t.stop());\n this.voiceStream = null;\n this.earcon('taken'); // \"I have it\" — the floor is no longer yours\n }\n }\n\n private stopVoiceCapture() {\n if (this.voiceRecorder && this.voiceRecorder.state !== 'inactive') {\n try { this.voiceRecorder.stop(); } catch {}\n }\n this.releaseMic();\n this.voiceChunks = [];\n }\n\n private stopPlayback() {\n if (!this.player) return;\n try { this.player.pause(); this.player.currentTime = 0; } catch {}\n this.player.onended = null;\n this.player.onerror = null;\n this.player = null;\n\n const synth = (window as any).speechSynthesis;\n if (synth) { try { synth.cancel(); } catch {} }\n }\n\n // What the state indicator reads. Plain words, because it is glanced at rather than read.\n get voiceStateLabel(): string {\n switch (this.voiceState) {\n // Changed: shown during room calibration. \"Wait\" rather than \"Listening\", because talking during this\n // window is actively harmful — the words get measured as background noise.\n case 'starting': return 'Getting ready';\n case 'listening': return 'Listening';\n // Changed: capturing is now shown. It tells the user it has locked onto their voice and is WAITING for\n // them to finish — without it, a long pause feels like the thing has stopped paying attention.\n case 'capturing': return 'Hearing you';\n case 'thinking': return 'Thinking';\n case 'speaking': return 'Speaking';\n default: return '';\n }\n }\n\n get voiceStateIcon(): string {\n switch (this.voiceState) {\n case 'starting': return 'graphic_eq'; // measuring the room, not the person\n case 'listening':\n case 'capturing': return 'mic';\n case 'thinking': return 'hourglass_top';\n case 'speaking': return 'volume_up';\n default: return 'mic_off';\n }\n }\n\n // Changed: \"Take photo\" — hands off to the device camera. On a phone this opens the camera app on the rear\n // lens and returns the shot; no in-page camera permission is involved because the OS handles it. Desktop\n // browsers ignore `capture` and fall back to the file picker.\n takePhoto() {\n this.cameraInput?.nativeElement.click();\n }\n\n // Changed: \"Choose photo\" — same input minus `capture`, so it opens the gallery / file picker\n choosePhoto() {\n this.galleryInput?.nativeElement.click();\n }\n\n // Changed: photo capture now STAGES the file instead of sending it. Nothing leaves until the user presses\n // send, so they can add a typed or spoken caption first — that caption is the actual command, the photo is\n // the evidence it refers to. `capture` opens the camera directly on mobile, the file picker on desktop.\n onPhotoSelected(event: Event) {\n const input = event.target as HTMLInputElement;\n const file = input.files && input.files.length ? input.files[0] : null;\n input.value = ''; // Reset so picking the same file twice still fires a change event\n\n if (!file) return;\n\n this.pendingPhoto = file;\n this.focusInput();\n }\n\n // Picks a container the browser can record AND the transcription endpoint accepts. Chrome/Edge/Firefox give\n // webm/opus, Safari gives mp4 — both are supported server-side, so no transcoding is needed anywhere.\n private pickAudioMimeType(): string {\n const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg;codecs=opus', 'audio/ogg'];\n return candidates.find(t => MediaRecorder.isTypeSupported(t)) || '';\n }\n\n private finishRecording() {\n const mode = this.stopMode;\n this.clearRecording();\n\n if (mode === 'cancel' || !this.chunks.length) {\n this.chunks = [];\n return;\n }\n\n const type = this.recorder?.mimeType || 'audio/webm';\n const blob = new Blob(this.chunks, { type });\n const fileName = `voice-note.${this.extensionFor(type)}`;\n this.chunks = [];\n\n // Stop → transcribe into the text box for review; the photo (if any) stays staged\n if (mode === 'transcribe') {\n this.isTranscribing = true;\n this.agentService.transcribeOnly(blob, fileName).subscribe({\n next: (res: any) => {\n this.isTranscribing = false;\n if (res?.success && res.data?.text) {\n const existing = this.inputText.trim();\n this.inputText = existing ? `${existing} ${res.data.text}` : res.data.text;\n this.focusInput();\n } else {\n this.mediaError = res?.message || 'Sorry, that recording could not be transcribed.';\n }\n },\n error: () => {\n this.isTranscribing = false;\n this.mediaError = 'Sorry, that recording could not be transcribed.';\n }\n });\n return;\n }\n\n // Send → the clip goes with the staged photo and whatever is typed, all in one message\n this.agentService.sendComposed(\n this.pendingPhoto, this.pendingPhoto?.name || 'photo.jpg',\n blob, fileName,\n this.inputText.trim()\n );\n\n this.pendingPhoto = null;\n this.inputText = '';\n }\n\n private extensionFor(mimeType: string): string {\n const type = (mimeType || '').toLowerCase();\n if (type.indexOf('mp4') >= 0) return 'mp4';\n if (type.indexOf('ogg') >= 0) return 'ogg';\n return 'webm';\n }\n\n // Releases the mic — without this the browser keeps showing the recording indicator\n private clearRecording() {\n this.isRecording = false;\n this.recordSeconds = 0;\n\n if (this.recordTimer) {\n clearInterval(this.recordTimer);\n this.recordTimer = null;\n }\n\n if (this.mediaStream) {\n this.mediaStream.getTracks().forEach(t => t.stop());\n this.mediaStream = null;\n }\n }\n\n private scrollToBottom() {\n try {\n if (this.messageContainer) {\n this.messageContainer.nativeElement.scrollTop = this.messageContainer.nativeElement.scrollHeight;\n }\n } catch (e) {}\n }\n\n private focusInput() {\n setTimeout(() => {\n if (this.messageInput) {\n this.messageInput.nativeElement.focus();\n }\n }, 300);\n }\n\n trackMessage(index: number, msg: AgentMessage): string {\n return `${index}-${msg.role}-${msg.createdDate}`;\n }\n\n // Changed: Set --agent-vh CSS variable from visualViewport so the chat resizes when the mobile keyboard opens\n private initMobileViewport() {\n if (!window.visualViewport) return;\n this.viewportHandler = () => {\n const vh = window.visualViewport!.height;\n document.documentElement.style.setProperty('--agent-vh', `${vh}px`);\n // Changed: one signal, a second consumer. When the on-screen keyboard opens the visual viewport\n // collapses well below the layout viewport; the fixed bottom tab bar has to get out of the way or it\n // sits between the keyboard and the field being typed into. Published as a class on <html> so the tab\n // bar reacts in CSS alone — deliberately NOT a second visualViewport listener of its own.\n // 0.75 is the usual heuristic: real keyboards take 30-50% of the height, browser chrome far less.\n document.documentElement.classList.toggle('keyboard-open', vh < window.innerHeight * 0.75);\n };\n window.visualViewport.addEventListener('resize', this.viewportHandler);\n this.viewportHandler(); // Set initial value\n }\n\n ngOnDestroy(): void {\n this.subs.forEach(s => s.unsubscribe());\n if (this.isRecording) this.cancelRecording(); // Changed: never leave the mic open behind a destroyed widget\n if (this.voiceMode) this.stopVoiceMode(); // Changed: same for a voice session — no orphaned mic\n this.onMicPressEnd(); // Changed: a held mic must not fire after teardown\n this.clearRecording();\n if (this.viewportHandler && window.visualViewport) {\n window.visualViewport.removeEventListener('resize', this.viewportHandler);\n document.documentElement.classList.remove('keyboard-open'); // Changed: never leave the tab bar hidden by a destroyed widget\n }\n }\n}\n","<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\n\n<!-- FAB toggle button -->\n<!-- Changed: the page presentation has no FAB — the page IS the chat, so a button to open it is nonsense.\n Changed: showFab also stands the FAB down on mobile (the bottom tab bar's Chat tab replaces it) and on\n the home/agent route at any width, where it would otherwise hover over the page it opens. -->\n<button mat-fab class=\"agent-fab\" *ngIf=\"showFab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\n</button>\n\n<!-- Chat window -->\n<!-- Changed: rendered unconditionally in page mode (isOpen only governs the floating window); .page-mode\n swaps the fixed overlay for in-flow layout and 'none' opts out of the slide-up animation -->\n<!-- Changed: showWindow keeps the floating window off wherever its FAB is off, so mobile and the chat route\n can never show two chat surfaces at once -->\n<div class=\"agent-window\" [class.page-mode]=\"pageMode\" *ngIf=\"showWindow\" [@slideUp]=\"pageMode ? 'none' : 'float'\">\n\n <!-- Header with online status -->\n <div class=\"agent-header\">\n <div class=\"header-info\">\n <mat-icon class=\"header-icon\">smart_toy</mat-icon>\n <div class=\"header-text\">\n <span class=\"header-title\">{{ agentName }}</span>\n <span class=\"status-badge\" [class.online]=\"isOnline\" [class.offline]=\"!isOnline\">\n <span class=\"status-dot\"></span>\n {{ isOnline ? 'Online' : 'Offline' }}\n </span>\n </div>\n </div>\n <div class=\"header-actions\">\n <!-- Changed: earlier conversations existed but were unreachable — the widget only ever showed the newest -->\n <button mat-icon-button matTooltip=\"Conversations\" (click)=\"toggleConversationList()\">\n <mat-icon>forum</mat-icon>\n </button>\n <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\n <mat-icon>add_comment</mat-icon>\n </button>\n <!-- Changed: minimise is float-only — there is nothing to minimise a routed page into -->\n <button mat-icon-button matTooltip=\"Close\" *ngIf=\"!pageMode\" (click)=\"toggleChat()\">\n <mat-icon>remove</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Conversation switcher. A dropdown rather than a panel: the window is small and this is a jump list,\n not somewhere the user spends time. -->\n <div class=\"agent-conversations\" *ngIf=\"showConversations\">\n <div class=\"conversation-row\" *ngFor=\"let c of conversations\"\n [class.current]=\"c.conversationID === currentConversationId\"\n (click)=\"selectConversation(c.conversationID)\">\n <mat-icon class=\"conversation-icon\">chat</mat-icon>\n <span class=\"conversation-title\">{{ c.title }}</span>\n </div>\n <div class=\"conversation-empty\" *ngIf=\"conversations.length === 0\">No earlier conversations</div>\n </div>\n\n <!-- Changed: the session banner. The agent has always known it was holding a half-built record; until the\n reply carried structured state, that fact only existed as prose the user had to read carefully. -->\n <div class=\"agent-session-banner\" *ngIf=\"sessionInfo?.sessionType\">\n <mat-icon class=\"session-icon\">edit_note</mat-icon>\n <span class=\"session-text\">{{ sessionVerb }} a {{ sessionInfo?.modelName }}</span>\n <!-- Changed: chips rather than a sentence. What is still required is the one thing the user has to act\n on, and a chip per field is scannable where a comma-separated list inside a sentence is not. -->\n <span class=\"session-chip\" *ngFor=\"let field of sessionInfo?.missingFields\">{{ field | camelToWords }}</span>\n </div>\n\n <!-- Messages area -->\n <div class=\"agent-messages\" #messageContainer>\n\n <!-- Welcome experience (show when no messages) -->\n <div class=\"agent-greeting\" *ngIf=\"messages.length === 0\">\n <mat-icon class=\"greeting-icon\">smart_toy</mat-icon>\n <p class=\"greeting-text\">{{ greeting }}</p>\n\n <!-- Three capability pillars -->\n <div class=\"capability-pillars\">\n\n <!-- Pillar 1: Navigate & Inquire -->\n <div class=\"pillar\" (click)=\"sendSuggested('How do I post a transaction?')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">explore</mat-icon>\n <span class=\"pillar-title\">Ask & Navigate</span>\n </div>\n <p class=\"pillar-desc\">Ask how to do things or find features in the app</p>\n <span class=\"pillar-example\">\"How do I post a transaction?\"</span>\n </div>\n\n <!-- Pillar 2: Create & Modify -->\n <div class=\"pillar\" (click)=\"sendSuggested('Create a new customer')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">edit_note</mat-icon>\n <span class=\"pillar-title\">Create & Modify</span>\n </div>\n <p class=\"pillar-desc\">Add, edit, or delete records using natural language</p>\n <span class=\"pillar-example\">\"Create a new customer called Acme Ltd\"</span>\n </div>\n\n <!-- Pillar 3: Get Information -->\n <div class=\"pillar\" (click)=\"sendSuggested('List all accounts')\">\n <div class=\"pillar-header\">\n <mat-icon class=\"pillar-icon\">search</mat-icon>\n <span class=\"pillar-title\">Get Information</span>\n </div>\n <p class=\"pillar-desc\">Retrieve details, list records, or get summaries</p>\n <span class=\"pillar-example\">\"Show me the details of Customer A\"</span>\n </div>\n\n </div>\n </div>\n\n <!-- Message bubbles -->\n <ng-container *ngFor=\"let msg of messages; trackBy: trackMessage\">\n\n <!-- Changed: a conversation boundary is drawn, not spoken. Without a visible line, the agent starting a\n fresh thread after a long gap just looks like it forgetting what was said. -->\n <div class=\"conversation-divider\" *ngIf=\"msg.divider\">\n <span class=\"divider-label\">{{ msg.content }}</span>\n </div>\n\n <div *ngIf=\"!msg.divider\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\n <!-- Changed: agent replies are markdown and were being printed verbatim, so a carefully formatted\n answer arrived as a wall of asterisks. User messages stay plain text — they are not markdown,\n and interpreting them as such would be putting words in the user's mouth. -->\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\" [class.pending-bubble]=\"msg.pending\">\n <span *ngIf=\"msg.role === 'assistant'\" [innerHTML]=\"msg.content | agentMarkdown\"></span>\n <span *ngIf=\"msg.role !== 'assistant'\">{{ msg.content }}</span>\n </div>\n </div>\n\n </ng-container>\n\n <!-- Typing indicator -->\n <div class=\"message-row agent-row\" *ngIf=\"isTyping\">\n <div class=\"message-bubble agent-bubble typing-bubble\">\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n </div>\n </div>\n\n </div>\n\n <!-- Media error (mic blocked / unsupported browser) -->\n <div class=\"agent-media-error\" *ngIf=\"mediaError\">\n <mat-icon class=\"media-error-icon\">error_outline</mat-icon>\n <span>{{ mediaError }}</span>\n </div>\n\n <!-- Staged attachment: a photo waits here until the user adds a caption and presses send -->\n <div class=\"agent-attachment\" *ngIf=\"pendingPhoto\">\n <mat-icon class=\"attach-icon\">image</mat-icon>\n <span class=\"attach-label\">Photo attached</span>\n <button mat-icon-button class=\"attach-remove\" matTooltip=\"Remove photo\" (click)=\"clearPhoto()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <!-- Transcribing indicator for the Stop button -->\n <div class=\"agent-transcribing\" *ngIf=\"isTranscribing\">\n <mat-icon class=\"transcribing-icon\">graphic_eq</mat-icon>\n <span>Transcribing…</span>\n </div>\n\n <!-- Voice mode: replaces the composer entirely. Everything here is sized to be used without looking —\n a big state indicator, the last exchange, and a thumb-sized exit. -->\n <div class=\"agent-voice\" *ngIf=\"voiceMode\">\n\n <div class=\"voice-state\" [class.is-starting]=\"voiceState === 'starting'\"\n [class.is-listening]=\"voiceState === 'listening' || voiceState === 'capturing'\"\n [class.is-thinking]=\"voiceState === 'thinking'\" [class.is-speaking]=\"voiceState === 'speaking'\">\n <mat-icon class=\"voice-state-icon\">{{ voiceStateIcon }}</mat-icon>\n <span class=\"voice-state-label\">{{ voiceStateLabel }}</span>\n </div>\n\n <!-- Live level, so the mic being open is visible as well as audible. Not shown while calibrating: the\n meter would invite exactly the talking that ruins the measurement. -->\n <div class=\"voice-meter\" *ngIf=\"voiceState === 'listening' || voiceState === 'capturing'\">\n <div class=\"voice-meter-fill\" [style.width.%]=\"voiceLevel * 100\"></div>\n </div>\n\n <!-- Deliberately NO transcript/reply repeated here: both already appear in the chat thread directly above,\n and duplicating them pushed the state indicator — the one thing that must be readable at a glance —\n down the panel. The state, the meter and the exit are all this needs. -->\n\n <!-- Says explicitly that pauses are fine, because the natural worry is that stopping to think will send -->\n <p class=\"voice-hint\" *ngIf=\"voiceState === 'starting'\">Checking the background noise — hold on…</p>\n <p class=\"voice-hint\" *ngIf=\"voiceState === 'capturing'\">Take your time — it waits until you're done.</p>\n <p class=\"voice-hint\" *ngIf=\"voiceState === 'listening'\">Just speak — it sends when you finish.</p>\n\n <button mat-flat-button color=\"warn\" class=\"voice-exit\" (click)=\"stopVoiceMode()\">\n <mat-icon>close</mat-icon>\n <span>Exit voice mode</span>\n </button>\n </div>\n\n <!-- Input area -->\n <div class=\"agent-input\" [class.recording]=\"isRecording\" *ngIf=\"!voiceMode\">\n\n <!-- Normal input: attach, text, voice note, hands-free, send -->\n <ng-container *ngIf=\"!isRecording\">\n <button mat-icon-button class=\"media-btn\" matTooltip=\"Attach\" [matMenuTriggerFor]=\"attachMenu\">\n <mat-icon>add</mat-icon>\n </button>\n <mat-form-field appearance=\"outline\" class=\"input-field\">\n <input matInput #messageInput placeholder=\"Type, speak or snap...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\n </mat-form-field>\n <!-- Tap records a voice note; HOLD enters hands-free voice mode. Deliberately no separate button for\n voice mode: the row is already at capacity, and a fifth control would crowd out the text field on a\n phone. The gesture is the accelerator, the \"+\" menu below is the discoverable path to the same\n thing — a gesture nobody can see is a feature nobody finds. -->\n <button mat-icon-button class=\"media-btn mic-btn\" [class.arming]=\"micHeld\"\n matTooltip=\"Tap to record · hold for hands-free\"\n (click)=\"onMicClick()\"\n (pointerdown)=\"onMicPressStart()\" (pointerup)=\"onMicPressEnd()\"\n (pointerleave)=\"onMicPressEnd()\" (pointercancel)=\"onMicPressEnd()\">\n <mat-icon>mic</mat-icon>\n </button>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!canSend\">\n <mat-icon>send</mat-icon>\n </button>\n </ng-container>\n\n <!-- Recording bar: replaces the input while a voice note is being captured.\n Three exits — discard it, stop and read it back as text, or send it straight away. -->\n <ng-container *ngIf=\"isRecording\">\n <button mat-icon-button class=\"media-btn cancel-btn\" matTooltip=\"Discard\" (click)=\"cancelRecording()\">\n <mat-icon>delete</mat-icon>\n </button>\n <div class=\"recording-status\">\n <span class=\"rec-dot\"></span>\n <span class=\"rec-label\">Recording</span>\n <span class=\"rec-time\">{{ recordDisplay }}</span>\n </div>\n <button mat-icon-button class=\"media-btn stop-btn\" matTooltip=\"Stop and add as text\" (click)=\"stopAndTranscribe()\">\n <mat-icon>stop_circle</mat-icon>\n </button>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" matTooltip=\"Send now\" (click)=\"stopRecording()\">\n <mat-icon>send</mat-icon>\n </button>\n </ng-container>\n\n <!-- Attach menu — keeps the row compact: one \"+\" instead of a button per source -->\n <mat-menu #attachMenu=\"matMenu\">\n <button mat-menu-item (click)=\"takePhoto()\">\n <mat-icon>photo_camera</mat-icon>\n <span>Take photo</span>\n </button>\n <button mat-menu-item (click)=\"choosePhoto()\">\n <mat-icon>photo_library</mat-icon>\n <span>Choose photo</span>\n </button>\n <!-- The discoverable way into voice mode. The mic long-press does the same thing faster, but only\n once you know it exists — this is how you find out. -->\n <button mat-menu-item (click)=\"startVoiceMode()\">\n <mat-icon>record_voice_over</mat-icon>\n <span>Hands-free voice mode</span>\n </button>\n </mat-menu>\n\n <!-- Two separate pickers on purpose. `capture=\"environment\"` hands off to the phone's camera app and\n opens the rear lens; WITHOUT it the same input opens the gallery/file picker. One input cannot do\n both, which is why the old single button always went to the camera on mobile. -->\n <input type=\"file\" #cameraInput accept=\"image/*\" capture=\"environment\" hidden (change)=\"onPhotoSelected($event)\" />\n <input type=\"file\" #galleryInput accept=\"image/*\" hidden (change)=\"onPhotoSelected($event)\" />\n </div>\n\n</div>\n"],"names":["i1","i1.AgentService","i2","i3","i4"],"mappings":"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"}
|