@nextpa/chat-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/constants/defaults.d.ts +6 -0
  4. package/dist/constants/defaults.js +69 -0
  5. package/dist/core/chat-manager.d.ts +17 -0
  6. package/dist/core/chat-manager.js +39 -0
  7. package/dist/core/config-manager.d.ts +14 -0
  8. package/dist/core/config-manager.js +75 -0
  9. package/dist/core/connection-manager.d.ts +68 -0
  10. package/dist/core/connection-manager.js +273 -0
  11. package/dist/core/connection-retry-handler.d.ts +11 -0
  12. package/dist/core/connection-retry-handler.js +33 -0
  13. package/dist/core/consent-manager.d.ts +15 -0
  14. package/dist/core/consent-manager.js +40 -0
  15. package/dist/core/conversation-persistence.d.ts +23 -0
  16. package/dist/core/conversation-persistence.js +109 -0
  17. package/dist/core/event-emitter.d.ts +7 -0
  18. package/dist/core/event-emitter.js +27 -0
  19. package/dist/core/greeting-gate.d.ts +16 -0
  20. package/dist/core/greeting-gate.js +53 -0
  21. package/dist/core/index.d.ts +9 -0
  22. package/dist/core/index.js +7 -0
  23. package/dist/core/link-target.d.ts +15 -0
  24. package/dist/core/link-target.js +64 -0
  25. package/dist/core/local-storage-persistence.d.ts +11 -0
  26. package/dist/core/local-storage-persistence.js +111 -0
  27. package/dist/core/navigation-manager.d.ts +24 -0
  28. package/dist/core/navigation-manager.js +132 -0
  29. package/dist/core/popover-manager.d.ts +14 -0
  30. package/dist/core/popover-manager.js +36 -0
  31. package/dist/core/session-beacon.d.ts +11 -0
  32. package/dist/core/session-beacon.js +60 -0
  33. package/dist/core/text-connection-manager.d.ts +58 -0
  34. package/dist/core/text-connection-manager.js +200 -0
  35. package/dist/core/text-mode-manager.d.ts +50 -0
  36. package/dist/core/text-mode-manager.js +154 -0
  37. package/dist/core/text-types.d.ts +20 -0
  38. package/dist/core/text-types.js +1 -0
  39. package/dist/core/text-widget-controller.d.ts +47 -0
  40. package/dist/core/text-widget-controller.js +278 -0
  41. package/dist/core/theme-manager.d.ts +10 -0
  42. package/dist/core/theme-manager.js +25 -0
  43. package/dist/core/types.d.ts +50 -0
  44. package/dist/core/types.js +1 -0
  45. package/dist/core/voice-state-manager.d.ts +85 -0
  46. package/dist/core/voice-state-manager.js +317 -0
  47. package/dist/core/widget-controller.d.ts +58 -0
  48. package/dist/core/widget-controller.js +533 -0
  49. package/dist/core/widget-core.d.ts +51 -0
  50. package/dist/core/widget-core.js +160 -0
  51. package/dist/google.d.ts +1 -0
  52. package/dist/google.js +12 -0
  53. package/dist/index.d.ts +37 -0
  54. package/dist/index.js +40 -0
  55. package/dist/openai.d.ts +1 -0
  56. package/dist/openai.js +7 -0
  57. package/dist/services/api.d.ts +114 -0
  58. package/dist/services/api.js +336 -0
  59. package/dist/services/index.d.ts +3 -0
  60. package/dist/services/index.js +3 -0
  61. package/dist/services/page-interaction/constants.d.ts +1 -0
  62. package/dist/services/page-interaction/constants.js +1 -0
  63. package/dist/services/page-interaction/content-extractor.d.ts +2 -0
  64. package/dist/services/page-interaction/content-extractor.js +104 -0
  65. package/dist/services/page-interaction/deep-query.d.ts +8 -0
  66. package/dist/services/page-interaction/deep-query.js +44 -0
  67. package/dist/services/page-interaction/dom-executor.d.ts +7 -0
  68. package/dist/services/page-interaction/dom-executor.js +172 -0
  69. package/dist/services/page-interaction/dom-extractor.d.ts +8 -0
  70. package/dist/services/page-interaction/dom-extractor.js +252 -0
  71. package/dist/services/page-interaction/extension-bridge.d.ts +10 -0
  72. package/dist/services/page-interaction/extension-bridge.js +86 -0
  73. package/dist/services/page-interaction/iframe-navigation-manager.d.ts +28 -0
  74. package/dist/services/page-interaction/iframe-navigation-manager.js +154 -0
  75. package/dist/services/page-interaction/index.d.ts +5 -0
  76. package/dist/services/page-interaction/index.js +4 -0
  77. package/dist/services/page-interaction/page-interaction-handler.d.ts +21 -0
  78. package/dist/services/page-interaction/page-interaction-handler.js +129 -0
  79. package/dist/services/page-interaction/selector-generator.d.ts +1 -0
  80. package/dist/services/page-interaction/selector-generator.js +48 -0
  81. package/dist/services/page-interaction/types.d.ts +75 -0
  82. package/dist/services/page-interaction/types.js +1 -0
  83. package/dist/services/realtime/google/google-audio.d.ts +44 -0
  84. package/dist/services/realtime/google/google-audio.js +410 -0
  85. package/dist/services/realtime/google/google-provider.d.ts +47 -0
  86. package/dist/services/realtime/google/google-provider.js +465 -0
  87. package/dist/services/realtime/google/google-tool-mapper.d.ts +3 -0
  88. package/dist/services/realtime/google/google-tool-mapper.js +70 -0
  89. package/dist/services/realtime/google/index.d.ts +1 -0
  90. package/dist/services/realtime/google/index.js +1 -0
  91. package/dist/services/realtime/openai/index.d.ts +1 -0
  92. package/dist/services/realtime/openai/index.js +1 -0
  93. package/dist/services/realtime/openai/openai-audio-monitor.d.ts +1 -0
  94. package/dist/services/realtime/openai/openai-audio-monitor.js +55 -0
  95. package/dist/services/realtime/openai/openai-provider.d.ts +50 -0
  96. package/dist/services/realtime/openai/openai-provider.js +579 -0
  97. package/dist/services/realtime/openai/openai-tool-mapper.d.ts +15 -0
  98. package/dist/services/realtime/openai/openai-tool-mapper.js +14 -0
  99. package/dist/services/realtime/provider-factory.d.ts +4 -0
  100. package/dist/services/realtime/provider-factory.js +12 -0
  101. package/dist/services/realtime/types.d.ts +78 -0
  102. package/dist/services/realtime/types.js +1 -0
  103. package/dist/services/realtime/vertexai/index.d.ts +1 -0
  104. package/dist/services/realtime/vertexai/index.js +1 -0
  105. package/dist/services/realtime/vertexai/vertexai-provider.d.ts +53 -0
  106. package/dist/services/realtime/vertexai/vertexai-provider.js +561 -0
  107. package/dist/services/realtime/voice-session-tracker.d.ts +10 -0
  108. package/dist/services/realtime/voice-session-tracker.js +62 -0
  109. package/dist/services/signalr.d.ts +37 -0
  110. package/dist/services/signalr.js +145 -0
  111. package/dist/services/thinking-audio-pulse.d.ts +2 -0
  112. package/dist/services/thinking-audio-pulse.js +72 -0
  113. package/dist/services/thinking-audio.d.ts +2 -0
  114. package/dist/services/thinking-audio.js +105 -0
  115. package/dist/services/voice-health-watchdog.d.ts +42 -0
  116. package/dist/services/voice-health-watchdog.js +90 -0
  117. package/dist/services/webrtc-event-handler.d.ts +4 -0
  118. package/dist/services/webrtc-event-handler.js +42 -0
  119. package/dist/services/webrtc-greeting.d.ts +3 -0
  120. package/dist/services/webrtc-greeting.js +62 -0
  121. package/dist/services/webrtc-microphone.d.ts +15 -0
  122. package/dist/services/webrtc-microphone.js +64 -0
  123. package/dist/services/webrtc.d.ts +44 -0
  124. package/dist/services/webrtc.js +196 -0
  125. package/dist/types/index.d.ts +151 -0
  126. package/dist/types/index.js +1 -0
  127. package/dist/utils/audio-context.d.ts +11 -0
  128. package/dist/utils/audio-context.js +33 -0
  129. package/dist/utils/connect-chime.d.ts +11 -0
  130. package/dist/utils/connect-chime.js +230 -0
  131. package/dist/utils/consent.d.ts +4 -0
  132. package/dist/utils/consent.js +30 -0
  133. package/dist/utils/language-detection.d.ts +1 -0
  134. package/dist/utils/language-detection.js +17 -0
  135. package/dist/utils/logger.d.ts +13 -0
  136. package/dist/utils/logger.js +40 -0
  137. package/dist/utils/mic-level-analyser.d.ts +13 -0
  138. package/dist/utils/mic-level-analyser.js +50 -0
  139. package/dist/utils/proactive-events.d.ts +5 -0
  140. package/dist/utils/proactive-events.js +1 -0
  141. package/dist/utils/styles.d.ts +4 -0
  142. package/dist/utils/styles.js +35 -0
  143. package/dist/utils/theme-detection.d.ts +3 -0
  144. package/dist/utils/theme-detection.js +121 -0
  145. package/dist/utils/tool-status.d.ts +10 -0
  146. package/dist/utils/tool-status.js +61 -0
  147. package/dist/utils/ui-sounds.d.ts +3 -0
  148. package/dist/utils/ui-sounds.js +57 -0
  149. package/package.json +56 -0
@@ -0,0 +1,30 @@
1
+ const CONSENT_COOKIE_PREFIX = 'nextpa_tc_consent_';
2
+ const CONSENT_EXPIRY_DAYS = 365;
3
+ export function getConsentCookieName(assistantId) {
4
+ return `${CONSENT_COOKIE_PREFIX}${assistantId}`;
5
+ }
6
+ export function hasUserConsented(assistantId) {
7
+ const cookieName = getConsentCookieName(assistantId);
8
+ const cookies = document.cookie.split(';');
9
+ for (const cookie of cookies) {
10
+ const [name, value] = cookie.trim().split('=');
11
+ if (name === cookieName && value === 'true') {
12
+ return true;
13
+ }
14
+ }
15
+ return false;
16
+ }
17
+ export function setUserConsent(assistantId, consented) {
18
+ const cookieName = getConsentCookieName(assistantId);
19
+ if (consented) {
20
+ const expiryDate = new Date();
21
+ expiryDate.setDate(expiryDate.getDate() + CONSENT_EXPIRY_DAYS);
22
+ document.cookie = `${cookieName}=true; expires=${expiryDate.toUTCString()}; path=/; SameSite=Lax`;
23
+ }
24
+ else {
25
+ document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
26
+ }
27
+ }
28
+ export function clearUserConsent(assistantId) {
29
+ setUserConsent(assistantId, false);
30
+ }
@@ -0,0 +1 @@
1
+ export declare function detectHostLanguage(): string | null;
@@ -0,0 +1,17 @@
1
+ import { logger } from './logger';
2
+ export function detectHostLanguage() {
3
+ try {
4
+ const lang = document.documentElement.lang;
5
+ if (!lang) {
6
+ logger.debug('[LanguageDetection] No lang attribute on <html> element');
7
+ return null;
8
+ }
9
+ const baseLang = lang.split('-')[0].toLowerCase();
10
+ logger.debug('[LanguageDetection] Detected host language:', baseLang);
11
+ return baseLang;
12
+ }
13
+ catch {
14
+ logger.debug('[LanguageDetection] Failed to detect host language');
15
+ return null;
16
+ }
17
+ }
@@ -0,0 +1,13 @@
1
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
2
+ declare class Logger {
3
+ private level;
4
+ private readonly prefix;
5
+ setLevel(level: LogLevel): void;
6
+ private shouldLog;
7
+ debug(...args: unknown[]): void;
8
+ info(...args: unknown[]): void;
9
+ warn(...args: unknown[]): void;
10
+ error(...args: unknown[]): void;
11
+ }
12
+ export declare const logger: Logger;
13
+ export {};
@@ -0,0 +1,40 @@
1
+ const LOG_LEVELS = {
2
+ debug: 0,
3
+ info: 1,
4
+ warn: 2,
5
+ error: 3,
6
+ none: 4,
7
+ };
8
+ class Logger {
9
+ level = 'info';
10
+ prefix = '[NextPA Widget]';
11
+ setLevel(level) {
12
+ this.level = level;
13
+ }
14
+ shouldLog(level) {
15
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
16
+ }
17
+ debug(...args) {
18
+ if (this.shouldLog('debug')) {
19
+ console.debug(this.prefix, ...args);
20
+ }
21
+ }
22
+ info(...args) {
23
+ if (this.shouldLog('info')) {
24
+ console.info(this.prefix, ...args);
25
+ }
26
+ }
27
+ warn(...args) {
28
+ if (this.shouldLog('warn')) {
29
+ console.warn(this.prefix, ...args);
30
+ }
31
+ }
32
+ error(...args) {
33
+ if (this.shouldLog('error')) {
34
+ console.error(this.prefix, ...args);
35
+ }
36
+ }
37
+ }
38
+ export const logger = new Logger();
39
+ // Framework-neutral default; the host app raises/lowers verbosity via
40
+ // `logger.setLevel(...)` (no build-time env coupling in the shared engine).
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Reports a 0..1 microphone amplitude (time-domain RMS) from a MediaStream each
3
+ * animation frame. Drives recording level meters (e.g. the dictation waveform)
4
+ * without needing a realtime connection. Fails silently if Web Audio is
5
+ * unavailable — the level simply stays at its last value.
6
+ */
7
+ export declare class MicLevelAnalyser {
8
+ private context;
9
+ private analyser;
10
+ private rafId;
11
+ start(stream: MediaStream, onLevel: (level: number) => void): void;
12
+ stop(): void;
13
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Reports a 0..1 microphone amplitude (time-domain RMS) from a MediaStream each
3
+ * animation frame. Drives recording level meters (e.g. the dictation waveform)
4
+ * without needing a realtime connection. Fails silently if Web Audio is
5
+ * unavailable — the level simply stays at its last value.
6
+ */
7
+ export class MicLevelAnalyser {
8
+ context = null;
9
+ analyser = null;
10
+ rafId = null;
11
+ start(stream, onLevel) {
12
+ this.stop();
13
+ try {
14
+ this.context = new AudioContext();
15
+ const source = this.context.createMediaStreamSource(stream);
16
+ this.analyser = this.context.createAnalyser();
17
+ // 1024 samples ≈ 21 ms at 48 kHz — reacts to syllables, stable for RMS.
18
+ this.analyser.fftSize = 1024;
19
+ this.analyser.smoothingTimeConstant = 0.4;
20
+ source.connect(this.analyser);
21
+ const buffer = new Float32Array(this.analyser.fftSize);
22
+ const tick = () => {
23
+ if (!this.analyser)
24
+ return;
25
+ this.analyser.getFloatTimeDomainData(buffer);
26
+ let sumSquares = 0;
27
+ for (const sample of buffer) {
28
+ sumSquares += sample * sample;
29
+ }
30
+ onLevel(Math.min(1, Math.sqrt(sumSquares / buffer.length)));
31
+ this.rafId = requestAnimationFrame(tick);
32
+ };
33
+ this.rafId = requestAnimationFrame(tick);
34
+ }
35
+ catch {
36
+ this.stop();
37
+ }
38
+ }
39
+ stop() {
40
+ if (this.rafId !== null) {
41
+ cancelAnimationFrame(this.rafId);
42
+ this.rafId = null;
43
+ }
44
+ this.analyser = null;
45
+ if (this.context) {
46
+ void this.context.close();
47
+ this.context = null;
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,5 @@
1
+ export declare const PROACTIVE_OPEN_WIDGET_EVENT = "nextpa-widget:open-with-prompt";
2
+ export interface ProactiveOpenWidgetEventDetail {
3
+ readonly prompt: string | null;
4
+ }
5
+ export type ProactiveOpenWidgetEvent = CustomEvent<ProactiveOpenWidgetEventDetail>;
@@ -0,0 +1 @@
1
+ export const PROACTIVE_OPEN_WIDGET_EVENT = 'nextpa-widget:open-with-prompt';
@@ -0,0 +1,4 @@
1
+ import type { WidgetConfig } from '../types';
2
+ export declare function getPlacementClasses(placement: WidgetConfig['placement'] | undefined): string;
3
+ export declare function getMinimizedWidthClasses(size: WidgetConfig['size'] | undefined): string;
4
+ export declare function getMessageBubbleClasses(isUser: boolean, isDark: boolean): string;
@@ -0,0 +1,35 @@
1
+ export function getPlacementClasses(placement) {
2
+ switch (placement) {
3
+ case 'top-left':
4
+ return 'top-5 left-5';
5
+ case 'top-center':
6
+ return 'top-5 left-1/2 -translate-x-1/2';
7
+ case 'top-right':
8
+ return 'top-5 right-5';
9
+ case 'bottom-left':
10
+ return 'bottom-5 left-5';
11
+ case 'bottom-center':
12
+ return 'bottom-5 left-1/2 -translate-x-1/2';
13
+ case 'bottom-right':
14
+ default:
15
+ return 'bottom-5 right-5';
16
+ }
17
+ }
18
+ export function getMinimizedWidthClasses(size) {
19
+ switch (size) {
20
+ case 'small':
21
+ return 'min-w-[104px] w-auto';
22
+ case 'large':
23
+ return 'min-w-[232px] max-w-[320px] w-auto';
24
+ case 'compact':
25
+ default:
26
+ return 'min-w-[200px] max-w-[280px] w-auto';
27
+ }
28
+ }
29
+ export function getMessageBubbleClasses(isUser, isDark) {
30
+ const base = 'max-w-[90%] break-words rounded-2xl border px-3 py-2.5 text-sm leading-relaxed';
31
+ if (isUser) {
32
+ return `${base} whitespace-pre-wrap self-end ${isDark ? 'border-blue-500/30 bg-blue-600 text-white' : 'border-blue-500/20 bg-blue-500/10 text-gray-900'}`;
33
+ }
34
+ return `${base} self-start ${isDark ? 'border-gray-700 bg-gray-800 text-gray-100' : 'border-gray-200 bg-gray-100 text-gray-900'}`;
35
+ }
@@ -0,0 +1,3 @@
1
+ export type ResolvedTheme = 'light' | 'dark';
2
+ export declare function detectHostTheme(): ResolvedTheme;
3
+ export declare function observeThemeChanges(callback: (theme: ResolvedTheme) => void): () => void;
@@ -0,0 +1,121 @@
1
+ import { logger } from './logger';
2
+ export function detectHostTheme() {
3
+ const root = document.documentElement;
4
+ const body = document.body;
5
+ if (hasDarkClass(root) || hasDarkClass(body)) {
6
+ logger.debug('[ThemeDetection] Detected dark theme via CSS class on html/body');
7
+ return 'dark';
8
+ }
9
+ if (hasLightClass(root) || hasLightClass(body)) {
10
+ logger.debug('[ThemeDetection] Detected light theme via CSS class on html/body');
11
+ return 'light';
12
+ }
13
+ if (hasDarkDataAttribute(root) || hasDarkDataAttribute(body)) {
14
+ logger.debug('[ThemeDetection] Detected dark theme via data attribute on html/body');
15
+ return 'dark';
16
+ }
17
+ if (hasLightDataAttribute(root) || hasLightDataAttribute(body)) {
18
+ logger.debug('[ThemeDetection] Detected light theme via data attribute on html/body');
19
+ return 'light';
20
+ }
21
+ const domTheme = detectThemeFromDomElements();
22
+ if (domTheme) {
23
+ logger.debug('[ThemeDetection] Detected', domTheme, 'theme via DOM element');
24
+ return domTheme;
25
+ }
26
+ if (globalThis.matchMedia('(prefers-color-scheme: dark)').matches) {
27
+ logger.debug('[ThemeDetection] Detected dark theme via system preference');
28
+ return 'dark';
29
+ }
30
+ logger.debug('[ThemeDetection] Defaulting to light theme');
31
+ return 'light';
32
+ }
33
+ function detectThemeFromDomElements() {
34
+ const darkClassSelectors = '.dark, .dark-mode, .theme-dark, .dark-theme';
35
+ const lightClassSelectors = '.light, .light-mode, .theme-light, .light-theme';
36
+ const dataAttributeSelectors = '[data-theme], [data-mode], [data-bs-theme], [data-color-scheme]';
37
+ const darkElement = document.querySelector(darkClassSelectors);
38
+ if (darkElement) {
39
+ return 'dark';
40
+ }
41
+ const lightElement = document.querySelector(lightClassSelectors);
42
+ if (lightElement) {
43
+ return 'light';
44
+ }
45
+ const elementsWithDataAttr = document.querySelectorAll(dataAttributeSelectors);
46
+ for (const element of elementsWithDataAttr) {
47
+ if (element instanceof HTMLElement) {
48
+ if (hasDarkDataAttribute(element)) {
49
+ return 'dark';
50
+ }
51
+ if (hasLightDataAttribute(element)) {
52
+ return 'light';
53
+ }
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ function hasDarkClass(element) {
59
+ return (element.classList.contains('dark') ||
60
+ element.classList.contains('dark-mode') ||
61
+ element.classList.contains('theme-dark') ||
62
+ element.classList.contains('dark-theme'));
63
+ }
64
+ function hasLightClass(element) {
65
+ return (element.classList.contains('light') ||
66
+ element.classList.contains('light-mode') ||
67
+ element.classList.contains('theme-light') ||
68
+ element.classList.contains('light-theme'));
69
+ }
70
+ function hasDarkDataAttribute(element) {
71
+ return (element.dataset.theme === 'dark' ||
72
+ element.dataset.mode === 'dark' ||
73
+ element.dataset.bsTheme === 'dark' ||
74
+ element.dataset.colorScheme === 'dark');
75
+ }
76
+ function hasLightDataAttribute(element) {
77
+ return (element.dataset.theme === 'light' ||
78
+ element.dataset.mode === 'light' ||
79
+ element.dataset.bsTheme === 'light' ||
80
+ element.dataset.colorScheme === 'light');
81
+ }
82
+ export function observeThemeChanges(callback) {
83
+ let currentTheme = detectHostTheme();
84
+ let debounceTimer = null;
85
+ const handlePotentialThemeChange = () => {
86
+ if (debounceTimer) {
87
+ clearTimeout(debounceTimer);
88
+ }
89
+ debounceTimer = setTimeout(() => {
90
+ const newTheme = detectHostTheme();
91
+ if (newTheme !== currentTheme) {
92
+ currentTheme = newTheme;
93
+ logger.debug('[ThemeDetection] Theme changed to:', newTheme);
94
+ callback(newTheme);
95
+ }
96
+ }, 50);
97
+ };
98
+ const observer = new MutationObserver(handlePotentialThemeChange);
99
+ observer.observe(document.documentElement, {
100
+ attributes: true,
101
+ attributeFilter: ['class', 'data-theme', 'data-mode', 'data-bs-theme', 'data-color-scheme'],
102
+ subtree: true,
103
+ });
104
+ const mediaQuery = globalThis.matchMedia('(prefers-color-scheme: dark)');
105
+ const mediaQueryHandler = () => {
106
+ const newTheme = detectHostTheme();
107
+ if (newTheme !== currentTheme) {
108
+ currentTheme = newTheme;
109
+ logger.debug('[ThemeDetection] System preference changed, theme is now:', newTheme);
110
+ callback(newTheme);
111
+ }
112
+ };
113
+ mediaQuery.addEventListener('change', mediaQueryHandler);
114
+ return () => {
115
+ if (debounceTimer) {
116
+ clearTimeout(debounceTimer);
117
+ }
118
+ observer.disconnect();
119
+ mediaQuery.removeEventListener('change', mediaQueryHandler);
120
+ };
121
+ }
@@ -0,0 +1,10 @@
1
+ export declare function isGerman(language?: string): boolean;
2
+ export declare const DELEGATION_TOOLS: Set<string>;
3
+ export declare function getIntermediateStatus(language?: string): string;
4
+ export declare function getSynthesizingStatus(language?: string): string;
5
+ export declare function getToolStatusLabel(toolName: string, language?: string): string;
6
+ export interface ToolCallState {
7
+ readonly agentStatus: string | null;
8
+ readonly isDelegationActive: boolean;
9
+ }
10
+ export declare function resolveToolCallState(toolName: string, status: string, isDelegationActive: boolean, language?: string, agentLabel?: string): ToolCallState;
@@ -0,0 +1,61 @@
1
+ const toolStatusLabelsDe = {
2
+ search_web: 'Durchsuche das Web...',
3
+ fetch_page: 'Lade Webseite...',
4
+ fetch_pages: 'Lade mehrere Webseiten...',
5
+ read_page: 'Lese Seiteninhalt...',
6
+ fill_fields: 'Fülle Formularfelder aus...',
7
+ click_element: 'Klicke auf Element...',
8
+ navigate_to: 'Navigiere...',
9
+ delegate_research: 'Recherchiere...',
10
+ interact_with_page: 'Arbeite auf der Seite...',
11
+ fan_out_research: 'Parallele Recherche...',
12
+ };
13
+ const toolStatusLabelsEn = {
14
+ search_web: 'Searching the web...',
15
+ fetch_page: 'Reading a web page...',
16
+ fetch_pages: 'Reading multiple pages...',
17
+ read_page: 'Reading page content...',
18
+ fill_fields: 'Filling form fields...',
19
+ click_element: 'Clicking element...',
20
+ navigate_to: 'Navigating...',
21
+ delegate_research: 'Researching...',
22
+ interact_with_page: 'Working on page...',
23
+ fan_out_research: 'Parallel research...',
24
+ };
25
+ export function isGerman(language) {
26
+ return language?.startsWith('de') ?? true;
27
+ }
28
+ export const DELEGATION_TOOLS = new Set(['delegate_research', 'interact_with_page', 'fan_out_research']);
29
+ export function getIntermediateStatus(language) {
30
+ return isGerman(language) ? 'Analysiere Ergebnisse...' : 'Analyzing results...';
31
+ }
32
+ export function getSynthesizingStatus(language) {
33
+ return isGerman(language) ? 'Bereite Antwort vor...' : 'Preparing response...';
34
+ }
35
+ export function getToolStatusLabel(toolName, language) {
36
+ const labels = isGerman(language) ? toolStatusLabelsDe : toolStatusLabelsEn;
37
+ const fallback = isGerman(language) ? 'Arbeite...' : 'Working...';
38
+ return labels[toolName] ?? fallback;
39
+ }
40
+ export function resolveToolCallState(toolName, status, isDelegationActive, language, agentLabel) {
41
+ if (status === 'started' || status === 'in_progress') {
42
+ const baseLabel = getToolStatusLabel(toolName, language);
43
+ const label = agentLabel ? `${agentLabel}: ${baseLabel}` : baseLabel;
44
+ return {
45
+ agentStatus: label,
46
+ isDelegationActive: isDelegationActive || DELEGATION_TOOLS.has(toolName),
47
+ };
48
+ }
49
+ if (status === 'synthesizing' && isDelegationActive) {
50
+ return { agentStatus: getSynthesizingStatus(language), isDelegationActive };
51
+ }
52
+ if (DELEGATION_TOOLS.has(toolName) && (status === 'completed' || status === 'failed')) {
53
+ return { agentStatus: getSynthesizingStatus(language), isDelegationActive: false };
54
+ }
55
+ if (isDelegationActive) {
56
+ const baseLabel = getIntermediateStatus(language);
57
+ const label = agentLabel ? `${agentLabel}: ${baseLabel}` : baseLabel;
58
+ return { agentStatus: label, isDelegationActive };
59
+ }
60
+ return { agentStatus: null, isDelegationActive };
61
+ }
@@ -0,0 +1,3 @@
1
+ export declare function playMuteSound(): void;
2
+ export declare function playUnmuteSound(): void;
3
+ export declare function playEndCallSound(): void;
@@ -0,0 +1,57 @@
1
+ import { getSharedAudioContext, withResumedContext } from './audio-context';
2
+ import { logger } from './logger';
3
+ const MUTE_PULSES = [
4
+ { frequency: 523.25, durationSec: 0.06 },
5
+ { frequency: 349.23, durationSec: 0.08 },
6
+ ];
7
+ const UNMUTE_PULSES = [
8
+ { frequency: 349.23, durationSec: 0.06 },
9
+ { frequency: 523.25, durationSec: 0.08 },
10
+ ];
11
+ const END_CALL_PULSES = [
12
+ { frequency: 440, durationSec: 0.07 },
13
+ { frequency: 329.63, durationSec: 0.07 },
14
+ { frequency: 246.94, durationSec: 0.1 },
15
+ ];
16
+ const PEAK_GAIN = 0.18;
17
+ const ATTACK_SEC = 0.004;
18
+ const HOLD_SEC = 0.01;
19
+ const GAP_SEC = 0.015;
20
+ function schedulePulse(ctx, pulse, startAt, destination) {
21
+ const envelope = ctx.createGain();
22
+ const peakAt = startAt + ATTACK_SEC;
23
+ const holdEnd = peakAt + HOLD_SEC;
24
+ const releaseAt = startAt + pulse.durationSec;
25
+ envelope.gain.setValueAtTime(0.0001, startAt);
26
+ envelope.gain.exponentialRampToValueAtTime(PEAK_GAIN, peakAt);
27
+ envelope.gain.setValueAtTime(PEAK_GAIN, holdEnd);
28
+ envelope.gain.exponentialRampToValueAtTime(0.0001, releaseAt);
29
+ envelope.connect(destination);
30
+ const osc = ctx.createOscillator();
31
+ osc.type = 'sine';
32
+ osc.frequency.value = pulse.frequency;
33
+ osc.connect(envelope);
34
+ osc.start(startAt);
35
+ osc.stop(releaseAt + 0.02);
36
+ }
37
+ function playPulseSequence(label, pulses) {
38
+ const ctx = getSharedAudioContext();
39
+ if (!ctx)
40
+ return;
41
+ withResumedContext(ctx, () => {
42
+ let cursor = ctx.currentTime + 0.01;
43
+ for (const pulse of pulses) {
44
+ schedulePulse(ctx, pulse, cursor, ctx.destination);
45
+ cursor += pulse.durationSec + GAP_SEC;
46
+ }
47
+ }, (err) => logger.debug(`[UiSounds] ${label} failed`, err));
48
+ }
49
+ export function playMuteSound() {
50
+ playPulseSequence('mute', MUTE_PULSES);
51
+ }
52
+ export function playUnmuteSound() {
53
+ playPulseSequence('unmute', UNMUTE_PULSES);
54
+ }
55
+ export function playEndCallSound() {
56
+ playPulseSequence('end-call', END_CALL_PULSES);
57
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@nextpa/chat-core",
3
+ "version": "0.1.0",
4
+ "description": "Framework-neutral voice & text chat engine for the NextPA platform.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://developers.nextpa.dev",
8
+ "bugs": {
9
+ "url": "https://developers.nextpa.dev"
10
+ },
11
+ "keywords": [
12
+ "nextpa",
13
+ "chat",
14
+ "voice",
15
+ "realtime",
16
+ "ai",
17
+ "assistant",
18
+ "sdk",
19
+ "openai",
20
+ "gemini"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./openai": {
31
+ "types": "./dist/openai.d.ts",
32
+ "default": "./dist/openai.js"
33
+ },
34
+ "./google": {
35
+ "types": "./dist/google.d.ts",
36
+ "default": "./dist/google.js"
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "peerDependencies": {
44
+ "@google/genai": "^2.8.0",
45
+ "@microsoft/signalr": "^10.0.0",
46
+ "@openai/agents-realtime": "^0.11.6"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "@google/genai": {
50
+ "optional": true
51
+ },
52
+ "@openai/agents-realtime": {
53
+ "optional": true
54
+ }
55
+ }
56
+ }