@pmate/chat-sdk 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 (105) hide show
  1. package/README.md +5 -0
  2. package/dist/assets/ChatInput.stories-6Z6tmO-P.js +922 -0
  3. package/dist/assets/Color-AVL7NMMY-CdXTB89n.js +1 -0
  4. package/dist/assets/DocsRenderer-PQXLIZUC-CtoiPcLN.js +1243 -0
  5. package/dist/assets/client-BG97-bzh.js +9 -0
  6. package/dist/assets/iframe-CWturJ6d.css +1 -0
  7. package/dist/assets/iframe-fcxekwVB.js +1071 -0
  8. package/dist/assets/index-u9_9PRd-.js +1 -0
  9. package/dist/assets/preload-helper-PPVm8Dsz.js +1 -0
  10. package/dist/assets/react-18-DKDc3Hbl.js +1 -0
  11. package/dist/favicon-wrapper.svg +46 -0
  12. package/dist/favicon.svg +1 -0
  13. package/dist/iframe.html +687 -0
  14. package/dist/index.html +148 -0
  15. package/dist/index.json +1 -0
  16. package/dist/nunito-sans-bold-italic.woff2 +0 -0
  17. package/dist/nunito-sans-bold.woff2 +0 -0
  18. package/dist/nunito-sans-italic.woff2 +0 -0
  19. package/dist/nunito-sans-regular.woff2 +0 -0
  20. package/dist/project.json +1 -0
  21. package/dist/sb-addons/docs-1/manager-bundle.js +151 -0
  22. package/dist/sb-addons/docs-1/manager-bundle.js.LEGAL.txt +0 -0
  23. package/dist/sb-addons/storybook-core-server-presets-0/common-manager-bundle.js +971 -0
  24. package/dist/sb-addons/storybook-core-server-presets-0/common-manager-bundle.js.LEGAL.txt +0 -0
  25. package/dist/sb-common-assets/favicon-wrapper.svg +46 -0
  26. package/dist/sb-common-assets/favicon.svg +1 -0
  27. package/dist/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  28. package/dist/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  29. package/dist/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  30. package/dist/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  31. package/dist/sb-manager/globals-module-info.js +799 -0
  32. package/dist/sb-manager/globals-runtime.js +69791 -0
  33. package/dist/sb-manager/globals.js +34 -0
  34. package/dist/sb-manager/runtime.js +13198 -0
  35. package/dist/vite-inject-mocker-entry.js +2 -0
  36. package/lib/atoms/attachmentAtoms.d.ts +4 -0
  37. package/lib/atoms/attachmentAtoms.js +2 -0
  38. package/lib/atoms/chatInputAtoms.d.ts +16 -0
  39. package/lib/atoms/chatInputAtoms.js +6 -0
  40. package/lib/atoms/voiceAtoms.d.ts +6 -0
  41. package/lib/atoms/voiceAtoms.js +3 -0
  42. package/lib/components/AttachmentPreview.d.ts +6 -0
  43. package/lib/components/AttachmentPreview.js +13 -0
  44. package/lib/components/ChatInputDesktop.d.ts +2 -0
  45. package/lib/components/ChatInputDesktop.js +181 -0
  46. package/lib/components/ChatInputMobile.d.ts +2 -0
  47. package/lib/components/ChatInputMobile.js +338 -0
  48. package/lib/components/EmojiPicker.d.ts +10 -0
  49. package/lib/components/EmojiPicker.js +62 -0
  50. package/lib/components/EmojiTrigger.d.ts +2 -0
  51. package/lib/components/EmojiTrigger.js +18 -0
  52. package/lib/components/HoldToTalkButton.d.ts +13 -0
  53. package/lib/components/HoldToTalkButton.js +38 -0
  54. package/lib/components/ImageTrigger.d.ts +2 -0
  55. package/lib/components/ImageTrigger.js +18 -0
  56. package/lib/components/InputSurface.d.ts +7 -0
  57. package/lib/components/InputSurface.js +26 -0
  58. package/lib/components/SendAction.d.ts +5 -0
  59. package/lib/components/SendAction.js +18 -0
  60. package/lib/components/StatusHint.d.ts +6 -0
  61. package/lib/components/StatusHint.js +16 -0
  62. package/lib/components/TextComposer.d.ts +5 -0
  63. package/lib/components/TextComposer.js +16 -0
  64. package/lib/components/VoiceModeSwitch.d.ts +7 -0
  65. package/lib/components/VoiceModeSwitch.js +14 -0
  66. package/lib/components/WaveformFeedback.d.ts +6 -0
  67. package/lib/components/WaveformFeedback.js +44 -0
  68. package/lib/components/icons.d.ts +11 -0
  69. package/lib/components/icons.js +29 -0
  70. package/lib/hooks/useAttachmentState.d.ts +6 -0
  71. package/lib/hooks/useAttachmentState.js +21 -0
  72. package/lib/hooks/useChatInputState.d.ts +14 -0
  73. package/lib/hooks/useChatInputState.js +25 -0
  74. package/lib/hooks/useTranscriptComposer.d.ts +1 -0
  75. package/lib/hooks/useTranscriptComposer.js +11 -0
  76. package/lib/hooks/useVoiceInputController.d.ts +14 -0
  77. package/lib/hooks/useVoiceInputController.js +133 -0
  78. package/lib/index.d.ts +23 -0
  79. package/lib/index.js +23 -0
  80. package/lib/recorder-processor.js +20 -0
  81. package/lib/services/agentSdkASRClient.d.ts +28 -0
  82. package/lib/services/agentSdkASRClient.js +25 -0
  83. package/lib/services/authTokenResolver.d.ts +7 -0
  84. package/lib/services/authTokenResolver.js +7 -0
  85. package/lib/stories/ChatInput.stories.d.ts +15 -0
  86. package/lib/stories/ChatInput.stories.js +38 -0
  87. package/lib/tests/agentSdkASRClient.test.d.ts +1 -0
  88. package/lib/tests/agentSdkASRClient.test.js +39 -0
  89. package/lib/tests/defaultTokens.test.d.ts +1 -0
  90. package/lib/tests/defaultTokens.test.js +16 -0
  91. package/lib/tests/transcriptComposer.test.d.ts +1 -0
  92. package/lib/tests/transcriptComposer.test.js +10 -0
  93. package/lib/tokens/defaultTokens.d.ts +3 -0
  94. package/lib/tokens/defaultTokens.js +91 -0
  95. package/lib/types/attachment.d.ts +8 -0
  96. package/lib/types/attachment.js +1 -0
  97. package/lib/types/chatInput.d.ts +139 -0
  98. package/lib/types/chatInput.js +1 -0
  99. package/lib/types/voice.d.ts +17 -0
  100. package/lib/types/voice.js +1 -0
  101. package/lib/voice/MicStateManager.d.ts +43 -0
  102. package/lib/voice/MicStateManager.js +155 -0
  103. package/lib/voice/Recorder.d.ts +44 -0
  104. package/lib/voice/Recorder.js +224 -0
  105. package/package.json +59 -0
@@ -0,0 +1,338 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Provider, createStore } from "jotai";
4
+ import { useEffect, useRef, useState } from "react";
5
+ import { chatInputErrorAtom, chatInputModeAtom, chatInputStatusAtom, chatInputTextAtom, chatInputTranscriptAtom, } from "../atoms/chatInputAtoms";
6
+ import { waveformLevelsAtom } from "../atoms/voiceAtoms";
7
+ import { attachmentListAtom } from "../atoms/attachmentAtoms";
8
+ import { mergeChatInputTokens } from "../tokens/defaultTokens";
9
+ import { useAttachmentState } from "../hooks/useAttachmentState";
10
+ import { useChatInputState } from "../hooks/useChatInputState";
11
+ import { useVoiceInputController } from "../hooks/useVoiceInputController";
12
+ import { AttachmentPreview } from "./AttachmentPreview";
13
+ import { EmojiPicker } from "./EmojiPicker";
14
+ import { EmojiTrigger } from "./EmojiTrigger";
15
+ import { HoldToTalkButton } from "./HoldToTalkButton";
16
+ import { ImageTrigger } from "./ImageTrigger";
17
+ import { InputSurface } from "./InputSurface";
18
+ import { SendAction } from "./SendAction";
19
+ import { StatusHint } from "./StatusHint";
20
+ import { TextComposer } from "./TextComposer";
21
+ import { WaveformFeedback } from "./WaveformFeedback";
22
+ import { KeyboardOutlineIcon, MicOutlineIcon } from "./icons";
23
+ function MobileInner(props) {
24
+ const tokens = mergeChatInputTokens(props.tokens);
25
+ const state = useChatInputState();
26
+ const attachments = useAttachmentState();
27
+ const [recordingChoice, setRecordingChoice] = useState("send");
28
+ const [isEmojiOpen, setIsEmojiOpen] = useState(false);
29
+ const [isUploadingImage, setIsUploadingImage] = useState(false);
30
+ const cancelZoneRef = useRef(null);
31
+ const transcribeZoneRef = useRef(null);
32
+ const textComposerRef = useRef(null);
33
+ const emojiAnchorRef = useRef(null);
34
+ const imageInputRef = useRef(null);
35
+ const voice = useVoiceInputController({
36
+ ...props.voice,
37
+ onVoiceSend: async (blob) => {
38
+ await props.onSend?.({
39
+ text: "",
40
+ attachments: state.attachments,
41
+ audio: blob,
42
+ mode: "voice",
43
+ });
44
+ },
45
+ });
46
+ useEffect(() => {
47
+ if (props.value !== undefined) {
48
+ state.setText(props.value);
49
+ }
50
+ }, [props.value]);
51
+ const statusText = state.status === "recording"
52
+ ? "正在录音"
53
+ : state.status === "transcribing"
54
+ ? "正在转写..."
55
+ : (state.error ?? (isUploadingImage ? "正在上传图片..." : state.transcript) ?? undefined);
56
+ const onSend = async () => {
57
+ if (!props.onSend) {
58
+ return;
59
+ }
60
+ state.setStatus("sending");
61
+ await props.onSend({
62
+ text: state.text,
63
+ attachments: state.attachments,
64
+ transcript: state.transcript || undefined,
65
+ mode: "text",
66
+ });
67
+ state.setText("");
68
+ state.setTranscript("");
69
+ state.setAttachments([]);
70
+ props.onChange?.("");
71
+ state.setStatus("idle");
72
+ };
73
+ const handleImageFile = async (file) => {
74
+ if (!props.image?.uploadImage) {
75
+ attachments.addAttachment({
76
+ name: file.name,
77
+ file,
78
+ previewUrl: URL.createObjectURL(file),
79
+ });
80
+ return;
81
+ }
82
+ setIsUploadingImage(true);
83
+ state.setError(null);
84
+ try {
85
+ const uploaded = await props.image.uploadImage(file);
86
+ attachments.addAttachment({
87
+ name: uploaded.name ?? file.name,
88
+ file,
89
+ url: uploaded.url,
90
+ previewUrl: uploaded.previewUrl ?? uploaded.url,
91
+ });
92
+ }
93
+ catch (error) {
94
+ state.setError(error instanceof Error ? error.message : "Failed to upload image");
95
+ state.setStatus("error");
96
+ }
97
+ finally {
98
+ setIsUploadingImage(false);
99
+ }
100
+ };
101
+ const isRecordingOverlayVisible = state.status === "recording" || state.status === "transcribing";
102
+ const updateRecordingChoice = (clientX, clientY) => {
103
+ const isInside = (node) => {
104
+ if (!node) {
105
+ return false;
106
+ }
107
+ const rect = node.getBoundingClientRect();
108
+ const inset = 20;
109
+ return (clientX >= rect.left - inset &&
110
+ clientX <= rect.right + inset &&
111
+ clientY >= rect.top - inset &&
112
+ clientY <= rect.bottom + inset);
113
+ };
114
+ if (isInside(cancelZoneRef.current)) {
115
+ setRecordingChoice("cancel");
116
+ return;
117
+ }
118
+ if (isInside(transcribeZoneRef.current)) {
119
+ setRecordingChoice("transcribe");
120
+ return;
121
+ }
122
+ setRecordingChoice("send");
123
+ };
124
+ const insertEmoji = (emoji) => {
125
+ const textarea = textComposerRef.current;
126
+ const start = textarea?.selectionStart ?? state.text.length;
127
+ const end = textarea?.selectionEnd ?? state.text.length;
128
+ const nextText = `${state.text.slice(0, start)}${emoji}${state.text.slice(end)}`;
129
+ state.setText(nextText);
130
+ state.setStatus(nextText.trim() ? "typing" : "idle");
131
+ props.onChange?.(nextText);
132
+ requestAnimationFrame(() => {
133
+ textarea?.focus();
134
+ const caret = start + emoji.length;
135
+ textarea?.setSelectionRange(caret, caret);
136
+ });
137
+ };
138
+ return (_jsxs("div", { style: {
139
+ position: "relative",
140
+ width: "100%",
141
+ maxWidth: 420,
142
+ padding: 0,
143
+ background: "#ECE9E3",
144
+ borderRadius: 0,
145
+ border: 0,
146
+ boxShadow: "none",
147
+ overflow: "visible",
148
+ }, children: [isRecordingOverlayVisible ? (_jsxs("div", { style: {
149
+ position: "absolute",
150
+ left: 0,
151
+ right: 0,
152
+ bottom: 72,
153
+ height: 250,
154
+ pointerEvents: "none",
155
+ background: "linear-gradient(180deg, rgba(22, 16, 45, 0) 0%, rgba(22, 16, 45, 0.24) 34%, rgba(22, 16, 45, 0.6) 100%)",
156
+ borderRadius: 28,
157
+ }, children: [_jsxs("div", { style: {
158
+ position: "absolute",
159
+ left: "50%",
160
+ transform: "translateX(-50%)",
161
+ bottom: 126,
162
+ width: 188,
163
+ padding: "18px 18px 22px",
164
+ borderRadius: 26,
165
+ background: recordingChoice === "transcribe" ? "#E7DCFF" : "#F3EEFF",
166
+ boxShadow: "0 18px 40px rgba(40, 22, 102, 0.18)",
167
+ display: "grid",
168
+ justifyItems: "center",
169
+ gap: 8,
170
+ }, children: [_jsx(WaveformFeedback, { levels: voice.waveformLevels, tokens: tokens, compact: true }), _jsx("div", { style: {
171
+ color: recordingChoice === "transcribe" ? "#5B35D4" : "#6D4AFF",
172
+ fontSize: 13,
173
+ lineHeight: "18px",
174
+ fontWeight: 600,
175
+ }, children: recordingChoice === "transcribe"
176
+ ? "松开转文字"
177
+ : state.status === "transcribing"
178
+ ? "正在转写..."
179
+ : "松开发送" }), _jsx("div", { style: {
180
+ position: "absolute",
181
+ bottom: -9,
182
+ width: 18,
183
+ height: 18,
184
+ background: recordingChoice === "transcribe" ? "#E7DCFF" : "#F3EEFF",
185
+ transform: "rotate(45deg)",
186
+ borderRadius: 3,
187
+ } })] }), _jsxs("div", { style: {
188
+ position: "absolute",
189
+ left: 8,
190
+ right: 8,
191
+ bottom: 6,
192
+ display: "grid",
193
+ gridTemplateColumns: "1fr 1fr",
194
+ gap: 14,
195
+ pointerEvents: "none",
196
+ }, children: [_jsx("div", { ref: cancelZoneRef, style: {
197
+ height: 84,
198
+ borderRadius: "46px 46px 24px 24px",
199
+ background: recordingChoice === "cancel"
200
+ ? "rgba(211, 72, 99, 0.94)"
201
+ : "rgba(59, 45, 109, 0.62)",
202
+ color: "#FFFFFF",
203
+ display: "flex",
204
+ alignItems: "center",
205
+ justifyContent: "center",
206
+ fontSize: 15,
207
+ fontWeight: 600,
208
+ letterSpacing: "0.02em",
209
+ transform: recordingChoice === "cancel" ? "scale(1.03)" : "scale(1)",
210
+ transition: `all ${tokens.motion.stateTransition}`,
211
+ boxShadow: recordingChoice === "cancel"
212
+ ? "0 16px 28px rgba(211, 72, 99, 0.22)"
213
+ : "none",
214
+ }, children: "\u53D6\u6D88" }), _jsx("div", { ref: transcribeZoneRef, style: {
215
+ height: 84,
216
+ borderRadius: "46px 46px 24px 24px",
217
+ background: recordingChoice === "transcribe"
218
+ ? "rgba(109, 74, 255, 0.94)"
219
+ : "rgba(59, 45, 109, 0.62)",
220
+ color: "#FFFFFF",
221
+ display: "flex",
222
+ alignItems: "center",
223
+ justifyContent: "center",
224
+ fontSize: 15,
225
+ fontWeight: 600,
226
+ letterSpacing: "0.02em",
227
+ transform: recordingChoice === "transcribe" ? "scale(1.03)" : "scale(1)",
228
+ transition: `all ${tokens.motion.stateTransition}`,
229
+ boxShadow: recordingChoice === "transcribe"
230
+ ? "0 16px 28px rgba(109, 74, 255, 0.24)"
231
+ : "none",
232
+ }, children: "\u8F6C\u6587\u5B57" })] })] })) : null, _jsx(AttachmentPreview, { attachments: attachments.attachments, tokens: tokens }), _jsx("input", { ref: imageInputRef, type: "file", accept: props.image?.accept ?? "image/*", style: { display: "none" }, onChange: (event) => {
233
+ const file = event.target.files?.[0];
234
+ if (file) {
235
+ void handleImageFile(file);
236
+ }
237
+ event.currentTarget.value = "";
238
+ } }), _jsxs("div", { style: {
239
+ position: "relative",
240
+ display: "grid",
241
+ gridTemplateColumns: "28px minmax(0, 1fr) auto auto",
242
+ gap: 4,
243
+ alignItems: "end",
244
+ padding: "4px 0 env(safe-area-inset-bottom, 0px)",
245
+ }, children: [_jsx(EmojiPicker, { open: isEmojiOpen, isMobile: true, anchorRef: emojiAnchorRef, onSelect: insertEmoji, onClose: () => setIsEmojiOpen(false) }), _jsx("button", { type: "button", "aria-label": state.mode === "voice" ? "切换到文字输入" : "切换到语音输入", disabled: props.disabled || props.enableVoice === false, onClick: () => {
246
+ const next = state.mode === "voice" ? "text" : "voice";
247
+ state.setMode(next);
248
+ state.setStatus(next === "voice" ? "voice-mode-ready" : "idle");
249
+ }, style: {
250
+ display: "inline-flex",
251
+ alignItems: "center",
252
+ justifyContent: "center",
253
+ width: 28,
254
+ height: 28,
255
+ border: 0,
256
+ borderRadius: 999,
257
+ background: "transparent",
258
+ color: "#111111",
259
+ cursor: "pointer",
260
+ alignSelf: "center",
261
+ opacity: props.disabled ? 0.45 : 1,
262
+ }, children: state.mode === "voice" ? _jsx(KeyboardOutlineIcon, { size: 24 }) : _jsx(MicOutlineIcon, { size: 24 }) }), _jsx(InputSurface, { tokens: tokens, status: state.status, styleOverride: {
263
+ borderRadius: 14,
264
+ background: "#FFFFFF",
265
+ borderColor: state.status === "error" ? tokens.color.borderError : "#E5E7EB",
266
+ boxShadow: "none",
267
+ minHeight: state.mode === "voice" ? 54 : 44,
268
+ padding: state.mode === "voice" ? "0" : "0 12px",
269
+ }, children: state.mode === "voice" ? (_jsx("div", { children: _jsx(HoldToTalkButton, { tokens: tokens, active: isRecordingOverlayVisible, disabled: props.disabled, text: "\u6309\u4F4F \u8BF4\u8BDD", activeText: "\u677E\u5F00 \u53D1\u9001", onPressStart: () => {
270
+ setRecordingChoice("send");
271
+ voice.startRecording();
272
+ }, onPressMove: (event) => {
273
+ updateRecordingChoice(event.clientX, event.clientY);
274
+ }, onPressEnd: () => {
275
+ if (recordingChoice === "cancel") {
276
+ voice.cancelRecording();
277
+ }
278
+ else if (recordingChoice === "transcribe") {
279
+ voice.stopRecording("transcribe");
280
+ }
281
+ else {
282
+ voice.stopRecording("send");
283
+ }
284
+ setRecordingChoice("send");
285
+ }, onPressCancel: () => {
286
+ setRecordingChoice("send");
287
+ voice.cancelRecording();
288
+ } }) })) : (_jsx(TextComposer, { ref: textComposerRef, tokens: tokens, value: state.text, rows: 1, placeholder: "\u53D1\u6D88\u606F", className: "placeholder:text-[#8A8A8E]", onChange: (event) => {
289
+ state.setText(event.target.value);
290
+ state.setStatus(event.target.value.trim() ? "typing" : "idle");
291
+ props.onChange?.(event.target.value);
292
+ }, style: {
293
+ minHeight: 18,
294
+ paddingTop: 10,
295
+ paddingBottom: 10,
296
+ color: "#171717",
297
+ fontSize: 16,
298
+ lineHeight: "22px",
299
+ caretColor: tokens.color.brandPrimary,
300
+ } })) }), _jsx(EmojiTrigger, { ref: emojiAnchorRef, "data-icon-size": 24, onClick: () => setIsEmojiOpen((prev) => !prev), style: {
301
+ color: "#111111",
302
+ alignSelf: "center",
303
+ width: 28,
304
+ height: 28,
305
+ } }), state.mode === "text" && state.text.trim() ? (_jsx(SendAction, { tokens: tokens, disabled: props.disabled, onClick: () => void onSend(), style: {
306
+ minWidth: 64,
307
+ minHeight: 30,
308
+ borderRadius: 12,
309
+ alignSelf: "center",
310
+ padding: "0 12px",
311
+ }, children: "\u53D1\u9001" })) : (_jsx(ImageTrigger, { "data-icon-size": 24, onClick: () => imageInputRef.current?.click(), disabled: props.disabled || isUploadingImage, style: {
312
+ color: "#111111",
313
+ alignSelf: "center",
314
+ width: 28,
315
+ height: 28,
316
+ opacity: props.disabled || isUploadingImage ? 0.4 : 1,
317
+ } }))] }), _jsx(StatusHint, { tokens: tokens, text: state.status === "recording" || state.status === "transcribing" ? undefined : statusText, tone: state.status === "error" ? "error" : "secondary" })] }));
318
+ }
319
+ export function ChatInputMobile(props) {
320
+ const stateKey = JSON.stringify({
321
+ initialState: props.initialState,
322
+ defaultValue: props.defaultValue,
323
+ voiceModeDefault: props.voiceModeDefault,
324
+ });
325
+ const storeRef = useRef(null);
326
+ if (!storeRef.current || storeRef.current.key !== stateKey) {
327
+ const store = createStore();
328
+ store.set(chatInputModeAtom, props.initialState?.mode ?? props.voiceModeDefault ?? "text");
329
+ store.set(chatInputStatusAtom, props.initialState?.status ?? "idle");
330
+ store.set(chatInputTextAtom, props.initialState?.text ?? props.defaultValue ?? "");
331
+ store.set(chatInputTranscriptAtom, props.initialState?.transcript ?? "");
332
+ store.set(chatInputErrorAtom, props.initialState?.error ?? null);
333
+ store.set(attachmentListAtom, props.initialState?.attachments ?? []);
334
+ store.set(waveformLevelsAtom, props.initialState?.waveformLevels ?? [0.3, 0.5, 0.75, 0.55, 0.35]);
335
+ storeRef.current = { key: stateKey, store };
336
+ }
337
+ return (_jsx(Provider, { store: storeRef.current.store, children: _jsx(MobileInner, { ...props }) }));
338
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ type EmojiPickerProps = {
3
+ open: boolean;
4
+ isMobile: boolean;
5
+ anchorRef: React.RefObject<HTMLElement | null>;
6
+ onSelect: (emoji: string) => void;
7
+ onClose: () => void;
8
+ };
9
+ export declare function EmojiPicker({ open, isMobile, anchorRef, onSelect, onClose, }: EmojiPickerProps): import("react/jsx-runtime").JSX.Element | null;
10
+ export {};
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import data from "@emoji-mart/data";
3
+ import Picker from "@emoji-mart/react";
4
+ import React, { useEffect, useRef } from "react";
5
+ export function EmojiPicker({ open, isMobile, anchorRef, onSelect, onClose, }) {
6
+ const panelRef = useRef(null);
7
+ useEffect(() => {
8
+ if (!open) {
9
+ return;
10
+ }
11
+ const onPointerDown = (event) => {
12
+ const target = event.target;
13
+ if (target &&
14
+ panelRef.current?.contains(target)) {
15
+ return;
16
+ }
17
+ if (target &&
18
+ anchorRef.current?.contains(target)) {
19
+ return;
20
+ }
21
+ onClose();
22
+ };
23
+ document.addEventListener("mousedown", onPointerDown);
24
+ return () => document.removeEventListener("mousedown", onPointerDown);
25
+ }, [anchorRef, onClose, open]);
26
+ if (!open) {
27
+ return null;
28
+ }
29
+ const handleSelect = (emoji) => {
30
+ if (!emoji.native) {
31
+ return;
32
+ }
33
+ onSelect(emoji.native);
34
+ onClose();
35
+ };
36
+ if (isMobile) {
37
+ return (_jsx("div", { style: {
38
+ position: "absolute",
39
+ left: 0,
40
+ right: 0,
41
+ bottom: "calc(100% + 10px)",
42
+ zIndex: 40,
43
+ }, children: _jsx("div", { ref: panelRef, style: {
44
+ overflow: "hidden",
45
+ borderRadius: 22,
46
+ border: "1px solid rgba(109, 74, 255, 0.12)",
47
+ background: "#FFFFFF",
48
+ boxShadow: "0 16px 44px rgba(38, 22, 92, 0.18)",
49
+ }, children: _jsx(Picker, { data: data, previewPosition: "none", navPosition: "bottom", maxFrequentRows: 1, perLine: 8, emojiSize: 24, onEmojiSelect: handleSelect }) }) }));
50
+ }
51
+ return (_jsx("div", { ref: panelRef, style: {
52
+ position: "absolute",
53
+ right: 0,
54
+ bottom: "calc(100% + 12px)",
55
+ zIndex: 40,
56
+ overflow: "hidden",
57
+ borderRadius: 18,
58
+ border: "1px solid rgba(109, 74, 255, 0.12)",
59
+ background: "#FFFFFF",
60
+ boxShadow: "0 16px 44px rgba(38, 22, 92, 0.18)",
61
+ }, children: _jsx(Picker, { data: data, previewPosition: "none", perLine: 9, emojiSize: 20, onEmojiSelect: handleSelect }) }));
62
+ }
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare const EmojiTrigger: React.ForwardRefExoticComponent<React.ButtonHTMLAttributes<HTMLButtonElement> & React.RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { forwardRef } from "react";
3
+ import { SmileOutlineIcon } from "./icons";
4
+ export const EmojiTrigger = forwardRef(function EmojiTrigger(props, ref) {
5
+ const iconSize = Number(props["data-icon-size"] ?? 26);
6
+ return (_jsx("button", { ref: ref, type: "button", "aria-label": "Emoji", ...props, style: {
7
+ display: "inline-flex",
8
+ alignItems: "center",
9
+ justifyContent: "center",
10
+ width: 32,
11
+ height: 32,
12
+ border: 0,
13
+ background: "transparent",
14
+ color: "inherit",
15
+ cursor: "pointer",
16
+ ...(props.style ?? {}),
17
+ }, children: _jsx(SmileOutlineIcon, { size: iconSize }) }));
18
+ });
@@ -0,0 +1,13 @@
1
+ import type { PointerEventHandler } from "react";
2
+ import type { ChatInputTokens } from "../types/chatInput";
3
+ export declare function HoldToTalkButton({ tokens, active, disabled, text, activeText, onPressStart, onPressMove, onPressEnd, onPressCancel, }: {
4
+ tokens: ChatInputTokens;
5
+ active?: boolean;
6
+ disabled?: boolean;
7
+ text?: string;
8
+ activeText?: string;
9
+ onPressStart?: PointerEventHandler<HTMLButtonElement>;
10
+ onPressMove?: PointerEventHandler<HTMLButtonElement>;
11
+ onPressEnd?: PointerEventHandler<HTMLButtonElement>;
12
+ onPressCancel?: PointerEventHandler<HTMLButtonElement>;
13
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ export function HoldToTalkButton({ tokens, active, disabled, text = "按住 说话", activeText = "松开 结束", onPressStart, onPressMove, onPressEnd, onPressCancel, }) {
4
+ return (_jsx("button", { type: "button", disabled: disabled, onContextMenu: (event) => {
5
+ event.preventDefault();
6
+ }, onSelect: (event) => {
7
+ event.preventDefault();
8
+ }, onDragStart: (event) => {
9
+ event.preventDefault();
10
+ }, onPointerDown: (event) => {
11
+ try {
12
+ event.currentTarget.setPointerCapture(event.pointerId);
13
+ }
14
+ catch {
15
+ // ignore unsupported pointer capture cases in test environments
16
+ }
17
+ onPressStart?.(event);
18
+ }, onPointerMove: onPressMove, onPointerUp: onPressEnd, onPointerCancel: onPressCancel, style: {
19
+ width: "100%",
20
+ borderRadius: tokens.radius.mobileHoldToTalk,
21
+ background: active ? "#ECE2FF" : "#FFFFFF",
22
+ color: active ? "#4E2DBF" : "#171717",
23
+ border: `1px solid ${active ? "#BFA9FF" : "#E5E7EB"}`,
24
+ boxShadow: active ? "0 14px 28px rgba(109, 74, 255, 0.22)" : "none",
25
+ padding: "15px 18px",
26
+ minHeight: 60,
27
+ fontSize: 16,
28
+ lineHeight: "22px",
29
+ fontWeight: 600,
30
+ opacity: disabled ? 0.55 : 1,
31
+ userSelect: "none",
32
+ WebkitUserSelect: "none",
33
+ WebkitTouchCallout: "none",
34
+ WebkitTapHighlightColor: "transparent",
35
+ touchAction: "none",
36
+ transition: `all ${tokens.motion.stateTransition}`,
37
+ }, children: active ? activeText : text }));
38
+ }
@@ -0,0 +1,2 @@
1
+ import type { ButtonHTMLAttributes } from "react";
2
+ export declare function ImageTrigger(props: ButtonHTMLAttributes<HTMLButtonElement>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { PlusOutlineIcon } from "./icons";
4
+ export function ImageTrigger(props) {
5
+ const iconSize = Number(props["data-icon-size"] ?? 26);
6
+ return (_jsx("button", { type: "button", "aria-label": "More", ...props, style: {
7
+ display: "inline-flex",
8
+ alignItems: "center",
9
+ justifyContent: "center",
10
+ width: 32,
11
+ height: 32,
12
+ border: 0,
13
+ background: "transparent",
14
+ color: "inherit",
15
+ cursor: "pointer",
16
+ ...(props.style ?? {}),
17
+ }, children: _jsx(PlusOutlineIcon, { size: iconSize }) }));
18
+ }
@@ -0,0 +1,7 @@
1
+ import type { CSSProperties, PropsWithChildren } from "react";
2
+ import type { ChatInputStatus, ChatInputTokens } from "../types/chatInput";
3
+ export declare function InputSurface({ tokens, status, children, styleOverride, }: PropsWithChildren<{
4
+ tokens: ChatInputTokens;
5
+ status: ChatInputStatus;
6
+ styleOverride?: CSSProperties;
7
+ }>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ export function InputSurface({ tokens, status, children, styleOverride, }) {
4
+ const background = status === "recording"
5
+ ? tokens.color.surfaceInputRecording
6
+ : status === "error"
7
+ ? tokens.color.surfaceInputError
8
+ : tokens.color.surfaceInputDefault;
9
+ const borderColor = status === "error"
10
+ ? tokens.color.borderError
11
+ : status === "recording"
12
+ ? tokens.color.borderFocus
13
+ : tokens.color.borderDefault;
14
+ return (_jsx("div", { style: {
15
+ background,
16
+ borderWidth: 1,
17
+ borderStyle: "solid",
18
+ borderColor,
19
+ borderRadius: tokens.radius.inputSurface,
20
+ boxShadow: status === "recording"
21
+ ? tokens.shadow.recordingActive
22
+ : tokens.shadow.inputElevated,
23
+ padding: tokens.spacing.inputOuter,
24
+ ...styleOverride,
25
+ }, className: "w-full", children: children }));
26
+ }
@@ -0,0 +1,5 @@
1
+ import type { ButtonHTMLAttributes } from "react";
2
+ import type { ChatInputTokens } from "../types/chatInput";
3
+ export declare function SendAction({ tokens, disabled, children, style, ...props }: ButtonHTMLAttributes<HTMLButtonElement> & {
4
+ tokens: ChatInputTokens;
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ export function SendAction({ tokens, disabled, children = "Send", style, ...props }) {
4
+ return (_jsx("button", { type: "button", disabled: disabled, style: {
5
+ borderRadius: tokens.radius.actionButton,
6
+ background: disabled
7
+ ? tokens.color.actionSendDisabled
8
+ : tokens.color.actionSendDefault,
9
+ color: tokens.color.actionSendTextOnPrimary,
10
+ padding: "8px 14px",
11
+ fontSize: tokens.typography.actionLabel.size,
12
+ lineHeight: `${tokens.typography.actionLabel.lineHeight}px`,
13
+ fontWeight: tokens.typography.actionLabel.weight,
14
+ border: 0,
15
+ cursor: disabled ? "not-allowed" : "pointer",
16
+ ...style,
17
+ }, ...props, children: children }));
18
+ }
@@ -0,0 +1,6 @@
1
+ import type { ChatInputTokens } from "../types/chatInput";
2
+ export declare function StatusHint({ text, tokens, tone, }: {
3
+ text?: string;
4
+ tokens: ChatInputTokens;
5
+ tone?: "secondary" | "error";
6
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ export function StatusHint({ text, tokens, tone = "secondary", }) {
4
+ if (!text) {
5
+ return null;
6
+ }
7
+ return (_jsx("div", { style: {
8
+ color: tone === "error"
9
+ ? tokens.color.statusError
10
+ : tokens.color.textSecondary,
11
+ marginTop: tokens.spacing.statusHintTop,
12
+ fontSize: tokens.typography.statusHint.size,
13
+ lineHeight: `${tokens.typography.statusHint.lineHeight}px`,
14
+ fontWeight: tokens.typography.statusHint.weight,
15
+ }, children: text }));
16
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ import type { ChatInputTokens } from "../types/chatInput";
3
+ export declare const TextComposer: React.ForwardRefExoticComponent<React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
4
+ tokens: ChatInputTokens;
5
+ } & React.RefAttributes<HTMLTextAreaElement>>;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { forwardRef } from "react";
3
+ export const TextComposer = forwardRef(function TextComposer({ tokens, style, ...props }, ref) {
4
+ return (_jsx("textarea", { ref: ref, rows: 3, style: {
5
+ width: "100%",
6
+ resize: "none",
7
+ outline: "none",
8
+ background: "transparent",
9
+ color: tokens.color.textPrimary,
10
+ fontFamily: tokens.typography.familyBase,
11
+ fontSize: tokens.typography.inputText.size,
12
+ lineHeight: `${tokens.typography.inputText.lineHeight}px`,
13
+ fontWeight: tokens.typography.inputText.weight,
14
+ ...style,
15
+ }, ...props }));
16
+ });
@@ -0,0 +1,7 @@
1
+ import type { ChatInputMode, ChatInputTokens } from "../types/chatInput";
2
+ export declare function VoiceModeSwitch({ mode, tokens, disabled, onChange, }: {
3
+ mode: ChatInputMode;
4
+ tokens: ChatInputTokens;
5
+ disabled?: boolean;
6
+ onChange: (next: ChatInputMode) => void;
7
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ export function VoiceModeSwitch({ mode, tokens, disabled, onChange, }) {
4
+ return (_jsx("div", { className: "flex gap-2", children: ["text", "voice"].map((item) => {
5
+ const active = mode === item;
6
+ return (_jsx("button", { type: "button", disabled: disabled, onClick: () => onChange(item), style: {
7
+ borderRadius: tokens.radius.actionButton,
8
+ background: active ? tokens.color.brandPrimary : tokens.color.surfaceInputFocus,
9
+ color: active ? tokens.color.actionSendTextOnPrimary : tokens.color.textSecondary,
10
+ padding: "6px 12px",
11
+ opacity: disabled ? 0.5 : 1,
12
+ }, children: item === "text" ? "Text" : "Voice" }, item));
13
+ }) }));
14
+ }