@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.1.1 → 0.1.3
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/AgentAssistantContext.d.ts.map +1 -1
- package/dist/context/AgentAssistantContext.js +21 -1
- package/dist/legacy/chatbot/ui/ChatBotHeader.d.ts +15 -0
- package/dist/legacy/chatbot/ui/ChatBotHeader.d.ts.map +1 -0
- package/dist/legacy/chatbot/ui/ChatBotHeader.js +62 -0
- package/dist/legacy/chatbot/ui/ChatBotUI.d.ts.map +1 -1
- package/dist/legacy/chatbot/ui/ChatBotUI.js +3 -71
- package/dist/legacy/chatbot/ui/chatBotHeaderParts.d.ts +15 -0
- package/dist/legacy/chatbot/ui/chatBotHeaderParts.d.ts.map +1 -0
- package/dist/legacy/chatbot/ui/chatBotHeaderParts.js +50 -0
- package/dist/voice/useVoiceTranscriptMessages.d.ts +7 -5
- package/dist/voice/useVoiceTranscriptMessages.d.ts.map +1 -1
- package/dist/voice/useVoiceTranscriptMessages.js +79 -76
- package/dist/voice/voiceUserBubble.d.ts +10 -0
- package/dist/voice/voiceUserBubble.d.ts.map +1 -0
- package/dist/voice/voiceUserBubble.js +52 -0
- package/package.json +3 -3
- package/src/context/AgentAssistantContext.tsx +20 -2
- package/src/legacy/chatbot/ui/ChatBotHeader.tsx +143 -0
- package/src/legacy/chatbot/ui/ChatBotUI.tsx +13 -144
- package/src/legacy/chatbot/ui/chatBotHeaderParts.tsx +115 -0
- package/src/voice/useVoiceTranscriptMessages.ts +87 -72
- package/src/voice/voiceUserBubble.ts +71 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VoiceStatus } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
1
|
+
import type { Message, VoiceStatus } from '@bytexbyte/nxtlinq-ai-agent-core-development';
|
|
2
2
|
import {
|
|
3
3
|
useNxtlinqAgent,
|
|
4
4
|
useNxtlinqVoice,
|
|
@@ -93,7 +93,9 @@ export function AgentAssistantProvider({
|
|
|
93
93
|
const voiceTranscriptApi = useMemo(
|
|
94
94
|
() => ({
|
|
95
95
|
getMessages: () => agent.agent.getSnapshot().messages,
|
|
96
|
-
|
|
96
|
+
updateMessages: (updater: (prev: Message[]) => Message[]) => {
|
|
97
|
+
agent.setMessages(updater(agent.agent.getSnapshot().messages));
|
|
98
|
+
},
|
|
97
99
|
syncVoiceTurnHistory: agent.syncVoiceTurnHistory,
|
|
98
100
|
}),
|
|
99
101
|
[agent.agent, agent.setMessages, agent.syncVoiceTurnHistory],
|
|
@@ -162,6 +164,22 @@ export function AgentAssistantProvider({
|
|
|
162
164
|
orchestrationCallbacks,
|
|
163
165
|
);
|
|
164
166
|
|
|
167
|
+
// iOS 切換 App 或相機後返回時,若 WebRTC App Channel 已斷開則自動停止語音 session,
|
|
168
|
+
// 避免麥克風圖示顯示活躍但狀態卡在 Idle 的問題。
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (typeof document === 'undefined') return;
|
|
171
|
+
const handleVisibilityChange = () => {
|
|
172
|
+
if (document.visibilityState !== 'visible') return;
|
|
173
|
+
if (voice.voiceSessionId == null) return;
|
|
174
|
+
const session = agent.agent.getVoiceSession();
|
|
175
|
+
if (!session?.isAppChannelOpen()) {
|
|
176
|
+
void wrappedStopVoice();
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
180
|
+
return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
181
|
+
}, [voice.voiceSessionId, agent.agent, wrappedStopVoice]);
|
|
182
|
+
|
|
165
183
|
const theme = useMemo(
|
|
166
184
|
() => ({
|
|
167
185
|
...defaultAgentAssistantTheme,
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/** @jsxImportSource @emotion/react */
|
|
2
|
+
import { css } from '@emotion/react';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ModelSelector } from './ModelSelector';
|
|
5
|
+
import {
|
|
6
|
+
DRAG_CORNER_EXCLUSION_PX,
|
|
7
|
+
HeaderActions,
|
|
8
|
+
HeaderLoadingIndicator,
|
|
9
|
+
PiiBadge,
|
|
10
|
+
} from './chatBotHeaderParts';
|
|
11
|
+
import { chatHeader, headerTitle } from './styles/isolatedStyles';
|
|
12
|
+
|
|
13
|
+
export type ChatBotHeaderProps = {
|
|
14
|
+
mobileLayout: boolean;
|
|
15
|
+
isDragging: boolean;
|
|
16
|
+
onDragStart: (event: React.PointerEvent<HTMLDivElement>) => void;
|
|
17
|
+
isVoiceMode: boolean;
|
|
18
|
+
isVoiceConnecting: boolean;
|
|
19
|
+
onVoiceToggle: () => void;
|
|
20
|
+
piiDisplayMode: 'plain' | 'redacted';
|
|
21
|
+
isAITLoading: boolean;
|
|
22
|
+
onSettingsClick: () => void;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const ChatBotHeader: React.FC<ChatBotHeaderProps> = ({
|
|
27
|
+
mobileLayout,
|
|
28
|
+
isDragging,
|
|
29
|
+
onDragStart,
|
|
30
|
+
isVoiceMode,
|
|
31
|
+
isVoiceConnecting,
|
|
32
|
+
onVoiceToggle,
|
|
33
|
+
piiDisplayMode,
|
|
34
|
+
isAITLoading,
|
|
35
|
+
onSettingsClick,
|
|
36
|
+
onClose,
|
|
37
|
+
}) => {
|
|
38
|
+
const actions = (
|
|
39
|
+
<HeaderActions
|
|
40
|
+
isVoiceMode={isVoiceMode}
|
|
41
|
+
isVoiceConnecting={isVoiceConnecting}
|
|
42
|
+
onVoiceToggle={onVoiceToggle}
|
|
43
|
+
onSettingsClick={onSettingsClick}
|
|
44
|
+
onClose={onClose}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const metaChips = !isVoiceMode ? (
|
|
49
|
+
<>
|
|
50
|
+
<div css={css`position: relative !important; pointer-events: auto !important;`}>
|
|
51
|
+
<ModelSelector />
|
|
52
|
+
</div>
|
|
53
|
+
{piiDisplayMode === 'redacted' && <PiiBadge />}
|
|
54
|
+
{isAITLoading && <HeaderLoadingIndicator />}
|
|
55
|
+
</>
|
|
56
|
+
) : null;
|
|
57
|
+
|
|
58
|
+
const dragOverlay = (
|
|
59
|
+
<div
|
|
60
|
+
css={css`
|
|
61
|
+
position: absolute !important;
|
|
62
|
+
left: ${DRAG_CORNER_EXCLUSION_PX}px !important;
|
|
63
|
+
right: ${DRAG_CORNER_EXCLUSION_PX}px !important;
|
|
64
|
+
top: 0 !important;
|
|
65
|
+
bottom: 0 !important;
|
|
66
|
+
z-index: 1 !important;
|
|
67
|
+
cursor: ${isDragging ? 'grabbing' : 'grab'} !important;
|
|
68
|
+
user-select: none !important;
|
|
69
|
+
`}
|
|
70
|
+
onPointerDown={onDragStart}
|
|
71
|
+
title="Drag to move"
|
|
72
|
+
aria-hidden
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div css={[chatHeader, css`
|
|
78
|
+
position: relative !important;
|
|
79
|
+
${mobileLayout ? css`
|
|
80
|
+
padding: 10px 12px !important;
|
|
81
|
+
flex-direction: column !important;
|
|
82
|
+
align-items: stretch !important;
|
|
83
|
+
gap: 8px !important;
|
|
84
|
+
` : ''}
|
|
85
|
+
`]}>
|
|
86
|
+
{dragOverlay}
|
|
87
|
+
{mobileLayout ? (
|
|
88
|
+
<div css={css`
|
|
89
|
+
position: relative !important;
|
|
90
|
+
z-index: 2 !important;
|
|
91
|
+
display: flex !important;
|
|
92
|
+
flex-direction: column !important;
|
|
93
|
+
gap: 8px !important;
|
|
94
|
+
width: 100% !important;
|
|
95
|
+
`}>
|
|
96
|
+
<div css={css`
|
|
97
|
+
display: flex !important;
|
|
98
|
+
justify-content: space-between !important;
|
|
99
|
+
align-items: center !important;
|
|
100
|
+
gap: 8px !important;
|
|
101
|
+
pointer-events: none !important;
|
|
102
|
+
`}>
|
|
103
|
+
<h3 css={[headerTitle, css`pointer-events: none !important;`]}>AI Agent</h3>
|
|
104
|
+
{actions}
|
|
105
|
+
</div>
|
|
106
|
+
{metaChips && (
|
|
107
|
+
<div css={css`
|
|
108
|
+
display: flex !important;
|
|
109
|
+
align-items: center !important;
|
|
110
|
+
flex-wrap: wrap !important;
|
|
111
|
+
gap: 8px !important;
|
|
112
|
+
pointer-events: none !important;
|
|
113
|
+
`}>
|
|
114
|
+
{metaChips}
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
) : (
|
|
119
|
+
<div css={css`
|
|
120
|
+
position: relative !important;
|
|
121
|
+
z-index: 2 !important;
|
|
122
|
+
display: flex !important;
|
|
123
|
+
justify-content: space-between !important;
|
|
124
|
+
align-items: center !important;
|
|
125
|
+
width: 100% !important;
|
|
126
|
+
pointer-events: none !important;
|
|
127
|
+
`}>
|
|
128
|
+
<div css={css`
|
|
129
|
+
display: flex !important;
|
|
130
|
+
align-items: center !important;
|
|
131
|
+
gap: 10px !important;
|
|
132
|
+
min-width: 0 !important;
|
|
133
|
+
pointer-events: none !important;
|
|
134
|
+
`}>
|
|
135
|
+
<h3 css={headerTitle}>AI Agent</h3>
|
|
136
|
+
{metaChips}
|
|
137
|
+
</div>
|
|
138
|
+
{actions}
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
@@ -3,28 +3,22 @@ import { css } from '@emotion/react';
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { useDraggable, useLocalStorage, useResizable, walletTextUtils } from '@bytexbyte/nxtlinq-ai-agent-web-development';
|
|
5
5
|
import { useChatBot } from '../context/ChatBotContext';
|
|
6
|
-
import
|
|
6
|
+
import { ChatBotHeader } from './ChatBotHeader';
|
|
7
7
|
import { MessageInput } from './MessageInput';
|
|
8
8
|
import { MessageList } from './MessageList';
|
|
9
|
-
import { ModelSelector } from './ModelSelector';
|
|
10
9
|
import { PermissionForm } from './PermissionForm';
|
|
11
10
|
import { PresetMessages } from './PresetMessages';
|
|
12
11
|
import { VoiceModePanel } from './VoiceModePanel';
|
|
13
12
|
import {
|
|
14
|
-
chatHeader,
|
|
15
13
|
chatWindow,
|
|
16
|
-
closeButton,
|
|
17
14
|
errorToast,
|
|
18
15
|
floatingButton,
|
|
19
|
-
headerButton,
|
|
20
|
-
headerTitle,
|
|
21
16
|
idvBanner,
|
|
22
17
|
idvBannerText,
|
|
23
18
|
idvBannerTitle,
|
|
24
19
|
idvDismissButton,
|
|
25
20
|
idvVerifyButton,
|
|
26
21
|
infoToast,
|
|
27
|
-
loadingSpinner,
|
|
28
22
|
modalOverlay,
|
|
29
23
|
resizeHandleNE,
|
|
30
24
|
resizeHandleNW,
|
|
@@ -36,8 +30,6 @@ import {
|
|
|
36
30
|
warningToast
|
|
37
31
|
} from './styles/isolatedStyles';
|
|
38
32
|
|
|
39
|
-
/** Header drag band inset so it does not overlap corner resize hit areas */
|
|
40
|
-
const DRAG_CORNER_EXCLUSION_PX = 20;
|
|
41
33
|
const MOBILE_BREAKPOINT = 768;
|
|
42
34
|
const MOBILE_EDGE_MARGIN = 12;
|
|
43
35
|
const MOBILE_FAB_POSITION = { right: MOBILE_EDGE_MARGIN, bottom: MOBILE_EDGE_MARGIN } as const;
|
|
@@ -716,141 +708,18 @@ export const ChatBotUI: React.FC = () => {
|
|
|
716
708
|
</>
|
|
717
709
|
)}
|
|
718
710
|
|
|
719
|
-
<
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
z-index: 1 !important;
|
|
732
|
-
cursor: ${isDragging ? 'grabbing' : 'grab'} !important;
|
|
733
|
-
user-select: none !important;
|
|
734
|
-
`}
|
|
735
|
-
onPointerDown={handleDragStart}
|
|
736
|
-
title="Drag to move"
|
|
737
|
-
aria-hidden
|
|
738
|
-
/>
|
|
739
|
-
<div
|
|
740
|
-
css={css`
|
|
741
|
-
position: relative !important;
|
|
742
|
-
z-index: 2 !important;
|
|
743
|
-
display: flex !important;
|
|
744
|
-
justify-content: space-between !important;
|
|
745
|
-
align-items: center !important;
|
|
746
|
-
width: 100% !important;
|
|
747
|
-
pointer-events: none !important;
|
|
748
|
-
`}
|
|
749
|
-
>
|
|
750
|
-
<div css={css`
|
|
751
|
-
display: flex !important;
|
|
752
|
-
align-items: center !important;
|
|
753
|
-
gap: 10px !important;
|
|
754
|
-
pointer-events: none !important;
|
|
755
|
-
`}>
|
|
756
|
-
<h3 css={headerTitle}>
|
|
757
|
-
AI Agent
|
|
758
|
-
</h3>
|
|
759
|
-
{!isVoiceMode && (
|
|
760
|
-
<div css={css`
|
|
761
|
-
position: relative !important;
|
|
762
|
-
pointer-events: auto !important;
|
|
763
|
-
`}>
|
|
764
|
-
<ModelSelector />
|
|
765
|
-
</div>
|
|
766
|
-
)}
|
|
767
|
-
{!isVoiceMode && piiDisplayMode === 'redacted' && (
|
|
768
|
-
<div
|
|
769
|
-
css={css`
|
|
770
|
-
display: inline-flex !important;
|
|
771
|
-
align-items: center !important;
|
|
772
|
-
gap: 4px !important;
|
|
773
|
-
padding: 2px 8px !important;
|
|
774
|
-
background-color: rgba(255, 255, 255, 0.2) !important;
|
|
775
|
-
border: 1px solid rgba(255, 255, 255, 0.4) !important;
|
|
776
|
-
border-radius: 10px !important;
|
|
777
|
-
font-size: 10px !important;
|
|
778
|
-
font-weight: 600 !important;
|
|
779
|
-
color: #ffffff !important;
|
|
780
|
-
white-space: nowrap !important;
|
|
781
|
-
line-height: 1.4 !important;
|
|
782
|
-
user-select: none !important;
|
|
783
|
-
`}
|
|
784
|
-
title="PII Protection is active — sensitive data is automatically anonymized before sending to AI"
|
|
785
|
-
>
|
|
786
|
-
<svg width="10" height="10" viewBox="0 0 24 24" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
|
|
787
|
-
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm-2 16l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z"/>
|
|
788
|
-
</svg>
|
|
789
|
-
PII Protected
|
|
790
|
-
</div>
|
|
791
|
-
)}
|
|
792
|
-
{isAITLoading && (
|
|
793
|
-
<div css={css`
|
|
794
|
-
display: flex !important;
|
|
795
|
-
align-items: center !important;
|
|
796
|
-
gap: 5px !important;
|
|
797
|
-
font-size: 12px !important;
|
|
798
|
-
opacity: 0.8 !important;
|
|
799
|
-
`}>
|
|
800
|
-
<div css={loadingSpinner} />
|
|
801
|
-
Loading...
|
|
802
|
-
</div>
|
|
803
|
-
)}
|
|
804
|
-
</div>
|
|
805
|
-
<div css={css`
|
|
806
|
-
display: flex !important;
|
|
807
|
-
align-items: center !important;
|
|
808
|
-
gap: 10px !important;
|
|
809
|
-
pointer-events: auto !important;
|
|
810
|
-
`}>
|
|
811
|
-
<button
|
|
812
|
-
onClick={() => void ((isVoiceMode || isVoiceConnecting) ? exitVoiceMode() : enterVoiceMode())}
|
|
813
|
-
css={[headerButton, css`
|
|
814
|
-
width: auto !important;
|
|
815
|
-
min-width: 82px !important;
|
|
816
|
-
padding: 0 10px !important;
|
|
817
|
-
gap: 6px !important;
|
|
818
|
-
font-size: 12px !important;
|
|
819
|
-
font-weight: 600 !important;
|
|
820
|
-
line-height: 1 !important;
|
|
821
|
-
white-space: nowrap !important;
|
|
822
|
-
`]}
|
|
823
|
-
title={
|
|
824
|
-
isVoiceConnecting
|
|
825
|
-
? 'Cancel voice connection and return to text mode'
|
|
826
|
-
: isVoiceMode
|
|
827
|
-
? 'Switch to text mode'
|
|
828
|
-
: 'Switch to voice mode'
|
|
829
|
-
}
|
|
830
|
-
onPointerDown={(e) => e.stopPropagation()}
|
|
831
|
-
>
|
|
832
|
-
<GraphicEqIcon css={css`font-size: 16px !important; color: #fff !important;`} />
|
|
833
|
-
{(isVoiceMode || isVoiceConnecting) ? 'Text Mode' : 'Voice Mode'}
|
|
834
|
-
</button>
|
|
835
|
-
<button
|
|
836
|
-
onClick={handleSettingsClick}
|
|
837
|
-
css={headerButton}
|
|
838
|
-
title="AIT Settings"
|
|
839
|
-
onPointerDown={(e) => e.stopPropagation()}
|
|
840
|
-
>
|
|
841
|
-
⚙️
|
|
842
|
-
</button>
|
|
843
|
-
<button
|
|
844
|
-
onClick={handleClose}
|
|
845
|
-
css={closeButton}
|
|
846
|
-
onPointerDown={(e) => e.stopPropagation()}
|
|
847
|
-
title="Minimize"
|
|
848
|
-
>
|
|
849
|
-
−
|
|
850
|
-
</button>
|
|
851
|
-
</div>
|
|
852
|
-
</div>
|
|
853
|
-
</div>
|
|
711
|
+
<ChatBotHeader
|
|
712
|
+
mobileLayout={mobileLayout}
|
|
713
|
+
isDragging={isDragging}
|
|
714
|
+
onDragStart={handleDragStart}
|
|
715
|
+
isVoiceMode={isVoiceMode}
|
|
716
|
+
isVoiceConnecting={isVoiceConnecting}
|
|
717
|
+
onVoiceToggle={() => void ((isVoiceMode || isVoiceConnecting) ? exitVoiceMode() : enterVoiceMode())}
|
|
718
|
+
piiDisplayMode={piiDisplayMode}
|
|
719
|
+
isAITLoading={isAITLoading}
|
|
720
|
+
onSettingsClick={handleSettingsClick}
|
|
721
|
+
onClose={handleClose}
|
|
722
|
+
/>
|
|
854
723
|
|
|
855
724
|
{showIDVSuggestion && hitAddress && !props.requireWalletIDVVerification && !hasBerifymeToken && !isWalletVerifiedWithBerifyme && (
|
|
856
725
|
<div
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** @jsxImportSource @emotion/react */
|
|
2
|
+
import { css } from '@emotion/react';
|
|
3
|
+
import GraphicEqIcon from '@mui/icons-material/GraphicEq';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { closeButton, headerButton, loadingSpinner } from './styles/isolatedStyles';
|
|
6
|
+
|
|
7
|
+
export const DRAG_CORNER_EXCLUSION_PX = 20;
|
|
8
|
+
|
|
9
|
+
const voiceButtonStyles = css`
|
|
10
|
+
width: auto !important;
|
|
11
|
+
min-width: 82px !important;
|
|
12
|
+
padding: 0 10px !important;
|
|
13
|
+
gap: 6px !important;
|
|
14
|
+
font-size: 12px !important;
|
|
15
|
+
font-weight: 600 !important;
|
|
16
|
+
line-height: 1 !important;
|
|
17
|
+
white-space: nowrap !important;
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
export const piiBadgeStyles = css`
|
|
21
|
+
display: inline-flex !important;
|
|
22
|
+
align-items: center !important;
|
|
23
|
+
gap: 4px !important;
|
|
24
|
+
padding: 2px 8px !important;
|
|
25
|
+
background-color: rgba(255, 255, 255, 0.2) !important;
|
|
26
|
+
border: 1px solid rgba(255, 255, 255, 0.4) !important;
|
|
27
|
+
border-radius: 10px !important;
|
|
28
|
+
font-size: 10px !important;
|
|
29
|
+
font-weight: 600 !important;
|
|
30
|
+
color: #ffffff !important;
|
|
31
|
+
white-space: nowrap !important;
|
|
32
|
+
line-height: 1.4 !important;
|
|
33
|
+
user-select: none !important;
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
export const PiiBadge: React.FC = () => (
|
|
37
|
+
<div
|
|
38
|
+
css={piiBadgeStyles}
|
|
39
|
+
title="PII Protection is active — sensitive data is automatically anonymized before sending to AI"
|
|
40
|
+
>
|
|
41
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="#ffffff" xmlns="http://www.w3.org/2000/svg">
|
|
42
|
+
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm-2 16l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z" />
|
|
43
|
+
</svg>
|
|
44
|
+
PII Protected
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
type HeaderActionsProps = {
|
|
49
|
+
isVoiceMode: boolean;
|
|
50
|
+
isVoiceConnecting: boolean;
|
|
51
|
+
onVoiceToggle: () => void;
|
|
52
|
+
onSettingsClick: () => void;
|
|
53
|
+
onClose: () => void;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const HeaderActions: React.FC<HeaderActionsProps> = ({
|
|
57
|
+
isVoiceMode,
|
|
58
|
+
isVoiceConnecting,
|
|
59
|
+
onVoiceToggle,
|
|
60
|
+
onSettingsClick,
|
|
61
|
+
onClose,
|
|
62
|
+
}) => (
|
|
63
|
+
<div css={css`
|
|
64
|
+
display: flex !important;
|
|
65
|
+
align-items: center !important;
|
|
66
|
+
gap: 10px !important;
|
|
67
|
+
flex-shrink: 0 !important;
|
|
68
|
+
pointer-events: auto !important;
|
|
69
|
+
`}>
|
|
70
|
+
<button
|
|
71
|
+
onClick={onVoiceToggle}
|
|
72
|
+
css={[headerButton, voiceButtonStyles]}
|
|
73
|
+
title={
|
|
74
|
+
isVoiceConnecting
|
|
75
|
+
? 'Cancel voice connection and return to text mode'
|
|
76
|
+
: isVoiceMode
|
|
77
|
+
? 'Switch to text mode'
|
|
78
|
+
: 'Switch to voice mode'
|
|
79
|
+
}
|
|
80
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
81
|
+
>
|
|
82
|
+
<GraphicEqIcon css={css`font-size: 16px !important; color: #fff !important;`} />
|
|
83
|
+
{(isVoiceMode || isVoiceConnecting) ? 'Text Mode' : 'Voice Mode'}
|
|
84
|
+
</button>
|
|
85
|
+
<button
|
|
86
|
+
onClick={onSettingsClick}
|
|
87
|
+
css={headerButton}
|
|
88
|
+
title="AIT Settings"
|
|
89
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
90
|
+
>
|
|
91
|
+
⚙️
|
|
92
|
+
</button>
|
|
93
|
+
<button
|
|
94
|
+
onClick={onClose}
|
|
95
|
+
css={closeButton}
|
|
96
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
97
|
+
title="Minimize"
|
|
98
|
+
>
|
|
99
|
+
−
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
export const HeaderLoadingIndicator: React.FC = () => (
|
|
105
|
+
<div css={css`
|
|
106
|
+
display: flex !important;
|
|
107
|
+
align-items: center !important;
|
|
108
|
+
gap: 5px !important;
|
|
109
|
+
font-size: 12px !important;
|
|
110
|
+
opacity: 0.8 !important;
|
|
111
|
+
`}>
|
|
112
|
+
<div css={loadingSpinner} />
|
|
113
|
+
Loading...
|
|
114
|
+
</div>
|
|
115
|
+
);
|