@smart-cloud/ai-kit-ui 1.3.0 → 1.3.2
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/ai-kit-ui.css +52 -0
- package/dist/index.cjs +9 -9
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -9
- package/package.json +2 -2
- package/src/ai-chatbot/AiChatbot.tsx +436 -77
- package/src/ai-chatbot/attachmentStorage.ts +66 -0
- package/src/doc-search/DocSearch.tsx +196 -59
- package/src/i18n/ar.ts +10 -0
- package/src/i18n/de.ts +10 -0
- package/src/i18n/en.ts +10 -0
- package/src/i18n/es.ts +10 -0
- package/src/i18n/fr.ts +10 -0
- package/src/i18n/he.ts +10 -0
- package/src/i18n/hi.ts +10 -0
- package/src/i18n/hu.ts +10 -0
- package/src/i18n/id.ts +10 -0
- package/src/i18n/it.ts +10 -0
- package/src/i18n/ja.ts +10 -0
- package/src/i18n/ko.ts +10 -0
- package/src/i18n/nb.ts +10 -0
- package/src/i18n/nl.ts +10 -0
- package/src/i18n/pl.ts +10 -0
- package/src/i18n/pt.ts +10 -0
- package/src/i18n/ru.ts +10 -0
- package/src/i18n/sv.ts +10 -0
- package/src/i18n/th.ts +10 -0
- package/src/i18n/tr.ts +10 -0
- package/src/i18n/ua.ts +10 -0
- package/src/i18n/zh.ts +10 -0
- package/src/styles/ai-kit-ui.css +52 -0
|
@@ -83,6 +83,9 @@ export async function persistAttachmentBlob(
|
|
|
83
83
|
blob: Blob,
|
|
84
84
|
meta: { name: string; type: string; size: number },
|
|
85
85
|
): Promise<string | null> {
|
|
86
|
+
// Enforce storage quota before adding new attachment
|
|
87
|
+
await enforceStorageQuota();
|
|
88
|
+
|
|
86
89
|
const record: AttachmentRecord = {
|
|
87
90
|
id,
|
|
88
91
|
blob,
|
|
@@ -170,3 +173,66 @@ export async function cleanupDanglingAttachments(
|
|
|
170
173
|
console.warn("[AiChatbot] Failed to cleanup attachments", error);
|
|
171
174
|
});
|
|
172
175
|
}
|
|
176
|
+
|
|
177
|
+
// Storage quota management
|
|
178
|
+
const MAX_STORAGE_BYTES = 50 * 1024 * 1024; // 50 MB limit
|
|
179
|
+
|
|
180
|
+
export async function getAllAttachments(): Promise<StoredAttachment[]> {
|
|
181
|
+
const db = await getDatabase();
|
|
182
|
+
if (!db) return [];
|
|
183
|
+
|
|
184
|
+
return new Promise<StoredAttachment[]>((resolve, reject) => {
|
|
185
|
+
try {
|
|
186
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
187
|
+
const request = tx.objectStore(STORE_NAME).getAll();
|
|
188
|
+
|
|
189
|
+
request.onsuccess = () => {
|
|
190
|
+
const records = (request.result || []) as AttachmentRecord[];
|
|
191
|
+
resolve(records);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
request.onerror = () => reject(request.error);
|
|
195
|
+
tx.onerror = () => reject(tx.error);
|
|
196
|
+
tx.onabort = () => reject(tx.error);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
reject(error);
|
|
199
|
+
}
|
|
200
|
+
}).catch((error) => {
|
|
201
|
+
console.warn("[AiChatbot] Failed to get all attachments", error);
|
|
202
|
+
return [];
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function enforceStorageQuota(): Promise<void> {
|
|
207
|
+
const attachments = await getAllAttachments();
|
|
208
|
+
if (attachments.length === 0) return;
|
|
209
|
+
|
|
210
|
+
// Sort by creation time (oldest first)
|
|
211
|
+
const sorted = attachments.sort((a, b) => a.createdAt - b.createdAt);
|
|
212
|
+
|
|
213
|
+
// Calculate total size
|
|
214
|
+
let totalSize = sorted.reduce((sum, att) => sum + att.size, 0);
|
|
215
|
+
|
|
216
|
+
// Delete oldest attachments until under quota
|
|
217
|
+
const toDelete: string[] = [];
|
|
218
|
+
let i = 0;
|
|
219
|
+
while (totalSize > MAX_STORAGE_BYTES && i < sorted.length) {
|
|
220
|
+
const attachment = sorted[i];
|
|
221
|
+
if (attachment) {
|
|
222
|
+
toDelete.push(attachment.id);
|
|
223
|
+
totalSize -= attachment.size;
|
|
224
|
+
}
|
|
225
|
+
i++;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Delete the attachments
|
|
229
|
+
for (const id of toDelete) {
|
|
230
|
+
await deleteAttachmentBlob(id);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (toDelete.length > 0) {
|
|
234
|
+
console.log(
|
|
235
|
+
`[AiChatbot] Deleted ${toDelete.length} old attachment(s) to enforce storage quota`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Loader,
|
|
8
8
|
Modal,
|
|
9
9
|
Paper,
|
|
10
|
+
Progress,
|
|
10
11
|
Stack,
|
|
11
12
|
Text,
|
|
12
13
|
TextInput,
|
|
@@ -38,6 +39,8 @@ I18n.putVocabularies(translations);
|
|
|
38
39
|
|
|
39
40
|
type Props = DocSearchProps & AiKitShellInjectedProps;
|
|
40
41
|
|
|
42
|
+
const USE_AUDIO = false; // Set to true to enable audio recording feature (requires backend support for audio input)
|
|
43
|
+
|
|
41
44
|
function groupChunksByDoc(result: SearchResult | null) {
|
|
42
45
|
const docs = result?.citations?.docs ?? [];
|
|
43
46
|
const chunks = result?.citations?.chunks ?? [];
|
|
@@ -84,9 +87,20 @@ const DocSearchBase: FC<Props> = (props) => {
|
|
|
84
87
|
const [query, setQuery] = useState<string>("");
|
|
85
88
|
const [recording, setRecording] = useState<boolean>(false);
|
|
86
89
|
const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
|
|
90
|
+
const [audioLevel, setAudioLevel] = useState<number>(0);
|
|
87
91
|
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
|
88
92
|
const audioChunksRef = useRef<Blob[]>([]);
|
|
89
|
-
|
|
93
|
+
const audioContextRef = useRef<AudioContext | null>(null);
|
|
94
|
+
const analyserRef = useRef<AnalyserNode | null>(null);
|
|
95
|
+
const animationFrameRef = useRef<number | null>(null);
|
|
96
|
+
|
|
97
|
+
// Audio cache: store uploaded audio to avoid re-uploading within session
|
|
98
|
+
const audioCacheRef = useRef<{
|
|
99
|
+
blob: Blob;
|
|
100
|
+
uploadTimestamp: number;
|
|
101
|
+
} | null>(null);
|
|
102
|
+
const AUDIO_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
103
|
+
|
|
90
104
|
const { busy, error, statusEvent, result, run, cancel, reset } =
|
|
91
105
|
useAiRun<SearchResult>();
|
|
92
106
|
|
|
@@ -140,25 +154,75 @@ const DocSearchBase: FC<Props> = (props) => {
|
|
|
140
154
|
|
|
141
155
|
const startRecording = useCallback(async () => {
|
|
142
156
|
try {
|
|
157
|
+
// Clear query input when starting audio recording
|
|
158
|
+
setQuery("");
|
|
159
|
+
|
|
143
160
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
144
161
|
const mediaRecorder = new MediaRecorder(stream, {
|
|
145
162
|
mimeType: "audio/webm",
|
|
146
163
|
});
|
|
147
|
-
|
|
164
|
+
|
|
165
|
+
// Setup audio analysis for visual feedback
|
|
166
|
+
const audioContext = new AudioContext();
|
|
167
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
168
|
+
const analyser = audioContext.createAnalyser();
|
|
169
|
+
analyser.fftSize = 2048; // Higher for smoother results
|
|
170
|
+
analyser.smoothingTimeConstant = 0.8; // Smoother transitions
|
|
171
|
+
source.connect(analyser);
|
|
172
|
+
|
|
173
|
+
audioContextRef.current = audioContext;
|
|
174
|
+
analyserRef.current = analyser;
|
|
175
|
+
|
|
176
|
+
// Monitor audio level using time domain data (more reliable)
|
|
177
|
+
const dataArray = new Uint8Array(analyser.fftSize);
|
|
178
|
+
const updateLevel = () => {
|
|
179
|
+
if (!analyserRef.current) return;
|
|
180
|
+
analyserRef.current.getByteTimeDomainData(dataArray);
|
|
181
|
+
|
|
182
|
+
// Calculate RMS (Root Mean Square) for accurate volume
|
|
183
|
+
let sum = 0;
|
|
184
|
+
for (let i = 0; i < dataArray.length; i++) {
|
|
185
|
+
const normalized = (dataArray[i]! - 128) / 128; // Normalize to -1 to 1
|
|
186
|
+
sum += normalized * normalized;
|
|
187
|
+
}
|
|
188
|
+
const rms = Math.sqrt(sum / dataArray.length);
|
|
189
|
+
|
|
190
|
+
// Convert to percentage (0-100) with scaling for better visibility
|
|
191
|
+
const level = Math.min(100, rms * 200);
|
|
192
|
+
setAudioLevel(level);
|
|
193
|
+
|
|
194
|
+
animationFrameRef.current = requestAnimationFrame(updateLevel);
|
|
195
|
+
};
|
|
196
|
+
updateLevel();
|
|
197
|
+
|
|
148
198
|
audioChunksRef.current = [];
|
|
149
|
-
|
|
199
|
+
|
|
150
200
|
mediaRecorder.ondataavailable = (event) => {
|
|
151
201
|
if (event.data.size > 0) {
|
|
152
202
|
audioChunksRef.current.push(event.data);
|
|
153
203
|
}
|
|
154
204
|
};
|
|
155
|
-
|
|
205
|
+
|
|
156
206
|
mediaRecorder.onstop = () => {
|
|
157
|
-
const audioBlob = new Blob(audioChunksRef.current, {
|
|
207
|
+
const audioBlob = new Blob(audioChunksRef.current, {
|
|
208
|
+
type: "audio/webm",
|
|
209
|
+
});
|
|
158
210
|
setAudioBlob(audioBlob);
|
|
159
|
-
stream.getTracks().forEach(track => track.stop());
|
|
211
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
212
|
+
|
|
213
|
+
// Cleanup audio analysis
|
|
214
|
+
if (animationFrameRef.current) {
|
|
215
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
216
|
+
animationFrameRef.current = null;
|
|
217
|
+
}
|
|
218
|
+
if (audioContextRef.current) {
|
|
219
|
+
audioContextRef.current.close();
|
|
220
|
+
audioContextRef.current = null;
|
|
221
|
+
}
|
|
222
|
+
analyserRef.current = null;
|
|
223
|
+
setAudioLevel(0);
|
|
160
224
|
};
|
|
161
|
-
|
|
225
|
+
|
|
162
226
|
mediaRecorderRef.current = mediaRecorder;
|
|
163
227
|
mediaRecorder.start();
|
|
164
228
|
setRecording(true);
|
|
@@ -177,46 +241,71 @@ const DocSearchBase: FC<Props> = (props) => {
|
|
|
177
241
|
const clearAudio = useCallback(() => {
|
|
178
242
|
setAudioBlob(null);
|
|
179
243
|
audioChunksRef.current = [];
|
|
244
|
+
setAudioLevel(0);
|
|
245
|
+
// Clear audio cache when user manually clears audio
|
|
246
|
+
audioCacheRef.current = null;
|
|
247
|
+
}, []);
|
|
248
|
+
|
|
249
|
+
// Cleanup on unmount
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
return () => {
|
|
252
|
+
if (animationFrameRef.current) {
|
|
253
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
254
|
+
}
|
|
255
|
+
if (audioContextRef.current) {
|
|
256
|
+
audioContextRef.current.close();
|
|
257
|
+
}
|
|
258
|
+
};
|
|
180
259
|
}, []);
|
|
181
260
|
|
|
182
261
|
const onSearch = useCallback(async () => {
|
|
183
262
|
let q: string | undefined;
|
|
184
|
-
let audio: { format: string; data: string } | undefined;
|
|
185
263
|
|
|
186
|
-
// Get text query if available
|
|
264
|
+
// Get text query if available (not recording audio)
|
|
187
265
|
if (!audioBlob) {
|
|
188
|
-
q =
|
|
189
|
-
|
|
190
|
-
|
|
266
|
+
q =
|
|
267
|
+
typeof inputText === "function"
|
|
268
|
+
? (inputText as () => string)()
|
|
269
|
+
: inputText;
|
|
191
270
|
if (!q) return;
|
|
192
271
|
setQuery(q);
|
|
193
272
|
}
|
|
194
273
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
274
|
+
console.log("Starting search with query:", q, "and audio:", audioBlob);
|
|
275
|
+
|
|
276
|
+
// Check if we can reuse cached audio (same blob within TTL)
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
const isSameAudio =
|
|
279
|
+
audioBlob &&
|
|
280
|
+
audioCacheRef.current?.blob === audioBlob &&
|
|
281
|
+
now - audioCacheRef.current.uploadTimestamp < AUDIO_CACHE_TTL;
|
|
282
|
+
|
|
283
|
+
if (audioBlob && !isSameAudio) {
|
|
284
|
+
// Update cache for new audio blob
|
|
285
|
+
audioCacheRef.current = {
|
|
286
|
+
blob: audioBlob,
|
|
287
|
+
uploadTimestamp: now,
|
|
288
|
+
};
|
|
289
|
+
console.log("Audio cache updated for new recording");
|
|
290
|
+
} else if (isSameAudio) {
|
|
291
|
+
console.log(
|
|
292
|
+
"Reusing cached audio (no re-upload needed within",
|
|
293
|
+
Math.round(
|
|
294
|
+
(AUDIO_CACHE_TTL - (now - audioCacheRef.current!.uploadTimestamp)) /
|
|
295
|
+
1000,
|
|
296
|
+
),
|
|
297
|
+
"seconds)",
|
|
298
|
+
);
|
|
210
299
|
}
|
|
211
300
|
|
|
212
301
|
reset();
|
|
213
302
|
await run(async ({ signal, onStatus }) => {
|
|
214
303
|
return await sendSearchMessage(
|
|
215
|
-
{
|
|
216
|
-
sessionId,
|
|
304
|
+
{
|
|
305
|
+
sessionId,
|
|
217
306
|
...(q && { query: q }),
|
|
218
|
-
audio,
|
|
219
|
-
topK
|
|
307
|
+
...(audioBlob && { audio: audioBlob }), // Pass Blob directly
|
|
308
|
+
topK,
|
|
220
309
|
},
|
|
221
310
|
{ signal, onStatus, context },
|
|
222
311
|
);
|
|
@@ -410,34 +499,47 @@ const DocSearchBase: FC<Props> = (props) => {
|
|
|
410
499
|
}}
|
|
411
500
|
/>
|
|
412
501
|
|
|
413
|
-
{
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
502
|
+
{
|
|
503
|
+
/* Microphone button */ USE_AUDIO && (
|
|
504
|
+
<>
|
|
505
|
+
{audioBlob ? (
|
|
506
|
+
<Button
|
|
507
|
+
variant="outline"
|
|
508
|
+
size="sm"
|
|
509
|
+
color="red"
|
|
510
|
+
onClick={clearAudio}
|
|
511
|
+
disabled={busy}
|
|
512
|
+
title={I18n.get("Clear audio")}
|
|
513
|
+
>
|
|
514
|
+
<IconMicrophoneOff size={18} />
|
|
515
|
+
</Button>
|
|
516
|
+
) : (
|
|
517
|
+
<Button
|
|
518
|
+
variant={recording ? "filled" : "outline"}
|
|
519
|
+
size="sm"
|
|
520
|
+
color={recording ? "red" : "gray"}
|
|
521
|
+
onClick={recording ? stopRecording : startRecording}
|
|
522
|
+
disabled={busy}
|
|
523
|
+
title={
|
|
524
|
+
recording
|
|
525
|
+
? I18n.get("Stop recording")
|
|
526
|
+
: I18n.get("Record audio")
|
|
527
|
+
}
|
|
528
|
+
style={
|
|
529
|
+
recording
|
|
530
|
+
? {
|
|
531
|
+
transform: `scale(${1 + audioLevel / 300})`,
|
|
532
|
+
transition: "transform 0.1s ease-out",
|
|
533
|
+
}
|
|
534
|
+
: undefined
|
|
535
|
+
}
|
|
536
|
+
>
|
|
537
|
+
<IconMicrophone size={18} />
|
|
538
|
+
</Button>
|
|
539
|
+
)}
|
|
540
|
+
</>
|
|
541
|
+
)
|
|
542
|
+
}
|
|
441
543
|
|
|
442
544
|
<Button
|
|
443
545
|
variant="filled"
|
|
@@ -461,6 +563,41 @@ const DocSearchBase: FC<Props> = (props) => {
|
|
|
461
563
|
) : null}
|
|
462
564
|
</Group>
|
|
463
565
|
|
|
566
|
+
{
|
|
567
|
+
/* Audio level indicator when recording */ USE_AUDIO && (
|
|
568
|
+
<>
|
|
569
|
+
{recording && (
|
|
570
|
+
<Stack gap="xs">
|
|
571
|
+
<Text size="xs" c="dimmed">
|
|
572
|
+
{I18n.get("Recording...")} 🎤
|
|
573
|
+
</Text>
|
|
574
|
+
<Progress
|
|
575
|
+
value={audioLevel}
|
|
576
|
+
size="sm"
|
|
577
|
+
color="red"
|
|
578
|
+
animated
|
|
579
|
+
striped
|
|
580
|
+
/>
|
|
581
|
+
</Stack>
|
|
582
|
+
)}
|
|
583
|
+
|
|
584
|
+
{/* Audio playback when recorded */}
|
|
585
|
+
{audioBlob && !recording && (
|
|
586
|
+
<Stack gap="xs">
|
|
587
|
+
<Text size="xs" c="dimmed">
|
|
588
|
+
{I18n.get("Recorded audio:")}
|
|
589
|
+
</Text>
|
|
590
|
+
<audio
|
|
591
|
+
controls
|
|
592
|
+
src={URL.createObjectURL(audioBlob)}
|
|
593
|
+
className="ai-kit-audio-player"
|
|
594
|
+
/>
|
|
595
|
+
</Stack>
|
|
596
|
+
)}
|
|
597
|
+
</>
|
|
598
|
+
)
|
|
599
|
+
}
|
|
600
|
+
|
|
464
601
|
{error ? (
|
|
465
602
|
<Alert color="red" title={I18n.get("Error")}>
|
|
466
603
|
{error}
|
package/src/i18n/ar.ts
CHANGED
|
@@ -20,6 +20,9 @@ export const arDict: Record<string, string> = {
|
|
|
20
20
|
"Ask me": "اسألني",
|
|
21
21
|
Assistant: "المساعد",
|
|
22
22
|
"Assistant is thinking…": "المساعد يفكر…",
|
|
23
|
+
"Audio message": "رسالة صوتية",
|
|
24
|
+
"Audio no longer available": "لم يعد الصوت متاحًا",
|
|
25
|
+
"Audio recorded": "تم تسجيل الصوت",
|
|
23
26
|
auto: "آلي",
|
|
24
27
|
"Auto-detect": "الكشف التلقائي",
|
|
25
28
|
Cancel: "يلغي",
|
|
@@ -28,6 +31,8 @@ export const arDict: Record<string, string> = {
|
|
|
28
31
|
casual: "غير رسمي",
|
|
29
32
|
"Checking capabilities…": "إمكانيات الفحص…",
|
|
30
33
|
Chinese: "الصينية",
|
|
34
|
+
Clear: "مسح",
|
|
35
|
+
"Clear audio": "مسح الصوت",
|
|
31
36
|
"Click again to confirm": "انقر مرة أخرى للتأكيد",
|
|
32
37
|
Close: "يغلق",
|
|
33
38
|
"Close chat": "إغلاق الدردشة",
|
|
@@ -72,6 +77,7 @@ export const arDict: Record<string, string> = {
|
|
|
72
77
|
HTML: "HTML",
|
|
73
78
|
Hungarian: "المجرية",
|
|
74
79
|
"I'm ready to assist you.": "أنا جاهز لمساعدتك.",
|
|
80
|
+
"Image no longer available": "لم تعد الصورة متاحة",
|
|
75
81
|
Indonesian: "إندونيسيا",
|
|
76
82
|
"Initializing on-device AI…": "جارٍ تهيئة الذكاء الاصطناعي على الجهاز…",
|
|
77
83
|
"Inlining images as base64": "تضمين الصور بصيغة base64",
|
|
@@ -123,6 +129,9 @@ export const arDict: Record<string, string> = {
|
|
|
123
129
|
"Ready.": "جاهز.",
|
|
124
130
|
"Received backend response.": "تم استلام رد من الخادم الخلفي.",
|
|
125
131
|
"Receiving response...": "جارٍ استلام الرد...",
|
|
132
|
+
Record: "تسجيل",
|
|
133
|
+
"Record audio": "تسجيل صوت",
|
|
134
|
+
"Recording...": "جارٍ التسجيل…",
|
|
126
135
|
Reference: "مرجع",
|
|
127
136
|
References: "المراجع",
|
|
128
137
|
Regenerate: "تجديد",
|
|
@@ -156,6 +165,7 @@ export const arDict: Record<string, string> = {
|
|
|
156
165
|
Sources: "مصادر",
|
|
157
166
|
Spanish: "الأسبانية",
|
|
158
167
|
Stop: "قف",
|
|
168
|
+
"Stop recording": "إيقاف التسجيل",
|
|
159
169
|
"Suggested change": "التغيير المقترح",
|
|
160
170
|
Summarize: "لخص",
|
|
161
171
|
"Summarize again": "أعد التلخيص",
|
package/src/i18n/de.ts
CHANGED
|
@@ -20,6 +20,9 @@ export const deDict: Record<string, string> = {
|
|
|
20
20
|
"Ask me": "Frag mich",
|
|
21
21
|
Assistant: "Assistent",
|
|
22
22
|
"Assistant is thinking…": "Der Assistent denkt nach…",
|
|
23
|
+
"Audio message": "Sprachnachricht",
|
|
24
|
+
"Audio no longer available": "Audio nicht mehr verfügbar",
|
|
25
|
+
"Audio recorded": "Audio aufgenommen",
|
|
23
26
|
auto: "automatisch",
|
|
24
27
|
"Auto-detect": "Automatisch erkennen",
|
|
25
28
|
Cancel: "Abbrechen",
|
|
@@ -28,6 +31,8 @@ export const deDict: Record<string, string> = {
|
|
|
28
31
|
casual: "locker",
|
|
29
32
|
"Checking capabilities…": "Fähigkeiten werden geprüft…",
|
|
30
33
|
Chinese: "Chinesisch",
|
|
34
|
+
Clear: "Löschen",
|
|
35
|
+
"Clear audio": "Audio löschen",
|
|
31
36
|
"Click again to confirm": "Zum Bestätigen erneut klicken",
|
|
32
37
|
Close: "Schließen",
|
|
33
38
|
"Close chat": "Chat schließen",
|
|
@@ -72,6 +77,7 @@ export const deDict: Record<string, string> = {
|
|
|
72
77
|
HTML: "HTML",
|
|
73
78
|
Hungarian: "Ungarisch",
|
|
74
79
|
"I'm ready to assist you.": "Ich bin bereit, dir zu helfen.",
|
|
80
|
+
"Image no longer available": "Bild nicht mehr verfügbar",
|
|
75
81
|
Indonesian: "Indonesisch",
|
|
76
82
|
"Initializing on-device AI…": "On-Device-KI wird initialisiert…",
|
|
77
83
|
"Inlining images as base64": "Bilder werden als Base64 eingebettet",
|
|
@@ -123,6 +129,9 @@ export const deDict: Record<string, string> = {
|
|
|
123
129
|
"Ready.": "Bereit.",
|
|
124
130
|
"Received backend response.": "Backend-Antwort erhalten.",
|
|
125
131
|
"Receiving response...": "Antwort wird empfangen...",
|
|
132
|
+
Record: "Aufnehmen",
|
|
133
|
+
"Record audio": "Audio aufnehmen",
|
|
134
|
+
"Recording...": "Aufnahme…",
|
|
126
135
|
Reference: "Referenz",
|
|
127
136
|
References: "Referenzen",
|
|
128
137
|
Regenerate: "Neu generieren",
|
|
@@ -156,6 +165,7 @@ export const deDict: Record<string, string> = {
|
|
|
156
165
|
Sources: "Quellen",
|
|
157
166
|
Spanish: "Spanisch",
|
|
158
167
|
Stop: "Stoppen",
|
|
168
|
+
"Stop recording": "Aufnahme stoppen",
|
|
159
169
|
"Suggested change": "Vorgeschlagene Änderung",
|
|
160
170
|
Summarize: "Zusammenfassen",
|
|
161
171
|
"Summarize again": "Erneut zusammenfassen",
|
package/src/i18n/en.ts
CHANGED
|
@@ -20,6 +20,9 @@ export const enDict: Record<string, string> = {
|
|
|
20
20
|
"Ask me": "Ask me",
|
|
21
21
|
Assistant: "Assistant",
|
|
22
22
|
"Assistant is thinking…": "Assistant is thinking…",
|
|
23
|
+
"Audio message": "Audio message",
|
|
24
|
+
"Audio no longer available": "Audio no longer available",
|
|
25
|
+
"Audio recorded": "Audio recorded",
|
|
23
26
|
auto: "auto",
|
|
24
27
|
"Auto-detect": "Auto-detect",
|
|
25
28
|
Cancel: "Cancel",
|
|
@@ -28,6 +31,8 @@ export const enDict: Record<string, string> = {
|
|
|
28
31
|
casual: "casual",
|
|
29
32
|
"Checking capabilities…": "Checking capabilities…",
|
|
30
33
|
Chinese: "Chinese",
|
|
34
|
+
Clear: "Clear",
|
|
35
|
+
"Clear audio": "Clear audio",
|
|
31
36
|
"Click again to confirm": "Click again to confirm",
|
|
32
37
|
Close: "Close",
|
|
33
38
|
"Close chat": "Close chat",
|
|
@@ -72,6 +77,7 @@ export const enDict: Record<string, string> = {
|
|
|
72
77
|
HTML: "HTML",
|
|
73
78
|
Hungarian: "Hungarian",
|
|
74
79
|
"I'm ready to assist you.": "I'm ready to assist you.",
|
|
80
|
+
"Image no longer available": "Image no longer available",
|
|
75
81
|
Indonesian: "Indonesian",
|
|
76
82
|
"Initializing on-device AI…": "Initializing on-device AI…",
|
|
77
83
|
"Inlining images as base64": "Inlining images as base64",
|
|
@@ -123,6 +129,9 @@ export const enDict: Record<string, string> = {
|
|
|
123
129
|
"Ready.": "Ready.",
|
|
124
130
|
"Received backend response.": "Received backend response.",
|
|
125
131
|
"Receiving response...": "Receiving response...",
|
|
132
|
+
Record: "Record",
|
|
133
|
+
"Record audio": "Record audio",
|
|
134
|
+
"Recording...": "Recording...",
|
|
126
135
|
Reference: "Reference",
|
|
127
136
|
References: "References",
|
|
128
137
|
Regenerate: "Regenerate",
|
|
@@ -156,6 +165,7 @@ export const enDict: Record<string, string> = {
|
|
|
156
165
|
Sources: "Sources",
|
|
157
166
|
Spanish: "Spanish",
|
|
158
167
|
Stop: "Stop",
|
|
168
|
+
"Stop recording": "Stop recording",
|
|
159
169
|
"Suggested change": "Suggested change",
|
|
160
170
|
Summarize: "Summarize",
|
|
161
171
|
"Summarize again": "Summarize again",
|
package/src/i18n/es.ts
CHANGED
|
@@ -20,6 +20,9 @@ export const esDict: Record<string, string> = {
|
|
|
20
20
|
"Ask me": "Pregúntame",
|
|
21
21
|
Assistant: "Asistente",
|
|
22
22
|
"Assistant is thinking…": "El asistente está pensando…",
|
|
23
|
+
"Audio message": "Mensaje de audio",
|
|
24
|
+
"Audio no longer available": "El audio ya no está disponible",
|
|
25
|
+
"Audio recorded": "Audio grabado",
|
|
23
26
|
auto: "Automático",
|
|
24
27
|
"Auto-detect": "Detección automática",
|
|
25
28
|
Cancel: "Cancelar",
|
|
@@ -28,6 +31,8 @@ export const esDict: Record<string, string> = {
|
|
|
28
31
|
casual: "casual",
|
|
29
32
|
"Checking capabilities…": "Comprobando capacidades…",
|
|
30
33
|
Chinese: "Chino",
|
|
34
|
+
Clear: "Borrar",
|
|
35
|
+
"Clear audio": "Borrar audio",
|
|
31
36
|
"Click again to confirm": "Haz clic de nuevo para confirmar",
|
|
32
37
|
Close: "Cerrar",
|
|
33
38
|
"Close chat": "Cerrar chat",
|
|
@@ -74,6 +79,7 @@ export const esDict: Record<string, string> = {
|
|
|
74
79
|
HTML: "HTML",
|
|
75
80
|
Hungarian: "húngaro",
|
|
76
81
|
"I'm ready to assist you.": "Estoy listo para ayudarte.",
|
|
82
|
+
"Image no longer available": "La imagen ya no está disponible",
|
|
77
83
|
Indonesian: "indonesio",
|
|
78
84
|
"Initializing on-device AI…": "Inicializando IA en el dispositivo…",
|
|
79
85
|
"Inlining images as base64": "Inserción de imágenes en base64",
|
|
@@ -125,6 +131,9 @@ export const esDict: Record<string, string> = {
|
|
|
125
131
|
"Ready.": "Listo.",
|
|
126
132
|
"Received backend response.": "Se recibió una respuesta del backend.",
|
|
127
133
|
"Receiving response...": "Recibiendo respuesta...",
|
|
134
|
+
Record: "Grabar",
|
|
135
|
+
"Record audio": "Grabar audio",
|
|
136
|
+
"Recording...": "Grabando…",
|
|
128
137
|
Reference: "Referencia",
|
|
129
138
|
References: "Referencias",
|
|
130
139
|
Regenerate: "Regenerado",
|
|
@@ -158,6 +167,7 @@ export const esDict: Record<string, string> = {
|
|
|
158
167
|
Sources: "Fuentes",
|
|
159
168
|
Spanish: "Español",
|
|
160
169
|
Stop: "Detener",
|
|
170
|
+
"Stop recording": "Detener grabación",
|
|
161
171
|
"Suggested change": "Cambio sugerido",
|
|
162
172
|
Summarize: "Resumir",
|
|
163
173
|
"Summarize again": "Resumir de nuevo",
|
package/src/i18n/fr.ts
CHANGED
|
@@ -20,6 +20,9 @@ export const frDict: Record<string, string> = {
|
|
|
20
20
|
"Ask me": "Demandez-moi",
|
|
21
21
|
Assistant: "Assistant",
|
|
22
22
|
"Assistant is thinking…": "L'assistant réfléchit…",
|
|
23
|
+
"Audio message": "Message audio",
|
|
24
|
+
"Audio no longer available": "L’audio n’est plus disponible",
|
|
25
|
+
"Audio recorded": "Audio enregistré",
|
|
23
26
|
auto: "Automatique",
|
|
24
27
|
"Auto-detect": "Détection automatique",
|
|
25
28
|
Cancel: "Annuler",
|
|
@@ -28,6 +31,8 @@ export const frDict: Record<string, string> = {
|
|
|
28
31
|
casual: "occasionnel",
|
|
29
32
|
"Checking capabilities…": "Vérification des capacités…",
|
|
30
33
|
Chinese: "Chinois",
|
|
34
|
+
Clear: "Effacer",
|
|
35
|
+
"Clear audio": "Effacer l’audio",
|
|
31
36
|
"Click again to confirm": "Cliquez à nouveau pour confirmer",
|
|
32
37
|
Close: "Fermer",
|
|
33
38
|
"Close chat": "Fermer le chat",
|
|
@@ -74,6 +79,7 @@ export const frDict: Record<string, string> = {
|
|
|
74
79
|
HTML: "HTML",
|
|
75
80
|
Hungarian: "hongrois",
|
|
76
81
|
"I'm ready to assist you.": "Je suis prêt à vous aider.",
|
|
82
|
+
"Image no longer available": "L’image n’est plus disponible",
|
|
77
83
|
Indonesian: "indonésien",
|
|
78
84
|
"Initializing on-device AI…": "Initialisation de l'IA embarquée…",
|
|
79
85
|
"Inlining images as base64": "Intégration des images en base64",
|
|
@@ -125,6 +131,9 @@ export const frDict: Record<string, string> = {
|
|
|
125
131
|
"Ready.": "Prêt.",
|
|
126
132
|
"Received backend response.": "Réponse du serveur reçue.",
|
|
127
133
|
"Receiving response...": "Réception de la réponse...",
|
|
134
|
+
Record: "Enregistrer",
|
|
135
|
+
"Record audio": "Enregistrer un audio",
|
|
136
|
+
"Recording...": "Enregistrement…",
|
|
128
137
|
Reference: "Référence",
|
|
129
138
|
References: "Références",
|
|
130
139
|
Regenerate: "Régénérer",
|
|
@@ -158,6 +167,7 @@ export const frDict: Record<string, string> = {
|
|
|
158
167
|
Sources: "Sources",
|
|
159
168
|
Spanish: "Espagnol",
|
|
160
169
|
Stop: "Arrêt",
|
|
170
|
+
"Stop recording": "Arrêter l’enregistrement",
|
|
161
171
|
"Suggested change": "Changement suggéré",
|
|
162
172
|
Summarize: "Résumer",
|
|
163
173
|
"Summarize again": "Résumons à nouveau",
|
package/src/i18n/he.ts
CHANGED
|
@@ -20,6 +20,9 @@ export const heDict: Record<string, string> = {
|
|
|
20
20
|
"Ask me": "שאל אותי",
|
|
21
21
|
Assistant: "עוזר",
|
|
22
22
|
"Assistant is thinking…": "העוזר חושב…",
|
|
23
|
+
"Audio message": "הודעת שמע",
|
|
24
|
+
"Audio no longer available": "האודיו אינו זמין עוד",
|
|
25
|
+
"Audio recorded": "האודיו הוקלט",
|
|
23
26
|
auto: "אוטומטי",
|
|
24
27
|
"Auto-detect": "זיהוי אוטומטי",
|
|
25
28
|
Cancel: "לְבַטֵל",
|
|
@@ -28,6 +31,8 @@ export const heDict: Record<string, string> = {
|
|
|
28
31
|
casual: "אַגָבִי",
|
|
29
32
|
"Checking capabilities…": "בודק יכולות…",
|
|
30
33
|
Chinese: "סִינִית",
|
|
34
|
+
Clear: "נקה",
|
|
35
|
+
"Clear audio": "נקה שמע",
|
|
31
36
|
"Click again to confirm": "לחץ שוב כדי לאשר",
|
|
32
37
|
Close: "לִסְגוֹר",
|
|
33
38
|
"Close chat": "סגור צ׳אט",
|
|
@@ -72,6 +77,7 @@ export const heDict: Record<string, string> = {
|
|
|
72
77
|
HTML: "HTML",
|
|
73
78
|
Hungarian: "הוּנגָרִי",
|
|
74
79
|
"I'm ready to assist you.": "אני מוכן לעזור לך.",
|
|
80
|
+
"Image no longer available": "התמונה אינה זמינה עוד",
|
|
75
81
|
Indonesian: "אינדונזית",
|
|
76
82
|
"Initializing on-device AI…": "מאתחל את הבינה המלאכותית במכשיר…",
|
|
77
83
|
"Inlining images as base64": "תמונות מוטבעות כ-base64",
|
|
@@ -123,6 +129,9 @@ export const heDict: Record<string, string> = {
|
|
|
123
129
|
"Ready.": "מוכן.",
|
|
124
130
|
"Received backend response.": "התקבלה תגובה מה-backend.",
|
|
125
131
|
"Receiving response...": "מתקבלת תגובה...",
|
|
132
|
+
Record: "הקלט",
|
|
133
|
+
"Record audio": "הקלט שמע",
|
|
134
|
+
"Recording...": "מקליט...",
|
|
126
135
|
Reference: "מקור",
|
|
127
136
|
References: "מקורות",
|
|
128
137
|
Regenerate: "לְהִתְחַדֵשׁ",
|
|
@@ -156,6 +165,7 @@ export const heDict: Record<string, string> = {
|
|
|
156
165
|
Sources: "מקורות",
|
|
157
166
|
Spanish: "סְפָרַדִית",
|
|
158
167
|
Stop: "לְהַפְסִיק",
|
|
168
|
+
"Stop recording": "עצור הקלטה",
|
|
159
169
|
"Suggested change": "שינוי מוצע",
|
|
160
170
|
Summarize: "לְסַכֵּם",
|
|
161
171
|
"Summarize again": "לסכם שוב",
|