tin-spa 20.6.5 → 20.6.7

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.
@@ -1514,18 +1514,20 @@ class SignalRService {
1514
1514
  // Changed: Single connection state observable — used by real-time table indicators
1515
1515
  this.dataHubConnected = new BehaviorSubject(false);
1516
1516
  this.dataHubConnected$ = this.dataHubConnected.asObservable();
1517
+ this.currentToken = ''; // Changed: Live token reference for SignalR reconnection
1517
1518
  }
1518
1519
  // Changed: Single startConnection method handles both notification and entity change listeners
1519
1520
  startConnection(hubUrl, token) {
1520
1521
  // Changed: Allow disabling real-time via localStorage flag (used by E2E tests)
1521
1522
  if (localStorage.getItem('disableRealTime') === 'true')
1522
1523
  return;
1524
+ this.currentToken = token; // Changed: Store token so it can be updated after refresh
1523
1525
  if (this.hubConnection && this.hubConnection.state !== signalR.HubConnectionState.Disconnected)
1524
1526
  return;
1525
1527
  if (this.hubConnection)
1526
1528
  this.stopConnection();
1527
1529
  this.hubConnection = new signalR.HubConnectionBuilder()
1528
- .withUrl(hubUrl, { accessTokenFactory: () => token })
1530
+ .withUrl(hubUrl, { accessTokenFactory: () => this.currentToken }) // Changed: Use live reference so reconnections use the latest token
1529
1531
  .withAutomaticReconnect([0, 2000, 5000, 10000, 30000, 60000, 60000, 60000, 60000, 60000]) // Changed: Extended retry policy for entity broadcast reliability
1530
1532
  .build();
1531
1533
  // Changed: Notification count listener (previously on separate notification hub)
@@ -1580,6 +1582,10 @@ class SignalRService {
1580
1582
  console.error('SignalR connection error:', err);
1581
1583
  });
1582
1584
  }
1585
+ // Changed: Update the token used for SignalR reconnection (called after silent refresh)
1586
+ updateToken(token) {
1587
+ this.currentToken = token;
1588
+ }
1583
1589
  stopConnection() {
1584
1590
  if (this.hubConnection) {
1585
1591
  this.hubConnection.stop();
@@ -1607,6 +1613,7 @@ class AuthService {
1607
1613
  this.signalRService = signalRService;
1608
1614
  this.googleAuthInitialized = false;
1609
1615
  this.isRefreshing = false; // Changed: Guard to prevent concurrent refresh calls
1616
+ this.refreshTimer = null; // Changed: Proactive token refresh timer
1610
1617
  this.socialUserSource = new BehaviorSubject(null);
1611
1618
  this.socialUserObserv = this.socialUserSource.asObservable();
1612
1619
  //Logged in
@@ -1644,6 +1651,7 @@ class AuthService {
1644
1651
  this.tenantNameSource = new BehaviorSubject("Tenant Name");
1645
1652
  this.tenantNameObserv = this.tenantNameSource.asObservable();
1646
1653
  this.initializeGoogleAuth();
1654
+ this.initVisibilityRefresh(); // Changed: Refresh token when tab becomes visible after being hidden
1647
1655
  }
1648
1656
  initializeGoogleAuth() {
1649
1657
  if (this.googleAuthInitialized) {
@@ -1670,6 +1678,10 @@ class AuthService {
1670
1678
  });
1671
1679
  }
1672
1680
  }
1681
+ if (this.refreshTimer) {
1682
+ clearTimeout(this.refreshTimer);
1683
+ this.refreshTimer = null;
1684
+ } // Changed: Cancel proactive refresh timer
1673
1685
  this.signalRService.stopConnection(); // Changed: Stop consolidated SignalR connection before clearing session
1674
1686
  this.Updateloggedin(false);
1675
1687
  this.UpdateAutoLogin(false);
@@ -1757,6 +1769,7 @@ class AuthService {
1757
1769
  this.Updateloggedin(true);
1758
1770
  this.UpdateToken(data.token);
1759
1771
  this.UpdateTokenExpire(data.expiration);
1772
+ this.signalRService.updateToken(data.token); // Changed: Keep SignalR reconnection token in sync
1760
1773
  this.UpdateRole(data.role);
1761
1774
  this.updateLoggedUserFullName(data.firstName);
1762
1775
  this.updateTenantName(data.tenantName);
@@ -1771,6 +1784,45 @@ class AuthService {
1771
1784
  this.storage.storePersistent(Constants.AUTH_REFRESH_TOKEN, data.refreshToken);
1772
1785
  this.storage.storePersistent(Constants.AUTH_REFRESH_TOKEN_EXPIRE, data.refreshTokenExpiry);
1773
1786
  }
1787
+ this.scheduleTokenRefresh(data.expiration); // Changed: Schedule proactive refresh before token expires
1788
+ }
1789
+ // Changed: Schedule a silent refresh 5 minutes before the access token expires
1790
+ scheduleTokenRefresh(expiration) {
1791
+ if (this.refreshTimer)
1792
+ clearTimeout(this.refreshTimer); // Clear any existing timer
1793
+ const expiresAt = new Date(expiration).getTime();
1794
+ const now = Date.now();
1795
+ const refreshIn = expiresAt - now - (5 * 60 * 1000); // 5 minutes before expiry
1796
+ if (refreshIn <= 0)
1797
+ return; // Token already expired or about to — reactive refresh will handle it
1798
+ this.refreshTimer = setTimeout(() => {
1799
+ this.silentRefresh().then(success => {
1800
+ if (!success)
1801
+ this.sessionExpired();
1802
+ });
1803
+ }, refreshIn);
1804
+ }
1805
+ // Changed: When tab becomes visible, check if token needs immediate refresh
1806
+ initVisibilityRefresh() {
1807
+ document.addEventListener('visibilitychange', () => {
1808
+ if (document.visibilityState !== 'visible')
1809
+ return;
1810
+ if (!this.loggedin)
1811
+ return;
1812
+ const expire = this.tokenExpireSource.value;
1813
+ if (!expire)
1814
+ return;
1815
+ const expiresAt = new Date(expire).getTime();
1816
+ const now = Date.now();
1817
+ const minutesLeft = (expiresAt - now) / 60000;
1818
+ // If token expires in less than 10 minutes or is already expired, refresh immediately
1819
+ if (minutesLeft < 10 && this.hasRefreshToken()) {
1820
+ this.silentRefresh().then(success => {
1821
+ if (!success)
1822
+ this.sessionExpired();
1823
+ });
1824
+ }
1825
+ });
1774
1826
  }
1775
1827
  // Changed: Non-destructive check — only returns true if token is genuinely valid (not expired)
1776
1828
  hasValidSession() {
@@ -6494,6 +6546,8 @@ class AgentService {
6494
6546
  this.currentConversationId$ = this.currentConversationId.asObservable();
6495
6547
  this.greeting$ = this.greeting.asObservable();
6496
6548
  this.suggestedQuestions$ = this.suggestedQuestions.asObservable();
6549
+ // Changed: Load the most recent conversation's messages on first open
6550
+ this.hasLoadedInitial = false;
6497
6551
  this.initListeners();
6498
6552
  }
6499
6553
  // App name derived from the existing appConfig
@@ -6568,15 +6622,13 @@ class AgentService {
6568
6622
  error: () => this.typing.next(false)
6569
6623
  });
6570
6624
  }
6571
- // Start a new conversation
6625
+ // Start a new conversation — only when user explicitly clicks "New conversation"
6572
6626
  newConversation() {
6573
6627
  this.messages.next([]);
6574
6628
  this.currentConversationId.next(null);
6575
- this.dataService.CallApi({ url: 'agent/conversations', method: 'post' }, // Changed: was assistant/conversations
6576
- { appName: this.appName }).subscribe(res => {
6629
+ this.dataService.CallApi({ url: 'agent/conversations', method: 'post' }, { appName: this.appName }).subscribe(res => {
6577
6630
  if (res.success && res.data) {
6578
6631
  this.currentConversationId.next(res.data.conversationID);
6579
- this.loadConversations();
6580
6632
  }
6581
6633
  });
6582
6634
  }
@@ -6593,9 +6645,33 @@ class AgentService {
6593
6645
  });
6594
6646
  }
6595
6647
  // Toggle chat window open/closed
6596
- toggle() { this.isOpen.next(!this.isOpen.value); }
6597
- open() { this.isOpen.next(true); }
6648
+ toggle() {
6649
+ const opening = !this.isOpen.value;
6650
+ this.isOpen.next(opening);
6651
+ if (opening && !this.hasLoadedInitial) {
6652
+ this.hasLoadedInitial = true;
6653
+ this.restoreLastConversation(); // Changed: Load previous chat on first open
6654
+ }
6655
+ }
6656
+ open() {
6657
+ this.isOpen.next(true);
6658
+ if (!this.hasLoadedInitial) {
6659
+ this.hasLoadedInitial = true;
6660
+ this.restoreLastConversation();
6661
+ }
6662
+ }
6598
6663
  close() { this.isOpen.next(false); }
6664
+ // Changed: Fetch conversation list and load the most recent one's messages
6665
+ restoreLastConversation() {
6666
+ this.dataService.CallApi({ url: 'agent/conversations' }).subscribe(res => {
6667
+ if (res.success && res.data?.length > 0) {
6668
+ this.conversations.next(res.data);
6669
+ const latest = res.data[0]; // Backend returns ordered by CreatedDate desc
6670
+ this.currentConversationId.next(latest.conversationID);
6671
+ this.loadMessages(latest.conversationID);
6672
+ }
6673
+ });
6674
+ }
6599
6675
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AgentService, deps: [{ token: DataServiceLib }, { token: SignalRService }], target: i0.ɵɵFactoryTarget.Injectable }); }
6600
6676
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AgentService, providedIn: 'root' }); }
6601
6677
  }
@@ -6607,9 +6683,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
6607
6683
  class AnalyticsService {
6608
6684
  constructor(router) {
6609
6685
  this.router = router;
6610
- }
6611
- // Initialize route tracking — call once from app root or module constructor
6612
- init() {
6686
+ this.initialized = false;
6687
+ }
6688
+ // Changed: Accept production flag and measurement ID — only loads GA in production
6689
+ init(production, measurementId) {
6690
+ if (!production || !measurementId || this.initialized)
6691
+ return; // Changed: Skip GA on non-production environments
6692
+ this.initialized = true;
6693
+ this.loadGtagScript(measurementId); // Changed: Dynamically inject gtag script
6613
6694
  this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event) => {
6614
6695
  const url = event.urlAfterRedirects || event.url;
6615
6696
  // Strip hash prefix so GA sees clean paths (e.g. /home/invoices instead of #/home/invoices)
@@ -6617,6 +6698,17 @@ class AnalyticsService {
6617
6698
  this.sendPageView(pagePath);
6618
6699
  });
6619
6700
  }
6701
+ // Changed: Dynamically load the gtag.js script and configure the measurement ID
6702
+ loadGtagScript(measurementId) {
6703
+ const script = document.createElement('script');
6704
+ script.async = true;
6705
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
6706
+ document.head.appendChild(script);
6707
+ window.dataLayer = window.dataLayer || [];
6708
+ window.gtag = function () { window.dataLayer.push(arguments); };
6709
+ gtag('js', new Date());
6710
+ gtag('config', measurementId, { send_page_view: false }); // Changed: Disable auto page_view — route tracking sends them
6711
+ }
6620
6712
  // Send page_view event to GA4
6621
6713
  sendPageView(pagePath) {
6622
6714
  if (typeof gtag === 'function') {
@@ -9876,12 +9968,14 @@ class AgentComponent {
9876
9968
  this.isOnline = false;
9877
9969
  this.subs = [];
9878
9970
  this.shouldScroll = false;
9971
+ this.viewportHandler = null; // Changed: visualViewport resize handler reference
9879
9972
  } // Changed: was assistantService
9880
9973
  get agentName() {
9881
9974
  return this.agentService.agentName;
9882
9975
  }
9883
9976
  ngOnInit() {
9884
9977
  this.agentService.loadConfig();
9978
+ this.initMobileViewport(); // Changed: Listen for keyboard resize on mobile
9885
9979
  this.subs.push(this.agentService.messages$.subscribe(msgs => {
9886
9980
  this.messages = msgs;
9887
9981
  this.shouldScroll = true;
@@ -9903,6 +9997,15 @@ class AgentComponent {
9903
9997
  toggleChat() {
9904
9998
  this.agentService.toggle();
9905
9999
  }
10000
+ // Changed: Close chat when clicking outside the chat window and FAB button
10001
+ onDocumentClick(event) {
10002
+ if (!this.isOpen)
10003
+ return;
10004
+ const target = event.target;
10005
+ if (target.closest('.agent-window') || target.closest('.agent-fab'))
10006
+ return; // Click inside chat or FAB
10007
+ this.agentService.close();
10008
+ }
9906
10009
  sendMessage() {
9907
10010
  if (!this.inputText.trim())
9908
10011
  return;
@@ -9939,11 +10042,25 @@ class AgentComponent {
9939
10042
  trackMessage(index, msg) {
9940
10043
  return `${index}-${msg.role}-${msg.createdDate}`;
9941
10044
  }
10045
+ // Changed: Set --agent-vh CSS variable from visualViewport so the chat resizes when the mobile keyboard opens
10046
+ initMobileViewport() {
10047
+ if (!window.visualViewport)
10048
+ return;
10049
+ this.viewportHandler = () => {
10050
+ const vh = window.visualViewport.height;
10051
+ document.documentElement.style.setProperty('--agent-vh', `${vh}px`);
10052
+ };
10053
+ window.visualViewport.addEventListener('resize', this.viewportHandler);
10054
+ this.viewportHandler(); // Set initial value
10055
+ }
9942
10056
  ngOnDestroy() {
9943
10057
  this.subs.forEach(s => s.unsubscribe());
10058
+ if (this.viewportHandler && window.visualViewport) {
10059
+ window.visualViewport.removeEventListener('resize', this.viewportHandler);
10060
+ }
9944
10061
  }
9945
10062
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AgentComponent, deps: [{ token: AgentService }, { token: SignalRService }], target: i0.ɵɵFactoryTarget.Component }); }
9946
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: AgentComponent, isStandalone: false, selector: "spa-agent", viewQueries: [{ propertyName: "messageContainer", first: true, predicate: ["messageContainer"], descendants: true }, { propertyName: "messageInput", first: true, predicate: ["messageInput"], descendants: true }], ngImport: i0, template: "<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\n\n<!-- FAB toggle button -->\n<button mat-fab class=\"agent-fab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\n</button>\n\n<!-- Chat window -->\n<div class=\"agent-window\" *ngIf=\"isOpen\" @slideUp>\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 <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\n <mat-icon>add_comment</mat-icon>\n </button>\n <button mat-icon-button matTooltip=\"Close\" (click)=\"toggleChat()\">\n <mat-icon>remove</mat-icon>\n </button>\n </div>\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 <div *ngFor=\"let msg of messages; trackBy: trackMessage\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\">\n {{ msg.content }}\n </div>\n </div>\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 <!-- Input area -->\n <div class=\"agent-input\">\n <mat-form-field appearance=\"outline\" class=\"input-field\">\n <input matInput #messageInput placeholder=\"Type a message...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\n </mat-form-field>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!inputText.trim()\">\n <mat-icon>send</mat-icon>\n </button>\n </div>\n\n</div>\n", styles: [".agent-fab{position:fixed;bottom:24px;right:24px;z-index:1001;background:#1976d2;color:#fff}.agent-window{position:fixed;bottom:96px;right:24px;width:400px;height:580px;background:#fff;border-radius:16px;box-shadow:0 8px 32px #00000026;display:flex;flex-direction:column;z-index:1000;overflow:hidden}.agent-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#1976d2;color:#fff;min-height:56px}.header-info{display:flex;align-items:center;gap:10px}.header-icon{font-size:24px;width:24px;height:24px}.header-text{display:flex;flex-direction:column;gap:1px}.header-title{font-size:16px;font-weight:500;line-height:1.2}.status-badge{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:400;opacity:.9;line-height:1}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block}.status-badge.online .status-dot{background:#4caf50;box-shadow:0 0 4px #4caf5099}.status-badge.offline .status-dot{background:#9e9e9e}.header-actions{display:flex;gap:0}.header-actions button{color:#fff}.agent-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}.agent-greeting{display:flex;flex-direction:column;align-items:center;text-align:center;padding:20px 8px 8px;gap:10px}.greeting-icon{font-size:48px;width:48px;height:48px;color:#1976d2;opacity:.8}.greeting-text{font-size:15px;color:#424242;line-height:1.5;margin:0}.capability-pillars{display:flex;flex-direction:column;gap:10px;margin-top:8px;width:100%}.pillar{border:1px solid #e0e0e0;border-radius:12px;padding:12px 14px;text-align:left;cursor:pointer;transition:all .2s ease;background:#fafafa}.pillar:hover{border-color:#1976d2;background:#1976d20a;box-shadow:0 2px 8px #1976d21a}.pillar-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}.pillar-icon{font-size:20px;width:20px;height:20px;color:#1976d2}.pillar-title{font-size:13px;font-weight:600;color:#212121}.pillar-desc{font-size:12px;color:#616161;margin:0 0 6px;line-height:1.4}.pillar-example{font-size:12px;color:#1976d2;font-style:italic;opacity:.85}.message-row{display:flex;max-width:85%}.user-row{align-self:flex-end}.agent-row{align-self:flex-start}.message-bubble{padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.5;word-break:break-word;white-space:pre-wrap}.user-bubble{background:#1976d2;color:#fff;border-bottom-right-radius:4px}.agent-bubble{background:#f0f0f0;color:#212121;border-bottom-left-radius:4px}.typing-bubble{display:flex;align-items:center;gap:4px;padding:12px 18px}.typing-dot{width:8px;height:8px;border-radius:50%;background:#9e9e9e;animation:typingBounce 1.4s infinite ease-in-out}.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}@keyframes typingBounce{0%,60%,to{transform:translateY(0);opacity:.4}30%{transform:translateY(-6px);opacity:1}}.agent-input{display:flex;align-items:center;padding:8px 12px;border-top:1px solid #e0e0e0;gap:4px}.input-field{flex:1}.input-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.input-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.send-btn{margin-bottom:4px}@media (max-width: 600px){.agent-window{inset:0;width:100%;height:100%;border-radius:0}.agent-fab{bottom:72px;right:16px}}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], animations: [
10063
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: AgentComponent, isStandalone: false, selector: "spa-agent", host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "messageContainer", first: true, predicate: ["messageContainer"], descendants: true }, { propertyName: "messageInput", first: true, predicate: ["messageInput"], descendants: true }], ngImport: i0, template: "<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\n\n<!-- FAB toggle button -->\n<button mat-fab class=\"agent-fab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\n</button>\n\n<!-- Chat window -->\n<div class=\"agent-window\" *ngIf=\"isOpen\" @slideUp>\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 <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\n <mat-icon>add_comment</mat-icon>\n </button>\n <button mat-icon-button matTooltip=\"Close\" (click)=\"toggleChat()\">\n <mat-icon>remove</mat-icon>\n </button>\n </div>\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 <div *ngFor=\"let msg of messages; trackBy: trackMessage\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\">\n {{ msg.content }}\n </div>\n </div>\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 <!-- Input area -->\n <div class=\"agent-input\">\n <mat-form-field appearance=\"outline\" class=\"input-field\">\n <input matInput #messageInput placeholder=\"Type a message...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\n </mat-form-field>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!inputText.trim()\">\n <mat-icon>send</mat-icon>\n </button>\n </div>\n\n</div>\n", styles: [".agent-fab{position:fixed;bottom:24px;right:24px;z-index:1001;background:#1976d2;color:#fff}.agent-window{position:fixed;bottom:96px;right:24px;width:400px;height:580px;background:#fff;border-radius:16px;box-shadow:0 8px 32px #00000026;display:flex;flex-direction:column;z-index:1000;overflow:hidden}.agent-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#1976d2;color:#fff;min-height:56px}.header-info{display:flex;align-items:center;gap:10px}.header-icon{font-size:24px;width:24px;height:24px}.header-text{display:flex;flex-direction:column;gap:1px}.header-title{font-size:16px;font-weight:500;line-height:1.2}.status-badge{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:400;opacity:.9;line-height:1}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block}.status-badge.online .status-dot{background:#4caf50;box-shadow:0 0 4px #4caf5099}.status-badge.offline .status-dot{background:#9e9e9e}.header-actions{display:flex;gap:0}.header-actions button{color:#fff}.agent-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}.agent-greeting{display:flex;flex-direction:column;align-items:center;text-align:center;padding:20px 8px 8px;gap:10px}.greeting-icon{font-size:48px;width:48px;height:48px;color:#1976d2;opacity:.8}.greeting-text{font-size:15px;color:#424242;line-height:1.5;margin:0}.capability-pillars{display:flex;flex-direction:column;gap:10px;margin-top:8px;width:100%}.pillar{border:1px solid #e0e0e0;border-radius:12px;padding:12px 14px;text-align:left;cursor:pointer;transition:all .2s ease;background:#fafafa}.pillar:hover{border-color:#1976d2;background:#1976d20a;box-shadow:0 2px 8px #1976d21a}.pillar-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}.pillar-icon{font-size:20px;width:20px;height:20px;color:#1976d2}.pillar-title{font-size:13px;font-weight:600;color:#212121}.pillar-desc{font-size:12px;color:#616161;margin:0 0 6px;line-height:1.4}.pillar-example{font-size:12px;color:#1976d2;font-style:italic;opacity:.85}.message-row{display:flex;max-width:85%}.user-row{align-self:flex-end}.agent-row{align-self:flex-start}.message-bubble{padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.5;word-break:break-word;white-space:pre-wrap}.user-bubble{background:#1976d2;color:#fff;border-bottom-right-radius:4px}.agent-bubble{background:#f0f0f0;color:#212121;border-bottom-left-radius:4px}.typing-bubble{display:flex;align-items:center;gap:4px;padding:12px 18px}.typing-dot{width:8px;height:8px;border-radius:50%;background:#9e9e9e;animation:typingBounce 1.4s infinite ease-in-out}.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}@keyframes typingBounce{0%,60%,to{transform:translateY(0);opacity:.4}30%{transform:translateY(-6px);opacity:1}}.agent-input{display:flex;align-items:center;padding:8px 12px;border-top:1px solid #e0e0e0;gap:4px}.input-field{flex:1}.input-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.input-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.send-btn{margin-bottom:4px}@media (max-width: 600px){.agent-window{inset:0 0 auto;width:100%;height:100%;height:100dvh;height:var(--agent-vh, 100dvh);border-radius:0}.agent-fab{bottom:72px;right:16px}}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], animations: [
9947
10064
  trigger('slideUp', [
9948
10065
  transition(':enter', [
9949
10066
  style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }),
@@ -9967,13 +10084,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
9967
10084
  animate('200ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }))
9968
10085
  ])
9969
10086
  ])
9970
- ], template: "<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\n\n<!-- FAB toggle button -->\n<button mat-fab class=\"agent-fab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\n</button>\n\n<!-- Chat window -->\n<div class=\"agent-window\" *ngIf=\"isOpen\" @slideUp>\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 <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\n <mat-icon>add_comment</mat-icon>\n </button>\n <button mat-icon-button matTooltip=\"Close\" (click)=\"toggleChat()\">\n <mat-icon>remove</mat-icon>\n </button>\n </div>\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 <div *ngFor=\"let msg of messages; trackBy: trackMessage\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\">\n {{ msg.content }}\n </div>\n </div>\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 <!-- Input area -->\n <div class=\"agent-input\">\n <mat-form-field appearance=\"outline\" class=\"input-field\">\n <input matInput #messageInput placeholder=\"Type a message...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\n </mat-form-field>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!inputText.trim()\">\n <mat-icon>send</mat-icon>\n </button>\n </div>\n\n</div>\n", styles: [".agent-fab{position:fixed;bottom:24px;right:24px;z-index:1001;background:#1976d2;color:#fff}.agent-window{position:fixed;bottom:96px;right:24px;width:400px;height:580px;background:#fff;border-radius:16px;box-shadow:0 8px 32px #00000026;display:flex;flex-direction:column;z-index:1000;overflow:hidden}.agent-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#1976d2;color:#fff;min-height:56px}.header-info{display:flex;align-items:center;gap:10px}.header-icon{font-size:24px;width:24px;height:24px}.header-text{display:flex;flex-direction:column;gap:1px}.header-title{font-size:16px;font-weight:500;line-height:1.2}.status-badge{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:400;opacity:.9;line-height:1}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block}.status-badge.online .status-dot{background:#4caf50;box-shadow:0 0 4px #4caf5099}.status-badge.offline .status-dot{background:#9e9e9e}.header-actions{display:flex;gap:0}.header-actions button{color:#fff}.agent-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}.agent-greeting{display:flex;flex-direction:column;align-items:center;text-align:center;padding:20px 8px 8px;gap:10px}.greeting-icon{font-size:48px;width:48px;height:48px;color:#1976d2;opacity:.8}.greeting-text{font-size:15px;color:#424242;line-height:1.5;margin:0}.capability-pillars{display:flex;flex-direction:column;gap:10px;margin-top:8px;width:100%}.pillar{border:1px solid #e0e0e0;border-radius:12px;padding:12px 14px;text-align:left;cursor:pointer;transition:all .2s ease;background:#fafafa}.pillar:hover{border-color:#1976d2;background:#1976d20a;box-shadow:0 2px 8px #1976d21a}.pillar-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}.pillar-icon{font-size:20px;width:20px;height:20px;color:#1976d2}.pillar-title{font-size:13px;font-weight:600;color:#212121}.pillar-desc{font-size:12px;color:#616161;margin:0 0 6px;line-height:1.4}.pillar-example{font-size:12px;color:#1976d2;font-style:italic;opacity:.85}.message-row{display:flex;max-width:85%}.user-row{align-self:flex-end}.agent-row{align-self:flex-start}.message-bubble{padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.5;word-break:break-word;white-space:pre-wrap}.user-bubble{background:#1976d2;color:#fff;border-bottom-right-radius:4px}.agent-bubble{background:#f0f0f0;color:#212121;border-bottom-left-radius:4px}.typing-bubble{display:flex;align-items:center;gap:4px;padding:12px 18px}.typing-dot{width:8px;height:8px;border-radius:50%;background:#9e9e9e;animation:typingBounce 1.4s infinite ease-in-out}.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}@keyframes typingBounce{0%,60%,to{transform:translateY(0);opacity:.4}30%{transform:translateY(-6px);opacity:1}}.agent-input{display:flex;align-items:center;padding:8px 12px;border-top:1px solid #e0e0e0;gap:4px}.input-field{flex:1}.input-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.input-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.send-btn{margin-bottom:4px}@media (max-width: 600px){.agent-window{inset:0;width:100%;height:100%;border-radius:0}.agent-fab{bottom:72px;right:16px}}\n"] }]
10087
+ ], template: "<!-- Floating chat widget for in-app Agent (renamed from Assistant) -->\n\n<!-- FAB toggle button -->\n<button mat-fab class=\"agent-fab\" (click)=\"toggleChat()\" [matTooltip]=\"agentName\">\n <mat-icon>{{ isOpen ? 'close' : 'chat_bubble' }}</mat-icon>\n</button>\n\n<!-- Chat window -->\n<div class=\"agent-window\" *ngIf=\"isOpen\" @slideUp>\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 <button mat-icon-button matTooltip=\"New conversation\" (click)=\"newConversation()\">\n <mat-icon>add_comment</mat-icon>\n </button>\n <button mat-icon-button matTooltip=\"Close\" (click)=\"toggleChat()\">\n <mat-icon>remove</mat-icon>\n </button>\n </div>\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 <div *ngFor=\"let msg of messages; trackBy: trackMessage\" class=\"message-row\" [class.user-row]=\"msg.role === 'user'\" [class.agent-row]=\"msg.role === 'assistant'\">\n <div class=\"message-bubble\" [class.user-bubble]=\"msg.role === 'user'\" [class.agent-bubble]=\"msg.role === 'assistant'\">\n {{ msg.content }}\n </div>\n </div>\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 <!-- Input area -->\n <div class=\"agent-input\">\n <mat-form-field appearance=\"outline\" class=\"input-field\">\n <input matInput #messageInput placeholder=\"Type a message...\" [(ngModel)]=\"inputText\" (keydown)=\"onKeydown($event)\" autocomplete=\"off\" />\n </mat-form-field>\n <button mat-icon-button color=\"primary\" class=\"send-btn\" (click)=\"sendMessage()\" [disabled]=\"!inputText.trim()\">\n <mat-icon>send</mat-icon>\n </button>\n </div>\n\n</div>\n", styles: [".agent-fab{position:fixed;bottom:24px;right:24px;z-index:1001;background:#1976d2;color:#fff}.agent-window{position:fixed;bottom:96px;right:24px;width:400px;height:580px;background:#fff;border-radius:16px;box-shadow:0 8px 32px #00000026;display:flex;flex-direction:column;z-index:1000;overflow:hidden}.agent-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#1976d2;color:#fff;min-height:56px}.header-info{display:flex;align-items:center;gap:10px}.header-icon{font-size:24px;width:24px;height:24px}.header-text{display:flex;flex-direction:column;gap:1px}.header-title{font-size:16px;font-weight:500;line-height:1.2}.status-badge{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:400;opacity:.9;line-height:1}.status-dot{width:7px;height:7px;border-radius:50%;display:inline-block}.status-badge.online .status-dot{background:#4caf50;box-shadow:0 0 4px #4caf5099}.status-badge.offline .status-dot{background:#9e9e9e}.header-actions{display:flex;gap:0}.header-actions button{color:#fff}.agent-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px}.agent-greeting{display:flex;flex-direction:column;align-items:center;text-align:center;padding:20px 8px 8px;gap:10px}.greeting-icon{font-size:48px;width:48px;height:48px;color:#1976d2;opacity:.8}.greeting-text{font-size:15px;color:#424242;line-height:1.5;margin:0}.capability-pillars{display:flex;flex-direction:column;gap:10px;margin-top:8px;width:100%}.pillar{border:1px solid #e0e0e0;border-radius:12px;padding:12px 14px;text-align:left;cursor:pointer;transition:all .2s ease;background:#fafafa}.pillar:hover{border-color:#1976d2;background:#1976d20a;box-shadow:0 2px 8px #1976d21a}.pillar-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}.pillar-icon{font-size:20px;width:20px;height:20px;color:#1976d2}.pillar-title{font-size:13px;font-weight:600;color:#212121}.pillar-desc{font-size:12px;color:#616161;margin:0 0 6px;line-height:1.4}.pillar-example{font-size:12px;color:#1976d2;font-style:italic;opacity:.85}.message-row{display:flex;max-width:85%}.user-row{align-self:flex-end}.agent-row{align-self:flex-start}.message-bubble{padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.5;word-break:break-word;white-space:pre-wrap}.user-bubble{background:#1976d2;color:#fff;border-bottom-right-radius:4px}.agent-bubble{background:#f0f0f0;color:#212121;border-bottom-left-radius:4px}.typing-bubble{display:flex;align-items:center;gap:4px;padding:12px 18px}.typing-dot{width:8px;height:8px;border-radius:50%;background:#9e9e9e;animation:typingBounce 1.4s infinite ease-in-out}.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}@keyframes typingBounce{0%,60%,to{transform:translateY(0);opacity:.4}30%{transform:translateY(-6px);opacity:1}}.agent-input{display:flex;align-items:center;padding:8px 12px;border-top:1px solid #e0e0e0;gap:4px}.input-field{flex:1}.input-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.input-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.send-btn{margin-bottom:4px}@media (max-width: 600px){.agent-window{inset:0 0 auto;width:100%;height:100%;height:100dvh;height:var(--agent-vh, 100dvh);border-radius:0}.agent-fab{bottom:72px;right:16px}}\n"] }]
9971
10088
  }], ctorParameters: () => [{ type: AgentService }, { type: SignalRService }], propDecorators: { messageContainer: [{
9972
10089
  type: ViewChild,
9973
10090
  args: ['messageContainer']
9974
10091
  }], messageInput: [{
9975
10092
  type: ViewChild,
9976
10093
  args: ['messageInput']
10094
+ }], onDocumentClick: [{
10095
+ type: HostListener,
10096
+ args: ['document:click', ['$event']]
9977
10097
  }] } });
9978
10098
 
9979
10099
  class NavMenuComponent {
@@ -10188,10 +10308,14 @@ class LoaderInterceptor {
10188
10308
  });
10189
10309
  }
10190
10310
  intercept(request, next) {
10191
- let requestClone = this.addToken(request);
10192
- // Changed: Skip loader tracking for SignalR hub requests — they're background infrastructure
10193
- // and should never show the loading spinner or block UI interactions
10194
- if (request.url.includes('/hubs/')) {
10311
+ // Changed: Don't attach Bearer token to refresh/revoke — they use AllowAnonymous and authenticate via body
10312
+ const url = request.url || '';
10313
+ let requestClone = (url.includes('User/refresh') || url.includes('User/revoke'))
10314
+ ? request.clone()
10315
+ : this.addToken(request);
10316
+ // Changed: Skip loader tracking for SignalR hub and agent chat requests — they're background
10317
+ // infrastructure and should never show the loading spinner or block UI interactions
10318
+ if (request.url.includes('/hubs/') || url.includes('/agent/')) {
10195
10319
  return next.handle(requestClone).pipe(catchError((error) => throwError(() => error)));
10196
10320
  }
10197
10321
  this.requests.push(requestClone);
@@ -14073,12 +14197,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
14073
14197
  }] } });
14074
14198
 
14075
14199
  class TinSpaModule {
14076
- // Changed: Auto-initialize analytics route tracking when module loads
14077
- constructor(analytics) {
14078
- this.analytics = analytics;
14079
- analytics.init();
14080
- }
14081
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, deps: [{ token: AnalyticsService }], target: i0.ɵɵFactoryTarget.NgModule }); }
14200
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
14082
14201
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, declarations: [
14083
14202
  // Changed: Removed page components now declared in domain-specific modules (accounting, inventory, sales, purchasing, hr, payroll, manufacturing, loans, general, tenancy, workflow, overview)
14084
14203
  TinSpaComponent, TextComponent, TextMaskComponent, TextAreaComponent, TextSingleComponent, CheckComponent, DateComponent, DatetimeComponent, LabelComponent, SelectComponent,
@@ -14261,7 +14380,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
14261
14380
  AnalyticsService // Changed: GA4 route tracking for SPA page views
14262
14381
  ],
14263
14382
  }]
14264
- }], ctorParameters: () => [{ type: AnalyticsService }] });
14383
+ }] });
14265
14384
 
14266
14385
  class LoginComponent {
14267
14386
  constructor(httpService, storageService, router, messageService, dataService, authService, logService, route, notificationsService, signalRService, msalService, dialog) {