@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.1.3 → 0.1.4
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/ChatBot.d.ts +5 -0
- package/dist/ChatBot.d.ts.map +1 -0
- package/dist/ChatBot.js +35 -0
- package/dist/assets/images/adiSideItalicDataUri.d.ts +2 -0
- package/dist/assets/images/adiSideItalicDataUri.d.ts.map +1 -0
- package/dist/assets/images/adiSideItalicDataUri.js +1 -0
- package/dist/context/ChatBotContext.d.ts +5 -0
- package/dist/context/ChatBotContext.d.ts.map +1 -0
- package/dist/context/ChatBotContext.js +2908 -0
- package/dist/index.d.ts +5 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -11
- package/dist/legacy/chatbot/context/ChatBotContext.d.ts.map +1 -1
- package/dist/legacy/chatbot/context/ChatBotContext.js +14 -0
- package/dist/types/ChatBotTypes.d.ts +166 -0
- package/dist/types/ChatBotTypes.d.ts.map +1 -0
- package/dist/types/ChatBotTypes.js +1 -0
- package/dist/ui/BerifyMeModal.d.ts +17 -0
- package/dist/ui/BerifyMeModal.d.ts.map +1 -0
- package/dist/ui/BerifyMeModal.js +110 -0
- package/dist/ui/ChatBotHeader.d.ts +15 -0
- package/dist/ui/ChatBotHeader.d.ts.map +1 -0
- package/dist/ui/ChatBotHeader.js +62 -0
- package/dist/ui/ChatBotUI.d.ts +3 -0
- package/dist/ui/ChatBotUI.d.ts.map +1 -0
- package/dist/ui/ChatBotUI.js +557 -0
- package/dist/ui/MessageInput.d.ts +3 -0
- package/dist/ui/MessageInput.d.ts.map +1 -0
- package/dist/ui/MessageInput.js +321 -0
- package/dist/ui/MessageList.d.ts +4 -0
- package/dist/ui/MessageList.d.ts.map +1 -0
- package/dist/ui/MessageList.js +455 -0
- package/dist/ui/ModelSelector.d.ts +4 -0
- package/dist/ui/ModelSelector.d.ts.map +1 -0
- package/dist/ui/ModelSelector.js +122 -0
- package/dist/ui/NotificationModal.d.ts +15 -0
- package/dist/ui/NotificationModal.d.ts.map +1 -0
- package/dist/ui/NotificationModal.js +53 -0
- package/dist/ui/PermissionForm.d.ts +8 -0
- package/dist/ui/PermissionForm.d.ts.map +1 -0
- package/dist/ui/PermissionForm.js +465 -0
- package/dist/ui/PresetMessages.d.ts +4 -0
- package/dist/ui/PresetMessages.d.ts.map +1 -0
- package/dist/ui/PresetMessages.js +33 -0
- package/dist/ui/VoiceModePanel.d.ts +3 -0
- package/dist/ui/VoiceModePanel.d.ts.map +1 -0
- package/dist/ui/VoiceModePanel.js +95 -0
- package/dist/ui/chatBotHeaderParts.d.ts +15 -0
- package/dist/ui/chatBotHeaderParts.d.ts.map +1 -0
- package/dist/ui/chatBotHeaderParts.js +50 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +8 -0
- package/dist/ui/styles/isolatedStyles.d.ts +73 -0
- package/dist/ui/styles/isolatedStyles.d.ts.map +1 -0
- package/dist/ui/styles/isolatedStyles.js +985 -0
- package/package.json +2 -2
- package/src/{legacy/chatbot/context → context}/ChatBotContext.tsx +0 -1
- package/src/index.ts +17 -40
- package/src/{legacy/chatbot/ui → ui}/ModelSelector.tsx +1 -1
- package/src/{legacy/chatbot/ui → ui}/VoiceModePanel.tsx +1 -1
- package/src/ui/index.ts +8 -0
- package/src/NxtlinqAgentChat.tsx +0 -79
- package/src/components/AgentAssistantShell.tsx +0 -104
- package/src/components/AgentComposer.tsx +0 -134
- package/src/components/AgentMessageList.tsx +0 -78
- package/src/components/AgentRemoteAudio.tsx +0 -34
- package/src/components/AgentVoiceBar.tsx +0 -173
- package/src/components/PresetMessageChips.tsx +0 -41
- package/src/context/AgentAssistantContext.tsx +0 -294
- package/src/legacy/index.ts +0 -26
- package/src/theme/defaultTheme.ts +0 -22
- package/src/types.ts +0 -65
- package/src/voice/useVoiceConnectOrchestration.ts +0 -117
- package/src/voice/useVoiceMicState.ts +0 -117
- package/src/voice/useVoiceTranscriptMessages.ts +0 -188
- package/src/voice/voiceMicConstants.ts +0 -13
- package/src/voice/voiceUserBubble.ts +0 -71
- /package/src/{legacy/chatbot/ChatBot.tsx → ChatBot.tsx} +0 -0
- /package/src/{legacy/assets → assets}/images/adiSideItalicDataUri.ts +0 -0
- /package/src/{legacy/chatbot/types → types}/ChatBotTypes.ts +0 -0
- /package/src/{legacy/chatbot/ui → ui}/BerifyMeModal.tsx +0 -0
- /package/src/{legacy/chatbot/ui → ui}/ChatBotHeader.tsx +0 -0
- /package/src/{legacy/chatbot/ui → ui}/ChatBotUI.tsx +0 -0
- /package/src/{legacy/chatbot/ui → ui}/MessageInput.tsx +0 -0
- /package/src/{legacy/chatbot/ui → ui}/MessageList.tsx +0 -0
- /package/src/{legacy/chatbot/ui → ui}/NotificationModal.tsx +0 -0
- /package/src/{legacy/chatbot/ui → ui}/PermissionForm.tsx +0 -0
- /package/src/{legacy/chatbot/ui → ui}/PresetMessages.tsx +0 -0
- /package/src/{legacy/chatbot/ui → ui}/chatBotHeaderParts.tsx +0 -0
- /package/src/{legacy/chatbot/ui → ui}/styles/isolatedStyles.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytexbyte/nxtlinq-ai-agent-ui-react-development",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Official React Web UI for nxtlinq AI Agent — drop-in chat widget",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@bytexbyte/nxtlinq-ai-agent-core-development": "workspace:^",
|
|
53
|
-
"@bytexbyte/nxtlinq-ai-agent-web-development": "
|
|
53
|
+
"@bytexbyte/nxtlinq-ai-agent-web-development": "0.1.3",
|
|
54
54
|
"@types/react": "^18.2.64",
|
|
55
55
|
"@types/react-dom": "^18.2.25",
|
|
56
56
|
"react": "^18.2.0",
|
|
@@ -82,7 +82,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
82
82
|
// Set API hosts immediately based on environment (before any API calls)
|
|
83
83
|
setApiHosts(environment);
|
|
84
84
|
|
|
85
|
-
|
|
86
85
|
const nxtlinqApi = React.useMemo(() => createNxtlinqApi(apiKey, apiSecret), [apiKey, apiSecret]);
|
|
87
86
|
|
|
88
87
|
const apiKeyRef = React.useRef(apiKey);
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { ChatBot } from './ChatBot';
|
|
2
|
+
export { ChatBotProvider, useChatBot } from './context/ChatBotContext';
|
|
2
3
|
|
|
3
4
|
export {
|
|
4
|
-
ChatBot,
|
|
5
|
-
NxtlinqChatBot,
|
|
6
|
-
ChatBotProvider,
|
|
7
|
-
useChatBot,
|
|
8
5
|
ChatBotUI,
|
|
9
6
|
MessageInput,
|
|
10
7
|
MessageList,
|
|
@@ -13,7 +10,7 @@ export {
|
|
|
13
10
|
PermissionForm,
|
|
14
11
|
PresetMessages,
|
|
15
12
|
BerifyMeModal,
|
|
16
|
-
} from './
|
|
13
|
+
} from './ui/index';
|
|
17
14
|
|
|
18
15
|
export type {
|
|
19
16
|
AIModel,
|
|
@@ -23,41 +20,9 @@ export type {
|
|
|
23
20
|
NovaError,
|
|
24
21
|
NovaResponse,
|
|
25
22
|
ToolCall,
|
|
26
|
-
} from './legacy';
|
|
27
|
-
|
|
28
|
-
export type {
|
|
29
|
-
NxtlinqAgentChatProps,
|
|
30
|
-
AgentAssistantTheme,
|
|
31
|
-
PresetMessage,
|
|
32
|
-
Message,
|
|
33
23
|
ToolUse,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export { defaultAgentAssistantTheme } from './theme/defaultTheme';
|
|
37
|
-
|
|
38
|
-
export {
|
|
39
|
-
AgentAssistantProvider,
|
|
40
|
-
useAgentAssistant,
|
|
41
|
-
voiceStatusLabel,
|
|
42
|
-
type AgentAssistantContextValue,
|
|
43
|
-
type InteractionMode,
|
|
44
|
-
} from './context/AgentAssistantContext';
|
|
45
|
-
|
|
46
|
-
export { AgentMessageList } from './components/AgentMessageList';
|
|
47
|
-
export { AgentComposer } from './components/AgentComposer';
|
|
48
|
-
export { AgentVoiceBar } from './components/AgentVoiceBar';
|
|
49
|
-
export { AgentRemoteAudio } from './components/AgentRemoteAudio';
|
|
50
|
-
export { PresetMessageChips } from './components/PresetMessageChips';
|
|
51
|
-
export { AgentAssistantShell } from './components/AgentAssistantShell';
|
|
52
|
-
|
|
53
|
-
export {
|
|
54
|
-
NxtlinqAgentProvider,
|
|
55
|
-
useNxtlinqAgent,
|
|
56
|
-
useNxtlinqVoice,
|
|
57
|
-
createNxtlinqAgentWeb,
|
|
58
|
-
fileToAttachment,
|
|
59
|
-
type NxtlinqAgentProviderProps,
|
|
60
|
-
} from '@bytexbyte/nxtlinq-ai-agent-web-development';
|
|
24
|
+
PresetMessage,
|
|
25
|
+
} from './types/ChatBotTypes';
|
|
61
26
|
|
|
62
27
|
export type {
|
|
63
28
|
AgentEnvironment,
|
|
@@ -76,3 +41,15 @@ export {
|
|
|
76
41
|
VoiceNotSupportedError,
|
|
77
42
|
STORAGE_KEYS,
|
|
78
43
|
} from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
createNxtlinqApi,
|
|
47
|
+
useLocalStorage,
|
|
48
|
+
useSessionStorage,
|
|
49
|
+
useSpeechToTextFromMic,
|
|
50
|
+
useVoiceMode,
|
|
51
|
+
metakeepClient,
|
|
52
|
+
getEthers,
|
|
53
|
+
sleep,
|
|
54
|
+
walletTextUtils,
|
|
55
|
+
} from '@bytexbyte/nxtlinq-ai-agent-web-development';
|
|
@@ -3,7 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import { css } from '@emotion/react';
|
|
4
4
|
import { useChatBot } from '../context/ChatBotContext';
|
|
5
5
|
import { walletTextUtils } from '@bytexbyte/nxtlinq-ai-agent-web-development';
|
|
6
|
-
import { adiSideItalicDataUri } from '
|
|
6
|
+
import { adiSideItalicDataUri } from '../assets/images/adiSideItalicDataUri';
|
|
7
7
|
|
|
8
8
|
export const ModelSelector: React.FC = () => {
|
|
9
9
|
const {
|
|
@@ -134,7 +134,7 @@ export const VoiceModePanel: React.FC = () => {
|
|
|
134
134
|
<span>
|
|
135
135
|
<IconButton
|
|
136
136
|
onClick={toggleVoiceMicMute}
|
|
137
|
-
disabled={isVoiceConnecting || isMicHeldForAssistant}
|
|
137
|
+
disabled={voiceStatus !== 'listening' || isVoiceConnecting || isMicHeldForAssistant}
|
|
138
138
|
size="small"
|
|
139
139
|
css={css`color: ${isMicMuted ? '#ef4444' : '#4b5563'} !important;`}
|
|
140
140
|
>
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { ChatBotUI } from './ChatBotUI';
|
|
2
|
+
export { MessageInput } from './MessageInput';
|
|
3
|
+
export { MessageList } from './MessageList';
|
|
4
|
+
export { ModelSelector } from './ModelSelector';
|
|
5
|
+
export { NotificationModal } from './NotificationModal';
|
|
6
|
+
export { PermissionForm } from './PermissionForm';
|
|
7
|
+
export { PresetMessages } from './PresetMessages';
|
|
8
|
+
export { BerifyMeModal } from './BerifyMeModal';
|
package/src/NxtlinqAgentChat.tsx
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { NxtlinqAgentProvider } from '@bytexbyte/nxtlinq-ai-agent-web-development';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { AgentAssistantShell } from './components/AgentAssistantShell';
|
|
4
|
-
import type { NxtlinqAgentChatProps } from './types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Drop-in React Web assistant UI wired to `@bytexbyte/nxtlinq-ai-agent-web-development`.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```tsx
|
|
11
|
-
* import { NxtlinqAgentChat } from '@bytexbyte/nxtlinq-ai-agent-ui-react-development';
|
|
12
|
-
*
|
|
13
|
-
* export default function Page() {
|
|
14
|
-
* return (
|
|
15
|
-
* <NxtlinqAgentChat
|
|
16
|
-
* style={{ height: '100vh' }}
|
|
17
|
-
* serviceId="..."
|
|
18
|
-
* apiKey="..."
|
|
19
|
-
* apiSecret="..."
|
|
20
|
-
* environment="staging"
|
|
21
|
-
* pseudoId={userId}
|
|
22
|
-
* loadHistoryOnMount
|
|
23
|
-
* />
|
|
24
|
-
* );
|
|
25
|
-
* }
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export function NxtlinqAgentChat({
|
|
29
|
-
title,
|
|
30
|
-
placeholder,
|
|
31
|
-
presetMessages,
|
|
32
|
-
loadHistoryOnMount = false,
|
|
33
|
-
historyLast,
|
|
34
|
-
enableVoice = true,
|
|
35
|
-
enableFileUpload = true,
|
|
36
|
-
startInVoiceMode = false,
|
|
37
|
-
startWithMicMuted = true,
|
|
38
|
-
holdMicDuringAssistant = true,
|
|
39
|
-
theme,
|
|
40
|
-
style,
|
|
41
|
-
headerStyle,
|
|
42
|
-
onMessage,
|
|
43
|
-
onError,
|
|
44
|
-
onToolUse,
|
|
45
|
-
children,
|
|
46
|
-
fetchImpl,
|
|
47
|
-
getTimezone,
|
|
48
|
-
resetOnIdentityChange,
|
|
49
|
-
...agentConfig
|
|
50
|
-
}: NxtlinqAgentChatProps): React.ReactElement {
|
|
51
|
-
return (
|
|
52
|
-
<NxtlinqAgentProvider
|
|
53
|
-
fetchImpl={fetchImpl}
|
|
54
|
-
getTimezone={getTimezone}
|
|
55
|
-
resetOnIdentityChange={resetOnIdentityChange}
|
|
56
|
-
onMessage={onMessage}
|
|
57
|
-
onError={onError}
|
|
58
|
-
onToolUse={onToolUse}
|
|
59
|
-
{...agentConfig}
|
|
60
|
-
>
|
|
61
|
-
<AgentAssistantShell
|
|
62
|
-
title={title}
|
|
63
|
-
placeholder={placeholder}
|
|
64
|
-
presetMessages={presetMessages}
|
|
65
|
-
loadHistoryOnMount={loadHistoryOnMount}
|
|
66
|
-
historyLast={historyLast}
|
|
67
|
-
enableVoice={enableVoice}
|
|
68
|
-
enableFileUpload={enableFileUpload}
|
|
69
|
-
startInVoiceMode={startInVoiceMode}
|
|
70
|
-
startWithMicMuted={startWithMicMuted}
|
|
71
|
-
holdMicDuringAssistant={holdMicDuringAssistant}
|
|
72
|
-
theme={theme}
|
|
73
|
-
style={style}
|
|
74
|
-
headerStyle={headerStyle}
|
|
75
|
-
/>
|
|
76
|
-
{children}
|
|
77
|
-
</NxtlinqAgentProvider>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
AgentAssistantProvider,
|
|
4
|
-
useAgentAssistant,
|
|
5
|
-
} from '../context/AgentAssistantContext';
|
|
6
|
-
import type { NxtlinqAgentChatProps } from '../types';
|
|
7
|
-
import { AgentComposer } from './AgentComposer';
|
|
8
|
-
import { AgentMessageList } from './AgentMessageList';
|
|
9
|
-
import { AgentRemoteAudio } from './AgentRemoteAudio';
|
|
10
|
-
import { AgentVoiceBar } from './AgentVoiceBar';
|
|
11
|
-
import { PresetMessageChips } from './PresetMessageChips';
|
|
12
|
-
|
|
13
|
-
export type AgentAssistantShellProps = Pick<
|
|
14
|
-
NxtlinqAgentChatProps,
|
|
15
|
-
| 'title'
|
|
16
|
-
| 'placeholder'
|
|
17
|
-
| 'presetMessages'
|
|
18
|
-
| 'enableVoice'
|
|
19
|
-
| 'enableFileUpload'
|
|
20
|
-
| 'theme'
|
|
21
|
-
| 'style'
|
|
22
|
-
| 'headerStyle'
|
|
23
|
-
| 'loadHistoryOnMount'
|
|
24
|
-
| 'historyLast'
|
|
25
|
-
| 'startInVoiceMode'
|
|
26
|
-
| 'startWithMicMuted'
|
|
27
|
-
| 'holdMicDuringAssistant'
|
|
28
|
-
>;
|
|
29
|
-
|
|
30
|
-
function AgentAssistantInner({
|
|
31
|
-
title,
|
|
32
|
-
headerStyle,
|
|
33
|
-
style,
|
|
34
|
-
loadHistoryOnMount,
|
|
35
|
-
historyLast,
|
|
36
|
-
startInVoiceMode,
|
|
37
|
-
}: AgentAssistantShellProps): React.ReactElement {
|
|
38
|
-
const { theme, loadHistory, startVoice, isVoiceAvailable } = useAgentAssistant();
|
|
39
|
-
const [historyReady, setHistoryReady] = useState(!loadHistoryOnMount);
|
|
40
|
-
const voiceAutoStartRef = useRef(false);
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (loadHistoryOnMount) {
|
|
44
|
-
void loadHistory({ last: historyLast ?? 50 }).finally(() => setHistoryReady(true));
|
|
45
|
-
}
|
|
46
|
-
}, [loadHistory, loadHistoryOnMount, historyLast]);
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (!startInVoiceMode || !isVoiceAvailable || voiceAutoStartRef.current) return;
|
|
50
|
-
voiceAutoStartRef.current = true;
|
|
51
|
-
void startVoice();
|
|
52
|
-
}, [startInVoiceMode, isVoiceAvailable, startVoice]);
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div
|
|
56
|
-
style={{
|
|
57
|
-
display: 'flex',
|
|
58
|
-
flexDirection: 'column',
|
|
59
|
-
flex: 1,
|
|
60
|
-
minHeight: 0,
|
|
61
|
-
backgroundColor: theme.colors.background,
|
|
62
|
-
...style,
|
|
63
|
-
}}
|
|
64
|
-
>
|
|
65
|
-
<header
|
|
66
|
-
style={{
|
|
67
|
-
padding: theme.spacing.md,
|
|
68
|
-
borderBottom: `1px solid ${theme.colors.border}`,
|
|
69
|
-
backgroundColor: theme.colors.surface,
|
|
70
|
-
fontSize: theme.typography.titleSize,
|
|
71
|
-
fontWeight: 600,
|
|
72
|
-
color: theme.colors.assistantText,
|
|
73
|
-
...headerStyle,
|
|
74
|
-
}}
|
|
75
|
-
>
|
|
76
|
-
{title ?? 'AI Assistant'}
|
|
77
|
-
</header>
|
|
78
|
-
<PresetMessageChips />
|
|
79
|
-
{historyReady ? <AgentMessageList /> : <div style={{ flex: 1 }} />}
|
|
80
|
-
<AgentVoiceBar />
|
|
81
|
-
<AgentComposer />
|
|
82
|
-
<AgentRemoteAudio />
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function AgentAssistantShell(props: AgentAssistantShellProps): React.ReactElement {
|
|
88
|
-
return (
|
|
89
|
-
<AgentAssistantProvider
|
|
90
|
-
ui={{
|
|
91
|
-
title: props.title,
|
|
92
|
-
placeholder: props.placeholder,
|
|
93
|
-
presetMessages: props.presetMessages,
|
|
94
|
-
enableVoice: props.enableVoice,
|
|
95
|
-
enableFileUpload: props.enableFileUpload,
|
|
96
|
-
theme: props.theme,
|
|
97
|
-
startWithMicMuted: props.startWithMicMuted,
|
|
98
|
-
holdMicDuringAssistant: props.holdMicDuringAssistant,
|
|
99
|
-
}}
|
|
100
|
-
>
|
|
101
|
-
<AgentAssistantInner {...props} />
|
|
102
|
-
</AgentAssistantProvider>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useAgentAssistant } from '../context/AgentAssistantContext';
|
|
3
|
-
|
|
4
|
-
export function AgentComposer(): React.ReactElement | null {
|
|
5
|
-
const {
|
|
6
|
-
theme,
|
|
7
|
-
placeholder,
|
|
8
|
-
inputText,
|
|
9
|
-
setInputText,
|
|
10
|
-
sendText,
|
|
11
|
-
isLoading,
|
|
12
|
-
isVoiceAvailable,
|
|
13
|
-
interactionMode,
|
|
14
|
-
startVoice,
|
|
15
|
-
isVoiceConnecting,
|
|
16
|
-
enableFileUpload,
|
|
17
|
-
sendMessage,
|
|
18
|
-
} = useAgentAssistant();
|
|
19
|
-
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
|
|
20
|
-
|
|
21
|
-
if (interactionMode === 'voice') {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const handleFiles = async (files: FileList | null) => {
|
|
26
|
-
if (!files?.length) return;
|
|
27
|
-
const { fileToAttachment } = await import('@bytexbyte/nxtlinq-ai-agent-web-development');
|
|
28
|
-
const attachments = await Promise.all(
|
|
29
|
-
Array.from(files).map((file) => fileToAttachment(file)),
|
|
30
|
-
);
|
|
31
|
-
await sendMessage(
|
|
32
|
-
attachments.length ? `Uploaded ${attachments.length} file(s)` : '',
|
|
33
|
-
{ attachments },
|
|
34
|
-
);
|
|
35
|
-
if (fileInputRef.current) fileInputRef.current.value = '';
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div
|
|
40
|
-
style={{
|
|
41
|
-
borderTop: `1px solid ${theme.colors.border}`,
|
|
42
|
-
backgroundColor: theme.colors.surface,
|
|
43
|
-
padding: theme.spacing.md,
|
|
44
|
-
}}
|
|
45
|
-
>
|
|
46
|
-
<div style={{ display: 'flex', gap: theme.spacing.sm, alignItems: 'flex-end' }}>
|
|
47
|
-
{enableFileUpload ? (
|
|
48
|
-
<>
|
|
49
|
-
<button
|
|
50
|
-
type="button"
|
|
51
|
-
onClick={() => fileInputRef.current?.click()}
|
|
52
|
-
disabled={isLoading}
|
|
53
|
-
style={{
|
|
54
|
-
width: 36,
|
|
55
|
-
height: 36,
|
|
56
|
-
borderRadius: theme.radius.button,
|
|
57
|
-
border: `1px solid ${theme.colors.border}`,
|
|
58
|
-
backgroundColor: theme.colors.surface,
|
|
59
|
-
cursor: 'pointer',
|
|
60
|
-
}}
|
|
61
|
-
aria-label="Upload file"
|
|
62
|
-
>
|
|
63
|
-
+
|
|
64
|
-
</button>
|
|
65
|
-
<input
|
|
66
|
-
ref={fileInputRef}
|
|
67
|
-
type="file"
|
|
68
|
-
multiple
|
|
69
|
-
accept="image/*,.pdf,.doc,.docx,.txt"
|
|
70
|
-
style={{ display: 'none' }}
|
|
71
|
-
onChange={(e) => void handleFiles(e.target.files)}
|
|
72
|
-
/>
|
|
73
|
-
</>
|
|
74
|
-
) : null}
|
|
75
|
-
<textarea
|
|
76
|
-
value={inputText}
|
|
77
|
-
onChange={(e) => setInputText(e.target.value)}
|
|
78
|
-
placeholder={placeholder}
|
|
79
|
-
rows={2}
|
|
80
|
-
disabled={isLoading}
|
|
81
|
-
maxLength={4000}
|
|
82
|
-
style={{
|
|
83
|
-
flex: 1,
|
|
84
|
-
resize: 'none',
|
|
85
|
-
border: `1px solid ${theme.colors.border}`,
|
|
86
|
-
borderRadius: theme.radius.panel,
|
|
87
|
-
padding: theme.spacing.sm,
|
|
88
|
-
fontSize: theme.typography.bodySize,
|
|
89
|
-
color: theme.colors.assistantText,
|
|
90
|
-
}}
|
|
91
|
-
/>
|
|
92
|
-
<button
|
|
93
|
-
type="button"
|
|
94
|
-
onClick={() => void sendText()}
|
|
95
|
-
disabled={isLoading || !inputText.trim()}
|
|
96
|
-
style={{
|
|
97
|
-
backgroundColor: theme.colors.primary,
|
|
98
|
-
color: theme.colors.primaryText,
|
|
99
|
-
border: 'none',
|
|
100
|
-
borderRadius: theme.radius.button,
|
|
101
|
-
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
|
102
|
-
fontWeight: 600,
|
|
103
|
-
opacity: isLoading || !inputText.trim() ? 0.5 : 1,
|
|
104
|
-
cursor: 'pointer',
|
|
105
|
-
}}
|
|
106
|
-
>
|
|
107
|
-
Send
|
|
108
|
-
</button>
|
|
109
|
-
</div>
|
|
110
|
-
{isVoiceAvailable ? (
|
|
111
|
-
<button
|
|
112
|
-
type="button"
|
|
113
|
-
onClick={() => {
|
|
114
|
-
startVoice().catch((err: unknown) => {
|
|
115
|
-
console.warn('[nxtlinq] startVoice failed:', err);
|
|
116
|
-
});
|
|
117
|
-
}}
|
|
118
|
-
disabled={isVoiceConnecting}
|
|
119
|
-
style={{
|
|
120
|
-
display: 'block',
|
|
121
|
-
margin: `${theme.spacing.sm}px auto 0`,
|
|
122
|
-
border: 'none',
|
|
123
|
-
background: 'none',
|
|
124
|
-
color: theme.colors.primary,
|
|
125
|
-
fontSize: theme.typography.captionSize,
|
|
126
|
-
cursor: 'pointer',
|
|
127
|
-
}}
|
|
128
|
-
>
|
|
129
|
-
{isVoiceConnecting ? 'Connecting voice…' : 'Switch to voice mode'}
|
|
130
|
-
</button>
|
|
131
|
-
) : null}
|
|
132
|
-
</div>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import type { Message } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
|
-
import React, { useEffect, useRef } from 'react';
|
|
3
|
-
import { useAgentAssistant } from '../context/AgentAssistantContext';
|
|
4
|
-
|
|
5
|
-
function MessageBubble({ message }: { message: Message }): React.ReactElement {
|
|
6
|
-
const { theme } = useAgentAssistant();
|
|
7
|
-
const isUser = message.role === 'user';
|
|
8
|
-
const displayText =
|
|
9
|
-
message.partialContent && message.isStreaming
|
|
10
|
-
? message.partialContent
|
|
11
|
-
: message.content;
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<div
|
|
15
|
-
style={{
|
|
16
|
-
display: 'flex',
|
|
17
|
-
justifyContent: isUser ? 'flex-end' : 'flex-start',
|
|
18
|
-
marginBottom: theme.spacing.sm,
|
|
19
|
-
}}
|
|
20
|
-
>
|
|
21
|
-
<div
|
|
22
|
-
style={{
|
|
23
|
-
maxWidth: '85%',
|
|
24
|
-
padding: `${theme.spacing.sm}px ${theme.spacing.md}px`,
|
|
25
|
-
borderRadius: theme.radius.bubble,
|
|
26
|
-
backgroundColor: isUser ? theme.colors.userBubble : theme.colors.assistantBubble,
|
|
27
|
-
color: isUser ? theme.colors.userText : theme.colors.assistantText,
|
|
28
|
-
fontSize: theme.typography.bodySize,
|
|
29
|
-
whiteSpace: 'pre-wrap',
|
|
30
|
-
}}
|
|
31
|
-
>
|
|
32
|
-
{displayText || ' '}
|
|
33
|
-
{message.error ? (
|
|
34
|
-
<div style={{ color: theme.colors.error, fontSize: theme.typography.captionSize }}>
|
|
35
|
-
{message.error}
|
|
36
|
-
</div>
|
|
37
|
-
) : null}
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function AgentMessageList(): React.ReactElement {
|
|
44
|
-
const { messages, isLoading, theme } = useAgentAssistant();
|
|
45
|
-
const endRef = useRef<HTMLDivElement | null>(null);
|
|
46
|
-
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
endRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
49
|
-
}, [messages.length, messages[messages.length - 1]?.content, messages[messages.length - 1]?.partialContent]);
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<div
|
|
53
|
-
style={{
|
|
54
|
-
flex: 1,
|
|
55
|
-
minHeight: 0,
|
|
56
|
-
overflowY: 'auto',
|
|
57
|
-
padding: theme.spacing.md,
|
|
58
|
-
backgroundColor: theme.colors.background,
|
|
59
|
-
}}
|
|
60
|
-
>
|
|
61
|
-
{messages.length === 0 ? (
|
|
62
|
-
<p style={{ textAlign: 'center', color: theme.colors.mutedText, marginTop: 40 }}>
|
|
63
|
-
Send a message to start the conversation.
|
|
64
|
-
</p>
|
|
65
|
-
) : (
|
|
66
|
-
messages.map((message: Message) => (
|
|
67
|
-
<MessageBubble key={message.id} message={message} />
|
|
68
|
-
))
|
|
69
|
-
)}
|
|
70
|
-
{isLoading ? (
|
|
71
|
-
<p style={{ color: theme.colors.mutedText, fontSize: theme.typography.captionSize }}>
|
|
72
|
-
Thinking…
|
|
73
|
-
</p>
|
|
74
|
-
) : null}
|
|
75
|
-
<div ref={endRef} />
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
|
2
|
-
import { useAgentAssistant } from '../context/AgentAssistantContext';
|
|
3
|
-
|
|
4
|
-
/** Plays assistant WebRTC remote audio with retry until stream is ready. */
|
|
5
|
-
export function AgentRemoteAudio(): React.ReactElement | null {
|
|
6
|
-
const { getRemoteAudioStream, isVoiceActive, isVoiceChannelReady } = useAgentAssistant();
|
|
7
|
-
const audioRef = useRef<HTMLAudioElement | null>(null);
|
|
8
|
-
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
const audio = audioRef.current;
|
|
11
|
-
if (!audio || !isVoiceActive) return;
|
|
12
|
-
|
|
13
|
-
let cancelled = false;
|
|
14
|
-
const attach = () => {
|
|
15
|
-
if (cancelled) return;
|
|
16
|
-
const stream = getRemoteAudioStream();
|
|
17
|
-
if (!stream) return;
|
|
18
|
-
audio.srcObject = stream;
|
|
19
|
-
void audio.play().catch(() => undefined);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
attach();
|
|
23
|
-
const timer = window.setInterval(attach, 120);
|
|
24
|
-
return () => {
|
|
25
|
-
cancelled = true;
|
|
26
|
-
window.clearInterval(timer);
|
|
27
|
-
audio.pause();
|
|
28
|
-
audio.srcObject = null;
|
|
29
|
-
};
|
|
30
|
-
}, [getRemoteAudioStream, isVoiceActive, isVoiceChannelReady]);
|
|
31
|
-
|
|
32
|
-
if (!isVoiceActive) return null;
|
|
33
|
-
return <audio ref={audioRef} autoPlay playsInline style={{ display: 'none' }} />;
|
|
34
|
-
}
|