@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,55 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
import stringify from 'fast-json-stable-stringify';
|
|
3
|
+
|
|
4
|
+
export interface AITMetadata {
|
|
5
|
+
permissions: string[];
|
|
6
|
+
issuedBy: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CreateAITParams {
|
|
10
|
+
aitId: string;
|
|
11
|
+
controller: string;
|
|
12
|
+
serviceId: string;
|
|
13
|
+
metadataHash: string;
|
|
14
|
+
metadataCid: string;
|
|
15
|
+
isFromAIAgent?: boolean; // Identify if created by AI Agent
|
|
16
|
+
parentAITId?: string; // Parent AIT ID, if created by AI Agent
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const generateAITId = (address: string): string => {
|
|
20
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
21
|
+
return `did:polygon:nxtlinq:${address}:${timestamp}`;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const createAITMetadata = (permissions: string[], issuedBy: string): AITMetadata => {
|
|
25
|
+
return {
|
|
26
|
+
permissions,
|
|
27
|
+
issuedBy,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const generateMetadataHash = (metadata: AITMetadata): string => {
|
|
32
|
+
const metadataStr = stringify(metadata);
|
|
33
|
+
return ethers.keccak256(ethers.toUtf8Bytes(metadataStr));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const prepareAITCreation = (address: string, permissions: string[], serviceId: string, isFromAIAgent = false, parentAITId?: string) => {
|
|
37
|
+
const aitId = generateAITId(address);
|
|
38
|
+
const metadata = createAITMetadata(permissions, address);
|
|
39
|
+
const metadataHash = generateMetadataHash(metadata);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
aitId,
|
|
43
|
+
metadata,
|
|
44
|
+
metadataHash,
|
|
45
|
+
createAITParams: {
|
|
46
|
+
aitId,
|
|
47
|
+
controller: address,
|
|
48
|
+
serviceId,
|
|
49
|
+
metadataHash,
|
|
50
|
+
metadataCid: '', // Will be set after metadata upload
|
|
51
|
+
isFromAIAgent,
|
|
52
|
+
parentAITId,
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Safely get ethers library instance
|
|
5
|
+
* Tries imported ethers first, then falls back to window.ethers
|
|
6
|
+
* @throws Error if ethers is not available
|
|
7
|
+
*/
|
|
8
|
+
export const getEthers = (): typeof ethers => {
|
|
9
|
+
// Try to use the imported ethers first (most common case)
|
|
10
|
+
if (ethers && typeof ethers === 'object') {
|
|
11
|
+
return ethers;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Fallback: try to get from global window object (in case client site provides it)
|
|
15
|
+
if (typeof window !== 'undefined' && (window as any).ethers) {
|
|
16
|
+
return (window as any).ethers;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Provide error message
|
|
20
|
+
console.error('[Nxtlinq SDK] Failed to get ethers library');
|
|
21
|
+
throw new Error(
|
|
22
|
+
'ethers library is not properly loaded. Please ensure ethers v6 is available.'
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type NotificationType = 'success' | 'error' | 'warning' | 'info';
|
|
2
|
+
|
|
3
|
+
export interface Notification {
|
|
4
|
+
show: boolean;
|
|
5
|
+
type: NotificationType;
|
|
6
|
+
message: string;
|
|
7
|
+
autoHide?: boolean;
|
|
8
|
+
duration?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const createNotification = (
|
|
12
|
+
type: NotificationType,
|
|
13
|
+
message: string,
|
|
14
|
+
duration = 5000
|
|
15
|
+
): Notification => ({
|
|
16
|
+
show: true,
|
|
17
|
+
type,
|
|
18
|
+
message,
|
|
19
|
+
autoHide: true,
|
|
20
|
+
duration
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const getNotificationIcon = (type: NotificationType): string => {
|
|
24
|
+
switch (type) {
|
|
25
|
+
case 'success':
|
|
26
|
+
return '✅';
|
|
27
|
+
case 'error':
|
|
28
|
+
return '❌';
|
|
29
|
+
case 'warning':
|
|
30
|
+
return '⚠️';
|
|
31
|
+
default:
|
|
32
|
+
return 'ℹ️';
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const getNotificationStyles = (type: NotificationType) => {
|
|
37
|
+
const baseStyles = {
|
|
38
|
+
position: 'fixed' as const,
|
|
39
|
+
bottom: 20,
|
|
40
|
+
right: 20,
|
|
41
|
+
padding: '12px 24px',
|
|
42
|
+
borderRadius: 8,
|
|
43
|
+
zIndex: 2000,
|
|
44
|
+
fontWeight: 600,
|
|
45
|
+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
|
46
|
+
transition: 'all 0.3s ease',
|
|
47
|
+
display: 'flex',
|
|
48
|
+
alignItems: 'center',
|
|
49
|
+
gap: '12px',
|
|
50
|
+
minWidth: '300px',
|
|
51
|
+
maxWidth: '500px'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
switch (type) {
|
|
55
|
+
case 'success':
|
|
56
|
+
return { ...baseStyles, background: '#4caf50', color: 'white' };
|
|
57
|
+
case 'error':
|
|
58
|
+
return { ...baseStyles, background: '#f44336', color: 'white' };
|
|
59
|
+
case 'warning':
|
|
60
|
+
return { ...baseStyles, background: '#ff9800', color: 'white' };
|
|
61
|
+
default:
|
|
62
|
+
return { ...baseStyles, background: '#2196f3', color: 'white' };
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL utility functions for detecting and converting URLs to clickable links
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Regular expression to match Markdown-style links
|
|
9
|
+
* Matches [text](url) format
|
|
10
|
+
*/
|
|
11
|
+
const MARKDOWN_LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Regular expression to match URLs
|
|
15
|
+
* Supports http, https, ftp, and www protocols
|
|
16
|
+
*/
|
|
17
|
+
const URL_REGEX = /(https?:\/\/[^\s]+|www\.[^\s]+|[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(?:\/[^\s]*)?)/g;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if a string contains URLs
|
|
21
|
+
* @param text - The text to check
|
|
22
|
+
* @returns boolean indicating if URLs are found
|
|
23
|
+
*/
|
|
24
|
+
export const containsUrls = (text: string): boolean => {
|
|
25
|
+
return URL_REGEX.test(text);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert text with line breaks to JSX elements with <br/> tags
|
|
30
|
+
* @param text - The text containing line breaks
|
|
31
|
+
* @param keyPrefix - Prefix for React keys
|
|
32
|
+
* @returns Array of JSX elements and strings
|
|
33
|
+
*/
|
|
34
|
+
const convertLineBreaks = (text: string, keyPrefix: string = ''): (string | React.ReactElement)[] => {
|
|
35
|
+
const parts: (string | React.ReactElement)[] = [];
|
|
36
|
+
const lines = text.split('\n');
|
|
37
|
+
|
|
38
|
+
lines.forEach((line, index) => {
|
|
39
|
+
if (index > 0) {
|
|
40
|
+
parts.push(React.createElement('br', { key: `${keyPrefix}-br-${index}` }));
|
|
41
|
+
}
|
|
42
|
+
if (line) {
|
|
43
|
+
parts.push(line);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return parts;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Convert text with URLs to JSX elements with clickable links
|
|
52
|
+
* Supports both Markdown format [text](url) and plain URLs
|
|
53
|
+
* Also handles line breaks (\n)
|
|
54
|
+
* @param text - The text containing URLs
|
|
55
|
+
* @returns Array of JSX elements and strings
|
|
56
|
+
*/
|
|
57
|
+
export const convertUrlsToLinks = (text: string): (string | React.ReactElement)[] => {
|
|
58
|
+
const parts: (string | React.ReactElement)[] = [];
|
|
59
|
+
let lastIndex = 0;
|
|
60
|
+
let keyCounter = 0;
|
|
61
|
+
|
|
62
|
+
// First, find all markdown links and plain URLs
|
|
63
|
+
const items: Array<{ type: 'markdown' | 'url'; start: number; end: number; text?: string; url: string }> = [];
|
|
64
|
+
|
|
65
|
+
// Find markdown links
|
|
66
|
+
let markdownMatch;
|
|
67
|
+
MARKDOWN_LINK_REGEX.lastIndex = 0;
|
|
68
|
+
while ((markdownMatch = MARKDOWN_LINK_REGEX.exec(text)) !== null) {
|
|
69
|
+
items.push({
|
|
70
|
+
type: 'markdown',
|
|
71
|
+
start: markdownMatch.index,
|
|
72
|
+
end: markdownMatch.index + markdownMatch[0].length,
|
|
73
|
+
text: markdownMatch[1],
|
|
74
|
+
url: markdownMatch[2]
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Find plain URLs (but exclude those already matched as markdown links)
|
|
79
|
+
let urlMatch;
|
|
80
|
+
URL_REGEX.lastIndex = 0;
|
|
81
|
+
while ((urlMatch = URL_REGEX.exec(text)) !== null) {
|
|
82
|
+
const start = urlMatch.index;
|
|
83
|
+
const end = start + urlMatch[0].length;
|
|
84
|
+
|
|
85
|
+
// Check if this URL is already part of a markdown link
|
|
86
|
+
const isPartOfMarkdown = items.some(item =>
|
|
87
|
+
item.type === 'markdown' && start >= item.start && end <= item.end
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (!isPartOfMarkdown) {
|
|
91
|
+
items.push({
|
|
92
|
+
type: 'url',
|
|
93
|
+
start,
|
|
94
|
+
end,
|
|
95
|
+
url: urlMatch[0]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Sort items by start position
|
|
101
|
+
items.sort((a, b) => a.start - b.start);
|
|
102
|
+
|
|
103
|
+
// Build the result
|
|
104
|
+
items.forEach(item => {
|
|
105
|
+
// Add text before this item (with line breaks)
|
|
106
|
+
if (item.start > lastIndex) {
|
|
107
|
+
const textBefore = text.slice(lastIndex, item.start);
|
|
108
|
+
const textBeforeParts = convertLineBreaks(textBefore, `text-${keyCounter}`);
|
|
109
|
+
parts.push(...textBeforeParts);
|
|
110
|
+
keyCounter++;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Create clickable link
|
|
114
|
+
const href = item.url.startsWith('http') ? item.url : `https://${item.url}`;
|
|
115
|
+
const displayText = item.type === 'markdown' ? item.text : item.url;
|
|
116
|
+
|
|
117
|
+
parts.push(
|
|
118
|
+
React.createElement('a', {
|
|
119
|
+
key: `link-${keyCounter++}`,
|
|
120
|
+
href: href,
|
|
121
|
+
target: '_blank',
|
|
122
|
+
rel: 'noopener noreferrer',
|
|
123
|
+
style: {
|
|
124
|
+
color: '#007bff',
|
|
125
|
+
textDecoration: 'underline',
|
|
126
|
+
cursor: 'pointer'
|
|
127
|
+
},
|
|
128
|
+
className: 'ai-agent-url-link',
|
|
129
|
+
onClick: (e: React.MouseEvent) => {
|
|
130
|
+
e.preventDefault(); // Prevent default browser behavior
|
|
131
|
+
e.stopPropagation();
|
|
132
|
+
window.open(href, '_blank', 'noopener,noreferrer');
|
|
133
|
+
}
|
|
134
|
+
}, displayText)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
lastIndex = item.end;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Add remaining text after the last link (with line breaks)
|
|
141
|
+
if (lastIndex < text.length) {
|
|
142
|
+
const textAfter = text.slice(lastIndex);
|
|
143
|
+
const textAfterParts = convertLineBreaks(textAfter, `text-${keyCounter}`);
|
|
144
|
+
parts.push(...textAfterParts);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return parts.length > 0 ? parts : convertLineBreaks(text, 'default');
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Simple function to wrap URLs in anchor tags for basic HTML rendering
|
|
152
|
+
* @param text - The text containing URLs
|
|
153
|
+
* @returns HTML string with clickable links
|
|
154
|
+
*/
|
|
155
|
+
export const convertUrlsToHtml = (text: string): string => {
|
|
156
|
+
return text.replace(URL_REGEX, (url) => {
|
|
157
|
+
const href = url.startsWith('http') ? url : `https://${url}`;
|
|
158
|
+
return `<a href="${href}" target="_blank" rel="noopener noreferrer" style="color: #007bff; text-decoration: underline; cursor: pointer;">${url}</a>`;
|
|
159
|
+
});
|
|
160
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adilas Service ID constant
|
|
3
|
+
*/
|
|
4
|
+
export const ADILAS_SERVICE_ID = 'e48fc2b9-a7d1-49e3-85cb-9d621a0bf774';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if the given serviceId is the Adilas service
|
|
8
|
+
*/
|
|
9
|
+
export const isAdilasService = (serviceId: string | undefined | null): boolean => {
|
|
10
|
+
return serviceId ? serviceId.trim() === ADILAS_SERVICE_ID : false;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replace "wallet" with "account" in text when serviceId is Adilas service
|
|
15
|
+
* Preserves the original case of the word
|
|
16
|
+
*/
|
|
17
|
+
export const getWalletText = (defaultText: string, serviceId: string | undefined | null): string => {
|
|
18
|
+
if (!isAdilasService(serviceId)) return defaultText;
|
|
19
|
+
|
|
20
|
+
// Replace wallet with account, preserving case
|
|
21
|
+
return defaultText.replace(/\bwallet\b/gi, (match) => {
|
|
22
|
+
// Preserve original case: 'Wallet' -> 'Account', 'wallet' -> 'account'
|
|
23
|
+
return match.charAt(0) === 'W' ? 'Account' : 'account';
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
import metakeepClient from '../metakeepClient';
|
|
3
|
+
import { getEthers } from './ethersUtils';
|
|
4
|
+
|
|
5
|
+
export interface WalletConnectionResult {
|
|
6
|
+
address: string;
|
|
7
|
+
signer: ethers.JsonRpcSigner;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const connectWallet = async (): Promise<WalletConnectionResult> => {
|
|
11
|
+
if (typeof window === 'undefined') {
|
|
12
|
+
throw new Error('Web3 is not available in server-side rendering');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getEthers();
|
|
16
|
+
|
|
17
|
+
const web3Provider = await metakeepClient.ethereum;
|
|
18
|
+
if (!web3Provider) {
|
|
19
|
+
throw new Error('Web3 provider not available');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
await web3Provider.enable();
|
|
23
|
+
|
|
24
|
+
// ethers v6: Use BrowserProvider instead of Web3Provider
|
|
25
|
+
const ethersProvider = new ethers.BrowserProvider(web3Provider);
|
|
26
|
+
|
|
27
|
+
// ethers v6: getSigner() and getAddress() are now async
|
|
28
|
+
const signer = await ethersProvider.getSigner();
|
|
29
|
+
const address = await signer.getAddress();
|
|
30
|
+
|
|
31
|
+
localStorage.setItem('walletAddress', address);
|
|
32
|
+
|
|
33
|
+
return { address, signer };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const disconnectWallet = (): void => {
|
|
37
|
+
localStorage.removeItem('walletAddress');
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const getStoredWalletAddress = (): string | null => {
|
|
41
|
+
return localStorage.getItem('walletAddress');
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const validateToken = (token: string, expectedAddress: string): boolean => {
|
|
45
|
+
try {
|
|
46
|
+
const payload = JSON.parse(atob(token.split('.')[1]));
|
|
47
|
+
const address = payload.address;
|
|
48
|
+
return address === expectedAddress;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Error parsing token:', error);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export { createNxtlinqApi, setApiHosts, getAiAgentApiHost } from './api/nxtlinq-api';
|
|
2
|
+
export { startVoiceSession, createVoiceApi } from './api/voice';
|
|
3
|
+
export type * from './api/voice';
|
|
4
|
+
|
|
5
|
+
export { useDraggable } from './core/lib/useDraggable';
|
|
6
|
+
export { default as useLocalStorage } from './core/lib/useLocalStorage';
|
|
7
|
+
export { default as useSessionStorage } from './core/lib/useSessionStorage';
|
|
8
|
+
export { useResizable } from './core/lib/useResizable';
|
|
9
|
+
export type { ResizeCorner } from './core/lib/useResizable';
|
|
10
|
+
export { synthesizeSpeechToBuffer } from './core/lib/textToSpeech';
|
|
11
|
+
export { useSpeechToTextFromMic } from './core/lib/useSpeechToTextFromMic';
|
|
12
|
+
export { useVoiceMode } from './core/lib/useVoiceMode';
|
|
13
|
+
|
|
14
|
+
export { default as metakeepClient } from './core/metakeepClient';
|
|
15
|
+
export { getEthers, sleep } from './core/utils';
|
|
16
|
+
export * as walletTextUtils from './core/utils/walletTextUtils';
|
|
17
|
+
export {
|
|
18
|
+
connectWallet,
|
|
19
|
+
disconnectWallet,
|
|
20
|
+
validateToken,
|
|
21
|
+
} from './core/utils/walletUtils';
|
|
22
|
+
export {
|
|
23
|
+
createAITMetadata,
|
|
24
|
+
generateAITId,
|
|
25
|
+
prepareAITCreation,
|
|
26
|
+
} from './core/utils/aitUtils';
|
|
27
|
+
export {
|
|
28
|
+
createNotification,
|
|
29
|
+
getNotificationIcon,
|
|
30
|
+
} from './core/utils/notificationUtils';
|
|
31
|
+
export {
|
|
32
|
+
containsUrls,
|
|
33
|
+
convertUrlsToLinks,
|
|
34
|
+
convertUrlsToHtml,
|
|
35
|
+
} from './core/utils/urlUtils';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { HttpPort, PlatformPorts, StoragePort, WebRTCPort } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
import {
|
|
3
|
+
createBrowserStoragePort,
|
|
4
|
+
createBrowserWebRTCPort,
|
|
5
|
+
createDefaultHttpPort,
|
|
6
|
+
} from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
7
|
+
|
|
8
|
+
export type CreateWebPlatformPortsOptions = {
|
|
9
|
+
/** Override default `localStorage` port. */
|
|
10
|
+
storage?: StoragePort;
|
|
11
|
+
fetchImpl?: typeof fetch;
|
|
12
|
+
getTimezone?: () => string;
|
|
13
|
+
/** Pre-built WebRTC port (overrides browser default). */
|
|
14
|
+
webrtc?: WebRTCPort;
|
|
15
|
+
/** Set `false` to disable voice (no WebRTC port). */
|
|
16
|
+
enableWebRTC?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function resolveHttpPort(fetchImpl?: typeof fetch): HttpPort {
|
|
20
|
+
if (!fetchImpl) return createDefaultHttpPort();
|
|
21
|
+
return { fetch: fetchImpl };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createWebPlatformPorts(
|
|
25
|
+
options: CreateWebPlatformPortsOptions = {},
|
|
26
|
+
): PlatformPorts {
|
|
27
|
+
const enableWebRTC = options.enableWebRTC !== false;
|
|
28
|
+
return {
|
|
29
|
+
storage: options.storage ?? createBrowserStoragePort(),
|
|
30
|
+
http: resolveHttpPort(options.fetchImpl),
|
|
31
|
+
webrtc: enableWebRTC
|
|
32
|
+
? (options.webrtc ?? createBrowserWebRTCPort())
|
|
33
|
+
: undefined,
|
|
34
|
+
getTimezone:
|
|
35
|
+
options.getTimezone
|
|
36
|
+
?? (() => {
|
|
37
|
+
try {
|
|
38
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
39
|
+
} catch {
|
|
40
|
+
return 'UTC';
|
|
41
|
+
}
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Attachment } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
|
|
3
|
+
function readFileAsDataUri(file: File): Promise<string> {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const reader = new FileReader();
|
|
6
|
+
reader.onload = () => {
|
|
7
|
+
if (typeof reader.result === 'string') {
|
|
8
|
+
resolve(reader.result);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
reject(new Error('fileToAttachment: failed to read file'));
|
|
12
|
+
};
|
|
13
|
+
reader.onerror = () => reject(reader.error ?? new Error('fileToAttachment: read error'));
|
|
14
|
+
reader.readAsDataURL(file);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function attachmentType(mimeType: string): Attachment['type'] {
|
|
19
|
+
return mimeType.startsWith('image/') ? 'image' : 'file';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Build an {@link Attachment} from a browser {@link File}. */
|
|
23
|
+
export async function fileToAttachment(file: File): Promise<Attachment> {
|
|
24
|
+
const url = await readFileAsDataUri(file);
|
|
25
|
+
return {
|
|
26
|
+
type: attachmentType(file.type || 'application/octet-stream'),
|
|
27
|
+
url,
|
|
28
|
+
name: file.name,
|
|
29
|
+
mimeType: file.type || 'application/octet-stream',
|
|
30
|
+
size: file.size,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { VoiceSession, VoiceStatus } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
+
import { useCallback, useRef } from 'react';
|
|
3
|
+
import { ASSISTANT_MIC_HOLD_STATUSES } from './voiceMicConstants';
|
|
4
|
+
import {
|
|
5
|
+
MIC_BARGE_IN_RMS_THRESHOLD,
|
|
6
|
+
MIC_SILENCE_COMMIT_MS,
|
|
7
|
+
MIC_SILENCE_POLL_MS,
|
|
8
|
+
MIC_SPEECH_RMS_THRESHOLD,
|
|
9
|
+
} from './ws/voiceSilenceConstants';
|
|
10
|
+
|
|
11
|
+
export function useVoiceSilenceCommit(
|
|
12
|
+
getSession: () => VoiceSession | null,
|
|
13
|
+
onMutedAfterCommit: () => void,
|
|
14
|
+
voiceStatus: VoiceStatus,
|
|
15
|
+
) {
|
|
16
|
+
const lastSpeechAtRef = useRef(0);
|
|
17
|
+
const hadSpeechRef = useRef(false);
|
|
18
|
+
const commitInFlightRef = useRef(false);
|
|
19
|
+
const skipCommitOnMuteRef = useRef(false);
|
|
20
|
+
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
21
|
+
const voiceStatusRef = useRef(voiceStatus);
|
|
22
|
+
voiceStatusRef.current = voiceStatus;
|
|
23
|
+
const onMutedAfterCommitRef = useRef(onMutedAfterCommit);
|
|
24
|
+
onMutedAfterCommitRef.current = onMutedAfterCommit;
|
|
25
|
+
|
|
26
|
+
const clearPoll = useCallback(() => {
|
|
27
|
+
if (pollRef.current) {
|
|
28
|
+
clearInterval(pollRef.current);
|
|
29
|
+
pollRef.current = null;
|
|
30
|
+
}
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const tryCommit = useCallback((reason: 'silence' | 'manual') => {
|
|
34
|
+
const session = getSession();
|
|
35
|
+
if (commitInFlightRef.current) return;
|
|
36
|
+
if (!hadSpeechRef.current) {
|
|
37
|
+
session?.clearInputAudio?.();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
commitInFlightRef.current = true;
|
|
41
|
+
hadSpeechRef.current = false;
|
|
42
|
+
session?.commitInputAudio?.();
|
|
43
|
+
if (reason === 'silence') {
|
|
44
|
+
skipCommitOnMuteRef.current = true;
|
|
45
|
+
onMutedAfterCommitRef.current();
|
|
46
|
+
}
|
|
47
|
+
queueMicrotask(() => { commitInFlightRef.current = false; });
|
|
48
|
+
}, [getSession]);
|
|
49
|
+
|
|
50
|
+
const startPoll = useCallback(() => {
|
|
51
|
+
clearPoll();
|
|
52
|
+
pollRef.current = setInterval(() => {
|
|
53
|
+
if (commitInFlightRef.current || !hadSpeechRef.current) return;
|
|
54
|
+
if (Date.now() - lastSpeechAtRef.current < MIC_SILENCE_COMMIT_MS) return;
|
|
55
|
+
tryCommit('silence');
|
|
56
|
+
}, MIC_SILENCE_POLL_MS);
|
|
57
|
+
}, [clearPoll, tryCommit]);
|
|
58
|
+
|
|
59
|
+
const resetTurn = useCallback(() => {
|
|
60
|
+
hadSpeechRef.current = false;
|
|
61
|
+
lastSpeechAtRef.current = Date.now();
|
|
62
|
+
getSession()?.clearInputAudio?.();
|
|
63
|
+
}, [getSession]);
|
|
64
|
+
|
|
65
|
+
const onSpeechRms = useCallback((rms: number) => {
|
|
66
|
+
const threshold = ASSISTANT_MIC_HOLD_STATUSES.has(voiceStatusRef.current)
|
|
67
|
+
? MIC_BARGE_IN_RMS_THRESHOLD
|
|
68
|
+
: MIC_SPEECH_RMS_THRESHOLD;
|
|
69
|
+
if (rms >= threshold) {
|
|
70
|
+
lastSpeechAtRef.current = Date.now();
|
|
71
|
+
hadSpeechRef.current = true;
|
|
72
|
+
}
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
const consumeSkipCommitOnMute = useCallback(() => {
|
|
76
|
+
const skip = skipCommitOnMuteRef.current;
|
|
77
|
+
skipCommitOnMuteRef.current = false;
|
|
78
|
+
return skip;
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
const hasHadSpeech = useCallback(() => hadSpeechRef.current, []);
|
|
82
|
+
|
|
83
|
+
return { startPoll, clearPoll, resetTurn, onSpeechRms, tryCommit, consumeSkipCommitOnMute, hasHadSpeech };
|
|
84
|
+
}
|