tin-spa 20.6.4 → 20.6.6
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 +173 -17
- package/fesm2022/tin-spa.mjs.map +1 -1
- package/index.d.ts +22 -1
- package/package.json +1 -1
package/fesm2022/tin-spa.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { mergeMap, filter, startWith, map, catchError, finalize as finalize$1, t
|
|
|
15
15
|
import * as i2$1 from '@angular/material/snack-bar';
|
|
16
16
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
|
17
17
|
import * as i1$2 from '@angular/router';
|
|
18
|
-
import { Router, RouterModule
|
|
18
|
+
import { NavigationEnd, Router, RouterModule } from '@angular/router';
|
|
19
19
|
import * as i2$2 from '@abacritt/angularx-social-login';
|
|
20
20
|
import { SocialLoginModule } from '@abacritt/angularx-social-login';
|
|
21
21
|
import * as i1$1 from '@angular/common/http';
|
|
@@ -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
|
}
|
|
@@ -6604,6 +6680,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
6604
6680
|
args: [{ providedIn: 'root' }]
|
|
6605
6681
|
}], ctorParameters: () => [{ type: DataServiceLib }, { type: SignalRService }] });
|
|
6606
6682
|
|
|
6683
|
+
class AnalyticsService {
|
|
6684
|
+
constructor(router) {
|
|
6685
|
+
this.router = router;
|
|
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
|
|
6694
|
+
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event) => {
|
|
6695
|
+
const url = event.urlAfterRedirects || event.url;
|
|
6696
|
+
// Strip hash prefix so GA sees clean paths (e.g. /home/invoices instead of #/home/invoices)
|
|
6697
|
+
const pagePath = url.replace(/^#/, '') || '/';
|
|
6698
|
+
this.sendPageView(pagePath);
|
|
6699
|
+
});
|
|
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
|
+
}
|
|
6712
|
+
// Send page_view event to GA4
|
|
6713
|
+
sendPageView(pagePath) {
|
|
6714
|
+
if (typeof gtag === 'function') {
|
|
6715
|
+
gtag('event', 'page_view', {
|
|
6716
|
+
page_path: pagePath,
|
|
6717
|
+
page_location: window.location.origin + pagePath
|
|
6718
|
+
});
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AnalyticsService, deps: [{ token: i1$2.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6722
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AnalyticsService, providedIn: 'root' }); }
|
|
6723
|
+
}
|
|
6724
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: AnalyticsService, decorators: [{
|
|
6725
|
+
type: Injectable,
|
|
6726
|
+
args: [{ providedIn: 'root' }]
|
|
6727
|
+
}], ctorParameters: () => [{ type: i1$2.Router }] });
|
|
6728
|
+
|
|
6607
6729
|
// Functional guard for Angular 15+ (replaces deprecated class-based CanActivate)
|
|
6608
6730
|
// Changed: Returns Promise<boolean> to properly await token refresh before allowing navigation
|
|
6609
6731
|
const authGuard = async (route, state) => {
|
|
@@ -9846,12 +9968,14 @@ class AgentComponent {
|
|
|
9846
9968
|
this.isOnline = false;
|
|
9847
9969
|
this.subs = [];
|
|
9848
9970
|
this.shouldScroll = false;
|
|
9971
|
+
this.viewportHandler = null; // Changed: visualViewport resize handler reference
|
|
9849
9972
|
} // Changed: was assistantService
|
|
9850
9973
|
get agentName() {
|
|
9851
9974
|
return this.agentService.agentName;
|
|
9852
9975
|
}
|
|
9853
9976
|
ngOnInit() {
|
|
9854
9977
|
this.agentService.loadConfig();
|
|
9978
|
+
this.initMobileViewport(); // Changed: Listen for keyboard resize on mobile
|
|
9855
9979
|
this.subs.push(this.agentService.messages$.subscribe(msgs => {
|
|
9856
9980
|
this.messages = msgs;
|
|
9857
9981
|
this.shouldScroll = true;
|
|
@@ -9873,6 +9997,15 @@ class AgentComponent {
|
|
|
9873
9997
|
toggleChat() {
|
|
9874
9998
|
this.agentService.toggle();
|
|
9875
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
|
+
}
|
|
9876
10009
|
sendMessage() {
|
|
9877
10010
|
if (!this.inputText.trim())
|
|
9878
10011
|
return;
|
|
@@ -9909,11 +10042,25 @@ class AgentComponent {
|
|
|
9909
10042
|
trackMessage(index, msg) {
|
|
9910
10043
|
return `${index}-${msg.role}-${msg.createdDate}`;
|
|
9911
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
|
+
}
|
|
9912
10056
|
ngOnDestroy() {
|
|
9913
10057
|
this.subs.forEach(s => s.unsubscribe());
|
|
10058
|
+
if (this.viewportHandler && window.visualViewport) {
|
|
10059
|
+
window.visualViewport.removeEventListener('resize', this.viewportHandler);
|
|
10060
|
+
}
|
|
9914
10061
|
}
|
|
9915
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 }); }
|
|
9916
|
-
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: [
|
|
9917
10064
|
trigger('slideUp', [
|
|
9918
10065
|
transition(':enter', [
|
|
9919
10066
|
style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }),
|
|
@@ -9937,13 +10084,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
9937
10084
|
animate('200ms cubic-bezier(0.4, 0, 0.2, 1)', style({ opacity: 0, transform: 'translateY(20px) scale(0.95)' }))
|
|
9938
10085
|
])
|
|
9939
10086
|
])
|
|
9940
|
-
], 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"] }]
|
|
9941
10088
|
}], ctorParameters: () => [{ type: AgentService }, { type: SignalRService }], propDecorators: { messageContainer: [{
|
|
9942
10089
|
type: ViewChild,
|
|
9943
10090
|
args: ['messageContainer']
|
|
9944
10091
|
}], messageInput: [{
|
|
9945
10092
|
type: ViewChild,
|
|
9946
10093
|
args: ['messageInput']
|
|
10094
|
+
}], onDocumentClick: [{
|
|
10095
|
+
type: HostListener,
|
|
10096
|
+
args: ['document:click', ['$event']]
|
|
9947
10097
|
}] } });
|
|
9948
10098
|
|
|
9949
10099
|
class NavMenuComponent {
|
|
@@ -10158,10 +10308,14 @@ class LoaderInterceptor {
|
|
|
10158
10308
|
});
|
|
10159
10309
|
}
|
|
10160
10310
|
intercept(request, next) {
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
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/')) {
|
|
10165
10319
|
return next.handle(requestClone).pipe(catchError((error) => throwError(() => error)));
|
|
10166
10320
|
}
|
|
10167
10321
|
this.requests.push(requestClone);
|
|
@@ -14124,7 +14278,8 @@ class TinSpaModule {
|
|
|
14124
14278
|
LoansService,
|
|
14125
14279
|
TabService, // Changed: Added TabService to providers
|
|
14126
14280
|
provideCharts(withDefaultRegisterables()), // Changed: Register Chart.js with all default chart types
|
|
14127
|
-
SubscriptionService // Added: Subscription feature-checking service
|
|
14281
|
+
SubscriptionService, // Added: Subscription feature-checking service
|
|
14282
|
+
AnalyticsService // Changed: GA4 route tracking for SPA page views
|
|
14128
14283
|
], imports: [SpaMatModule,
|
|
14129
14284
|
HttpClientModule,
|
|
14130
14285
|
CurrencyInputModule,
|
|
@@ -14221,7 +14376,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
14221
14376
|
LoansService,
|
|
14222
14377
|
TabService, // Changed: Added TabService to providers
|
|
14223
14378
|
provideCharts(withDefaultRegisterables()), // Changed: Register Chart.js with all default chart types
|
|
14224
|
-
SubscriptionService // Added: Subscription feature-checking service
|
|
14379
|
+
SubscriptionService, // Added: Subscription feature-checking service
|
|
14380
|
+
AnalyticsService // Changed: GA4 route tracking for SPA page views
|
|
14225
14381
|
],
|
|
14226
14382
|
}]
|
|
14227
14383
|
}] });
|
|
@@ -21938,5 +22094,5 @@ const ALSQUARE_SVG_WHITE = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://w
|
|
|
21938
22094
|
* Generated bundle index. Do not edit.
|
|
21939
22095
|
*/
|
|
21940
22096
|
|
|
21941
|
-
export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, AccountingModule, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AgentComponent, AgentService, AlertComponent, AlertConfig, AlertMessage, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AssetsService, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralModule, GeneralService, GradesComponent, GroupsComponent, HRModule, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryModule, InventoryService, InvitationsTableComponent, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansModule, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, ManufacturingModule, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, OverviewModule, PageComponent, PageConfig, PayrollDashboardComponent, PayrollModule, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchaseStatus, PurchasingDashboardComponent, PurchasingModule, PushNotificationService, ReceiptStatus, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SalesModule, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaHomeModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, TabService, TableComponent, TableConfig, TabsComponent, TasksComponent, TenancyModule, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, WorkflowModule, authGuard, dialogOptions, featureGuard, loginConfig, messageDialog, viewerDialog };
|
|
22097
|
+
export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, AccountingModule, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AgentComponent, AgentService, AlertComponent, AlertConfig, AlertMessage, AnalyticsService, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AssetsService, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralModule, GeneralService, GradesComponent, GroupsComponent, HRModule, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryModule, InventoryService, InvitationsTableComponent, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansModule, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, ManufacturingModule, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, OverviewModule, PageComponent, PageConfig, PayrollDashboardComponent, PayrollModule, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchaseStatus, PurchasingDashboardComponent, PurchasingModule, PushNotificationService, ReceiptStatus, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SalesModule, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaHomeModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, TabService, TableComponent, TableConfig, TabsComponent, TasksComponent, TenancyModule, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, WorkflowModule, authGuard, dialogOptions, featureGuard, loginConfig, messageDialog, viewerDialog };
|
|
21942
22098
|
//# sourceMappingURL=tin-spa.mjs.map
|