@syncagent/angular
Advanced tools
+155
-3
| import * as rxjs from 'rxjs'; | ||
| import * as _angular_core from '@angular/core'; | ||
| import { Message, ToolData, SyncAgentConfig } from '@syncagent/js'; | ||
| export { ChatOptions, ChatResult, Message, SyncAgentClient, SyncAgentConfig, ToolData, ToolDefinition, ToolParameter, detectPageContext } from '@syncagent/js'; | ||
| import { Message, ToolData, SyncAgentConfig, GuestIdentity } from '@syncagent/js'; | ||
| export { ChatOptions, ChatResult, CustomerChatOptions, CustomerChatResult, FieldValidationResult, GuestFormConfig, GuestIdentificationRequiredError, GuestIdentity, Message, SyncAgentClient, SyncAgentConfig, ToolData, ToolDefinition, ToolParameter, detectPageContext, generateGuestIdentifier, validateEmail, validateGuestForm, validateName } from '@syncagent/js'; | ||
@@ -70,2 +70,154 @@ interface SyncAgentServiceConfig extends SyncAgentConfig { | ||
| export { SyncAgentService, type SyncAgentServiceConfig }; | ||
| interface CustomerChatServiceConfig extends SyncAgentConfig { | ||
| /** Customer identifier — bypasses guest flow when provided */ | ||
| externalUserId?: string; | ||
| /** Called when a guest completes identification */ | ||
| onGuestIdentified?: (identity: GuestIdentity) => void; | ||
| /** Called when the conversation is escalated to a human agent */ | ||
| onEscalated?: () => void; | ||
| /** Called when the conversation is resolved */ | ||
| onResolved?: (conversationId: string) => void; | ||
| } | ||
| /** | ||
| * Angular service for SyncAgent customer chat mode. | ||
| * Provide at component or module level. | ||
| * | ||
| * @example | ||
| * @Component({ | ||
| * providers: [CustomerChatService], | ||
| * }) | ||
| * export class SupportWidgetComponent { | ||
| * constructor(private chat: CustomerChatService) { | ||
| * chat.configure({ | ||
| * apiKey: environment.syncagentKey, | ||
| * connectionString: environment.databaseUrl, | ||
| * customerMode: true, | ||
| * externalUserId: currentUser.id, | ||
| * }); | ||
| * } | ||
| * } | ||
| */ | ||
| declare class CustomerChatService { | ||
| private client; | ||
| private _onEscalated?; | ||
| private _onResolved?; | ||
| private _onGuestIdentified?; | ||
| readonly isConfigured: _angular_core.WritableSignal<boolean>; | ||
| readonly messages: _angular_core.WritableSignal<Message[]>; | ||
| readonly conversationId: _angular_core.WritableSignal<string | null>; | ||
| readonly isLoading: _angular_core.WritableSignal<boolean>; | ||
| readonly isEscalated: _angular_core.WritableSignal<boolean>; | ||
| readonly isResolved: _angular_core.WritableSignal<boolean>; | ||
| readonly error: _angular_core.WritableSignal<Error | null>; | ||
| readonly welcomeMessage: _angular_core.WritableSignal<string | null>; | ||
| readonly isIdentified: _angular_core.WritableSignal<boolean>; | ||
| readonly guestIdentity: _angular_core.WritableSignal<GuestIdentity | null>; | ||
| private _messages$; | ||
| private _isLoading$; | ||
| private _error$; | ||
| private _escalated$; | ||
| private _resolved$; | ||
| private _isIdentified$; | ||
| private _guestIdentified$; | ||
| readonly messages$: rxjs.Observable<Message[]>; | ||
| readonly isLoading$: rxjs.Observable<boolean>; | ||
| readonly error$: rxjs.Observable<Error | null>; | ||
| /** Emits when the conversation is escalated to a human agent */ | ||
| readonly escalated$: rxjs.Observable<void>; | ||
| /** Emits the conversation ID when the conversation is resolved */ | ||
| readonly resolved$: rxjs.Observable<string>; | ||
| /** Emits the current identification state */ | ||
| readonly isIdentified$: rxjs.Observable<boolean>; | ||
| /** Emits the GuestIdentity when guest identification completes */ | ||
| readonly guestIdentified$: rxjs.Observable<GuestIdentity>; | ||
| configure(config: CustomerChatServiceConfig): void; | ||
| sendMessage(content: string, metadata?: Record<string, any>): Promise<void>; | ||
| rateConversation(rating: number): Promise<void>; | ||
| reset(): void; | ||
| /** | ||
| * Submit guest identification data. | ||
| * Validates, generates identifier, persists, and updates state. | ||
| */ | ||
| identifyGuest(data: { | ||
| name: string; | ||
| email: string; | ||
| phone?: string; | ||
| }): void; | ||
| private _setIdentified; | ||
| private _setMessages; | ||
| private _setLoading; | ||
| } | ||
| interface DualChatServiceConfig extends SyncAgentConfig { | ||
| context?: Record<string, any>; | ||
| onData?: (data: ToolData) => void; | ||
| onEscalated?: () => void; | ||
| onResolved?: (conversationId: string) => void; | ||
| } | ||
| /** | ||
| * Angular service for dual-mode SyncAgent chat (database + customer support). | ||
| * Provides both database agent and customer agent functionality through a single injectable. | ||
| * | ||
| * @example | ||
| * @Component({ | ||
| * providers: [DualChatService], | ||
| * }) | ||
| * export class DashboardComponent { | ||
| * constructor(private dual: DualChatService) { | ||
| * dual.configure({ | ||
| * apiKey: environment.syncagentKey, | ||
| * connectionString: environment.databaseUrl, | ||
| * externalUserId: currentUser.id, | ||
| * }); | ||
| * } | ||
| * | ||
| * askDb(question: string) { | ||
| * this.dual.sendDbMessage(question); | ||
| * } | ||
| * | ||
| * askSupport(message: string) { | ||
| * this.dual.sendSupportMessage(message); | ||
| * } | ||
| * } | ||
| */ | ||
| declare class DualChatService { | ||
| private client; | ||
| private config; | ||
| private abortController; | ||
| private _context?; | ||
| private _onData?; | ||
| private _onEscalated?; | ||
| private _onResolved?; | ||
| readonly dbMessages: _angular_core.WritableSignal<Message[]>; | ||
| readonly dbIsLoading: _angular_core.WritableSignal<boolean>; | ||
| readonly dbError: _angular_core.WritableSignal<Error | null>; | ||
| readonly supportMessages: _angular_core.WritableSignal<Message[]>; | ||
| readonly supportIsLoading: _angular_core.WritableSignal<boolean>; | ||
| readonly supportError: _angular_core.WritableSignal<Error | null>; | ||
| readonly supportConversationId: _angular_core.WritableSignal<string | null>; | ||
| readonly supportIsEscalated: _angular_core.WritableSignal<boolean>; | ||
| readonly supportIsResolved: _angular_core.WritableSignal<boolean>; | ||
| private _dbMessages$; | ||
| private _dbIsLoading$; | ||
| private _dbError$; | ||
| readonly dbMessages$: rxjs.Observable<Message[]>; | ||
| readonly dbIsLoading$: rxjs.Observable<boolean>; | ||
| readonly dbError$: rxjs.Observable<Error | null>; | ||
| private _supportMessages$; | ||
| private _supportIsLoading$; | ||
| private _supportError$; | ||
| readonly supportMessages$: rxjs.Observable<Message[]>; | ||
| readonly supportIsLoading$: rxjs.Observable<boolean>; | ||
| readonly supportError$: rxjs.Observable<Error | null>; | ||
| configure(config: DualChatServiceConfig): void; | ||
| sendDbMessage(content: string): Promise<void>; | ||
| sendSupportMessage(content: string, metadata?: Record<string, any>): Promise<void>; | ||
| stop(): void; | ||
| reset(): void; | ||
| private _setDbMessages; | ||
| private _setDbLoading; | ||
| private _setSupportMessages; | ||
| private _setSupportLoading; | ||
| } | ||
| export { CustomerChatService, type CustomerChatServiceConfig, DualChatService, type DualChatServiceConfig, SyncAgentService, type SyncAgentServiceConfig }; |
+155
-3
| import * as rxjs from 'rxjs'; | ||
| import * as _angular_core from '@angular/core'; | ||
| import { Message, ToolData, SyncAgentConfig } from '@syncagent/js'; | ||
| export { ChatOptions, ChatResult, Message, SyncAgentClient, SyncAgentConfig, ToolData, ToolDefinition, ToolParameter, detectPageContext } from '@syncagent/js'; | ||
| import { Message, ToolData, SyncAgentConfig, GuestIdentity } from '@syncagent/js'; | ||
| export { ChatOptions, ChatResult, CustomerChatOptions, CustomerChatResult, FieldValidationResult, GuestFormConfig, GuestIdentificationRequiredError, GuestIdentity, Message, SyncAgentClient, SyncAgentConfig, ToolData, ToolDefinition, ToolParameter, detectPageContext, generateGuestIdentifier, validateEmail, validateGuestForm, validateName } from '@syncagent/js'; | ||
@@ -70,2 +70,154 @@ interface SyncAgentServiceConfig extends SyncAgentConfig { | ||
| export { SyncAgentService, type SyncAgentServiceConfig }; | ||
| interface CustomerChatServiceConfig extends SyncAgentConfig { | ||
| /** Customer identifier — bypasses guest flow when provided */ | ||
| externalUserId?: string; | ||
| /** Called when a guest completes identification */ | ||
| onGuestIdentified?: (identity: GuestIdentity) => void; | ||
| /** Called when the conversation is escalated to a human agent */ | ||
| onEscalated?: () => void; | ||
| /** Called when the conversation is resolved */ | ||
| onResolved?: (conversationId: string) => void; | ||
| } | ||
| /** | ||
| * Angular service for SyncAgent customer chat mode. | ||
| * Provide at component or module level. | ||
| * | ||
| * @example | ||
| * @Component({ | ||
| * providers: [CustomerChatService], | ||
| * }) | ||
| * export class SupportWidgetComponent { | ||
| * constructor(private chat: CustomerChatService) { | ||
| * chat.configure({ | ||
| * apiKey: environment.syncagentKey, | ||
| * connectionString: environment.databaseUrl, | ||
| * customerMode: true, | ||
| * externalUserId: currentUser.id, | ||
| * }); | ||
| * } | ||
| * } | ||
| */ | ||
| declare class CustomerChatService { | ||
| private client; | ||
| private _onEscalated?; | ||
| private _onResolved?; | ||
| private _onGuestIdentified?; | ||
| readonly isConfigured: _angular_core.WritableSignal<boolean>; | ||
| readonly messages: _angular_core.WritableSignal<Message[]>; | ||
| readonly conversationId: _angular_core.WritableSignal<string | null>; | ||
| readonly isLoading: _angular_core.WritableSignal<boolean>; | ||
| readonly isEscalated: _angular_core.WritableSignal<boolean>; | ||
| readonly isResolved: _angular_core.WritableSignal<boolean>; | ||
| readonly error: _angular_core.WritableSignal<Error | null>; | ||
| readonly welcomeMessage: _angular_core.WritableSignal<string | null>; | ||
| readonly isIdentified: _angular_core.WritableSignal<boolean>; | ||
| readonly guestIdentity: _angular_core.WritableSignal<GuestIdentity | null>; | ||
| private _messages$; | ||
| private _isLoading$; | ||
| private _error$; | ||
| private _escalated$; | ||
| private _resolved$; | ||
| private _isIdentified$; | ||
| private _guestIdentified$; | ||
| readonly messages$: rxjs.Observable<Message[]>; | ||
| readonly isLoading$: rxjs.Observable<boolean>; | ||
| readonly error$: rxjs.Observable<Error | null>; | ||
| /** Emits when the conversation is escalated to a human agent */ | ||
| readonly escalated$: rxjs.Observable<void>; | ||
| /** Emits the conversation ID when the conversation is resolved */ | ||
| readonly resolved$: rxjs.Observable<string>; | ||
| /** Emits the current identification state */ | ||
| readonly isIdentified$: rxjs.Observable<boolean>; | ||
| /** Emits the GuestIdentity when guest identification completes */ | ||
| readonly guestIdentified$: rxjs.Observable<GuestIdentity>; | ||
| configure(config: CustomerChatServiceConfig): void; | ||
| sendMessage(content: string, metadata?: Record<string, any>): Promise<void>; | ||
| rateConversation(rating: number): Promise<void>; | ||
| reset(): void; | ||
| /** | ||
| * Submit guest identification data. | ||
| * Validates, generates identifier, persists, and updates state. | ||
| */ | ||
| identifyGuest(data: { | ||
| name: string; | ||
| email: string; | ||
| phone?: string; | ||
| }): void; | ||
| private _setIdentified; | ||
| private _setMessages; | ||
| private _setLoading; | ||
| } | ||
| interface DualChatServiceConfig extends SyncAgentConfig { | ||
| context?: Record<string, any>; | ||
| onData?: (data: ToolData) => void; | ||
| onEscalated?: () => void; | ||
| onResolved?: (conversationId: string) => void; | ||
| } | ||
| /** | ||
| * Angular service for dual-mode SyncAgent chat (database + customer support). | ||
| * Provides both database agent and customer agent functionality through a single injectable. | ||
| * | ||
| * @example | ||
| * @Component({ | ||
| * providers: [DualChatService], | ||
| * }) | ||
| * export class DashboardComponent { | ||
| * constructor(private dual: DualChatService) { | ||
| * dual.configure({ | ||
| * apiKey: environment.syncagentKey, | ||
| * connectionString: environment.databaseUrl, | ||
| * externalUserId: currentUser.id, | ||
| * }); | ||
| * } | ||
| * | ||
| * askDb(question: string) { | ||
| * this.dual.sendDbMessage(question); | ||
| * } | ||
| * | ||
| * askSupport(message: string) { | ||
| * this.dual.sendSupportMessage(message); | ||
| * } | ||
| * } | ||
| */ | ||
| declare class DualChatService { | ||
| private client; | ||
| private config; | ||
| private abortController; | ||
| private _context?; | ||
| private _onData?; | ||
| private _onEscalated?; | ||
| private _onResolved?; | ||
| readonly dbMessages: _angular_core.WritableSignal<Message[]>; | ||
| readonly dbIsLoading: _angular_core.WritableSignal<boolean>; | ||
| readonly dbError: _angular_core.WritableSignal<Error | null>; | ||
| readonly supportMessages: _angular_core.WritableSignal<Message[]>; | ||
| readonly supportIsLoading: _angular_core.WritableSignal<boolean>; | ||
| readonly supportError: _angular_core.WritableSignal<Error | null>; | ||
| readonly supportConversationId: _angular_core.WritableSignal<string | null>; | ||
| readonly supportIsEscalated: _angular_core.WritableSignal<boolean>; | ||
| readonly supportIsResolved: _angular_core.WritableSignal<boolean>; | ||
| private _dbMessages$; | ||
| private _dbIsLoading$; | ||
| private _dbError$; | ||
| readonly dbMessages$: rxjs.Observable<Message[]>; | ||
| readonly dbIsLoading$: rxjs.Observable<boolean>; | ||
| readonly dbError$: rxjs.Observable<Error | null>; | ||
| private _supportMessages$; | ||
| private _supportIsLoading$; | ||
| private _supportError$; | ||
| readonly supportMessages$: rxjs.Observable<Message[]>; | ||
| readonly supportIsLoading$: rxjs.Observable<boolean>; | ||
| readonly supportError$: rxjs.Observable<Error | null>; | ||
| configure(config: DualChatServiceConfig): void; | ||
| sendDbMessage(content: string): Promise<void>; | ||
| sendSupportMessage(content: string, metadata?: Record<string, any>): Promise<void>; | ||
| stop(): void; | ||
| reset(): void; | ||
| private _setDbMessages; | ||
| private _setDbLoading; | ||
| private _setSupportMessages; | ||
| private _setSupportLoading; | ||
| } | ||
| export { CustomerChatService, type CustomerChatServiceConfig, DualChatService, type DualChatServiceConfig, SyncAgentService, type SyncAgentServiceConfig }; |
+347
-4
@@ -31,5 +31,12 @@ "use strict"; | ||
| __export(index_exports, { | ||
| SyncAgentClient: () => import_js2.SyncAgentClient, | ||
| CustomerChatService: () => CustomerChatService, | ||
| DualChatService: () => DualChatService, | ||
| GuestIdentificationRequiredError: () => import_js5.GuestIdentificationRequiredError, | ||
| SyncAgentClient: () => import_js4.SyncAgentClient, | ||
| SyncAgentService: () => SyncAgentService, | ||
| detectPageContext: () => import_js2.detectPageContext | ||
| detectPageContext: () => import_js4.detectPageContext, | ||
| generateGuestIdentifier: () => import_js5.generateGuestIdentifier, | ||
| validateEmail: () => import_js5.validateEmail, | ||
| validateGuestForm: () => import_js5.validateGuestForm, | ||
| validateName: () => import_js5.validateName | ||
| }); | ||
@@ -162,9 +169,345 @@ module.exports = __toCommonJS(index_exports); | ||
| // src/customer-chat.service.ts | ||
| var import_core2 = require("@angular/core"); | ||
| var import_rxjs2 = require("rxjs"); | ||
| var import_js2 = require("@syncagent/js"); | ||
| var CustomerChatService = class { | ||
| constructor() { | ||
| this.client = null; | ||
| // Reactive state using Angular signals | ||
| this.isConfigured = (0, import_core2.signal)(false); | ||
| this.messages = (0, import_core2.signal)([]); | ||
| this.conversationId = (0, import_core2.signal)(null); | ||
| this.isLoading = (0, import_core2.signal)(false); | ||
| this.isEscalated = (0, import_core2.signal)(false); | ||
| this.isResolved = (0, import_core2.signal)(false); | ||
| this.error = (0, import_core2.signal)(null); | ||
| this.welcomeMessage = (0, import_core2.signal)(null); | ||
| // Guest identification state | ||
| this.isIdentified = (0, import_core2.signal)(false); | ||
| this.guestIdentity = (0, import_core2.signal)(null); | ||
| // RxJS observables | ||
| this._messages$ = new import_rxjs2.BehaviorSubject([]); | ||
| this._isLoading$ = new import_rxjs2.BehaviorSubject(false); | ||
| this._error$ = new import_rxjs2.BehaviorSubject(null); | ||
| this._escalated$ = new import_rxjs2.Subject(); | ||
| this._resolved$ = new import_rxjs2.Subject(); | ||
| this._isIdentified$ = new import_rxjs2.BehaviorSubject(false); | ||
| this._guestIdentified$ = new import_rxjs2.Subject(); | ||
| this.messages$ = this._messages$.asObservable(); | ||
| this.isLoading$ = this._isLoading$.asObservable(); | ||
| this.error$ = this._error$.asObservable(); | ||
| /** Emits when the conversation is escalated to a human agent */ | ||
| this.escalated$ = this._escalated$.asObservable(); | ||
| /** Emits the conversation ID when the conversation is resolved */ | ||
| this.resolved$ = this._resolved$.asObservable(); | ||
| /** Emits the current identification state */ | ||
| this.isIdentified$ = this._isIdentified$.asObservable(); | ||
| /** Emits the GuestIdentity when guest identification completes */ | ||
| this.guestIdentified$ = this._guestIdentified$.asObservable(); | ||
| } | ||
| configure(config) { | ||
| this.client = new import_js2.SyncAgentClient(config); | ||
| this._onEscalated = config.onEscalated; | ||
| this._onResolved = config.onResolved; | ||
| this._onGuestIdentified = config.onGuestIdentified; | ||
| this.isConfigured.set(true); | ||
| if (config.externalUserId) { | ||
| this._setIdentified(true, null); | ||
| } else { | ||
| const stored = this.client.getGuestIdentity(); | ||
| if (stored) { | ||
| this._setIdentified(true, stored); | ||
| } | ||
| } | ||
| } | ||
| async sendMessage(content, metadata) { | ||
| if (!this.client) throw new Error("CustomerChatService: call configure() first"); | ||
| if (!this.isIdentified()) { | ||
| const err = new import_js2.GuestIdentificationRequiredError(); | ||
| this.error.set(err); | ||
| this._error$.next(err); | ||
| return; | ||
| } | ||
| if (!content.trim() || this.isLoading()) return; | ||
| const userMsg = { role: "user", content }; | ||
| this._setMessages([...this.messages(), userMsg]); | ||
| this._setLoading(true); | ||
| this.error.set(null); | ||
| this._error$.next(null); | ||
| const chatOptions = { | ||
| conversationId: this.conversationId() || void 0, | ||
| metadata, | ||
| onEscalated: () => { | ||
| this.isEscalated.set(true); | ||
| this._escalated$.next(); | ||
| this._onEscalated?.(); | ||
| }, | ||
| onResolved: (id) => { | ||
| this.isResolved.set(true); | ||
| this._resolved$.next(id); | ||
| this._onResolved?.(id); | ||
| } | ||
| }; | ||
| try { | ||
| const result = await this.client.customerChat(content, chatOptions); | ||
| this.conversationId.set(result.conversationId); | ||
| if (result.welcomeMessage) { | ||
| this.welcomeMessage.set(result.welcomeMessage); | ||
| } | ||
| if (result.response) { | ||
| const assistantMsg = { role: "assistant", content: result.response }; | ||
| this._setMessages([...this.messages(), assistantMsg]); | ||
| } | ||
| } catch (e) { | ||
| const err = e instanceof Error ? e : new Error(String(e)); | ||
| this.error.set(err); | ||
| this._error$.next(err); | ||
| } finally { | ||
| this._setLoading(false); | ||
| } | ||
| } | ||
| async rateConversation(rating) { | ||
| if (!this.client) throw new Error("CustomerChatService: call configure() first"); | ||
| const id = this.conversationId(); | ||
| if (!id) throw new Error("No active conversation to rate"); | ||
| await this.client.rateConversation(id, rating); | ||
| } | ||
| reset() { | ||
| this._setMessages([]); | ||
| this.conversationId.set(null); | ||
| this._setLoading(false); | ||
| this.isEscalated.set(false); | ||
| this.isResolved.set(false); | ||
| this.error.set(null); | ||
| this._error$.next(null); | ||
| this.welcomeMessage.set(null); | ||
| } | ||
| /** | ||
| * Submit guest identification data. | ||
| * Validates, generates identifier, persists, and updates state. | ||
| */ | ||
| identifyGuest(data) { | ||
| const validation = (0, import_js2.validateGuestForm)(data); | ||
| if (!validation.valid) { | ||
| const err = new Error(Object.values(validation.errors).join(", ")); | ||
| this.error.set(err); | ||
| this._error$.next(err); | ||
| return; | ||
| } | ||
| const guestId = (0, import_js2.generateGuestIdentifier)(data.email); | ||
| const identity = { | ||
| name: data.name, | ||
| email: data.email, | ||
| phone: data.phone || null, | ||
| guestId | ||
| }; | ||
| this.client.setGuestIdentity(identity); | ||
| this._setIdentified(true, identity); | ||
| this._guestIdentified$.next(identity); | ||
| this.error.set(null); | ||
| this._error$.next(null); | ||
| this._onGuestIdentified?.(identity); | ||
| } | ||
| _setIdentified(value, identity) { | ||
| this.isIdentified.set(value); | ||
| this._isIdentified$.next(value); | ||
| this.guestIdentity.set(identity); | ||
| } | ||
| _setMessages(msgs) { | ||
| this.messages.set(msgs); | ||
| this._messages$.next(msgs); | ||
| } | ||
| _setLoading(v) { | ||
| this.isLoading.set(v); | ||
| this._isLoading$.next(v); | ||
| } | ||
| }; | ||
| CustomerChatService = __decorateClass([ | ||
| (0, import_core2.Injectable)() | ||
| ], CustomerChatService); | ||
| // src/dual-chat.service.ts | ||
| var import_core3 = require("@angular/core"); | ||
| var import_rxjs3 = require("rxjs"); | ||
| var import_js3 = require("@syncagent/js"); | ||
| var DualChatService = class { | ||
| constructor() { | ||
| this.client = null; | ||
| this.config = null; | ||
| this.abortController = null; | ||
| // DB mode signals | ||
| this.dbMessages = (0, import_core3.signal)([]); | ||
| this.dbIsLoading = (0, import_core3.signal)(false); | ||
| this.dbError = (0, import_core3.signal)(null); | ||
| // Support mode signals | ||
| this.supportMessages = (0, import_core3.signal)([]); | ||
| this.supportIsLoading = (0, import_core3.signal)(false); | ||
| this.supportError = (0, import_core3.signal)(null); | ||
| this.supportConversationId = (0, import_core3.signal)(null); | ||
| this.supportIsEscalated = (0, import_core3.signal)(false); | ||
| this.supportIsResolved = (0, import_core3.signal)(false); | ||
| // DB mode RxJS observables | ||
| this._dbMessages$ = new import_rxjs3.BehaviorSubject([]); | ||
| this._dbIsLoading$ = new import_rxjs3.BehaviorSubject(false); | ||
| this._dbError$ = new import_rxjs3.BehaviorSubject(null); | ||
| this.dbMessages$ = this._dbMessages$.asObservable(); | ||
| this.dbIsLoading$ = this._dbIsLoading$.asObservable(); | ||
| this.dbError$ = this._dbError$.asObservable(); | ||
| // Support mode RxJS observables | ||
| this._supportMessages$ = new import_rxjs3.BehaviorSubject([]); | ||
| this._supportIsLoading$ = new import_rxjs3.BehaviorSubject(false); | ||
| this._supportError$ = new import_rxjs3.BehaviorSubject(null); | ||
| this.supportMessages$ = this._supportMessages$.asObservable(); | ||
| this.supportIsLoading$ = this._supportIsLoading$.asObservable(); | ||
| this.supportError$ = this._supportError$.asObservable(); | ||
| } | ||
| configure(config) { | ||
| this.client = new import_js3.SyncAgentClient(config); | ||
| this.config = config; | ||
| this._context = config.context; | ||
| this._onData = config.onData; | ||
| this._onEscalated = config.onEscalated; | ||
| this._onResolved = config.onResolved; | ||
| } | ||
| async sendDbMessage(content) { | ||
| if (!this.client) throw new Error("DualChatService: call configure() first"); | ||
| if (!content.trim() || this.dbIsLoading()) return; | ||
| const userMsg = { role: "user", content }; | ||
| const updated = [...this.dbMessages(), userMsg]; | ||
| this._setDbMessages(updated); | ||
| this._setDbLoading(true); | ||
| this.dbError.set(null); | ||
| this._dbError$.next(null); | ||
| const placeholder = { role: "assistant", content: "" }; | ||
| this._setDbMessages([...updated, placeholder]); | ||
| this.abortController = new AbortController(); | ||
| try { | ||
| await this.client.chat(updated, { | ||
| signal: this.abortController.signal, | ||
| context: this._context, | ||
| onData: (data) => { | ||
| this._onData?.(data); | ||
| }, | ||
| onToken: (token) => { | ||
| placeholder.content += token; | ||
| const msgs = [...this.dbMessages()]; | ||
| msgs[msgs.length - 1] = { ...placeholder }; | ||
| this._setDbMessages(msgs); | ||
| }, | ||
| onComplete: (text) => { | ||
| const msgs = [...this.dbMessages()]; | ||
| const finalMsg = { role: "assistant", content: text }; | ||
| msgs[msgs.length - 1] = finalMsg; | ||
| this._setDbMessages(msgs); | ||
| } | ||
| }); | ||
| } catch (e) { | ||
| if (e.name !== "AbortError") { | ||
| const err = e instanceof Error ? e : new Error(String(e)); | ||
| this.dbError.set(err); | ||
| this._dbError$.next(err); | ||
| const msgs = [...this.dbMessages()]; | ||
| if (msgs[msgs.length - 1]?.content === "") { | ||
| this._setDbMessages(msgs.slice(0, -1)); | ||
| } | ||
| } | ||
| } finally { | ||
| this._setDbLoading(false); | ||
| this.abortController = null; | ||
| } | ||
| } | ||
| async sendSupportMessage(content, metadata) { | ||
| if (!this.client) throw new Error("DualChatService: call configure() first"); | ||
| if (!this.config?.externalUserId) { | ||
| throw new Error("DualChatService: externalUserId is required for customer chat"); | ||
| } | ||
| if (!content.trim() || this.supportIsLoading()) return; | ||
| const userMsg = { role: "user", content }; | ||
| this._setSupportMessages([...this.supportMessages(), userMsg]); | ||
| this._setSupportLoading(true); | ||
| this.supportError.set(null); | ||
| this._supportError$.next(null); | ||
| const chatOptions = { | ||
| conversationId: this.supportConversationId() || void 0, | ||
| metadata, | ||
| onEscalated: () => { | ||
| this.supportIsEscalated.set(true); | ||
| this._onEscalated?.(); | ||
| }, | ||
| onResolved: (id) => { | ||
| this.supportIsResolved.set(true); | ||
| this._onResolved?.(id); | ||
| } | ||
| }; | ||
| try { | ||
| const result = await this.client.customerChat(content, chatOptions); | ||
| this.supportConversationId.set(result.conversationId); | ||
| if (result.response) { | ||
| const assistantMsg = { role: "assistant", content: result.response }; | ||
| this._setSupportMessages([...this.supportMessages(), assistantMsg]); | ||
| } | ||
| } catch (e) { | ||
| const err = e instanceof Error ? e : new Error(String(e)); | ||
| this.supportError.set(err); | ||
| this._supportError$.next(err); | ||
| } finally { | ||
| this._setSupportLoading(false); | ||
| } | ||
| } | ||
| stop() { | ||
| if (!this.client) throw new Error("DualChatService: call configure() first"); | ||
| this.abortController?.abort(); | ||
| } | ||
| reset() { | ||
| if (!this.client) throw new Error("DualChatService: call configure() first"); | ||
| this.abortController?.abort(); | ||
| this._setDbMessages([]); | ||
| this.dbError.set(null); | ||
| this._dbError$.next(null); | ||
| this._setDbLoading(false); | ||
| this._setSupportMessages([]); | ||
| this.supportError.set(null); | ||
| this._supportError$.next(null); | ||
| this._setSupportLoading(false); | ||
| this.supportConversationId.set(null); | ||
| this.supportIsEscalated.set(false); | ||
| this.supportIsResolved.set(false); | ||
| } | ||
| _setDbMessages(msgs) { | ||
| this.dbMessages.set(msgs); | ||
| this._dbMessages$.next(msgs); | ||
| } | ||
| _setDbLoading(v) { | ||
| this.dbIsLoading.set(v); | ||
| this._dbIsLoading$.next(v); | ||
| } | ||
| _setSupportMessages(msgs) { | ||
| this.supportMessages.set(msgs); | ||
| this._supportMessages$.next(msgs); | ||
| } | ||
| _setSupportLoading(v) { | ||
| this.supportIsLoading.set(v); | ||
| this._supportIsLoading$.next(v); | ||
| } | ||
| }; | ||
| DualChatService = __decorateClass([ | ||
| (0, import_core3.Injectable)() | ||
| ], DualChatService); | ||
| // src/index.ts | ||
| var import_js2 = require("@syncagent/js"); | ||
| var import_js4 = require("@syncagent/js"); | ||
| var import_js5 = require("@syncagent/js"); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| CustomerChatService, | ||
| DualChatService, | ||
| GuestIdentificationRequiredError, | ||
| SyncAgentClient, | ||
| SyncAgentService, | ||
| detectPageContext | ||
| detectPageContext, | ||
| generateGuestIdentifier, | ||
| validateEmail, | ||
| validateGuestForm, | ||
| validateName | ||
| }); |
+346
-3
@@ -136,8 +136,351 @@ var __defProp = Object.defineProperty; | ||
| // src/customer-chat.service.ts | ||
| import { Injectable as Injectable2, signal as signal2 } from "@angular/core"; | ||
| import { BehaviorSubject as BehaviorSubject2, Subject as Subject2 } from "rxjs"; | ||
| import { | ||
| SyncAgentClient as SyncAgentClient2, | ||
| validateGuestForm, | ||
| generateGuestIdentifier, | ||
| GuestIdentificationRequiredError | ||
| } from "@syncagent/js"; | ||
| var CustomerChatService = class { | ||
| constructor() { | ||
| this.client = null; | ||
| // Reactive state using Angular signals | ||
| this.isConfigured = signal2(false); | ||
| this.messages = signal2([]); | ||
| this.conversationId = signal2(null); | ||
| this.isLoading = signal2(false); | ||
| this.isEscalated = signal2(false); | ||
| this.isResolved = signal2(false); | ||
| this.error = signal2(null); | ||
| this.welcomeMessage = signal2(null); | ||
| // Guest identification state | ||
| this.isIdentified = signal2(false); | ||
| this.guestIdentity = signal2(null); | ||
| // RxJS observables | ||
| this._messages$ = new BehaviorSubject2([]); | ||
| this._isLoading$ = new BehaviorSubject2(false); | ||
| this._error$ = new BehaviorSubject2(null); | ||
| this._escalated$ = new Subject2(); | ||
| this._resolved$ = new Subject2(); | ||
| this._isIdentified$ = new BehaviorSubject2(false); | ||
| this._guestIdentified$ = new Subject2(); | ||
| this.messages$ = this._messages$.asObservable(); | ||
| this.isLoading$ = this._isLoading$.asObservable(); | ||
| this.error$ = this._error$.asObservable(); | ||
| /** Emits when the conversation is escalated to a human agent */ | ||
| this.escalated$ = this._escalated$.asObservable(); | ||
| /** Emits the conversation ID when the conversation is resolved */ | ||
| this.resolved$ = this._resolved$.asObservable(); | ||
| /** Emits the current identification state */ | ||
| this.isIdentified$ = this._isIdentified$.asObservable(); | ||
| /** Emits the GuestIdentity when guest identification completes */ | ||
| this.guestIdentified$ = this._guestIdentified$.asObservable(); | ||
| } | ||
| configure(config) { | ||
| this.client = new SyncAgentClient2(config); | ||
| this._onEscalated = config.onEscalated; | ||
| this._onResolved = config.onResolved; | ||
| this._onGuestIdentified = config.onGuestIdentified; | ||
| this.isConfigured.set(true); | ||
| if (config.externalUserId) { | ||
| this._setIdentified(true, null); | ||
| } else { | ||
| const stored = this.client.getGuestIdentity(); | ||
| if (stored) { | ||
| this._setIdentified(true, stored); | ||
| } | ||
| } | ||
| } | ||
| async sendMessage(content, metadata) { | ||
| if (!this.client) throw new Error("CustomerChatService: call configure() first"); | ||
| if (!this.isIdentified()) { | ||
| const err = new GuestIdentificationRequiredError(); | ||
| this.error.set(err); | ||
| this._error$.next(err); | ||
| return; | ||
| } | ||
| if (!content.trim() || this.isLoading()) return; | ||
| const userMsg = { role: "user", content }; | ||
| this._setMessages([...this.messages(), userMsg]); | ||
| this._setLoading(true); | ||
| this.error.set(null); | ||
| this._error$.next(null); | ||
| const chatOptions = { | ||
| conversationId: this.conversationId() || void 0, | ||
| metadata, | ||
| onEscalated: () => { | ||
| this.isEscalated.set(true); | ||
| this._escalated$.next(); | ||
| this._onEscalated?.(); | ||
| }, | ||
| onResolved: (id) => { | ||
| this.isResolved.set(true); | ||
| this._resolved$.next(id); | ||
| this._onResolved?.(id); | ||
| } | ||
| }; | ||
| try { | ||
| const result = await this.client.customerChat(content, chatOptions); | ||
| this.conversationId.set(result.conversationId); | ||
| if (result.welcomeMessage) { | ||
| this.welcomeMessage.set(result.welcomeMessage); | ||
| } | ||
| if (result.response) { | ||
| const assistantMsg = { role: "assistant", content: result.response }; | ||
| this._setMessages([...this.messages(), assistantMsg]); | ||
| } | ||
| } catch (e) { | ||
| const err = e instanceof Error ? e : new Error(String(e)); | ||
| this.error.set(err); | ||
| this._error$.next(err); | ||
| } finally { | ||
| this._setLoading(false); | ||
| } | ||
| } | ||
| async rateConversation(rating) { | ||
| if (!this.client) throw new Error("CustomerChatService: call configure() first"); | ||
| const id = this.conversationId(); | ||
| if (!id) throw new Error("No active conversation to rate"); | ||
| await this.client.rateConversation(id, rating); | ||
| } | ||
| reset() { | ||
| this._setMessages([]); | ||
| this.conversationId.set(null); | ||
| this._setLoading(false); | ||
| this.isEscalated.set(false); | ||
| this.isResolved.set(false); | ||
| this.error.set(null); | ||
| this._error$.next(null); | ||
| this.welcomeMessage.set(null); | ||
| } | ||
| /** | ||
| * Submit guest identification data. | ||
| * Validates, generates identifier, persists, and updates state. | ||
| */ | ||
| identifyGuest(data) { | ||
| const validation = validateGuestForm(data); | ||
| if (!validation.valid) { | ||
| const err = new Error(Object.values(validation.errors).join(", ")); | ||
| this.error.set(err); | ||
| this._error$.next(err); | ||
| return; | ||
| } | ||
| const guestId = generateGuestIdentifier(data.email); | ||
| const identity = { | ||
| name: data.name, | ||
| email: data.email, | ||
| phone: data.phone || null, | ||
| guestId | ||
| }; | ||
| this.client.setGuestIdentity(identity); | ||
| this._setIdentified(true, identity); | ||
| this._guestIdentified$.next(identity); | ||
| this.error.set(null); | ||
| this._error$.next(null); | ||
| this._onGuestIdentified?.(identity); | ||
| } | ||
| _setIdentified(value, identity) { | ||
| this.isIdentified.set(value); | ||
| this._isIdentified$.next(value); | ||
| this.guestIdentity.set(identity); | ||
| } | ||
| _setMessages(msgs) { | ||
| this.messages.set(msgs); | ||
| this._messages$.next(msgs); | ||
| } | ||
| _setLoading(v) { | ||
| this.isLoading.set(v); | ||
| this._isLoading$.next(v); | ||
| } | ||
| }; | ||
| CustomerChatService = __decorateClass([ | ||
| Injectable2() | ||
| ], CustomerChatService); | ||
| // src/dual-chat.service.ts | ||
| import { Injectable as Injectable3, signal as signal3 } from "@angular/core"; | ||
| import { BehaviorSubject as BehaviorSubject3 } from "rxjs"; | ||
| import { | ||
| SyncAgentClient as SyncAgentClient3 | ||
| } from "@syncagent/js"; | ||
| var DualChatService = class { | ||
| constructor() { | ||
| this.client = null; | ||
| this.config = null; | ||
| this.abortController = null; | ||
| // DB mode signals | ||
| this.dbMessages = signal3([]); | ||
| this.dbIsLoading = signal3(false); | ||
| this.dbError = signal3(null); | ||
| // Support mode signals | ||
| this.supportMessages = signal3([]); | ||
| this.supportIsLoading = signal3(false); | ||
| this.supportError = signal3(null); | ||
| this.supportConversationId = signal3(null); | ||
| this.supportIsEscalated = signal3(false); | ||
| this.supportIsResolved = signal3(false); | ||
| // DB mode RxJS observables | ||
| this._dbMessages$ = new BehaviorSubject3([]); | ||
| this._dbIsLoading$ = new BehaviorSubject3(false); | ||
| this._dbError$ = new BehaviorSubject3(null); | ||
| this.dbMessages$ = this._dbMessages$.asObservable(); | ||
| this.dbIsLoading$ = this._dbIsLoading$.asObservable(); | ||
| this.dbError$ = this._dbError$.asObservable(); | ||
| // Support mode RxJS observables | ||
| this._supportMessages$ = new BehaviorSubject3([]); | ||
| this._supportIsLoading$ = new BehaviorSubject3(false); | ||
| this._supportError$ = new BehaviorSubject3(null); | ||
| this.supportMessages$ = this._supportMessages$.asObservable(); | ||
| this.supportIsLoading$ = this._supportIsLoading$.asObservable(); | ||
| this.supportError$ = this._supportError$.asObservable(); | ||
| } | ||
| configure(config) { | ||
| this.client = new SyncAgentClient3(config); | ||
| this.config = config; | ||
| this._context = config.context; | ||
| this._onData = config.onData; | ||
| this._onEscalated = config.onEscalated; | ||
| this._onResolved = config.onResolved; | ||
| } | ||
| async sendDbMessage(content) { | ||
| if (!this.client) throw new Error("DualChatService: call configure() first"); | ||
| if (!content.trim() || this.dbIsLoading()) return; | ||
| const userMsg = { role: "user", content }; | ||
| const updated = [...this.dbMessages(), userMsg]; | ||
| this._setDbMessages(updated); | ||
| this._setDbLoading(true); | ||
| this.dbError.set(null); | ||
| this._dbError$.next(null); | ||
| const placeholder = { role: "assistant", content: "" }; | ||
| this._setDbMessages([...updated, placeholder]); | ||
| this.abortController = new AbortController(); | ||
| try { | ||
| await this.client.chat(updated, { | ||
| signal: this.abortController.signal, | ||
| context: this._context, | ||
| onData: (data) => { | ||
| this._onData?.(data); | ||
| }, | ||
| onToken: (token) => { | ||
| placeholder.content += token; | ||
| const msgs = [...this.dbMessages()]; | ||
| msgs[msgs.length - 1] = { ...placeholder }; | ||
| this._setDbMessages(msgs); | ||
| }, | ||
| onComplete: (text) => { | ||
| const msgs = [...this.dbMessages()]; | ||
| const finalMsg = { role: "assistant", content: text }; | ||
| msgs[msgs.length - 1] = finalMsg; | ||
| this._setDbMessages(msgs); | ||
| } | ||
| }); | ||
| } catch (e) { | ||
| if (e.name !== "AbortError") { | ||
| const err = e instanceof Error ? e : new Error(String(e)); | ||
| this.dbError.set(err); | ||
| this._dbError$.next(err); | ||
| const msgs = [...this.dbMessages()]; | ||
| if (msgs[msgs.length - 1]?.content === "") { | ||
| this._setDbMessages(msgs.slice(0, -1)); | ||
| } | ||
| } | ||
| } finally { | ||
| this._setDbLoading(false); | ||
| this.abortController = null; | ||
| } | ||
| } | ||
| async sendSupportMessage(content, metadata) { | ||
| if (!this.client) throw new Error("DualChatService: call configure() first"); | ||
| if (!this.config?.externalUserId) { | ||
| throw new Error("DualChatService: externalUserId is required for customer chat"); | ||
| } | ||
| if (!content.trim() || this.supportIsLoading()) return; | ||
| const userMsg = { role: "user", content }; | ||
| this._setSupportMessages([...this.supportMessages(), userMsg]); | ||
| this._setSupportLoading(true); | ||
| this.supportError.set(null); | ||
| this._supportError$.next(null); | ||
| const chatOptions = { | ||
| conversationId: this.supportConversationId() || void 0, | ||
| metadata, | ||
| onEscalated: () => { | ||
| this.supportIsEscalated.set(true); | ||
| this._onEscalated?.(); | ||
| }, | ||
| onResolved: (id) => { | ||
| this.supportIsResolved.set(true); | ||
| this._onResolved?.(id); | ||
| } | ||
| }; | ||
| try { | ||
| const result = await this.client.customerChat(content, chatOptions); | ||
| this.supportConversationId.set(result.conversationId); | ||
| if (result.response) { | ||
| const assistantMsg = { role: "assistant", content: result.response }; | ||
| this._setSupportMessages([...this.supportMessages(), assistantMsg]); | ||
| } | ||
| } catch (e) { | ||
| const err = e instanceof Error ? e : new Error(String(e)); | ||
| this.supportError.set(err); | ||
| this._supportError$.next(err); | ||
| } finally { | ||
| this._setSupportLoading(false); | ||
| } | ||
| } | ||
| stop() { | ||
| if (!this.client) throw new Error("DualChatService: call configure() first"); | ||
| this.abortController?.abort(); | ||
| } | ||
| reset() { | ||
| if (!this.client) throw new Error("DualChatService: call configure() first"); | ||
| this.abortController?.abort(); | ||
| this._setDbMessages([]); | ||
| this.dbError.set(null); | ||
| this._dbError$.next(null); | ||
| this._setDbLoading(false); | ||
| this._setSupportMessages([]); | ||
| this.supportError.set(null); | ||
| this._supportError$.next(null); | ||
| this._setSupportLoading(false); | ||
| this.supportConversationId.set(null); | ||
| this.supportIsEscalated.set(false); | ||
| this.supportIsResolved.set(false); | ||
| } | ||
| _setDbMessages(msgs) { | ||
| this.dbMessages.set(msgs); | ||
| this._dbMessages$.next(msgs); | ||
| } | ||
| _setDbLoading(v) { | ||
| this.dbIsLoading.set(v); | ||
| this._dbIsLoading$.next(v); | ||
| } | ||
| _setSupportMessages(msgs) { | ||
| this.supportMessages.set(msgs); | ||
| this._supportMessages$.next(msgs); | ||
| } | ||
| _setSupportLoading(v) { | ||
| this.supportIsLoading.set(v); | ||
| this._supportIsLoading$.next(v); | ||
| } | ||
| }; | ||
| DualChatService = __decorateClass([ | ||
| Injectable3() | ||
| ], DualChatService); | ||
| // src/index.ts | ||
| import { SyncAgentClient as SyncAgentClient2, detectPageContext } from "@syncagent/js"; | ||
| import { SyncAgentClient as SyncAgentClient4, detectPageContext } from "@syncagent/js"; | ||
| import { GuestIdentificationRequiredError as GuestIdentificationRequiredError2, validateGuestForm as validateGuestForm2, validateName, validateEmail, generateGuestIdentifier as generateGuestIdentifier2 } from "@syncagent/js"; | ||
| export { | ||
| SyncAgentClient2 as SyncAgentClient, | ||
| CustomerChatService, | ||
| DualChatService, | ||
| GuestIdentificationRequiredError2 as GuestIdentificationRequiredError, | ||
| SyncAgentClient4 as SyncAgentClient, | ||
| SyncAgentService, | ||
| detectPageContext | ||
| detectPageContext, | ||
| generateGuestIdentifier2 as generateGuestIdentifier, | ||
| validateEmail, | ||
| validateGuestForm2 as validateGuestForm, | ||
| validateName | ||
| }; |
+12
-3
| { | ||
| "name": "@syncagent/angular", | ||
| "version": "0.3.4", | ||
| "version": "0.4.0", | ||
| "description": "SyncAgent Angular SDK — service and component for AI database chat", | ||
@@ -21,3 +21,5 @@ "homepage": "https://syncagentdev.vercel.app/docs", | ||
| }, | ||
| "files": ["dist"], | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "scripts": { | ||
@@ -42,3 +44,10 @@ "build": "tsup", | ||
| "license": "MIT", | ||
| "keywords": ["syncagent", "angular", "ai", "database", "agent", "service"] | ||
| "keywords": [ | ||
| "syncagent", | ||
| "angular", | ||
| "ai", | ||
| "database", | ||
| "agent", | ||
| "service" | ||
| ] | ||
| } |
+514
-0
@@ -211,2 +211,516 @@ # @syncagent/angular | ||
| ## Customer Agent Mode | ||
| The `CustomerChatService` is a dedicated Angular service for building customer-facing support interfaces. It routes messages through SyncAgent's customer support pipeline — using your configured persona, flows, knowledge base, and escalation rules — instead of direct database access. | ||
| Provide it at the component or module level and call `configure()` before sending messages. | ||
| ```typescript | ||
| import { Component, OnInit } from "@angular/core"; | ||
| import { CustomerChatService } from "@syncagent/angular"; | ||
| import { environment } from "./environments/environment"; | ||
| @Component({ | ||
| selector: "app-support-widget", | ||
| providers: [CustomerChatService], | ||
| template: ` | ||
| <div class="chat"> | ||
| @if (chat.welcomeMessage()) { | ||
| <p class="welcome">{{ chat.welcomeMessage() }}</p> | ||
| } | ||
| <div class="messages"> | ||
| @for (msg of chat.messages(); track msg.content) { | ||
| <div [class]="'message ' + msg.role">{{ msg.content }}</div> | ||
| } | ||
| </div> | ||
| @if (chat.isEscalated()) { | ||
| <p class="notice">You've been connected to a human agent.</p> | ||
| } | ||
| @if (chat.error() as err) { | ||
| <p class="error">{{ err.message }}</p> | ||
| } | ||
| <div class="input-row"> | ||
| <input | ||
| [(ngModel)]="input" | ||
| (keydown.enter)="send()" | ||
| placeholder="How can we help?" | ||
| [disabled]="chat.isLoading()" | ||
| /> | ||
| <button (click)="send()" [disabled]="chat.isLoading() || !input.trim()">Send</button> | ||
| </div> | ||
| @if (chat.isResolved()) { | ||
| <div class="rating"> | ||
| <p>How was your experience?</p> | ||
| <button *ngFor="let r of [1,2,3,4,5]" (click)="rate(r)">{{ r }}⭐</button> | ||
| </div> | ||
| } | ||
| </div> | ||
| `, | ||
| }) | ||
| export class SupportWidgetComponent implements OnInit { | ||
| input = ""; | ||
| constructor(public chat: CustomerChatService) {} | ||
| ngOnInit() { | ||
| this.chat.configure({ | ||
| apiKey: environment.syncagentKey, | ||
| connectionString: environment.databaseUrl, | ||
| externalUserId: "user_abc123", | ||
| onEscalated: () => console.log("Escalated to human agent"), | ||
| onResolved: (id) => console.log("Conversation resolved:", id), | ||
| }); | ||
| } | ||
| send() { | ||
| if (!this.input.trim()) return; | ||
| this.chat.sendMessage(this.input); | ||
| this.input = ""; | ||
| } | ||
| rate(rating: number) { | ||
| this.chat.rateConversation(rating); | ||
| } | ||
| } | ||
| ``` | ||
| ### Angular Signals | ||
| | Signal | Type | Description | | ||
| |--------|------|-------------| | ||
| | `messages()` | `Message[]` | Conversation message history | | ||
| | `conversationId()` | `string \| null` | Current conversation ID | | ||
| | `isLoading()` | `boolean` | True while waiting for a response | | ||
| | `isEscalated()` | `boolean` | True when escalated to a human agent | | ||
| | `isResolved()` | `boolean` | True when the conversation is resolved | | ||
| | `error()` | `Error \| null` | Last error, if any | | ||
| | `welcomeMessage()` | `string \| null` | Welcome message (first interaction only) | | ||
| | `isConfigured()` | `boolean` | True after `configure()` has been called | | ||
| ### RxJS Observables | ||
| | Observable | Type | Description | | ||
| |------------|------|-------------| | ||
| | `messages$` | `Observable<Message[]>` | Conversation history stream | | ||
| | `isLoading$` | `Observable<boolean>` | Loading state stream | | ||
| | `error$` | `Observable<Error \| null>` | Error stream | | ||
| | `escalated$` | `Observable<void>` | Emits when escalated to a human agent | | ||
| | `resolved$` | `Observable<string>` | Emits the conversation ID when resolved | | ||
| ### `CustomerChatServiceConfig` | ||
| Extends `SyncAgentConfig` with customer-mode-specific options: | ||
| | Property | Type | Description | | ||
| |----------|------|-------------| | ||
| | `apiKey` | `string` | Your SyncAgent API key | | ||
| | `connectionString` | `string` | Database connection string | | ||
| | `customerMode` | `true` | Enables customer agent mode | | ||
| | `externalUserId` | `string` | Unique identifier for the end-user | | ||
| | `onEscalated` | `() => void` | Optional callback when escalated to a human agent | | ||
| | `onResolved` | `(conversationId: string) => void` | Optional callback when the conversation is resolved | | ||
| ### Methods | ||
| | Method | Signature | Description | | ||
| |--------|-----------|-------------| | ||
| | `configure(config)` | `configure(config: CustomerChatServiceConfig): void` | Initialize the service with customer mode config. Must be called before other methods. | | ||
| | `sendMessage(content, metadata?)` | `sendMessage(content: string, metadata?: Record<string, any>): Promise<void>` | Send a message and receive an AI response. Ignored if content is empty or already loading. | | ||
| | `rateConversation(rating)` | `rateConversation(rating: number): Promise<void>` | Rate the current conversation (1-5). Throws if no active conversation. | | ||
| | `reset()` | `reset(): void` | Clear all state (messages, conversation ID, errors) and start fresh. | | ||
| ### Guest Identification | ||
| When no `externalUserId` is provided, the `CustomerChatService` activates the guest identification flow. Guests must identify themselves before sending messages. The service exposes Angular signals and RxJS observables for reactive state management. | ||
| #### Signals & Observables | ||
| | Signal / Observable | Type | Description | | ||
| |---------------------|------|-------------| | ||
| | `isIdentified()` | `Signal<boolean>` | `true` when the guest has been identified (via form or stored identity) | | ||
| | `guestIdentity()` | `Signal<GuestIdentity \| null>` | The current guest identity, or `null` if not yet identified | | ||
| | `isIdentified$` | `Observable<boolean>` | Emits the current identification state (BehaviorSubject) | | ||
| | `guestIdentified$` | `Observable<GuestIdentity>` | Emits the `GuestIdentity` when identification completes | | ||
| #### `identifyGuest(data)` | ||
| | Parameter | Type | Required | Description | | ||
| |-----------|------|----------|-------------| | ||
| | `name` | `string` | Yes | Guest's display name | | ||
| | `email` | `string` | Yes | Guest's email address | | ||
| | `phone` | `string` | No | Guest's phone number | | ||
| Validates input, generates a deterministic guest identifier from the email, persists the identity to localStorage, updates signals/observables, and invokes the `onGuestIdentified` callback if configured. | ||
| #### Template Usage with Signals | ||
| ```typescript | ||
| import { Component, OnInit } from "@angular/core"; | ||
| import { FormsModule } from "@angular/forms"; | ||
| import { CustomerChatService } from "@syncagent/angular"; | ||
| import { environment } from "./environments/environment"; | ||
| @Component({ | ||
| selector: "app-support-widget", | ||
| providers: [CustomerChatService], | ||
| imports: [FormsModule], | ||
| template: ` | ||
| @if (!chat.isIdentified()) { | ||
| <form (ngSubmit)="identify()"> | ||
| <h2>Welcome</h2> | ||
| <p>Please introduce yourself to get started</p> | ||
| <label for="name">Name</label> | ||
| <input id="name" [(ngModel)]="name" name="name" required /> | ||
| <label for="email">Email</label> | ||
| <input id="email" [(ngModel)]="email" name="email" type="email" required /> | ||
| <label for="phone">Phone (optional)</label> | ||
| <input id="phone" [(ngModel)]="phone" name="phone" type="tel" /> | ||
| @if (chat.error() as err) { | ||
| <p class="error">{{ err.message }}</p> | ||
| } | ||
| <button type="submit">Start Chat</button> | ||
| </form> | ||
| } @else { | ||
| <div class="chat"> | ||
| <div class="messages"> | ||
| @for (msg of chat.messages(); track msg.content) { | ||
| <div [class]="'message ' + msg.role">{{ msg.content }}</div> | ||
| } | ||
| </div> | ||
| <div class="input-row"> | ||
| <input | ||
| [(ngModel)]="input" | ||
| (keydown.enter)="send()" | ||
| placeholder="How can we help?" | ||
| [disabled]="chat.isLoading()" | ||
| /> | ||
| <button (click)="send()" [disabled]="chat.isLoading() || !input.trim()">Send</button> | ||
| </div> | ||
| </div> | ||
| } | ||
| `, | ||
| }) | ||
| export class SupportWidgetComponent implements OnInit { | ||
| name = ""; | ||
| email = ""; | ||
| phone = ""; | ||
| input = ""; | ||
| constructor(public chat: CustomerChatService) {} | ||
| ngOnInit() { | ||
| this.chat.configure({ | ||
| apiKey: environment.syncagentKey, | ||
| connectionString: environment.databaseUrl, | ||
| onGuestIdentified: (identity) => { | ||
| console.log("Guest identified:", identity.guestId); | ||
| }, | ||
| }); | ||
| } | ||
| identify() { | ||
| this.chat.identifyGuest({ | ||
| name: this.name, | ||
| email: this.email, | ||
| phone: this.phone || undefined, | ||
| }); | ||
| } | ||
| send() { | ||
| if (!this.input.trim()) return; | ||
| this.chat.sendMessage(this.input); | ||
| this.input = ""; | ||
| } | ||
| } | ||
| ``` | ||
| #### Reactive Handling with `guestIdentified$` | ||
| ```typescript | ||
| import { Component, OnInit, OnDestroy } from "@angular/core"; | ||
| import { Subject, takeUntil } from "rxjs"; | ||
| import { CustomerChatService } from "@syncagent/angular"; | ||
| import { environment } from "./environments/environment"; | ||
| @Component({ | ||
| selector: "app-guest-chat", | ||
| providers: [CustomerChatService], | ||
| template: `<!-- ... -->`, | ||
| }) | ||
| export class GuestChatComponent implements OnInit, OnDestroy { | ||
| private destroy$ = new Subject<void>(); | ||
| constructor(public chat: CustomerChatService) {} | ||
| ngOnInit() { | ||
| this.chat.configure({ | ||
| apiKey: environment.syncagentKey, | ||
| connectionString: environment.databaseUrl, | ||
| }); | ||
| // React to guest identification | ||
| this.chat.guestIdentified$ | ||
| .pipe(takeUntil(this.destroy$)) | ||
| .subscribe((identity) => { | ||
| console.log(`Guest ${identity.name} identified as ${identity.guestId}`); | ||
| // e.g., send analytics event, update UI state | ||
| }); | ||
| // Track identification state changes | ||
| this.chat.isIdentified$ | ||
| .pipe(takeUntil(this.destroy$)) | ||
| .subscribe((identified) => { | ||
| console.log("Identification state:", identified); | ||
| }); | ||
| } | ||
| ngOnDestroy() { | ||
| this.destroy$.next(); | ||
| this.destroy$.complete(); | ||
| } | ||
| } | ||
| ``` | ||
| ### Dual Mode (Database + Customer Agent) | ||
| > ⚠️ **Deprecated:** `createDual()` will be removed in a future major version. Use `DualChatService` instead — pass `externalUserId` directly to enable both modes on a single instance. | ||
| **Legacy pattern (deprecated):** | ||
| ```typescript | ||
| import { Component, OnInit } from "@angular/core"; | ||
| import { SyncAgentClient } from "@syncagent/js"; | ||
| import { SyncAgentService, CustomerChatService } from "@syncagent/angular"; | ||
| import { environment } from "./environments/environment"; | ||
| @Component({ | ||
| selector: "app-dual-chat", | ||
| providers: [SyncAgentService, CustomerChatService], | ||
| template: `<!-- admin + customer UIs -->`, | ||
| }) | ||
| export class DualChatComponent implements OnInit { | ||
| constructor( | ||
| public admin: SyncAgentService, | ||
| public support: CustomerChatService, | ||
| ) {} | ||
| ngOnInit() { | ||
| const { db, support } = SyncAgentClient.createDual({ | ||
| apiKey: environment.syncagentKey, | ||
| connectionString: environment.databaseUrl, | ||
| externalUserId: "customer_123", | ||
| }); | ||
| // Admin: direct database queries | ||
| this.admin.configure({ | ||
| apiKey: environment.syncagentKey, | ||
| connectionString: environment.databaseUrl, | ||
| }); | ||
| // Customer: support pipeline | ||
| this.support.configure({ | ||
| apiKey: environment.syncagentKey, | ||
| connectionString: environment.databaseUrl, | ||
| customerMode: true, | ||
| externalUserId: "customer_123", | ||
| }); | ||
| } | ||
| } | ||
| ``` | ||
| **Before (deprecated):** | ||
| ```typescript | ||
| const { db, support } = SyncAgentClient.createDual({ | ||
| apiKey: environment.syncagentKey, | ||
| connectionString: environment.databaseUrl, | ||
| externalUserId: "customer_123", | ||
| }); | ||
| ``` | ||
| **After (recommended):** | ||
| ```typescript | ||
| import { DualChatService } from "@syncagent/angular"; | ||
| @Component({ | ||
| providers: [DualChatService], | ||
| }) | ||
| export class DualChatComponent implements OnInit { | ||
| constructor(public dual: DualChatService) {} | ||
| ngOnInit() { | ||
| this.dual.configure({ | ||
| apiKey: environment.syncagentKey, | ||
| connectionString: environment.databaseUrl, | ||
| externalUserId: "customer_123", | ||
| }); | ||
| } | ||
| } | ||
| ``` | ||
| > **Tip:** In Angular, you can also use `createDual()` to get both client instances and pass them to your services' internal logic, or simply configure each service independently as shown above. | ||
| ## Unified Dual Mode | ||
| The `DualChatService` provides a single injectable service that manages both database agent and customer support chat from one component. Instead of wiring up `SyncAgentService` and `CustomerChatService` separately, inject `DualChatService` and call `configure()` with your config including `externalUserId` — both modes become available immediately. | ||
| ```typescript | ||
| import { Component, OnInit } from "@angular/core"; | ||
| import { DualChatService } from "@syncagent/angular"; | ||
| import { environment } from "./environments/environment"; | ||
| @Component({ | ||
| selector: "app-dual-chat", | ||
| providers: [DualChatService], | ||
| template: ` | ||
| <div class="dual-chat"> | ||
| <section class="db-panel"> | ||
| <h3>Database Agent</h3> | ||
| <div class="messages"> | ||
| @for (msg of dual.dbMessages(); track msg.content) { | ||
| <div [class]="'message ' + msg.role">{{ msg.content }}</div> | ||
| } | ||
| </div> | ||
| @if (dual.dbError() as err) { | ||
| <p class="error">{{ err.message }}</p> | ||
| } | ||
| <div class="input-row"> | ||
| <input | ||
| [(ngModel)]="dbInput" | ||
| (keydown.enter)="sendDb()" | ||
| placeholder="Ask about your data..." | ||
| [disabled]="dual.dbIsLoading()" | ||
| /> | ||
| <button (click)="sendDb()" [disabled]="dual.dbIsLoading() || !dbInput.trim()">Send</button> | ||
| </div> | ||
| </section> | ||
| <section class="support-panel"> | ||
| <h3>Customer Support</h3> | ||
| <div class="messages"> | ||
| @for (msg of dual.supportMessages(); track msg.content) { | ||
| <div [class]="'message ' + msg.role">{{ msg.content }}</div> | ||
| } | ||
| </div> | ||
| @if (dual.supportIsEscalated()) { | ||
| <p class="notice">Connected to a human agent.</p> | ||
| } | ||
| @if (dual.supportError() as err) { | ||
| <p class="error">{{ err.message }}</p> | ||
| } | ||
| <div class="input-row"> | ||
| <input | ||
| [(ngModel)]="supportInput" | ||
| (keydown.enter)="sendSupport()" | ||
| placeholder="How can we help?" | ||
| [disabled]="dual.supportIsLoading()" | ||
| /> | ||
| <button (click)="sendSupport()" [disabled]="dual.supportIsLoading() || !supportInput.trim()">Send</button> | ||
| </div> | ||
| </section> | ||
| </div> | ||
| `, | ||
| }) | ||
| export class DualChatComponent implements OnInit { | ||
| dbInput = ""; | ||
| supportInput = ""; | ||
| constructor(public dual: DualChatService) {} | ||
| ngOnInit() { | ||
| this.dual.configure({ | ||
| apiKey: environment.syncagentKey, | ||
| connectionString: environment.databaseUrl, | ||
| externalUserId: "user_abc123", | ||
| onEscalated: () => console.log("Escalated to human agent"), | ||
| onResolved: (id) => console.log("Resolved:", id), | ||
| }); | ||
| } | ||
| sendDb() { | ||
| if (!this.dbInput.trim()) return; | ||
| this.dual.sendDbMessage(this.dbInput); | ||
| this.dbInput = ""; | ||
| } | ||
| sendSupport() { | ||
| if (!this.supportInput.trim()) return; | ||
| this.dual.sendSupportMessage(this.supportInput); | ||
| this.supportInput = ""; | ||
| } | ||
| } | ||
| ``` | ||
| ### Error Handling | ||
| If `sendDbMessage()`, `sendSupportMessage()`, `stop()`, or `reset()` is called before `configure()`, the service throws: | ||
| ``` | ||
| DualChatService: call configure() first | ||
| ``` | ||
| If `sendSupportMessage()` is called without `externalUserId` in the config, the service throws: | ||
| ``` | ||
| DualChatService: externalUserId is required for customer chat | ||
| ``` | ||
| ### Angular Signals | ||
| | Signal | Type | Description | | ||
| |--------|------|-------------| | ||
| | `dbMessages()` | `Message[]` | Database agent conversation message history | | ||
| | `dbIsLoading()` | `boolean` | True while the database agent is processing | | ||
| | `dbError()` | `Error \| null` | Last error from the database agent, if any | | ||
| | `supportMessages()` | `Message[]` | Customer support conversation message history | | ||
| | `supportIsLoading()` | `boolean` | True while the support agent is processing | | ||
| | `supportError()` | `Error \| null` | Last error from the support agent, if any | | ||
| | `supportConversationId()` | `string \| null` | Current support conversation ID | | ||
| | `supportIsEscalated()` | `boolean` | True when escalated to a human agent | | ||
| | `supportIsResolved()` | `boolean` | True when the support conversation is resolved | | ||
| ### RxJS Observables | ||
| | Observable | Type | Description | | ||
| |------------|------|-------------| | ||
| | `dbMessages$` | `Observable<Message[]>` | Database agent message history stream | | ||
| | `dbIsLoading$` | `Observable<boolean>` | Database agent loading state stream | | ||
| | `dbError$` | `Observable<Error \| null>` | Database agent error stream | | ||
| | `supportMessages$` | `Observable<Message[]>` | Support agent message history stream | | ||
| | `supportIsLoading$` | `Observable<boolean>` | Support agent loading state stream | | ||
| | `supportError$` | `Observable<Error \| null>` | Support agent error stream | | ||
| ### `DualChatServiceConfig` | ||
| | Property | Type | Description | | ||
| |----------|------|-------------| | ||
| | `apiKey` | `string` | Your SyncAgent API key | | ||
| | `connectionString` | `string` | Database connection string | | ||
| | `externalUserId` | `string` | Unique identifier for the end-user (enables customer mode) | | ||
| | `context` | `Record<string, any>` | Optional context passed to both agents | | ||
| | `onData` | `(data: ToolData) => void` | Optional callback when the database agent returns query results | | ||
| | `onEscalated` | `() => void` | Optional callback when the support conversation is escalated to a human agent | | ||
| | `onResolved` | `(conversationId: string) => void` | Optional callback when the support conversation is resolved | | ||
| ### Methods | ||
| | Method | Signature | Description | | ||
| |--------|-----------|-------------| | ||
| | `configure(config)` | `configure(config: DualChatServiceConfig): void` | Initialize the service with dual-mode config. Must be called before other methods. | | ||
| | `sendDbMessage(content)` | `sendDbMessage(content: string): Promise<void>` | Send a message to the database agent and receive a streamed response. | | ||
| | `sendSupportMessage(content, metadata?)` | `sendSupportMessage(content: string, metadata?: Record<string, any>): Promise<void>` | Send a message to the customer support agent. Requires `externalUserId` in config. | | ||
| | `stop()` | `stop(): void` | Abort any in-progress streams for both agents. | | ||
| | `reset()` | `reset(): void` | Clear all state (messages, errors, conversation) for both agents and start fresh. | | ||
| ## API Reference | ||
@@ -213,0 +727,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
80734
219.37%1206
223.32%796
182.27%1
Infinity%