@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.
Files changed (149) hide show
  1. package/dist/context/NxtlinqAgentContext.d.ts +12 -0
  2. package/dist/context/NxtlinqAgentContext.d.ts.map +1 -0
  3. package/dist/context/NxtlinqAgentContext.js +33 -0
  4. package/dist/createNxtlinqAgent.d.ts +9 -0
  5. package/dist/createNxtlinqAgent.d.ts.map +1 -0
  6. package/dist/createNxtlinqAgent.js +19 -0
  7. package/dist/hooks/useNxtlinqAgent.d.ts +18 -0
  8. package/dist/hooks/useNxtlinqAgent.d.ts.map +1 -0
  9. package/dist/hooks/useNxtlinqAgent.js +23 -0
  10. package/dist/hooks/useNxtlinqVoice.d.ts +21 -0
  11. package/dist/hooks/useNxtlinqVoice.d.ts.map +1 -0
  12. package/dist/hooks/useNxtlinqVoice.js +75 -0
  13. package/dist/index.d.ts +12 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +9 -0
  16. package/dist/legacy/api/nxtlinq-api.d.ts +8 -0
  17. package/dist/legacy/api/nxtlinq-api.d.ts.map +1 -0
  18. package/dist/legacy/api/nxtlinq-api.js +13 -0
  19. package/dist/legacy/api/voice.d.ts +11 -0
  20. package/dist/legacy/api/voice.d.ts.map +1 -0
  21. package/dist/legacy/api/voice.js +26 -0
  22. package/dist/legacy/core/lib/messageHistory.d.ts +2 -0
  23. package/dist/legacy/core/lib/messageHistory.d.ts.map +1 -0
  24. package/dist/legacy/core/lib/messageHistory.js +1 -0
  25. package/dist/legacy/core/lib/textToSpeech.d.ts +14 -0
  26. package/dist/legacy/core/lib/textToSpeech.d.ts.map +1 -0
  27. package/dist/legacy/core/lib/textToSpeech.js +82 -0
  28. package/dist/legacy/core/lib/useDraggable.d.ts +15 -0
  29. package/dist/legacy/core/lib/useDraggable.d.ts.map +1 -0
  30. package/dist/legacy/core/lib/useDraggable.js +158 -0
  31. package/dist/legacy/core/lib/useLocalStorage.d.ts +11 -0
  32. package/dist/legacy/core/lib/useLocalStorage.d.ts.map +1 -0
  33. package/dist/legacy/core/lib/useLocalStorage.js +83 -0
  34. package/dist/legacy/core/lib/useResizable.d.ts +17 -0
  35. package/dist/legacy/core/lib/useResizable.d.ts.map +1 -0
  36. package/dist/legacy/core/lib/useResizable.js +203 -0
  37. package/dist/legacy/core/lib/useSessionStorage.d.ts +11 -0
  38. package/dist/legacy/core/lib/useSessionStorage.d.ts.map +1 -0
  39. package/dist/legacy/core/lib/useSessionStorage.js +37 -0
  40. package/dist/legacy/core/lib/useSpeechToTextFromMic/helper.d.ts +26 -0
  41. package/dist/legacy/core/lib/useSpeechToTextFromMic/helper.d.ts.map +1 -0
  42. package/dist/legacy/core/lib/useSpeechToTextFromMic/helper.js +102 -0
  43. package/dist/legacy/core/lib/useSpeechToTextFromMic/index.d.ts +16 -0
  44. package/dist/legacy/core/lib/useSpeechToTextFromMic/index.d.ts.map +1 -0
  45. package/dist/legacy/core/lib/useSpeechToTextFromMic/index.js +92 -0
  46. package/dist/legacy/core/lib/useVoiceMode.d.ts +32 -0
  47. package/dist/legacy/core/lib/useVoiceMode.d.ts.map +1 -0
  48. package/dist/legacy/core/lib/useVoiceMode.js +373 -0
  49. package/dist/legacy/core/metakeepClient.d.ts +4 -0
  50. package/dist/legacy/core/metakeepClient.d.ts.map +1 -0
  51. package/dist/legacy/core/metakeepClient.js +10 -0
  52. package/dist/legacy/core/utils/aitUtils.d.ts +31 -0
  53. package/dist/legacy/core/utils/aitUtils.d.ts.map +1 -0
  54. package/dist/legacy/core/utils/aitUtils.js +35 -0
  55. package/dist/legacy/core/utils/ethersUtils.d.ts +8 -0
  56. package/dist/legacy/core/utils/ethersUtils.d.ts.map +1 -0
  57. package/dist/legacy/core/utils/ethersUtils.js +19 -0
  58. package/dist/legacy/core/utils/index.d.ts +3 -0
  59. package/dist/legacy/core/utils/index.d.ts.map +1 -0
  60. package/dist/legacy/core/utils/index.js +4 -0
  61. package/dist/legacy/core/utils/notificationUtils.d.ts +29 -0
  62. package/dist/legacy/core/utils/notificationUtils.d.ts.map +1 -0
  63. package/dist/legacy/core/utils/notificationUtils.js +47 -0
  64. package/dist/legacy/core/utils/urlUtils.d.ts +25 -0
  65. package/dist/legacy/core/utils/urlUtils.d.ts.map +1 -0
  66. package/dist/legacy/core/utils/urlUtils.js +135 -0
  67. package/dist/legacy/core/utils/walletTextUtils.d.ts +14 -0
  68. package/dist/legacy/core/utils/walletTextUtils.d.ts.map +1 -0
  69. package/dist/legacy/core/utils/walletTextUtils.js +23 -0
  70. package/dist/legacy/core/utils/walletUtils.d.ts +10 -0
  71. package/dist/legacy/core/utils/walletUtils.d.ts.map +1 -0
  72. package/dist/legacy/core/utils/walletUtils.js +38 -0
  73. package/dist/legacy/index.d.ts +19 -0
  74. package/dist/legacy/index.d.ts.map +1 -0
  75. package/dist/legacy/index.js +16 -0
  76. package/dist/ports/createWebPlatformPorts.d.ts +13 -0
  77. package/dist/ports/createWebPlatformPorts.d.ts.map +1 -0
  78. package/dist/ports/createWebPlatformPorts.js +25 -0
  79. package/dist/utils/fileToAttachment.d.ts +4 -0
  80. package/dist/utils/fileToAttachment.d.ts.map +1 -0
  81. package/dist/utils/fileToAttachment.js +28 -0
  82. package/dist/voice/useVoiceSilenceCommit.d.ts +11 -0
  83. package/dist/voice/useVoiceSilenceCommit.d.ts.map +1 -0
  84. package/dist/voice/useVoiceSilenceCommit.js +68 -0
  85. package/dist/voice/useVoiceTranscriptMessages.d.ts +16 -0
  86. package/dist/voice/useVoiceTranscriptMessages.d.ts.map +1 -0
  87. package/dist/voice/useVoiceTranscriptMessages.js +134 -0
  88. package/dist/voice/useWsRealtimeAudio.d.ts +18 -0
  89. package/dist/voice/useWsRealtimeAudio.d.ts.map +1 -0
  90. package/dist/voice/useWsRealtimeAudio.js +115 -0
  91. package/dist/voice/voiceMicConstants.d.ts +4 -0
  92. package/dist/voice/voiceMicConstants.d.ts.map +1 -0
  93. package/dist/voice/voiceMicConstants.js +10 -0
  94. package/dist/voice/ws/BrowserWsPcmPlayer.d.ts +23 -0
  95. package/dist/voice/ws/BrowserWsPcmPlayer.d.ts.map +1 -0
  96. package/dist/voice/ws/BrowserWsPcmPlayer.js +138 -0
  97. package/dist/voice/ws/BrowserWsPcmRecorder.d.ts +19 -0
  98. package/dist/voice/ws/BrowserWsPcmRecorder.d.ts.map +1 -0
  99. package/dist/voice/ws/BrowserWsPcmRecorder.js +76 -0
  100. package/dist/voice/ws/float32ToPcm16.d.ts +2 -0
  101. package/dist/voice/ws/float32ToPcm16.d.ts.map +1 -0
  102. package/dist/voice/ws/float32ToPcm16.js +8 -0
  103. package/dist/voice/ws/voiceSilenceConstants.d.ts +5 -0
  104. package/dist/voice/ws/voiceSilenceConstants.d.ts.map +1 -0
  105. package/dist/voice/ws/voiceSilenceConstants.js +4 -0
  106. package/dist/voice/ws/wsRealtimeConstants.d.ts +2 -0
  107. package/dist/voice/ws/wsRealtimeConstants.d.ts.map +1 -0
  108. package/dist/voice/ws/wsRealtimeConstants.js +1 -0
  109. package/dist/webAgentDefaults.d.ts +9 -0
  110. package/dist/webAgentDefaults.d.ts.map +1 -0
  111. package/dist/webAgentDefaults.js +9 -0
  112. package/package.json +55 -0
  113. package/src/context/NxtlinqAgentContext.tsx +79 -0
  114. package/src/createNxtlinqAgent.ts +36 -0
  115. package/src/hooks/useNxtlinqAgent.ts +73 -0
  116. package/src/hooks/useNxtlinqVoice.ts +143 -0
  117. package/src/index.ts +84 -0
  118. package/src/legacy/api/nxtlinq-api.ts +32 -0
  119. package/src/legacy/api/voice.ts +72 -0
  120. package/src/legacy/core/lib/messageHistory.ts +6 -0
  121. package/src/legacy/core/lib/textToSpeech.ts +127 -0
  122. package/src/legacy/core/lib/useDraggable.ts +193 -0
  123. package/src/legacy/core/lib/useLocalStorage.ts +89 -0
  124. package/src/legacy/core/lib/useResizable.ts +256 -0
  125. package/src/legacy/core/lib/useSessionStorage.ts +43 -0
  126. package/src/legacy/core/lib/useSpeechToTextFromMic/helper.ts +132 -0
  127. package/src/legacy/core/lib/useSpeechToTextFromMic/index.ts +126 -0
  128. package/src/legacy/core/lib/useVoiceMode.ts +407 -0
  129. package/src/legacy/core/metakeepClient.ts +12 -0
  130. package/src/legacy/core/utils/aitUtils.ts +55 -0
  131. package/src/legacy/core/utils/ethersUtils.ts +24 -0
  132. package/src/legacy/core/utils/index.ts +5 -0
  133. package/src/legacy/core/utils/notificationUtils.ts +64 -0
  134. package/src/legacy/core/utils/urlUtils.ts +160 -0
  135. package/src/legacy/core/utils/walletTextUtils.ts +26 -0
  136. package/src/legacy/core/utils/walletUtils.ts +53 -0
  137. package/src/legacy/index.ts +35 -0
  138. package/src/ports/createWebPlatformPorts.ts +44 -0
  139. package/src/utils/fileToAttachment.ts +32 -0
  140. package/src/voice/useVoiceSilenceCommit.ts +84 -0
  141. package/src/voice/useVoiceTranscriptMessages.ts +184 -0
  142. package/src/voice/useWsRealtimeAudio.ts +141 -0
  143. package/src/voice/voiceMicConstants.ts +13 -0
  144. package/src/voice/ws/BrowserWsPcmPlayer.ts +139 -0
  145. package/src/voice/ws/BrowserWsPcmRecorder.ts +83 -0
  146. package/src/voice/ws/float32ToPcm16.ts +8 -0
  147. package/src/voice/ws/voiceSilenceConstants.ts +4 -0
  148. package/src/voice/ws/wsRealtimeConstants.ts +1 -0
  149. 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,5 @@
1
+ export const sleep = (ms: number) => {
2
+ return new Promise(resolve => setTimeout(resolve, ms));
3
+ }
4
+
5
+ export { getEthers } from './ethersUtils';
@@ -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
+ }