@bytexbyte/nxtlinq-ai-agent-web-development 0.1.1
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/dist/context/NxtlinqAgentContext.d.ts +12 -0
- package/dist/context/NxtlinqAgentContext.d.ts.map +1 -0
- package/dist/context/NxtlinqAgentContext.js +33 -0
- package/dist/createNxtlinqAgent.d.ts +9 -0
- package/dist/createNxtlinqAgent.d.ts.map +1 -0
- package/dist/createNxtlinqAgent.js +19 -0
- package/dist/hooks/useNxtlinqAgent.d.ts +18 -0
- package/dist/hooks/useNxtlinqAgent.d.ts.map +1 -0
- package/dist/hooks/useNxtlinqAgent.js +23 -0
- package/dist/hooks/useNxtlinqVoice.d.ts +21 -0
- package/dist/hooks/useNxtlinqVoice.d.ts.map +1 -0
- package/dist/hooks/useNxtlinqVoice.js +75 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/legacy/api/nxtlinq-api.d.ts +8 -0
- package/dist/legacy/api/nxtlinq-api.d.ts.map +1 -0
- package/dist/legacy/api/nxtlinq-api.js +13 -0
- package/dist/legacy/api/voice.d.ts +11 -0
- package/dist/legacy/api/voice.d.ts.map +1 -0
- package/dist/legacy/api/voice.js +26 -0
- package/dist/legacy/core/lib/messageHistory.d.ts +2 -0
- package/dist/legacy/core/lib/messageHistory.d.ts.map +1 -0
- package/dist/legacy/core/lib/messageHistory.js +1 -0
- package/dist/legacy/core/lib/textToSpeech.d.ts +14 -0
- package/dist/legacy/core/lib/textToSpeech.d.ts.map +1 -0
- package/dist/legacy/core/lib/textToSpeech.js +82 -0
- package/dist/legacy/core/lib/useDraggable.d.ts +15 -0
- package/dist/legacy/core/lib/useDraggable.d.ts.map +1 -0
- package/dist/legacy/core/lib/useDraggable.js +158 -0
- package/dist/legacy/core/lib/useLocalStorage.d.ts +11 -0
- package/dist/legacy/core/lib/useLocalStorage.d.ts.map +1 -0
- package/dist/legacy/core/lib/useLocalStorage.js +83 -0
- package/dist/legacy/core/lib/useResizable.d.ts +17 -0
- package/dist/legacy/core/lib/useResizable.d.ts.map +1 -0
- package/dist/legacy/core/lib/useResizable.js +203 -0
- package/dist/legacy/core/lib/useSessionStorage.d.ts +11 -0
- package/dist/legacy/core/lib/useSessionStorage.d.ts.map +1 -0
- package/dist/legacy/core/lib/useSessionStorage.js +37 -0
- package/dist/legacy/core/lib/useSpeechToTextFromMic/helper.d.ts +26 -0
- package/dist/legacy/core/lib/useSpeechToTextFromMic/helper.d.ts.map +1 -0
- package/dist/legacy/core/lib/useSpeechToTextFromMic/helper.js +102 -0
- package/dist/legacy/core/lib/useSpeechToTextFromMic/index.d.ts +16 -0
- package/dist/legacy/core/lib/useSpeechToTextFromMic/index.d.ts.map +1 -0
- package/dist/legacy/core/lib/useSpeechToTextFromMic/index.js +92 -0
- package/dist/legacy/core/lib/useVoiceMode.d.ts +32 -0
- package/dist/legacy/core/lib/useVoiceMode.d.ts.map +1 -0
- package/dist/legacy/core/lib/useVoiceMode.js +373 -0
- package/dist/legacy/core/metakeepClient.d.ts +4 -0
- package/dist/legacy/core/metakeepClient.d.ts.map +1 -0
- package/dist/legacy/core/metakeepClient.js +10 -0
- package/dist/legacy/core/utils/aitUtils.d.ts +31 -0
- package/dist/legacy/core/utils/aitUtils.d.ts.map +1 -0
- package/dist/legacy/core/utils/aitUtils.js +35 -0
- package/dist/legacy/core/utils/ethersUtils.d.ts +8 -0
- package/dist/legacy/core/utils/ethersUtils.d.ts.map +1 -0
- package/dist/legacy/core/utils/ethersUtils.js +19 -0
- package/dist/legacy/core/utils/index.d.ts +3 -0
- package/dist/legacy/core/utils/index.d.ts.map +1 -0
- package/dist/legacy/core/utils/index.js +4 -0
- package/dist/legacy/core/utils/notificationUtils.d.ts +29 -0
- package/dist/legacy/core/utils/notificationUtils.d.ts.map +1 -0
- package/dist/legacy/core/utils/notificationUtils.js +47 -0
- package/dist/legacy/core/utils/urlUtils.d.ts +25 -0
- package/dist/legacy/core/utils/urlUtils.d.ts.map +1 -0
- package/dist/legacy/core/utils/urlUtils.js +135 -0
- package/dist/legacy/core/utils/walletTextUtils.d.ts +14 -0
- package/dist/legacy/core/utils/walletTextUtils.d.ts.map +1 -0
- package/dist/legacy/core/utils/walletTextUtils.js +23 -0
- package/dist/legacy/core/utils/walletUtils.d.ts +10 -0
- package/dist/legacy/core/utils/walletUtils.d.ts.map +1 -0
- package/dist/legacy/core/utils/walletUtils.js +38 -0
- package/dist/legacy/index.d.ts +19 -0
- package/dist/legacy/index.d.ts.map +1 -0
- package/dist/legacy/index.js +16 -0
- package/dist/ports/createWebPlatformPorts.d.ts +13 -0
- package/dist/ports/createWebPlatformPorts.d.ts.map +1 -0
- package/dist/ports/createWebPlatformPorts.js +25 -0
- package/dist/utils/fileToAttachment.d.ts +4 -0
- package/dist/utils/fileToAttachment.d.ts.map +1 -0
- package/dist/utils/fileToAttachment.js +28 -0
- package/dist/voice/useVoiceSilenceCommit.d.ts +11 -0
- package/dist/voice/useVoiceSilenceCommit.d.ts.map +1 -0
- package/dist/voice/useVoiceSilenceCommit.js +68 -0
- package/dist/voice/useVoiceTranscriptMessages.d.ts +16 -0
- package/dist/voice/useVoiceTranscriptMessages.d.ts.map +1 -0
- package/dist/voice/useVoiceTranscriptMessages.js +134 -0
- package/dist/voice/useWsRealtimeAudio.d.ts +18 -0
- package/dist/voice/useWsRealtimeAudio.d.ts.map +1 -0
- package/dist/voice/useWsRealtimeAudio.js +115 -0
- package/dist/voice/voiceMicConstants.d.ts +4 -0
- package/dist/voice/voiceMicConstants.d.ts.map +1 -0
- package/dist/voice/voiceMicConstants.js +10 -0
- package/dist/voice/ws/BrowserWsPcmPlayer.d.ts +23 -0
- package/dist/voice/ws/BrowserWsPcmPlayer.d.ts.map +1 -0
- package/dist/voice/ws/BrowserWsPcmPlayer.js +138 -0
- package/dist/voice/ws/BrowserWsPcmRecorder.d.ts +19 -0
- package/dist/voice/ws/BrowserWsPcmRecorder.d.ts.map +1 -0
- package/dist/voice/ws/BrowserWsPcmRecorder.js +76 -0
- package/dist/voice/ws/float32ToPcm16.d.ts +2 -0
- package/dist/voice/ws/float32ToPcm16.d.ts.map +1 -0
- package/dist/voice/ws/float32ToPcm16.js +8 -0
- package/dist/voice/ws/voiceSilenceConstants.d.ts +5 -0
- package/dist/voice/ws/voiceSilenceConstants.d.ts.map +1 -0
- package/dist/voice/ws/voiceSilenceConstants.js +4 -0
- package/dist/voice/ws/wsRealtimeConstants.d.ts +2 -0
- package/dist/voice/ws/wsRealtimeConstants.d.ts.map +1 -0
- package/dist/voice/ws/wsRealtimeConstants.js +1 -0
- package/dist/webAgentDefaults.d.ts +9 -0
- package/dist/webAgentDefaults.d.ts.map +1 -0
- package/dist/webAgentDefaults.js +9 -0
- package/package.json +55 -0
- package/src/context/NxtlinqAgentContext.tsx +79 -0
- package/src/createNxtlinqAgent.ts +36 -0
- package/src/hooks/useNxtlinqAgent.ts +73 -0
- package/src/hooks/useNxtlinqVoice.ts +143 -0
- package/src/index.ts +84 -0
- package/src/legacy/api/nxtlinq-api.ts +32 -0
- package/src/legacy/api/voice.ts +72 -0
- package/src/legacy/core/lib/messageHistory.ts +6 -0
- package/src/legacy/core/lib/textToSpeech.ts +127 -0
- package/src/legacy/core/lib/useDraggable.ts +193 -0
- package/src/legacy/core/lib/useLocalStorage.ts +89 -0
- package/src/legacy/core/lib/useResizable.ts +256 -0
- package/src/legacy/core/lib/useSessionStorage.ts +43 -0
- package/src/legacy/core/lib/useSpeechToTextFromMic/helper.ts +132 -0
- package/src/legacy/core/lib/useSpeechToTextFromMic/index.ts +126 -0
- package/src/legacy/core/lib/useVoiceMode.ts +407 -0
- package/src/legacy/core/metakeepClient.ts +12 -0
- package/src/legacy/core/utils/aitUtils.ts +55 -0
- package/src/legacy/core/utils/ethersUtils.ts +24 -0
- package/src/legacy/core/utils/index.ts +5 -0
- package/src/legacy/core/utils/notificationUtils.ts +64 -0
- package/src/legacy/core/utils/urlUtils.ts +160 -0
- package/src/legacy/core/utils/walletTextUtils.ts +26 -0
- package/src/legacy/core/utils/walletUtils.ts +53 -0
- package/src/legacy/index.ts +35 -0
- package/src/ports/createWebPlatformPorts.ts +44 -0
- package/src/utils/fileToAttachment.ts +32 -0
- package/src/voice/useVoiceSilenceCommit.ts +84 -0
- package/src/voice/useVoiceTranscriptMessages.ts +184 -0
- package/src/voice/useWsRealtimeAudio.ts +141 -0
- package/src/voice/voiceMicConstants.ts +13 -0
- package/src/voice/ws/BrowserWsPcmPlayer.ts +139 -0
- package/src/voice/ws/BrowserWsPcmRecorder.ts +83 -0
- package/src/voice/ws/float32ToPcm16.ts +8 -0
- package/src/voice/ws/voiceSilenceConstants.ts +4 -0
- package/src/voice/ws/wsRealtimeConstants.ts +1 -0
- package/src/webAgentDefaults.ts +12 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL utility functions for detecting and converting URLs to clickable links
|
|
3
|
+
*/
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
/**
|
|
6
|
+
* Regular expression to match Markdown-style links
|
|
7
|
+
* Matches [text](url) format
|
|
8
|
+
*/
|
|
9
|
+
const MARKDOWN_LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
10
|
+
/**
|
|
11
|
+
* Regular expression to match URLs
|
|
12
|
+
* Supports http, https, ftp, and www protocols
|
|
13
|
+
*/
|
|
14
|
+
const URL_REGEX = /(https?:\/\/[^\s]+|www\.[^\s]+|[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(?:\/[^\s]*)?)/g;
|
|
15
|
+
/**
|
|
16
|
+
* Check if a string contains URLs
|
|
17
|
+
* @param text - The text to check
|
|
18
|
+
* @returns boolean indicating if URLs are found
|
|
19
|
+
*/
|
|
20
|
+
export const containsUrls = (text) => {
|
|
21
|
+
return URL_REGEX.test(text);
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Convert text with line breaks to JSX elements with <br/> tags
|
|
25
|
+
* @param text - The text containing line breaks
|
|
26
|
+
* @param keyPrefix - Prefix for React keys
|
|
27
|
+
* @returns Array of JSX elements and strings
|
|
28
|
+
*/
|
|
29
|
+
const convertLineBreaks = (text, keyPrefix = '') => {
|
|
30
|
+
const parts = [];
|
|
31
|
+
const lines = text.split('\n');
|
|
32
|
+
lines.forEach((line, index) => {
|
|
33
|
+
if (index > 0) {
|
|
34
|
+
parts.push(React.createElement('br', { key: `${keyPrefix}-br-${index}` }));
|
|
35
|
+
}
|
|
36
|
+
if (line) {
|
|
37
|
+
parts.push(line);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return parts;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Convert text with URLs to JSX elements with clickable links
|
|
44
|
+
* Supports both Markdown format [text](url) and plain URLs
|
|
45
|
+
* Also handles line breaks (\n)
|
|
46
|
+
* @param text - The text containing URLs
|
|
47
|
+
* @returns Array of JSX elements and strings
|
|
48
|
+
*/
|
|
49
|
+
export const convertUrlsToLinks = (text) => {
|
|
50
|
+
const parts = [];
|
|
51
|
+
let lastIndex = 0;
|
|
52
|
+
let keyCounter = 0;
|
|
53
|
+
// First, find all markdown links and plain URLs
|
|
54
|
+
const items = [];
|
|
55
|
+
// Find markdown links
|
|
56
|
+
let markdownMatch;
|
|
57
|
+
MARKDOWN_LINK_REGEX.lastIndex = 0;
|
|
58
|
+
while ((markdownMatch = MARKDOWN_LINK_REGEX.exec(text)) !== null) {
|
|
59
|
+
items.push({
|
|
60
|
+
type: 'markdown',
|
|
61
|
+
start: markdownMatch.index,
|
|
62
|
+
end: markdownMatch.index + markdownMatch[0].length,
|
|
63
|
+
text: markdownMatch[1],
|
|
64
|
+
url: markdownMatch[2]
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Find plain URLs (but exclude those already matched as markdown links)
|
|
68
|
+
let urlMatch;
|
|
69
|
+
URL_REGEX.lastIndex = 0;
|
|
70
|
+
while ((urlMatch = URL_REGEX.exec(text)) !== null) {
|
|
71
|
+
const start = urlMatch.index;
|
|
72
|
+
const end = start + urlMatch[0].length;
|
|
73
|
+
// Check if this URL is already part of a markdown link
|
|
74
|
+
const isPartOfMarkdown = items.some(item => item.type === 'markdown' && start >= item.start && end <= item.end);
|
|
75
|
+
if (!isPartOfMarkdown) {
|
|
76
|
+
items.push({
|
|
77
|
+
type: 'url',
|
|
78
|
+
start,
|
|
79
|
+
end,
|
|
80
|
+
url: urlMatch[0]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Sort items by start position
|
|
85
|
+
items.sort((a, b) => a.start - b.start);
|
|
86
|
+
// Build the result
|
|
87
|
+
items.forEach(item => {
|
|
88
|
+
// Add text before this item (with line breaks)
|
|
89
|
+
if (item.start > lastIndex) {
|
|
90
|
+
const textBefore = text.slice(lastIndex, item.start);
|
|
91
|
+
const textBeforeParts = convertLineBreaks(textBefore, `text-${keyCounter}`);
|
|
92
|
+
parts.push(...textBeforeParts);
|
|
93
|
+
keyCounter++;
|
|
94
|
+
}
|
|
95
|
+
// Create clickable link
|
|
96
|
+
const href = item.url.startsWith('http') ? item.url : `https://${item.url}`;
|
|
97
|
+
const displayText = item.type === 'markdown' ? item.text : item.url;
|
|
98
|
+
parts.push(React.createElement('a', {
|
|
99
|
+
key: `link-${keyCounter++}`,
|
|
100
|
+
href: href,
|
|
101
|
+
target: '_blank',
|
|
102
|
+
rel: 'noopener noreferrer',
|
|
103
|
+
style: {
|
|
104
|
+
color: '#007bff',
|
|
105
|
+
textDecoration: 'underline',
|
|
106
|
+
cursor: 'pointer'
|
|
107
|
+
},
|
|
108
|
+
className: 'ai-agent-url-link',
|
|
109
|
+
onClick: (e) => {
|
|
110
|
+
e.preventDefault(); // Prevent default browser behavior
|
|
111
|
+
e.stopPropagation();
|
|
112
|
+
window.open(href, '_blank', 'noopener,noreferrer');
|
|
113
|
+
}
|
|
114
|
+
}, displayText));
|
|
115
|
+
lastIndex = item.end;
|
|
116
|
+
});
|
|
117
|
+
// Add remaining text after the last link (with line breaks)
|
|
118
|
+
if (lastIndex < text.length) {
|
|
119
|
+
const textAfter = text.slice(lastIndex);
|
|
120
|
+
const textAfterParts = convertLineBreaks(textAfter, `text-${keyCounter}`);
|
|
121
|
+
parts.push(...textAfterParts);
|
|
122
|
+
}
|
|
123
|
+
return parts.length > 0 ? parts : convertLineBreaks(text, 'default');
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Simple function to wrap URLs in anchor tags for basic HTML rendering
|
|
127
|
+
* @param text - The text containing URLs
|
|
128
|
+
* @returns HTML string with clickable links
|
|
129
|
+
*/
|
|
130
|
+
export const convertUrlsToHtml = (text) => {
|
|
131
|
+
return text.replace(URL_REGEX, (url) => {
|
|
132
|
+
const href = url.startsWith('http') ? url : `https://${url}`;
|
|
133
|
+
return `<a href="${href}" target="_blank" rel="noopener noreferrer" style="color: #007bff; text-decoration: underline; cursor: pointer;">${url}</a>`;
|
|
134
|
+
});
|
|
135
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adilas Service ID constant
|
|
3
|
+
*/
|
|
4
|
+
export declare const ADILAS_SERVICE_ID = "e48fc2b9-a7d1-49e3-85cb-9d621a0bf774";
|
|
5
|
+
/**
|
|
6
|
+
* Check if the given serviceId is the Adilas service
|
|
7
|
+
*/
|
|
8
|
+
export declare const isAdilasService: (serviceId: string | undefined | null) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Replace "wallet" with "account" in text when serviceId is Adilas service
|
|
11
|
+
* Preserves the original case of the word
|
|
12
|
+
*/
|
|
13
|
+
export declare const getWalletText: (defaultText: string, serviceId: string | undefined | null) => string;
|
|
14
|
+
//# sourceMappingURL=walletTextUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walletTextUtils.d.ts","sourceRoot":"","sources":["../../../../src/legacy/core/utils/walletTextUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,iBAAiB,yCAAyC,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,OAEtE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,EAAE,WAAW,MAAM,GAAG,SAAS,GAAG,IAAI,KAAG,MAQzF,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adilas Service ID constant
|
|
3
|
+
*/
|
|
4
|
+
export const ADILAS_SERVICE_ID = 'e48fc2b9-a7d1-49e3-85cb-9d621a0bf774';
|
|
5
|
+
/**
|
|
6
|
+
* Check if the given serviceId is the Adilas service
|
|
7
|
+
*/
|
|
8
|
+
export const isAdilasService = (serviceId) => {
|
|
9
|
+
return serviceId ? serviceId.trim() === ADILAS_SERVICE_ID : false;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Replace "wallet" with "account" in text when serviceId is Adilas service
|
|
13
|
+
* Preserves the original case of the word
|
|
14
|
+
*/
|
|
15
|
+
export const getWalletText = (defaultText, serviceId) => {
|
|
16
|
+
if (!isAdilasService(serviceId))
|
|
17
|
+
return defaultText;
|
|
18
|
+
// Replace wallet with account, preserving case
|
|
19
|
+
return defaultText.replace(/\bwallet\b/gi, (match) => {
|
|
20
|
+
// Preserve original case: 'Wallet' -> 'Account', 'wallet' -> 'account'
|
|
21
|
+
return match.charAt(0) === 'W' ? 'Account' : 'account';
|
|
22
|
+
});
|
|
23
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
export interface WalletConnectionResult {
|
|
3
|
+
address: string;
|
|
4
|
+
signer: ethers.JsonRpcSigner;
|
|
5
|
+
}
|
|
6
|
+
export declare const connectWallet: () => Promise<WalletConnectionResult>;
|
|
7
|
+
export declare const disconnectWallet: () => void;
|
|
8
|
+
export declare const getStoredWalletAddress: () => string | null;
|
|
9
|
+
export declare const validateToken: (token: string, expectedAddress: string) => boolean;
|
|
10
|
+
//# sourceMappingURL=walletUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walletUtils.d.ts","sourceRoot":"","sources":["../../../../src/legacy/core/utils/walletUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC;CAC9B;AAED,eAAO,MAAM,aAAa,QAAa,OAAO,CAAC,sBAAsB,CAwBpE,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,IAEnC,CAAC;AAEF,eAAO,MAAM,sBAAsB,QAAO,MAAM,GAAG,IAElD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,EAAE,iBAAiB,MAAM,KAAG,OAStE,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
import metakeepClient from '../metakeepClient';
|
|
3
|
+
import { getEthers } from './ethersUtils';
|
|
4
|
+
export const connectWallet = async () => {
|
|
5
|
+
if (typeof window === 'undefined') {
|
|
6
|
+
throw new Error('Web3 is not available in server-side rendering');
|
|
7
|
+
}
|
|
8
|
+
getEthers();
|
|
9
|
+
const web3Provider = await metakeepClient.ethereum;
|
|
10
|
+
if (!web3Provider) {
|
|
11
|
+
throw new Error('Web3 provider not available');
|
|
12
|
+
}
|
|
13
|
+
await web3Provider.enable();
|
|
14
|
+
// ethers v6: Use BrowserProvider instead of Web3Provider
|
|
15
|
+
const ethersProvider = new ethers.BrowserProvider(web3Provider);
|
|
16
|
+
// ethers v6: getSigner() and getAddress() are now async
|
|
17
|
+
const signer = await ethersProvider.getSigner();
|
|
18
|
+
const address = await signer.getAddress();
|
|
19
|
+
localStorage.setItem('walletAddress', address);
|
|
20
|
+
return { address, signer };
|
|
21
|
+
};
|
|
22
|
+
export const disconnectWallet = () => {
|
|
23
|
+
localStorage.removeItem('walletAddress');
|
|
24
|
+
};
|
|
25
|
+
export const getStoredWalletAddress = () => {
|
|
26
|
+
return localStorage.getItem('walletAddress');
|
|
27
|
+
};
|
|
28
|
+
export const validateToken = (token, expectedAddress) => {
|
|
29
|
+
try {
|
|
30
|
+
const payload = JSON.parse(atob(token.split('.')[1]));
|
|
31
|
+
const address = payload.address;
|
|
32
|
+
return address === expectedAddress;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error('Error parsing token:', error);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { createNxtlinqApi, setApiHosts, getAiAgentApiHost } from './api/nxtlinq-api';
|
|
2
|
+
export { startVoiceSession, createVoiceApi } from './api/voice';
|
|
3
|
+
export type * from './api/voice';
|
|
4
|
+
export { useDraggable } from './core/lib/useDraggable';
|
|
5
|
+
export { default as useLocalStorage } from './core/lib/useLocalStorage';
|
|
6
|
+
export { default as useSessionStorage } from './core/lib/useSessionStorage';
|
|
7
|
+
export { useResizable } from './core/lib/useResizable';
|
|
8
|
+
export type { ResizeCorner } from './core/lib/useResizable';
|
|
9
|
+
export { synthesizeSpeechToBuffer } from './core/lib/textToSpeech';
|
|
10
|
+
export { useSpeechToTextFromMic } from './core/lib/useSpeechToTextFromMic';
|
|
11
|
+
export { useVoiceMode } from './core/lib/useVoiceMode';
|
|
12
|
+
export { default as metakeepClient } from './core/metakeepClient';
|
|
13
|
+
export { getEthers, sleep } from './core/utils';
|
|
14
|
+
export * as walletTextUtils from './core/utils/walletTextUtils';
|
|
15
|
+
export { connectWallet, disconnectWallet, validateToken, } from './core/utils/walletUtils';
|
|
16
|
+
export { createAITMetadata, generateAITId, prepareAITCreation, } from './core/utils/aitUtils';
|
|
17
|
+
export { createNotification, getNotificationIcon, } from './core/utils/notificationUtils';
|
|
18
|
+
export { containsUrls, convertUrlsToLinks, convertUrlsToHtml, } from './core/utils/urlUtils';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/legacy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAChE,mBAAmB,aAAa,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,eAAe,MAAM,8BAA8B,CAAC;AAChE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { createNxtlinqApi, setApiHosts, getAiAgentApiHost } from './api/nxtlinq-api';
|
|
2
|
+
export { startVoiceSession, createVoiceApi } from './api/voice';
|
|
3
|
+
export { useDraggable } from './core/lib/useDraggable';
|
|
4
|
+
export { default as useLocalStorage } from './core/lib/useLocalStorage';
|
|
5
|
+
export { default as useSessionStorage } from './core/lib/useSessionStorage';
|
|
6
|
+
export { useResizable } from './core/lib/useResizable';
|
|
7
|
+
export { synthesizeSpeechToBuffer } from './core/lib/textToSpeech';
|
|
8
|
+
export { useSpeechToTextFromMic } from './core/lib/useSpeechToTextFromMic';
|
|
9
|
+
export { useVoiceMode } from './core/lib/useVoiceMode';
|
|
10
|
+
export { default as metakeepClient } from './core/metakeepClient';
|
|
11
|
+
export { getEthers, sleep } from './core/utils';
|
|
12
|
+
export * as walletTextUtils from './core/utils/walletTextUtils';
|
|
13
|
+
export { connectWallet, disconnectWallet, validateToken, } from './core/utils/walletUtils';
|
|
14
|
+
export { createAITMetadata, generateAITId, prepareAITCreation, } from './core/utils/aitUtils';
|
|
15
|
+
export { createNotification, getNotificationIcon, } from './core/utils/notificationUtils';
|
|
16
|
+
export { containsUrls, convertUrlsToLinks, convertUrlsToHtml, } from './core/utils/urlUtils';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PlatformPorts, StoragePort, WebRTCPort } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
export type CreateWebPlatformPortsOptions = {
|
|
3
|
+
/** Override default `localStorage` port. */
|
|
4
|
+
storage?: StoragePort;
|
|
5
|
+
fetchImpl?: typeof fetch;
|
|
6
|
+
getTimezone?: () => string;
|
|
7
|
+
/** Pre-built WebRTC port (overrides browser default). */
|
|
8
|
+
webrtc?: WebRTCPort;
|
|
9
|
+
/** Set `false` to disable voice (no WebRTC port). */
|
|
10
|
+
enableWebRTC?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function createWebPlatformPorts(options?: CreateWebPlatformPortsOptions): PlatformPorts;
|
|
13
|
+
//# sourceMappingURL=createWebPlatformPorts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createWebPlatformPorts.d.ts","sourceRoot":"","sources":["../../src/ports/createWebPlatformPorts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAOrH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,4CAA4C;IAC5C,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;IAC3B,yDAAyD;IACzD,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,qDAAqD;IACrD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAOF,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,6BAAkC,GAC1C,aAAa,CAkBf"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createBrowserStoragePort, createBrowserWebRTCPort, createDefaultHttpPort, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
function resolveHttpPort(fetchImpl) {
|
|
3
|
+
if (!fetchImpl)
|
|
4
|
+
return createDefaultHttpPort();
|
|
5
|
+
return { fetch: fetchImpl };
|
|
6
|
+
}
|
|
7
|
+
export function createWebPlatformPorts(options = {}) {
|
|
8
|
+
const enableWebRTC = options.enableWebRTC !== false;
|
|
9
|
+
return {
|
|
10
|
+
storage: options.storage ?? createBrowserStoragePort(),
|
|
11
|
+
http: resolveHttpPort(options.fetchImpl),
|
|
12
|
+
webrtc: enableWebRTC
|
|
13
|
+
? (options.webrtc ?? createBrowserWebRTCPort())
|
|
14
|
+
: undefined,
|
|
15
|
+
getTimezone: options.getTimezone
|
|
16
|
+
?? (() => {
|
|
17
|
+
try {
|
|
18
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return 'UTC';
|
|
22
|
+
}
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileToAttachment.d.ts","sourceRoot":"","sources":["../../src/utils/fileToAttachment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAqB/E,+DAA+D;AAC/D,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAStE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function readFileAsDataUri(file) {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const reader = new FileReader();
|
|
4
|
+
reader.onload = () => {
|
|
5
|
+
if (typeof reader.result === 'string') {
|
|
6
|
+
resolve(reader.result);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
reject(new Error('fileToAttachment: failed to read file'));
|
|
10
|
+
};
|
|
11
|
+
reader.onerror = () => reject(reader.error ?? new Error('fileToAttachment: read error'));
|
|
12
|
+
reader.readAsDataURL(file);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function attachmentType(mimeType) {
|
|
16
|
+
return mimeType.startsWith('image/') ? 'image' : 'file';
|
|
17
|
+
}
|
|
18
|
+
/** Build an {@link Attachment} from a browser {@link File}. */
|
|
19
|
+
export async function fileToAttachment(file) {
|
|
20
|
+
const url = await readFileAsDataUri(file);
|
|
21
|
+
return {
|
|
22
|
+
type: attachmentType(file.type || 'application/octet-stream'),
|
|
23
|
+
url,
|
|
24
|
+
name: file.name,
|
|
25
|
+
mimeType: file.type || 'application/octet-stream',
|
|
26
|
+
size: file.size,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VoiceSession, VoiceStatus } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
export declare function useVoiceSilenceCommit(getSession: () => VoiceSession | null, onMutedAfterCommit: () => void, voiceStatus: VoiceStatus): {
|
|
3
|
+
startPoll: () => void;
|
|
4
|
+
clearPoll: () => void;
|
|
5
|
+
resetTurn: () => void;
|
|
6
|
+
onSpeechRms: (rms: number) => void;
|
|
7
|
+
tryCommit: (reason: "silence" | "manual") => void;
|
|
8
|
+
consumeSkipCommitOnMute: () => boolean;
|
|
9
|
+
hasHadSpeech: () => boolean;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=useVoiceSilenceCommit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVoiceSilenceCommit.d.ts","sourceRoot":"","sources":["../../src/voice/useVoiceSilenceCommit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAU9F,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,YAAY,GAAG,IAAI,EACrC,kBAAkB,EAAE,MAAM,IAAI,EAC9B,WAAW,EAAE,WAAW;;;;uBAmDc,MAAM;wBAhCL,SAAS,GAAG,QAAQ;;;EAmD5D"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
import { ASSISTANT_MIC_HOLD_STATUSES } from './voiceMicConstants';
|
|
3
|
+
import { MIC_BARGE_IN_RMS_THRESHOLD, MIC_SILENCE_COMMIT_MS, MIC_SILENCE_POLL_MS, MIC_SPEECH_RMS_THRESHOLD, } from './ws/voiceSilenceConstants';
|
|
4
|
+
export function useVoiceSilenceCommit(getSession, onMutedAfterCommit, voiceStatus) {
|
|
5
|
+
const lastSpeechAtRef = useRef(0);
|
|
6
|
+
const hadSpeechRef = useRef(false);
|
|
7
|
+
const commitInFlightRef = useRef(false);
|
|
8
|
+
const skipCommitOnMuteRef = useRef(false);
|
|
9
|
+
const pollRef = useRef(null);
|
|
10
|
+
const voiceStatusRef = useRef(voiceStatus);
|
|
11
|
+
voiceStatusRef.current = voiceStatus;
|
|
12
|
+
const onMutedAfterCommitRef = useRef(onMutedAfterCommit);
|
|
13
|
+
onMutedAfterCommitRef.current = onMutedAfterCommit;
|
|
14
|
+
const clearPoll = useCallback(() => {
|
|
15
|
+
if (pollRef.current) {
|
|
16
|
+
clearInterval(pollRef.current);
|
|
17
|
+
pollRef.current = null;
|
|
18
|
+
}
|
|
19
|
+
}, []);
|
|
20
|
+
const tryCommit = useCallback((reason) => {
|
|
21
|
+
const session = getSession();
|
|
22
|
+
if (commitInFlightRef.current)
|
|
23
|
+
return;
|
|
24
|
+
if (!hadSpeechRef.current) {
|
|
25
|
+
session?.clearInputAudio?.();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
commitInFlightRef.current = true;
|
|
29
|
+
hadSpeechRef.current = false;
|
|
30
|
+
session?.commitInputAudio?.();
|
|
31
|
+
if (reason === 'silence') {
|
|
32
|
+
skipCommitOnMuteRef.current = true;
|
|
33
|
+
onMutedAfterCommitRef.current();
|
|
34
|
+
}
|
|
35
|
+
queueMicrotask(() => { commitInFlightRef.current = false; });
|
|
36
|
+
}, [getSession]);
|
|
37
|
+
const startPoll = useCallback(() => {
|
|
38
|
+
clearPoll();
|
|
39
|
+
pollRef.current = setInterval(() => {
|
|
40
|
+
if (commitInFlightRef.current || !hadSpeechRef.current)
|
|
41
|
+
return;
|
|
42
|
+
if (Date.now() - lastSpeechAtRef.current < MIC_SILENCE_COMMIT_MS)
|
|
43
|
+
return;
|
|
44
|
+
tryCommit('silence');
|
|
45
|
+
}, MIC_SILENCE_POLL_MS);
|
|
46
|
+
}, [clearPoll, tryCommit]);
|
|
47
|
+
const resetTurn = useCallback(() => {
|
|
48
|
+
hadSpeechRef.current = false;
|
|
49
|
+
lastSpeechAtRef.current = Date.now();
|
|
50
|
+
getSession()?.clearInputAudio?.();
|
|
51
|
+
}, [getSession]);
|
|
52
|
+
const onSpeechRms = useCallback((rms) => {
|
|
53
|
+
const threshold = ASSISTANT_MIC_HOLD_STATUSES.has(voiceStatusRef.current)
|
|
54
|
+
? MIC_BARGE_IN_RMS_THRESHOLD
|
|
55
|
+
: MIC_SPEECH_RMS_THRESHOLD;
|
|
56
|
+
if (rms >= threshold) {
|
|
57
|
+
lastSpeechAtRef.current = Date.now();
|
|
58
|
+
hadSpeechRef.current = true;
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
const consumeSkipCommitOnMute = useCallback(() => {
|
|
62
|
+
const skip = skipCommitOnMuteRef.current;
|
|
63
|
+
skipCommitOnMuteRef.current = false;
|
|
64
|
+
return skip;
|
|
65
|
+
}, []);
|
|
66
|
+
const hasHadSpeech = useCallback(() => hadSpeechRef.current, []);
|
|
67
|
+
return { startPoll, clearPoll, resetTurn, onSpeechRms, tryCommit, consumeSkipCommitOnMute, hasHadSpeech };
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Message, VoiceDoneEvent, VoiceTranscriptEvent } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
type InteractionMode = 'text' | 'voice';
|
|
3
|
+
type VoiceTranscriptAgentApi = {
|
|
4
|
+
getMessages: () => Message[];
|
|
5
|
+
setMessages: (messages: Message[]) => void;
|
|
6
|
+
syncVoiceTurnHistory: (options?: {
|
|
7
|
+
last?: number;
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export declare function useVoiceTranscriptMessages(api: VoiceTranscriptAgentApi, interactionMode: InteractionMode, voiceSessionId: string | null, getVoiceSessionId?: () => string | null): {
|
|
11
|
+
handleTranscript: (event: VoiceTranscriptEvent) => void;
|
|
12
|
+
handleDone: (event: VoiceDoneEvent) => void;
|
|
13
|
+
clearVoiceStream: () => void;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=useVoiceTranscriptMessages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVoiceTranscriptMessages.d.ts","sourceRoot":"","sources":["../../src/voice/useVoiceTranscriptMessages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,oBAAoB,EACrB,MAAM,8CAA8C,CAAC;AAKtD,KAAK,eAAe,GAAG,MAAM,GAAG,OAAO,CAAC;AAExC,KAAK,uBAAuB,GAAG;IAC7B,WAAW,EAAE,MAAM,OAAO,EAAE,CAAC;IAC7B,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAC3C,oBAAoB,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE,CAAC;AAWF,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,uBAAuB,EAC5B,eAAe,EAAE,eAAe,EAChC,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,iBAAiB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI;8BAqG7B,oBAAoB;wBA4BpB,cAAc;;EAwBzB"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { mergeStreamingTranscript } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
import { useCallback, useRef } from 'react';
|
|
3
|
+
import { flushSync } from 'react-dom';
|
|
4
|
+
const STREAM_PREFIX = 'voice-stream-';
|
|
5
|
+
function voiceMeta(sessionId) {
|
|
6
|
+
return {
|
|
7
|
+
voiceRealtime: true,
|
|
8
|
+
voiceSessionId: sessionId ?? undefined,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function useVoiceTranscriptMessages(api, interactionMode, voiceSessionId, getVoiceSessionId) {
|
|
12
|
+
const streamIdRef = useRef(null);
|
|
13
|
+
const sessionIdRef = useRef(voiceSessionId);
|
|
14
|
+
sessionIdRef.current = voiceSessionId;
|
|
15
|
+
const resolveSessionId = useCallback(() => getVoiceSessionId?.() ?? sessionIdRef.current, [getVoiceSessionId]);
|
|
16
|
+
const isVoiceUiActive = useCallback(() => interactionMode === 'voice', [interactionMode]);
|
|
17
|
+
const upsertStreaming = useCallback((text) => {
|
|
18
|
+
const messages = api.getMessages();
|
|
19
|
+
let streamId = streamIdRef.current;
|
|
20
|
+
if (!streamId) {
|
|
21
|
+
streamId = `${STREAM_PREFIX}${Date.now()}`;
|
|
22
|
+
streamIdRef.current = streamId;
|
|
23
|
+
}
|
|
24
|
+
const idx = messages.findIndex((m) => m.id === streamId);
|
|
25
|
+
const partialContent = idx >= 0
|
|
26
|
+
? mergeStreamingTranscript(messages[idx]?.partialContent ?? '', text)
|
|
27
|
+
: text;
|
|
28
|
+
const meta = voiceMeta(resolveSessionId());
|
|
29
|
+
const apply = () => {
|
|
30
|
+
if (idx >= 0) {
|
|
31
|
+
api.setMessages(messages.map((m, i) => i === idx
|
|
32
|
+
? { ...m, partialContent, isStreaming: true, metadata: { ...m.metadata, ...meta } }
|
|
33
|
+
: m));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
api.setMessages([
|
|
37
|
+
...messages,
|
|
38
|
+
{
|
|
39
|
+
id: streamId,
|
|
40
|
+
role: 'assistant',
|
|
41
|
+
content: '',
|
|
42
|
+
partialContent,
|
|
43
|
+
isStreaming: true,
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
metadata: meta,
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
};
|
|
49
|
+
flushSync(apply);
|
|
50
|
+
}, [api, resolveSessionId]);
|
|
51
|
+
const finalizeAssistant = useCallback((text, messageId) => {
|
|
52
|
+
const trimmed = text.trim();
|
|
53
|
+
streamIdRef.current = null;
|
|
54
|
+
if (!trimmed)
|
|
55
|
+
return;
|
|
56
|
+
const messages = api.getMessages();
|
|
57
|
+
const streamIdx = messages.findIndex((m) => m.isStreaming && m.role === 'assistant');
|
|
58
|
+
if (streamIdx >= 0) {
|
|
59
|
+
api.setMessages(messages.map((m, i) => i === streamIdx
|
|
60
|
+
? {
|
|
61
|
+
...m,
|
|
62
|
+
id: messageId ?? m.id,
|
|
63
|
+
content: trimmed,
|
|
64
|
+
partialContent: undefined,
|
|
65
|
+
isStreaming: false,
|
|
66
|
+
metadata: { ...m.metadata, ...voiceMeta(resolveSessionId()) },
|
|
67
|
+
}
|
|
68
|
+
: m));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const last = messages[messages.length - 1];
|
|
72
|
+
if (last?.role === 'assistant' && last.content === trimmed)
|
|
73
|
+
return;
|
|
74
|
+
api.setMessages([
|
|
75
|
+
...messages,
|
|
76
|
+
{
|
|
77
|
+
id: messageId ?? `voice-asst-${Date.now()}`,
|
|
78
|
+
role: 'assistant',
|
|
79
|
+
content: trimmed,
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
metadata: voiceMeta(resolveSessionId()),
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
}, [api, resolveSessionId]);
|
|
85
|
+
const handleTranscript = useCallback((event) => {
|
|
86
|
+
if (!isVoiceUiActive())
|
|
87
|
+
return;
|
|
88
|
+
const text = event.text?.trim() ?? '';
|
|
89
|
+
if (event.role === 'assistant') {
|
|
90
|
+
// Keep one streaming bubble for the whole turn; finalize only in handleDone.
|
|
91
|
+
if (text)
|
|
92
|
+
upsertStreaming(text);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (event.role === 'user' && !event.interim && text) {
|
|
96
|
+
const messages = api.getMessages();
|
|
97
|
+
const last = messages[messages.length - 1];
|
|
98
|
+
if (last?.role === 'user' && last.content === text)
|
|
99
|
+
return;
|
|
100
|
+
api.setMessages([
|
|
101
|
+
...messages,
|
|
102
|
+
{
|
|
103
|
+
id: `voice-user-${Date.now()}`,
|
|
104
|
+
role: 'user',
|
|
105
|
+
content: text,
|
|
106
|
+
timestamp: new Date().toISOString(),
|
|
107
|
+
metadata: voiceMeta(resolveSessionId()),
|
|
108
|
+
},
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
}, [api, finalizeAssistant, isVoiceUiActive, resolveSessionId, upsertStreaming]);
|
|
112
|
+
const handleDone = useCallback((event) => {
|
|
113
|
+
if (!isVoiceUiActive())
|
|
114
|
+
return;
|
|
115
|
+
if (event.guardrailsBlocked || event.billingBlocked || event.error) {
|
|
116
|
+
streamIdRef.current = null;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const reply = event.replyText?.trim() ?? '';
|
|
120
|
+
if (reply) {
|
|
121
|
+
finalizeAssistant(reply, event.assistantMessageId ?? undefined);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
streamIdRef.current = null;
|
|
125
|
+
}
|
|
126
|
+
void api.syncVoiceTurnHistory({ last: 20 }).catch((err) => {
|
|
127
|
+
console.warn('[nxtlinq] syncVoiceTurnHistory after voice turn failed', err);
|
|
128
|
+
});
|
|
129
|
+
}, [api, finalizeAssistant, isVoiceUiActive]);
|
|
130
|
+
const clearVoiceStream = useCallback(() => {
|
|
131
|
+
streamIdRef.current = null;
|
|
132
|
+
}, []);
|
|
133
|
+
return { handleTranscript, handleDone, clearVoiceStream };
|
|
134
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { VoiceSession, VoiceStatus } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
type WsVoiceCallbacks = {
|
|
3
|
+
onOpen?: () => void;
|
|
4
|
+
onAudioDelta?: (pcm16: Int16Array | ArrayBuffer) => void;
|
|
5
|
+
onClose?: (reason: string) => void;
|
|
6
|
+
onError?: (err: Error) => void;
|
|
7
|
+
};
|
|
8
|
+
export type UseWsRealtimeAudioOptions = {
|
|
9
|
+
voiceStatus: VoiceStatus;
|
|
10
|
+
muteAfterSilenceCommit: () => void;
|
|
11
|
+
};
|
|
12
|
+
export declare function useWsRealtimeAudio(isCaptureActive: boolean, isVoiceActive: boolean, options: UseWsRealtimeAudioOptions): {
|
|
13
|
+
bindSession: (session: VoiceSession | null, captureWhenUnmuted?: boolean) => void;
|
|
14
|
+
buildCallbacks: (overrides?: Partial<WsVoiceCallbacks>) => WsVoiceCallbacks;
|
|
15
|
+
getOutputAudioLevel: () => number;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=useWsRealtimeAudio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWsRealtimeAudio.d.ts","sourceRoot":"","sources":["../../src/voice/useWsRealtimeAudio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAM9F,KAAK,gBAAgB,GAAG;IACtB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,KAAK,IAAI,CAAC;IACzD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,WAAW,EAAE,WAAW,CAAC;IACzB,sBAAsB,EAAE,MAAM,IAAI,CAAC;CACpC,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,OAAO,EACtB,OAAO,EAAE,yBAAyB;2BA+EQ,YAAY,GAAG,IAAI;iCAQb,OAAO,CAAC,gBAAgB,CAAC,KAAG,gBAAgB;;EAgC7F"}
|