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.
- package/fesm2022/tin-spa.mjs +142 -23
- package/fesm2022/tin-spa.mjs.map +1 -1
- package/index.d.ts +13 -3
- package/package.json +1 -1
package/fesm2022/tin-spa.mjs
CHANGED
|
@@ -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: () =>
|
|
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' },
|
|
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() {
|
|
6597
|
-
|
|
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
|
-
|
|
6612
|
-
|
|
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
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
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
|
-
|
|
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
|
-
}]
|
|
14383
|
+
}] });
|
|
14265
14384
|
|
|
14266
14385
|
class LoginComponent {
|
|
14267
14386
|
constructor(httpService, storageService, router, messageService, dataService, authService, logService, route, notificationsService, signalRService, msalService, dialog) {
|