@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.1.2 → 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.
Files changed (93) hide show
  1. package/dist/ChatBot.d.ts +5 -0
  2. package/dist/ChatBot.d.ts.map +1 -0
  3. package/dist/ChatBot.js +35 -0
  4. package/dist/assets/images/adiSideItalicDataUri.d.ts +2 -0
  5. package/dist/assets/images/adiSideItalicDataUri.d.ts.map +1 -0
  6. package/dist/assets/images/adiSideItalicDataUri.js +1 -0
  7. package/dist/context/AgentAssistantContext.d.ts.map +1 -1
  8. package/dist/context/AgentAssistantContext.js +18 -0
  9. package/dist/context/ChatBotContext.d.ts +5 -0
  10. package/dist/context/ChatBotContext.d.ts.map +1 -0
  11. package/dist/context/ChatBotContext.js +2908 -0
  12. package/dist/index.d.ts +5 -13
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +4 -11
  15. package/dist/legacy/chatbot/context/ChatBotContext.d.ts.map +1 -1
  16. package/dist/legacy/chatbot/context/ChatBotContext.js +14 -0
  17. package/dist/types/ChatBotTypes.d.ts +166 -0
  18. package/dist/types/ChatBotTypes.d.ts.map +1 -0
  19. package/dist/types/ChatBotTypes.js +1 -0
  20. package/dist/ui/BerifyMeModal.d.ts +17 -0
  21. package/dist/ui/BerifyMeModal.d.ts.map +1 -0
  22. package/dist/ui/BerifyMeModal.js +110 -0
  23. package/dist/ui/ChatBotHeader.d.ts +15 -0
  24. package/dist/ui/ChatBotHeader.d.ts.map +1 -0
  25. package/dist/ui/ChatBotHeader.js +62 -0
  26. package/dist/ui/ChatBotUI.d.ts +3 -0
  27. package/dist/ui/ChatBotUI.d.ts.map +1 -0
  28. package/dist/ui/ChatBotUI.js +557 -0
  29. package/dist/ui/MessageInput.d.ts +3 -0
  30. package/dist/ui/MessageInput.d.ts.map +1 -0
  31. package/dist/ui/MessageInput.js +321 -0
  32. package/dist/ui/MessageList.d.ts +4 -0
  33. package/dist/ui/MessageList.d.ts.map +1 -0
  34. package/dist/ui/MessageList.js +455 -0
  35. package/dist/ui/ModelSelector.d.ts +4 -0
  36. package/dist/ui/ModelSelector.d.ts.map +1 -0
  37. package/dist/ui/ModelSelector.js +122 -0
  38. package/dist/ui/NotificationModal.d.ts +15 -0
  39. package/dist/ui/NotificationModal.d.ts.map +1 -0
  40. package/dist/ui/NotificationModal.js +53 -0
  41. package/dist/ui/PermissionForm.d.ts +8 -0
  42. package/dist/ui/PermissionForm.d.ts.map +1 -0
  43. package/dist/ui/PermissionForm.js +465 -0
  44. package/dist/ui/PresetMessages.d.ts +4 -0
  45. package/dist/ui/PresetMessages.d.ts.map +1 -0
  46. package/dist/ui/PresetMessages.js +33 -0
  47. package/dist/ui/VoiceModePanel.d.ts +3 -0
  48. package/dist/ui/VoiceModePanel.d.ts.map +1 -0
  49. package/dist/ui/VoiceModePanel.js +95 -0
  50. package/dist/ui/chatBotHeaderParts.d.ts +15 -0
  51. package/dist/ui/chatBotHeaderParts.d.ts.map +1 -0
  52. package/dist/ui/chatBotHeaderParts.js +50 -0
  53. package/dist/ui/index.d.ts +9 -0
  54. package/dist/ui/index.d.ts.map +1 -0
  55. package/dist/ui/index.js +8 -0
  56. package/dist/ui/styles/isolatedStyles.d.ts +73 -0
  57. package/dist/ui/styles/isolatedStyles.d.ts.map +1 -0
  58. package/dist/ui/styles/isolatedStyles.js +985 -0
  59. package/package.json +3 -3
  60. package/src/{legacy/chatbot/context → context}/ChatBotContext.tsx +0 -1
  61. package/src/index.ts +17 -40
  62. package/src/{legacy/chatbot/ui → ui}/ModelSelector.tsx +1 -1
  63. package/src/{legacy/chatbot/ui → ui}/VoiceModePanel.tsx +1 -1
  64. package/src/ui/index.ts +8 -0
  65. package/src/NxtlinqAgentChat.tsx +0 -79
  66. package/src/components/AgentAssistantShell.tsx +0 -104
  67. package/src/components/AgentComposer.tsx +0 -134
  68. package/src/components/AgentMessageList.tsx +0 -78
  69. package/src/components/AgentRemoteAudio.tsx +0 -34
  70. package/src/components/AgentVoiceBar.tsx +0 -173
  71. package/src/components/PresetMessageChips.tsx +0 -41
  72. package/src/context/AgentAssistantContext.tsx +0 -278
  73. package/src/legacy/index.ts +0 -26
  74. package/src/theme/defaultTheme.ts +0 -22
  75. package/src/types.ts +0 -65
  76. package/src/voice/useVoiceConnectOrchestration.ts +0 -117
  77. package/src/voice/useVoiceMicState.ts +0 -117
  78. package/src/voice/useVoiceTranscriptMessages.ts +0 -188
  79. package/src/voice/voiceMicConstants.ts +0 -13
  80. package/src/voice/voiceUserBubble.ts +0 -71
  81. /package/src/{legacy/chatbot/ChatBot.tsx → ChatBot.tsx} +0 -0
  82. /package/src/{legacy/assets → assets}/images/adiSideItalicDataUri.ts +0 -0
  83. /package/src/{legacy/chatbot/types → types}/ChatBotTypes.ts +0 -0
  84. /package/src/{legacy/chatbot/ui → ui}/BerifyMeModal.tsx +0 -0
  85. /package/src/{legacy/chatbot/ui → ui}/ChatBotHeader.tsx +0 -0
  86. /package/src/{legacy/chatbot/ui → ui}/ChatBotUI.tsx +0 -0
  87. /package/src/{legacy/chatbot/ui → ui}/MessageInput.tsx +0 -0
  88. /package/src/{legacy/chatbot/ui → ui}/MessageList.tsx +0 -0
  89. /package/src/{legacy/chatbot/ui → ui}/NotificationModal.tsx +0 -0
  90. /package/src/{legacy/chatbot/ui → ui}/PermissionForm.tsx +0 -0
  91. /package/src/{legacy/chatbot/ui → ui}/PresetMessages.tsx +0 -0
  92. /package/src/{legacy/chatbot/ui → ui}/chatBotHeaderParts.tsx +0 -0
  93. /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.2",
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",
@@ -49,8 +49,8 @@
49
49
  "uuid": "^11.1.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@bytexbyte/nxtlinq-ai-agent-core-development": "0.3.9",
53
- "@bytexbyte/nxtlinq-ai-agent-web-development": "0.1.2",
52
+ "@bytexbyte/nxtlinq-ai-agent-core-development": "workspace:^",
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 { NxtlinqAgentChat } from './NxtlinqAgentChat';
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 './legacy';
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
- } from './types';
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 '../../assets/images/adiSideItalicDataUri';
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
  >
@@ -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';
@@ -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
- }