strapi-plugin-ai-sdk 0.6.2 → 0.6.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.
@@ -1,2754 +0,0 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Layouts, Page } from "@strapi/strapi/admin";
3
- import { Link, useNavigate, Routes, Route } from "react-router-dom";
4
- import { Box, Typography, TextInput, Button, Main, SearchForm, Searchbar, Table, Thead, Tr, Th, Tbody, Td, Flex, Pagination, Modal, Field, Textarea, SingleSelect, SingleSelectOption } from "@strapi/design-system";
5
- import { useState, useEffect, useCallback, useRef, createContext, useContext, forwardRef, useMemo } from "react";
6
- import styled from "styled-components";
7
- import { P as PLUGIN_ID } from "./index-DNcK7AKT.mjs";
8
- import * as THREE from "three";
9
- import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
10
- import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
11
- import { Plus, Trash, VolumeUp, VolumeMute, Sparkle, ArrowLeft, Pencil } from "@strapi/icons";
12
- import Markdown from "react-markdown";
13
- const COOKIE_REGEX = /(?:^|;\s*)jwtToken=([^;]*)/;
14
- function getCookieValue() {
15
- const match = COOKIE_REGEX.exec(document.cookie);
16
- return match ? decodeURIComponent(match[1]) : void 0;
17
- }
18
- function getToken() {
19
- const raw = localStorage.getItem("jwtToken");
20
- if (raw) {
21
- try {
22
- return JSON.parse(raw);
23
- } catch {
24
- return raw;
25
- }
26
- }
27
- return getCookieValue() ?? null;
28
- }
29
- function getBackendURL() {
30
- return globalThis.strapi?.backendURL ?? "";
31
- }
32
- function parseSSELine(line) {
33
- const trimmed = line.trim();
34
- if (!trimmed.startsWith("data:")) return null;
35
- const payload = trimmed.slice(5).trim();
36
- if (payload === "[DONE]") return null;
37
- try {
38
- const parsed = JSON.parse(payload);
39
- switch (parsed.type) {
40
- case "text-delta":
41
- return { type: "text-delta", delta: parsed.delta };
42
- case "tool-input-available":
43
- return {
44
- type: "tool-input-available",
45
- toolCallId: parsed.toolCallId,
46
- toolName: parsed.toolName,
47
- input: parsed.input
48
- };
49
- case "tool-output-available":
50
- return {
51
- type: "tool-output-available",
52
- toolCallId: parsed.toolCallId,
53
- output: parsed.output
54
- };
55
- default:
56
- return null;
57
- }
58
- } catch {
59
- }
60
- return null;
61
- }
62
- async function readSSEStream(reader, callbacksOrOnDelta) {
63
- const callbacks = typeof callbacksOrOnDelta === "function" ? { onTextDelta: callbacksOrOnDelta } : callbacksOrOnDelta;
64
- const decoder = new TextDecoder();
65
- let buffer = "";
66
- let accumulated = "";
67
- const toolNameMap = /* @__PURE__ */ new Map();
68
- while (true) {
69
- const { done, value } = await reader.read();
70
- if (done) break;
71
- buffer += decoder.decode(value, { stream: true });
72
- const lines = buffer.split("\n");
73
- buffer = lines.pop() ?? "";
74
- for (const line of lines) {
75
- const event = parseSSELine(line);
76
- if (!event) continue;
77
- switch (event.type) {
78
- case "text-delta":
79
- accumulated += event.delta;
80
- callbacks.onTextDelta(accumulated);
81
- break;
82
- case "tool-input-available":
83
- toolNameMap.set(event.toolCallId, event.toolName);
84
- callbacks.onToolInput?.(event.toolCallId, event.toolName, event.input);
85
- break;
86
- case "tool-output-available": {
87
- const resolvedName = toolNameMap.get(event.toolCallId) ?? "";
88
- callbacks.onToolOutput?.(event.toolCallId, resolvedName, event.output);
89
- break;
90
- }
91
- }
92
- }
93
- }
94
- return accumulated;
95
- }
96
- function generateId() {
97
- return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
98
- }
99
- function toUIMessages(messages) {
100
- return messages.map((message) => ({
101
- id: message.id,
102
- role: message.role,
103
- parts: [{ type: "text", text: message.content }]
104
- }));
105
- }
106
- async function fetchChatStream(messages) {
107
- const token = getToken();
108
- const response = await fetch(`${getBackendURL()}/${PLUGIN_ID}/chat`, {
109
- method: "POST",
110
- headers: {
111
- "Content-Type": "application/json",
112
- ...token ? { Authorization: `Bearer ${token}` } : {}
113
- },
114
- body: JSON.stringify({ messages: toUIMessages(messages) })
115
- });
116
- if (!response.ok) throw new Error(`Request failed: ${response.status}`);
117
- const reader = response.body?.getReader();
118
- if (!reader) throw new Error("No response stream");
119
- return reader;
120
- }
121
- function updateMessage(setMessages, id, updater) {
122
- setMessages((prev) => prev.map((message) => message.id === id ? updater(message) : message));
123
- }
124
- function removeMessage(setMessages, id) {
125
- setMessages((prev) => prev.filter((message) => message.id !== id));
126
- }
127
- function addToolCall(setMessages, assistantId, toolCallId, toolName, input) {
128
- updateMessage(setMessages, assistantId, (message) => ({
129
- ...message,
130
- toolCalls: [...message.toolCalls ?? [], { toolCallId, toolName, input }]
131
- }));
132
- }
133
- function updateToolOutput(setMessages, assistantId, toolCallId, output) {
134
- updateMessage(setMessages, assistantId, (message) => ({
135
- ...message,
136
- toolCalls: message.toolCalls?.map(
137
- (tc) => tc.toolCallId === toolCallId ? { ...tc, output } : tc
138
- )
139
- }));
140
- }
141
- function useChat(options) {
142
- const [messages, setMessages] = useState(options?.initialMessages ?? []);
143
- const [isLoading, setIsLoading] = useState(false);
144
- const [error, setError] = useState(null);
145
- useEffect(() => {
146
- setMessages(options?.initialMessages ?? []);
147
- setError(null);
148
- }, [options?.conversationId]);
149
- const sendMessage = useCallback(
150
- async (text) => {
151
- const trimmed = text.trim();
152
- if (!trimmed || isLoading) return;
153
- const userMessage = { id: generateId(), role: "user", content: trimmed };
154
- const assistantId = generateId();
155
- setMessages((prev) => [...prev, userMessage, { id: assistantId, role: "assistant", content: "" }]);
156
- setIsLoading(true);
157
- setError(null);
158
- try {
159
- const reader = await fetchChatStream([...messages, userMessage]);
160
- let streamStarted = false;
161
- const result = await readSSEStream(reader, {
162
- onTextDelta: (content) => {
163
- if (!streamStarted) {
164
- streamStarted = true;
165
- options?.onStreamStart?.();
166
- }
167
- updateMessage(setMessages, assistantId, (message) => ({ ...message, content }));
168
- },
169
- onToolInput: (toolCallId, toolName, input) => {
170
- if (toolName === "triggerAnimation" && input && typeof input === "object" && "animation" in input) {
171
- options?.onAnimationTrigger?.(String(input.animation));
172
- }
173
- addToolCall(setMessages, assistantId, toolCallId, toolName, input);
174
- },
175
- onToolOutput: (toolCallId, _toolName, output) => {
176
- updateToolOutput(setMessages, assistantId, toolCallId, output);
177
- }
178
- });
179
- if (result) {
180
- options?.onStreamEnd?.(result);
181
- }
182
- if (!result) {
183
- updateMessage(setMessages, assistantId, (message) => ({ ...message, content: message.content || "No response received." }));
184
- }
185
- } catch (err) {
186
- setError(err instanceof Error ? err.message : "Something went wrong");
187
- removeMessage(setMessages, assistantId);
188
- } finally {
189
- setIsLoading(false);
190
- }
191
- },
192
- [isLoading, messages, options]
193
- );
194
- const clearMessages = useCallback(() => {
195
- setMessages([]);
196
- setError(null);
197
- }, []);
198
- return { messages, setMessages, sendMessage, clearMessages, isLoading, error };
199
- }
200
- const BASE$2 = () => `${getBackendURL()}/${PLUGIN_ID}/conversations`;
201
- function headers$2() {
202
- const token = getToken();
203
- return {
204
- "Content-Type": "application/json",
205
- ...token ? { Authorization: `Bearer ${token}` } : {}
206
- };
207
- }
208
- async function request$2(url, init) {
209
- const res = await fetch(url, { headers: headers$2(), ...init });
210
- if (!res.ok) throw new Error(`Request failed: ${res.status}`);
211
- const json = await res.json();
212
- return json.data;
213
- }
214
- function fetchConversations() {
215
- return request$2(BASE$2());
216
- }
217
- function fetchConversation(documentId) {
218
- return request$2(`${BASE$2()}/${documentId}`);
219
- }
220
- function createConversation(data) {
221
- return request$2(BASE$2(), {
222
- method: "POST",
223
- body: JSON.stringify(data)
224
- });
225
- }
226
- function updateConversation(documentId, data) {
227
- return request$2(`${BASE$2()}/${documentId}`, {
228
- method: "PUT",
229
- body: JSON.stringify(data)
230
- });
231
- }
232
- function deleteConversation(documentId) {
233
- return request$2(`${BASE$2()}/${documentId}`, { method: "DELETE" });
234
- }
235
- function useConversations() {
236
- const [conversations, setConversations] = useState([]);
237
- const [activeId, setActiveId] = useState(null);
238
- const [initialMessages, setInitialMessages] = useState([]);
239
- useEffect(() => {
240
- fetchConversations().then(async (list) => {
241
- setConversations(list);
242
- if (list.length > 0) {
243
- const most_recent = list[0];
244
- const conversation = await fetchConversation(most_recent.documentId);
245
- setActiveId(most_recent.documentId);
246
- setInitialMessages(conversation.messages || []);
247
- }
248
- }).catch((err) => console.error("Failed to load conversations:", err));
249
- }, []);
250
- const selectConversation = useCallback(async (documentId) => {
251
- try {
252
- const conversation = await fetchConversation(documentId);
253
- setActiveId(documentId);
254
- setInitialMessages(conversation.messages || []);
255
- } catch (err) {
256
- console.error("Failed to load conversation:", err);
257
- }
258
- }, []);
259
- const startNewConversation = useCallback(() => {
260
- setActiveId(null);
261
- setInitialMessages([]);
262
- }, []);
263
- const saveMessages = useCallback(
264
- async (messages) => {
265
- if (messages.length === 0) return;
266
- const firstUserMsg = messages.find((m) => m.role === "user");
267
- const title = firstUserMsg ? firstUserMsg.content.slice(0, 80) + (firstUserMsg.content.length > 80 ? "…" : "") : "New conversation";
268
- try {
269
- if (activeId) {
270
- await updateConversation(activeId, { title, messages });
271
- setConversations(
272
- (prev) => prev.map(
273
- (c) => c.documentId === activeId ? { ...c, title, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : c
274
- )
275
- );
276
- } else {
277
- const created = await createConversation({ title, messages });
278
- setInitialMessages(messages);
279
- setActiveId(created.documentId);
280
- setConversations((prev) => [
281
- {
282
- documentId: created.documentId,
283
- title: created.title,
284
- createdAt: created.createdAt,
285
- updatedAt: created.updatedAt
286
- },
287
- ...prev
288
- ]);
289
- }
290
- } catch (err) {
291
- console.error("Failed to save conversation:", err);
292
- }
293
- },
294
- [activeId]
295
- );
296
- const removeConversation = useCallback(
297
- async (documentId) => {
298
- try {
299
- await deleteConversation(documentId);
300
- setConversations((prev) => prev.filter((c) => c.documentId !== documentId));
301
- if (activeId === documentId) {
302
- setActiveId(null);
303
- setInitialMessages([]);
304
- }
305
- } catch (err) {
306
- console.error("Failed to delete conversation:", err);
307
- }
308
- },
309
- [activeId]
310
- );
311
- return {
312
- conversations,
313
- activeId,
314
- initialMessages,
315
- selectConversation,
316
- startNewConversation,
317
- saveMessages,
318
- removeConversation
319
- };
320
- }
321
- function stripMarkdown(text) {
322
- return text.replace(/```[\s\S]*?```/g, "").replace(/`([^`]+)`/g, "$1").replace(/!\[.*?\]\(.*?\)/g, "").replace(/\[([^\]]+)\]\(.*?\)/g, "$1").replace(/#{1,6}\s*/g, "").replace(/[*_]{1,3}([^*_]+)[*_]{1,3}/g, "$1").replace(/>\s?/g, "").replace(/[-*+]\s/g, "").replace(/\d+\.\s/g, "").replace(/\n{2,}/g, " ").replace(/\s{2,}/g, " ").trim();
323
- }
324
- function useAudioPlayer(options) {
325
- const [isPlaying, setIsPlaying] = useState(false);
326
- const audioRef = useRef(null);
327
- const optionsRef = useRef(options);
328
- optionsRef.current = options;
329
- const stop = useCallback(() => {
330
- if (audioRef.current) {
331
- audioRef.current.pause();
332
- audioRef.current = null;
333
- }
334
- setIsPlaying(false);
335
- }, []);
336
- const speak2 = useCallback(
337
- async (text) => {
338
- stop();
339
- const clean = stripMarkdown(text);
340
- if (clean.length < 3) {
341
- optionsRef.current?.onPlayStart?.(0);
342
- optionsRef.current?.onPlayEnded?.();
343
- return;
344
- }
345
- const token = getToken();
346
- try {
347
- const response = await fetch(`${getBackendURL()}/${PLUGIN_ID}/tts`, {
348
- method: "POST",
349
- headers: {
350
- "Content-Type": "application/json",
351
- ...token ? { Authorization: `Bearer ${token}` } : {}
352
- },
353
- body: JSON.stringify({ text: clean })
354
- });
355
- if (!response.ok) {
356
- optionsRef.current?.onPlayStart?.(0);
357
- optionsRef.current?.onPlayEnded?.();
358
- return;
359
- }
360
- const blob = await response.blob();
361
- const url = URL.createObjectURL(blob);
362
- const audio = new Audio(url);
363
- audioRef.current = audio;
364
- const duration = await new Promise((resolve) => {
365
- audio.onloadedmetadata = () => {
366
- resolve(Number.isFinite(audio.duration) ? audio.duration : 0);
367
- };
368
- audio.onerror = () => resolve(0);
369
- });
370
- audio.onplay = () => {
371
- setIsPlaying(true);
372
- optionsRef.current?.onPlayStart?.(duration);
373
- };
374
- audio.onended = () => {
375
- setIsPlaying(false);
376
- audioRef.current = null;
377
- URL.revokeObjectURL(url);
378
- optionsRef.current?.onPlayEnded?.();
379
- };
380
- audio.onerror = () => {
381
- setIsPlaying(false);
382
- audioRef.current = null;
383
- URL.revokeObjectURL(url);
384
- optionsRef.current?.onPlayEnded?.();
385
- };
386
- await audio.play();
387
- } catch {
388
- setIsPlaying(false);
389
- optionsRef.current?.onPlayStart?.(0);
390
- optionsRef.current?.onPlayEnded?.();
391
- }
392
- },
393
- [stop]
394
- );
395
- return { speak: speak2, stop, isPlaying };
396
- }
397
- function useTextReveal() {
398
- const [visibleText, setVisibleText] = useState("");
399
- const [isRevealing, setIsRevealing] = useState(false);
400
- const rafRef = useRef(null);
401
- const fullTextRef = useRef("");
402
- const stopReveal = useCallback(() => {
403
- if (rafRef.current !== null) {
404
- cancelAnimationFrame(rafRef.current);
405
- rafRef.current = null;
406
- }
407
- if (fullTextRef.current) {
408
- setVisibleText(fullTextRef.current);
409
- }
410
- setIsRevealing(false);
411
- }, []);
412
- const startReveal = useCallback(
413
- (text, duration) => {
414
- stopReveal();
415
- fullTextRef.current = text;
416
- if (duration <= 0 || !text) {
417
- setVisibleText(text);
418
- setIsRevealing(false);
419
- return;
420
- }
421
- const totalChars = text.length;
422
- const durationMs = duration * 1e3;
423
- const startTime = performance.now();
424
- setVisibleText("");
425
- setIsRevealing(true);
426
- const tick = (now) => {
427
- const elapsed = now - startTime;
428
- const progress = Math.min(elapsed / durationMs, 1);
429
- const targetChar = Math.floor(progress * totalChars);
430
- let snapTo = targetChar;
431
- if (snapTo < totalChars) {
432
- const nextSpace = text.indexOf(" ", snapTo);
433
- snapTo = nextSpace === -1 ? totalChars : nextSpace;
434
- }
435
- setVisibleText(text.slice(0, snapTo));
436
- if (progress < 1) {
437
- rafRef.current = requestAnimationFrame(tick);
438
- } else {
439
- setVisibleText(text);
440
- setIsRevealing(false);
441
- rafRef.current = null;
442
- }
443
- };
444
- rafRef.current = requestAnimationFrame(tick);
445
- },
446
- [stopReveal]
447
- );
448
- const reset = useCallback(() => {
449
- stopReveal();
450
- fullTextRef.current = "";
451
- setVisibleText("");
452
- setIsRevealing(false);
453
- }, [stopReveal]);
454
- return { visibleText, isRevealing, startReveal, stopReveal, reset };
455
- }
456
- const AvatarAnimationContext = createContext(null);
457
- function AvatarAnimationProvider({ children }) {
458
- const [currentAnimation, setCurrentAnimation] = useState("idle");
459
- const [requestId, setRequestId] = useState(0);
460
- const trigger = useCallback((animation) => {
461
- setCurrentAnimation(animation);
462
- setRequestId((prev) => prev + 1);
463
- }, []);
464
- const clearAnimation = useCallback(() => {
465
- setCurrentAnimation("idle");
466
- setRequestId((prev) => prev + 1);
467
- }, []);
468
- return /* @__PURE__ */ jsx(AvatarAnimationContext.Provider, { value: { currentAnimation, requestId, trigger, clearAnimation }, children });
469
- }
470
- function useAvatarAnimation() {
471
- const ctx = useContext(AvatarAnimationContext);
472
- if (!ctx) throw new Error("useAvatarAnimation must be used within AvatarAnimationProvider");
473
- return ctx;
474
- }
475
- const BASE$1 = () => `${getBackendURL()}/${PLUGIN_ID}/memories`;
476
- function headers$1() {
477
- const token = getToken();
478
- return {
479
- "Content-Type": "application/json",
480
- ...token ? { Authorization: `Bearer ${token}` } : {}
481
- };
482
- }
483
- async function request$1(url, init) {
484
- const res = await fetch(url, { headers: headers$1(), ...init });
485
- if (!res.ok) throw new Error(`Request failed: ${res.status}`);
486
- const json = await res.json();
487
- return json.data;
488
- }
489
- function fetchMemories() {
490
- return request$1(BASE$1());
491
- }
492
- function createMemory(data) {
493
- return request$1(BASE$1(), {
494
- method: "POST",
495
- body: JSON.stringify(data)
496
- });
497
- }
498
- function updateMemory(documentId, data) {
499
- return request$1(`${BASE$1()}/${documentId}`, {
500
- method: "PUT",
501
- body: JSON.stringify(data)
502
- });
503
- }
504
- function deleteMemory(documentId) {
505
- return request$1(`${BASE$1()}/${documentId}`, { method: "DELETE" });
506
- }
507
- function useMemories() {
508
- const [memories, setMemories] = useState([]);
509
- const [loading, setLoading] = useState(true);
510
- const load = useCallback(async () => {
511
- try {
512
- const data = await fetchMemories();
513
- setMemories(data);
514
- } catch (err) {
515
- console.error("Failed to load memories:", err);
516
- } finally {
517
- setLoading(false);
518
- }
519
- }, []);
520
- useEffect(() => {
521
- load();
522
- }, [load]);
523
- const addMemory = useCallback(async (data) => {
524
- try {
525
- const created = await createMemory(data);
526
- setMemories((prev) => [created, ...prev]);
527
- } catch (err) {
528
- console.error("Failed to create memory:", err);
529
- }
530
- }, []);
531
- const editMemory = useCallback(async (documentId, data) => {
532
- try {
533
- const updated = await updateMemory(documentId, data);
534
- setMemories(
535
- (prev) => prev.map((m) => m.documentId === documentId ? { ...m, ...updated } : m)
536
- );
537
- } catch (err) {
538
- console.error("Failed to update memory:", err);
539
- }
540
- }, []);
541
- const removeMemory = useCallback(async (documentId) => {
542
- try {
543
- await deleteMemory(documentId);
544
- setMemories((prev) => prev.filter((m) => m.documentId !== documentId));
545
- } catch (err) {
546
- console.error("Failed to delete memory:", err);
547
- }
548
- }, []);
549
- return { memories, loading, addMemory, editMemory, removeMemory, refresh: load };
550
- }
551
- function captureRestPose(refs) {
552
- return {
553
- rootY: refs.root.position.y,
554
- hips: refs.hips.quaternion.clone(),
555
- head: refs.head.quaternion.clone(),
556
- leftArm: refs.leftArm.quaternion.clone(),
557
- rightArm: refs.rightArm.quaternion.clone()
558
- };
559
- }
560
- const _euler = new THREE.Euler();
561
- const _quat = new THREE.Quaternion();
562
- function applyAdditiveRotation(target, restQ, x, y, z) {
563
- _euler.set(x, y, z);
564
- _quat.setFromEuler(_euler);
565
- target.quaternion.copy(restQ).multiply(_quat);
566
- }
567
- const idle = (refs, rest) => {
568
- let elapsed = 0;
569
- return {
570
- update(delta) {
571
- elapsed += delta;
572
- refs.root.position.y = rest.rootY + Math.sin(elapsed * 1.2) * 0.012;
573
- applyAdditiveRotation(
574
- refs.head,
575
- rest.head,
576
- Math.sin(elapsed * 0.5) * 0.06,
577
- Math.sin(elapsed * 0.3) * 0.05,
578
- Math.sin(elapsed * 0.7) * 0.05
579
- );
580
- applyAdditiveRotation(
581
- refs.leftArm,
582
- rest.leftArm,
583
- Math.sin(elapsed * 0.3) * 0.015,
584
- 0,
585
- Math.sin(elapsed * 0.4) * 0.01
586
- );
587
- applyAdditiveRotation(
588
- refs.rightArm,
589
- rest.rightArm,
590
- Math.sin(elapsed * 0.35) * 0.015,
591
- 0,
592
- Math.sin(elapsed * 0.35) * -0.01
593
- );
594
- return false;
595
- },
596
- reset() {
597
- elapsed = 0;
598
- refs.root.position.y = rest.rootY;
599
- refs.head.quaternion.copy(rest.head);
600
- refs.leftArm.quaternion.copy(rest.leftArm);
601
- refs.rightArm.quaternion.copy(rest.rightArm);
602
- }
603
- };
604
- };
605
- const speak = (refs, rest) => {
606
- let elapsed = 0;
607
- return {
608
- update(delta) {
609
- elapsed += delta;
610
- const env = Math.min(elapsed / 0.5, 1);
611
- const nod2 = Math.sin(elapsed * 3.5) * 0.12 * env;
612
- const turn = Math.sin(elapsed * 1.8) * 0.1 * env;
613
- const tilt = Math.sin(elapsed * 2.3) * 0.06 * env;
614
- applyAdditiveRotation(refs.head, rest.head, nod2, turn, tilt);
615
- const rz = Math.sin(elapsed * 0.8) * 0.06 * env;
616
- const rx = Math.sin(elapsed * 0.6) * 0.04 * env;
617
- applyAdditiveRotation(refs.rightArm, rest.rightArm, rx, 0, rz);
618
- const lz = Math.sin(elapsed * 0.7 + 1) * 0.04 * env;
619
- const lx = Math.sin(elapsed * 0.5 + 0.5) * 0.03 * env;
620
- applyAdditiveRotation(refs.leftArm, rest.leftArm, lx, 0, -lz);
621
- refs.root.position.y = rest.rootY + Math.sin(elapsed * 2.5) * 8e-3 * env;
622
- return false;
623
- },
624
- reset() {
625
- elapsed = 0;
626
- refs.root.position.y = rest.rootY;
627
- refs.head.quaternion.copy(rest.head);
628
- refs.leftArm.quaternion.copy(rest.leftArm);
629
- refs.rightArm.quaternion.copy(rest.rightArm);
630
- }
631
- };
632
- };
633
- const wave = (refs, rest) => {
634
- let elapsed = 0;
635
- const duration = 2.5;
636
- return {
637
- update(delta) {
638
- elapsed += delta;
639
- const t = Math.min(elapsed / duration, 1);
640
- let rx = 0;
641
- let ry = 0;
642
- let rz = 0;
643
- if (t < 0.2) {
644
- const f = t / 0.2;
645
- rx = -f * 1.4;
646
- rz = f * 1.3;
647
- } else if (t < 0.8) {
648
- const w = (t - 0.2) / 0.6;
649
- rx = -1.4;
650
- rz = 1.3;
651
- ry = Math.sin(w * Math.PI * 6) * 0.5;
652
- } else {
653
- const f = 1 - (t - 0.8) / 0.2;
654
- rx = -1.4 * f;
655
- rz = 1.3 * f;
656
- }
657
- applyAdditiveRotation(refs.rightArm, rest.rightArm, rx, ry, rz);
658
- const headTilt = t < 0.8 ? Math.sin(t * Math.PI * 4) * 0.08 : 0;
659
- applyAdditiveRotation(refs.head, rest.head, 0, 0, headTilt);
660
- return elapsed >= duration;
661
- },
662
- reset() {
663
- elapsed = 0;
664
- refs.rightArm.quaternion.copy(rest.rightArm);
665
- refs.head.quaternion.copy(rest.head);
666
- }
667
- };
668
- };
669
- const nod = (refs, rest) => {
670
- let elapsed = 0;
671
- const duration = 2;
672
- return {
673
- update(delta) {
674
- elapsed += delta;
675
- const t = Math.min(elapsed / duration, 1);
676
- const env = t > 0.85 ? (1 - t) / 0.15 : 1;
677
- applyAdditiveRotation(
678
- refs.head,
679
- rest.head,
680
- Math.sin(t * Math.PI * 6) * 0.18 * env,
681
- 0,
682
- 0
683
- );
684
- return elapsed >= duration;
685
- },
686
- reset() {
687
- elapsed = 0;
688
- refs.head.quaternion.copy(rest.head);
689
- }
690
- };
691
- };
692
- const think = (refs, rest) => {
693
- let elapsed = 0;
694
- const duration = 3.5;
695
- return {
696
- update(delta) {
697
- elapsed += delta;
698
- const t = Math.min(elapsed / duration, 1);
699
- let hx = 0, hz = 0, ax = 0, az = 0;
700
- if (t < 0.2) {
701
- const f = t / 0.2;
702
- hz = f * 0.2;
703
- hx = f * 0.12;
704
- } else if (t > 0.8) {
705
- const f = 1 - (t - 0.8) / 0.2;
706
- hz = 0.2 * f;
707
- hx = 0.12 * f;
708
- } else {
709
- hz = 0.2 + Math.sin(elapsed * 0.8) * 0.03;
710
- hx = 0.12;
711
- }
712
- if (t < 0.2) {
713
- const f = t / 0.2;
714
- az = -f * 0.6;
715
- ax = f * 0.35;
716
- } else if (t < 0.8) {
717
- const hold = (t - 0.2) / 0.6;
718
- az = -0.6 + Math.sin(hold * Math.PI * 2) * 0.06;
719
- ax = 0.35;
720
- } else {
721
- const f = 1 - (t - 0.8) / 0.2;
722
- az = -0.6 * f;
723
- ax = 0.35 * f;
724
- }
725
- applyAdditiveRotation(refs.head, rest.head, hx, 0, hz);
726
- applyAdditiveRotation(refs.rightArm, rest.rightArm, ax, 0, az);
727
- return elapsed >= duration;
728
- },
729
- reset() {
730
- elapsed = 0;
731
- refs.head.quaternion.copy(rest.head);
732
- refs.rightArm.quaternion.copy(rest.rightArm);
733
- }
734
- };
735
- };
736
- const celebrate = (refs, rest) => {
737
- let elapsed = 0;
738
- const duration = 3;
739
- return {
740
- update(delta) {
741
- elapsed += delta;
742
- const t = Math.min(elapsed / duration, 1);
743
- const env = t > 0.8 ? (1 - t) / 0.2 : 1;
744
- const bounce = Math.abs(Math.sin(t * Math.PI * 6)) * 0.05 * env;
745
- refs.root.position.y = rest.rootY + bounce;
746
- let lz = 0, rz = 0;
747
- if (t < 0.15) {
748
- const f = t / 0.15;
749
- lz = f * 1.2;
750
- rz = -f * 1.2;
751
- } else if (t < 0.8) {
752
- const w = (t - 0.15) / 0.65;
753
- lz = 1.2 + Math.sin(w * Math.PI * 8) * 0.25;
754
- rz = -1.2 - Math.sin(w * Math.PI * 8) * 0.25;
755
- } else {
756
- const f = 1 - (t - 0.8) / 0.2;
757
- lz = 1.2 * f;
758
- rz = -1.2 * f;
759
- }
760
- applyAdditiveRotation(refs.leftArm, rest.leftArm, 0, 0, lz);
761
- applyAdditiveRotation(refs.rightArm, rest.rightArm, 0, 0, rz);
762
- applyAdditiveRotation(
763
- refs.head,
764
- rest.head,
765
- Math.sin(elapsed * 4) * 0.1 * env,
766
- 0,
767
- Math.sin(elapsed * 3) * 0.06 * env
768
- );
769
- return elapsed >= duration;
770
- },
771
- reset() {
772
- elapsed = 0;
773
- refs.root.position.y = rest.rootY;
774
- refs.leftArm.quaternion.copy(rest.leftArm);
775
- refs.rightArm.quaternion.copy(rest.rightArm);
776
- refs.head.quaternion.copy(rest.head);
777
- }
778
- };
779
- };
780
- const shake = (refs, rest) => {
781
- let elapsed = 0;
782
- const duration = 1.5;
783
- return {
784
- update(delta) {
785
- elapsed += delta;
786
- const t = Math.min(elapsed / duration, 1);
787
- const env = t > 0.8 ? (1 - t) / 0.2 : 1;
788
- applyAdditiveRotation(
789
- refs.head,
790
- rest.head,
791
- 0,
792
- Math.sin(t * Math.PI * 6) * 0.3 * env,
793
- 0
794
- );
795
- return elapsed >= duration;
796
- },
797
- reset() {
798
- elapsed = 0;
799
- refs.head.quaternion.copy(rest.head);
800
- }
801
- };
802
- };
803
- const spin = (refs, rest) => {
804
- let elapsed = 0;
805
- const duration = 2;
806
- return {
807
- update(delta) {
808
- elapsed += delta;
809
- const t = Math.min(elapsed / duration, 1);
810
- const ease = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
811
- _euler.set(0, ease * Math.PI * 2, 0);
812
- _quat.setFromEuler(_euler);
813
- refs.hips.quaternion.copy(rest.hips).multiply(_quat);
814
- refs.root.position.y = rest.rootY + Math.sin(t * Math.PI) * 0.03;
815
- return elapsed >= duration;
816
- },
817
- reset() {
818
- elapsed = 0;
819
- refs.hips.quaternion.copy(rest.hips);
820
- refs.root.position.y = rest.rootY;
821
- }
822
- };
823
- };
824
- const animationRegistry = {
825
- idle,
826
- speak,
827
- wave,
828
- nod,
829
- think,
830
- celebrate,
831
- shake,
832
- spin
833
- };
834
- const SKIN = 16769228;
835
- const SKIN_SHADOW = 15780016;
836
- const HAIR_MAIN = 5978764;
837
- const HAIR_HIGHLIGHT = 8278709;
838
- const SHIRT = 4802047;
839
- const SHIRT_ACCENT = 7105023;
840
- const EYE_IRIS = 4491519;
841
- const EYE_IRIS_INNER = 6728447;
842
- const EYE_PUPIL = 1710638;
843
- const EYE_WHITE = 16777215;
844
- const EYE_HIGHLIGHT = 16777215;
845
- const CHEEK = 16751001;
846
- const MOUTH = 14708848;
847
- const EYEBROW = 3809894;
848
- function toon(color) {
849
- const gradientMap = new THREE.DataTexture(
850
- new Uint8Array([80, 160, 255]),
851
- 3,
852
- 1,
853
- THREE.RedFormat
854
- );
855
- gradientMap.needsUpdate = true;
856
- return new THREE.MeshToonMaterial({ color, gradientMap });
857
- }
858
- function basic(color, opts) {
859
- return new THREE.MeshBasicMaterial({ color, ...opts });
860
- }
861
- function phong(color, opts) {
862
- return new THREE.MeshPhongMaterial({ color, shininess: 20, ...opts });
863
- }
864
- function sphere(r, w = 16, h = 16) {
865
- return new THREE.SphereGeometry(r, w, h);
866
- }
867
- function add(parent, geo, mat, pos, scale, rot) {
868
- const m = new THREE.Mesh(geo, mat);
869
- if (pos) m.position.set(...pos);
870
- if (scale) m.scale.set(...scale);
871
- if (rot) m.rotation.set(...rot);
872
- parent.add(m);
873
- return m;
874
- }
875
- function buildEye(parent, x) {
876
- const eyeGroup = new THREE.Group();
877
- eyeGroup.position.set(x, 0.04, 0.26);
878
- parent.add(eyeGroup);
879
- add(eyeGroup, sphere(0.065, 16, 16), basic(EYE_WHITE), void 0, [0.8, 1, 0.5]);
880
- add(eyeGroup, sphere(0.048, 16, 16), phong(EYE_IRIS, { shininess: 60 }), [0, -5e-3, 0.025], [0.85, 1, 0.6]);
881
- add(eyeGroup, sphere(0.032, 12, 12), phong(EYE_IRIS_INNER, { shininess: 80 }), [0, 5e-3, 0.035], [0.85, 0.9, 0.5]);
882
- add(eyeGroup, sphere(0.02, 10, 10), basic(EYE_PUPIL), [0, -5e-3, 0.04], [0.8, 1, 0.5]);
883
- add(eyeGroup, sphere(0.015, 8, 8), basic(EYE_HIGHLIGHT), [0.015, 0.02, 0.05]);
884
- add(eyeGroup, sphere(8e-3, 6, 6), basic(EYE_HIGHLIGHT), [-0.01, -0.015, 0.05]);
885
- add(
886
- eyeGroup,
887
- new THREE.TorusGeometry(0.055, 8e-3, 8, 16, Math.PI),
888
- basic(EYEBROW),
889
- [0, 0.04, 0.02],
890
- void 0,
891
- [Math.PI, 0, 0]
892
- );
893
- return eyeGroup;
894
- }
895
- function buildPlaceholderModel() {
896
- const root = new THREE.Group();
897
- add(root, new THREE.CylinderGeometry(0.28, 0.24, 0.5, 16), toon(SHIRT), [0, -0.5, 0]);
898
- add(root, new THREE.CylinderGeometry(0.14, 0.28, 0.12, 16), toon(SHIRT_ACCENT), [0, -0.22, 0]);
899
- add(root, sphere(0.1, 12, 12), toon(SHIRT), [-0.28, -0.28, 0]);
900
- add(root, sphere(0.1, 12, 12), toon(SHIRT), [0.28, -0.28, 0]);
901
- add(root, new THREE.CylinderGeometry(0.07, 0.09, 0.12, 8), toon(SKIN), [0, -0.1, 0]);
902
- const head = new THREE.Group();
903
- head.position.set(0, 0.28, 0);
904
- root.add(head);
905
- add(head, sphere(0.34, 32, 32), toon(SKIN), void 0, [1, 1.05, 0.95]);
906
- add(
907
- head,
908
- sphere(0.2, 16, 16),
909
- phong(SKIN_SHADOW, { transparent: true, opacity: 0.3 }),
910
- [0, -0.18, 0.12],
911
- [1.4, 0.5, 1]
912
- );
913
- buildEye(head, -0.11);
914
- buildEye(head, 0.11);
915
- add(
916
- head,
917
- new THREE.CapsuleGeometry(0.012, 0.06, 4, 8),
918
- basic(EYEBROW),
919
- [-0.11, 0.12, 0.28],
920
- void 0,
921
- [0, 0, 0.15]
922
- );
923
- add(
924
- head,
925
- new THREE.CapsuleGeometry(0.012, 0.06, 4, 8),
926
- basic(EYEBROW),
927
- [0.11, 0.12, 0.28],
928
- void 0,
929
- [0, 0, -0.15]
930
- );
931
- add(head, sphere(0.012, 6, 6), phong(SKIN_SHADOW), [0, -0.04, 0.32]);
932
- add(
933
- head,
934
- new THREE.TorusGeometry(0.025, 6e-3, 8, 12, Math.PI),
935
- basic(MOUTH),
936
- [0, -0.1, 0.3],
937
- void 0,
938
- [0.15, 0, 0]
939
- );
940
- add(
941
- head,
942
- sphere(0.04, 8, 8),
943
- basic(CHEEK, { transparent: true, opacity: 0.35 }),
944
- [-0.19, -0.04, 0.22],
945
- [1.3, 0.7, 0.5]
946
- );
947
- add(
948
- head,
949
- sphere(0.04, 8, 8),
950
- basic(CHEEK, { transparent: true, opacity: 0.35 }),
951
- [0.19, -0.04, 0.22],
952
- [1.3, 0.7, 0.5]
953
- );
954
- const hairMat = toon(HAIR_MAIN);
955
- const hairHiMat = toon(HAIR_HIGHLIGHT);
956
- add(head, sphere(0.36, 24, 24), hairMat, [0, 0.08, -0.06], [1.05, 1, 1]);
957
- add(head, sphere(0.28, 20, 20), hairHiMat, [0, 0.22, 0], [1.1, 0.7, 0.95]);
958
- add(head, sphere(0.1, 12, 12), hairMat, [-0.18, 0.2, 0.2], [0.9, 0.7, 0.7]);
959
- add(head, sphere(0.12, 12, 12), hairHiMat, [-0.06, 0.22, 0.22], [0.8, 0.65, 0.7]);
960
- add(head, sphere(0.11, 12, 12), hairMat, [0.08, 0.23, 0.2], [0.85, 0.6, 0.7]);
961
- add(head, sphere(0.09, 12, 12), hairHiMat, [0.19, 0.19, 0.18], [0.8, 0.65, 0.65]);
962
- add(head, sphere(0.09, 12, 12), hairMat, [-0.3, 0, 0.05], [0.5, 1.4, 0.6]);
963
- add(head, sphere(0.08, 12, 12), hairHiMat, [-0.28, -0.15, 0.08], [0.45, 1, 0.55]);
964
- add(head, sphere(0.09, 12, 12), hairMat, [0.3, 0, 0.05], [0.5, 1.4, 0.6]);
965
- add(head, sphere(0.08, 12, 12), hairHiMat, [0.28, -0.15, 0.08], [0.45, 1, 0.55]);
966
- const ahoge = new THREE.Group();
967
- ahoge.position.set(0.02, 0.38, 0.1);
968
- ahoge.rotation.set(-0.3, 0, 0.2);
969
- head.add(ahoge);
970
- add(ahoge, new THREE.ConeGeometry(0.02, 0.15, 6), hairHiMat, [0, 0.07, 0]);
971
- const leftArm = new THREE.Group();
972
- leftArm.position.set(-0.35, -0.3, 0);
973
- root.add(leftArm);
974
- add(leftArm, new THREE.CapsuleGeometry(0.06, 0.18, 8, 8), toon(SHIRT), [0, -0.12, 0]);
975
- add(leftArm, sphere(0.055, 10, 10), toon(SKIN), [0, -0.28, 0]);
976
- const rightArm = new THREE.Group();
977
- rightArm.position.set(0.35, -0.3, 0);
978
- root.add(rightArm);
979
- add(rightArm, new THREE.CapsuleGeometry(0.06, 0.18, 8, 8), toon(SHIRT), [0, -0.12, 0]);
980
- add(rightArm, sphere(0.055, 10, 10), toon(SKIN), [0, -0.28, 0]);
981
- return { scene: root, refs: { root, hips: root, head, leftArm, rightArm } };
982
- }
983
- const MODEL_PATH = "/models/avatar.glb";
984
- function collectSkeletonBones(root) {
985
- const bones = [];
986
- const seen = /* @__PURE__ */ new Set();
987
- root.traverse((child) => {
988
- if (child.isSkinnedMesh) {
989
- const skeleton = child.skeleton;
990
- if (skeleton) {
991
- for (const bone of skeleton.bones) {
992
- if (!seen.has(bone.id)) {
993
- seen.add(bone.id);
994
- bones.push(bone);
995
- }
996
- }
997
- }
998
- }
999
- });
1000
- return bones;
1001
- }
1002
- function findBone(bones, names) {
1003
- for (const bone of bones) {
1004
- if (names.includes(bone.name)) return bone;
1005
- }
1006
- for (const bone of bones) {
1007
- for (const name of names) {
1008
- if (bone.name.startsWith(name)) return bone;
1009
- }
1010
- }
1011
- const lower = names.map((n) => n.toLowerCase());
1012
- for (const bone of bones) {
1013
- const boneLower = bone.name.toLowerCase();
1014
- for (const name of lower) {
1015
- if (boneLower.startsWith(name)) return bone;
1016
- }
1017
- }
1018
- return null;
1019
- }
1020
- function extractRefsFromGLTF(model) {
1021
- const bones = collectSkeletonBones(model);
1022
- const hips = findBone(bones, [
1023
- "Hips",
1024
- "hips",
1025
- "J_Bip_C_Hips",
1026
- "mixamorigHips",
1027
- "Pelvis",
1028
- "pelvis",
1029
- "Root",
1030
- "root"
1031
- ]);
1032
- const head = findBone(bones, [
1033
- "Head",
1034
- "head",
1035
- "J_Bip_C_Head",
1036
- "mixamorigHead"
1037
- ]);
1038
- const leftArm = findBone(bones, [
1039
- "Left arm",
1040
- "Left_arm",
1041
- "LeftArm",
1042
- "leftArm",
1043
- "J_Bip_L_UpperArm",
1044
- "mixamorigLeftArm",
1045
- "arm_L",
1046
- "Arm.L",
1047
- "Left shoulder",
1048
- "Left_shoulder"
1049
- ]);
1050
- const rightArm = findBone(bones, [
1051
- "Right arm",
1052
- "Right_arm",
1053
- "RightArm",
1054
- "rightArm",
1055
- "J_Bip_R_UpperArm",
1056
- "mixamorigRightArm",
1057
- "arm_R",
1058
- "Arm.R",
1059
- "Right shoulder",
1060
- "Right_shoulder"
1061
- ]);
1062
- return {
1063
- root: model,
1064
- hips: hips ?? model,
1065
- head: head ?? model,
1066
- leftArm: leftArm ?? model,
1067
- rightArm: rightArm ?? model
1068
- };
1069
- }
1070
- function Avatar3D() {
1071
- const containerRef = useRef(null);
1072
- const { currentAnimation, requestId, clearAnimation } = useAvatarAnimation();
1073
- const stateRef = useRef(null);
1074
- useEffect(() => {
1075
- const el = containerRef.current;
1076
- if (!el) return;
1077
- const width = el.clientWidth;
1078
- const height = el.clientHeight;
1079
- const renderer = new THREE.WebGLRenderer({ antialias: true });
1080
- renderer.setSize(width, height);
1081
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
1082
- renderer.toneMapping = THREE.NoToneMapping;
1083
- renderer.outputColorSpace = THREE.SRGBColorSpace;
1084
- el.appendChild(renderer.domElement);
1085
- const scene = new THREE.Scene();
1086
- scene.background = new THREE.Color(15263472);
1087
- const camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 100);
1088
- camera.position.set(0, 0.85, -1.8);
1089
- scene.add(new THREE.AmbientLight(16777215, 0.7));
1090
- const keyLight = new THREE.DirectionalLight(16777215, 0.9);
1091
- keyLight.position.set(1, 2, 3);
1092
- scene.add(keyLight);
1093
- const fillLight = new THREE.DirectionalLight(14544639, 0.3);
1094
- fillLight.position.set(-2, 1, -1);
1095
- scene.add(fillLight);
1096
- const rimLight = new THREE.DirectionalLight(11193599, 0.4);
1097
- rimLight.position.set(0, 1, -3);
1098
- scene.add(rimLight);
1099
- const controls = new OrbitControls(camera, renderer.domElement);
1100
- controls.target.set(0, 0.8, 0);
1101
- controls.enablePan = false;
1102
- controls.enableZoom = true;
1103
- controls.minDistance = 1;
1104
- controls.maxDistance = 3.5;
1105
- controls.minPolarAngle = Math.PI / 3;
1106
- controls.maxPolarAngle = Math.PI / 1.8;
1107
- controls.minAzimuthAngle = -Math.PI / 6;
1108
- controls.maxAzimuthAngle = Math.PI / 6;
1109
- const clock = new THREE.Clock();
1110
- function setupAnimations(refs) {
1111
- const rest = captureRestPose(refs);
1112
- const idleClip = animationRegistry.idle(refs, rest);
1113
- const state = {
1114
- renderer,
1115
- scene,
1116
- camera,
1117
- controls,
1118
- refs,
1119
- rest,
1120
- idleClip,
1121
- activeClip: null,
1122
- lastRequestId: -1,
1123
- animationFrameId: 0,
1124
- clock
1125
- };
1126
- stateRef.current = state;
1127
- function animate() {
1128
- state.animationFrameId = requestAnimationFrame(animate);
1129
- const delta = Math.min(state.clock.getDelta(), 0.1);
1130
- state.idleClip.update(delta);
1131
- if (state.activeClip) {
1132
- const done = state.activeClip.update(delta);
1133
- if (done) {
1134
- state.activeClip.reset();
1135
- state.activeClip = null;
1136
- }
1137
- }
1138
- state.controls.update();
1139
- renderer.render(scene, camera);
1140
- }
1141
- animate();
1142
- }
1143
- {
1144
- const savedCreateImageBitmap = window.createImageBitmap;
1145
- window.createImageBitmap = void 0;
1146
- const loader = new GLTFLoader();
1147
- loader.load(
1148
- MODEL_PATH,
1149
- (gltf) => {
1150
- const model = gltf.scene;
1151
- const box = new THREE.Box3().setFromObject(model);
1152
- const size = box.getSize(new THREE.Vector3());
1153
- const maxDim = Math.max(size.x, size.y, size.z);
1154
- const scale = 1.2 / maxDim;
1155
- model.scale.setScalar(scale);
1156
- model.rotation.y = 0.45;
1157
- const scaledBox = new THREE.Box3().setFromObject(model);
1158
- const center = scaledBox.getCenter(new THREE.Vector3());
1159
- model.position.x -= center.x;
1160
- model.position.z -= center.z;
1161
- window.createImageBitmap = savedCreateImageBitmap;
1162
- const wrapper = new THREE.Group();
1163
- wrapper.add(model);
1164
- scene.add(wrapper);
1165
- const finalBox = new THREE.Box3().setFromObject(wrapper);
1166
- const min = finalBox.min;
1167
- const max = finalBox.max;
1168
- const modelHeight = max.y - min.y;
1169
- const faceY = min.y + modelHeight * 0.78;
1170
- camera.position.set(0, faceY, -modelHeight * 0.9);
1171
- controls.target.set(0, faceY, 0);
1172
- controls.update();
1173
- const refs = extractRefsFromGLTF(model);
1174
- refs.root = wrapper;
1175
- refs.hips = wrapper;
1176
- setupAnimations(refs);
1177
- },
1178
- void 0,
1179
- () => {
1180
- window.createImageBitmap = savedCreateImageBitmap;
1181
- console.info("[ai-sdk] No custom avatar found at /models/avatar.glb — using built-in avatar. To use a custom model, place a .glb file at <strapi-project>/public/models/avatar.glb");
1182
- const { scene: model, refs } = buildPlaceholderModel();
1183
- scene.add(model);
1184
- setupAnimations(refs);
1185
- }
1186
- );
1187
- }
1188
- const observer = new ResizeObserver(() => {
1189
- const w = el.clientWidth;
1190
- const h = el.clientHeight;
1191
- camera.aspect = w / h;
1192
- camera.updateProjectionMatrix();
1193
- renderer.setSize(w, h);
1194
- });
1195
- observer.observe(el);
1196
- return () => {
1197
- observer.disconnect();
1198
- const s = stateRef.current;
1199
- if (s) cancelAnimationFrame(s.animationFrameId);
1200
- controls.dispose();
1201
- renderer.dispose();
1202
- if (el.contains(renderer.domElement)) el.removeChild(renderer.domElement);
1203
- stateRef.current = null;
1204
- };
1205
- }, []);
1206
- useEffect(() => {
1207
- const state = stateRef.current;
1208
- if (!state || requestId === state.lastRequestId) return;
1209
- state.lastRequestId = requestId;
1210
- if (state.activeClip) {
1211
- state.activeClip.reset();
1212
- state.activeClip = null;
1213
- }
1214
- if (currentAnimation !== "idle") {
1215
- const factory = animationRegistry[currentAnimation];
1216
- if (factory) {
1217
- state.activeClip = factory(state.refs, state.rest);
1218
- }
1219
- }
1220
- }, [currentAnimation, requestId]);
1221
- useEffect(() => {
1222
- if (currentAnimation === "idle") return;
1223
- const interval = setInterval(() => {
1224
- const state = stateRef.current;
1225
- if (state && state.activeClip === null) clearAnimation();
1226
- }, 100);
1227
- return () => clearInterval(interval);
1228
- }, [currentAnimation, clearAnimation]);
1229
- return /* @__PURE__ */ jsx("div", { ref: containerRef, style: { width: "100%", height: "100%" } });
1230
- }
1231
- const Panel = styled.div`
1232
- width: 280px;
1233
- min-width: 280px;
1234
- border-right: 1px solid #eaeaef;
1235
- background: linear-gradient(180deg, #f0f0ff 0%, #f6f6f9 100%);
1236
- display: flex;
1237
- flex-direction: column;
1238
- align-items: center;
1239
- padding-top: 24px;
1240
- gap: 12px;
1241
- `;
1242
- const AvatarContainer = styled.div`
1243
- width: 250px;
1244
- height: 300px;
1245
- border-radius: 16px;
1246
- overflow: hidden;
1247
- background: linear-gradient(180deg, #e8e8ff 0%, #f0f0f8 100%);
1248
- box-shadow: 0 2px 8px rgba(73, 69, 255, 0.1);
1249
- `;
1250
- const Label = styled.div`
1251
- font-size: 13px;
1252
- font-weight: 600;
1253
- color: #666687;
1254
- letter-spacing: 0.5px;
1255
- text-transform: uppercase;
1256
- `;
1257
- function AvatarPanel() {
1258
- return /* @__PURE__ */ jsxs(Panel, { children: [
1259
- /* @__PURE__ */ jsx(AvatarContainer, { children: /* @__PURE__ */ jsx(Avatar3D, {}) }),
1260
- /* @__PURE__ */ jsx(Label, { children: "AI Assistant" })
1261
- ] });
1262
- }
1263
- const SidebarRoot = styled.div`
1264
- width: ${({ $open }) => $open ? "260px" : "0px"};
1265
- min-width: ${({ $open }) => $open ? "260px" : "0px"};
1266
- display: flex;
1267
- flex-direction: column;
1268
- border-right: ${({ $open, theme }) => $open ? `1px solid ${theme.colors.neutral200}` : "none"};
1269
- background: ${({ theme }) => theme.colors.neutral100};
1270
- overflow: hidden;
1271
- transition: width 0.2s ease, min-width 0.2s ease;
1272
- `;
1273
- const NewChatButton = styled.button`
1274
- display: flex;
1275
- align-items: center;
1276
- gap: 8px;
1277
- width: 100%;
1278
- padding: 8px 12px;
1279
- border: 1px solid ${({ theme }) => theme.colors.neutral200};
1280
- border-radius: 4px;
1281
- background: ${({ theme }) => theme.colors.neutral0};
1282
- color: ${({ theme }) => theme.colors.neutral800};
1283
- font-size: 14px;
1284
- font-weight: 500;
1285
- cursor: pointer;
1286
-
1287
- &:hover {
1288
- background: ${({ theme }) => theme.colors.neutral100};
1289
- }
1290
-
1291
- svg {
1292
- width: 16px;
1293
- height: 16px;
1294
- }
1295
- `;
1296
- const ConversationList = styled.div`
1297
- flex: 1;
1298
- overflow-y: auto;
1299
- `;
1300
- const ConversationItem = styled.button`
1301
- width: 100%;
1302
- display: flex;
1303
- align-items: center;
1304
- justify-content: space-between;
1305
- padding: 10px 12px;
1306
- border: none;
1307
- background: ${({ $active, theme }) => $active ? theme.colors.neutral200 : "transparent"};
1308
- cursor: pointer;
1309
- text-align: left;
1310
-
1311
- &:hover {
1312
- background: ${({ theme }) => theme.colors.neutral200};
1313
- }
1314
-
1315
- &:hover .delete-btn {
1316
- opacity: 1;
1317
- }
1318
- `;
1319
- const DeleteBtn$1 = styled.button`
1320
- opacity: 0;
1321
- transition: opacity 0.15s;
1322
- flex-shrink: 0;
1323
- display: flex;
1324
- align-items: center;
1325
- justify-content: center;
1326
- width: 24px;
1327
- height: 24px;
1328
- padding: 0;
1329
- border: none;
1330
- border-radius: 4px;
1331
- background: transparent;
1332
- color: ${({ theme }) => theme.colors.neutral600};
1333
- cursor: pointer;
1334
-
1335
- &:hover {
1336
- background: ${({ theme }) => theme.colors.neutral300};
1337
- color: ${({ theme }) => theme.colors.danger600};
1338
- }
1339
-
1340
- svg {
1341
- width: 14px;
1342
- height: 14px;
1343
- }
1344
- `;
1345
- const TitleText = styled(Typography)`
1346
- overflow: hidden;
1347
- text-overflow: ellipsis;
1348
- white-space: nowrap;
1349
- flex: 1;
1350
- min-width: 0;
1351
- `;
1352
- function ConversationSidebar({
1353
- conversations,
1354
- activeId,
1355
- open,
1356
- onSelect,
1357
- onNew,
1358
- onDelete
1359
- }) {
1360
- return /* @__PURE__ */ jsxs(SidebarRoot, { $open: open, children: [
1361
- /* @__PURE__ */ jsx(Box, { padding: 3, children: /* @__PURE__ */ jsxs(NewChatButton, { onClick: onNew, children: [
1362
- /* @__PURE__ */ jsx(Plus, {}),
1363
- "New Chat"
1364
- ] }) }),
1365
- /* @__PURE__ */ jsxs(ConversationList, { children: [
1366
- conversations.map((conv) => /* @__PURE__ */ jsxs(
1367
- ConversationItem,
1368
- {
1369
- $active: conv.documentId === activeId,
1370
- onClick: () => onSelect(conv.documentId),
1371
- children: [
1372
- /* @__PURE__ */ jsx(TitleText, { variant: "omega", textColor: "neutral800", children: conv.title }),
1373
- /* @__PURE__ */ jsx(
1374
- DeleteBtn$1,
1375
- {
1376
- className: "delete-btn",
1377
- onClick: (e) => {
1378
- e.stopPropagation();
1379
- onDelete(conv.documentId);
1380
- },
1381
- "aria-label": "Delete conversation",
1382
- children: /* @__PURE__ */ jsx(Trash, {})
1383
- }
1384
- )
1385
- ]
1386
- },
1387
- conv.documentId
1388
- )),
1389
- conversations.length === 0 && /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral500", children: "No conversations yet" }) })
1390
- ] })
1391
- ] });
1392
- }
1393
- const PanelRoot = styled.div`
1394
- width: ${({ $open }) => $open ? "280px" : "0px"};
1395
- min-width: ${({ $open }) => $open ? "280px" : "0px"};
1396
- display: flex;
1397
- flex-direction: column;
1398
- border-left: ${({ $open, theme }) => $open ? `1px solid ${theme.colors.neutral200}` : "none"};
1399
- background: ${({ theme }) => theme.colors.neutral100};
1400
- overflow: hidden;
1401
- transition: width 0.2s ease, min-width 0.2s ease;
1402
- `;
1403
- const PanelHeader = styled.div`
1404
- padding: 12px 16px;
1405
- border-bottom: 1px solid ${({ theme }) => theme.colors.neutral200};
1406
- `;
1407
- const MemoryList = styled.div`
1408
- flex: 1;
1409
- overflow-y: auto;
1410
- padding: 8px 0;
1411
- `;
1412
- const MemoryItem = styled.div`
1413
- display: flex;
1414
- align-items: flex-start;
1415
- gap: 8px;
1416
- padding: 8px 16px;
1417
-
1418
- &:hover .memory-delete {
1419
- opacity: 1;
1420
- }
1421
- `;
1422
- const MemoryContent = styled.div`
1423
- flex: 1;
1424
- min-width: 0;
1425
- `;
1426
- const CategoryBadge = styled.span`
1427
- display: inline-block;
1428
- font-size: 11px;
1429
- padding: 1px 6px;
1430
- border-radius: 3px;
1431
- background: ${({ theme }) => theme.colors.neutral200};
1432
- color: ${({ theme }) => theme.colors.neutral600};
1433
- margin-bottom: 2px;
1434
- `;
1435
- const DeleteBtn = styled.button`
1436
- opacity: 0;
1437
- transition: opacity 0.15s;
1438
- flex-shrink: 0;
1439
- display: flex;
1440
- align-items: center;
1441
- justify-content: center;
1442
- width: 22px;
1443
- height: 22px;
1444
- margin-top: 2px;
1445
- padding: 0;
1446
- border: none;
1447
- border-radius: 4px;
1448
- background: transparent;
1449
- color: ${({ theme }) => theme.colors.neutral500};
1450
- cursor: pointer;
1451
-
1452
- &:hover {
1453
- background: ${({ theme }) => theme.colors.neutral300};
1454
- color: ${({ theme }) => theme.colors.danger600};
1455
- }
1456
-
1457
- svg {
1458
- width: 12px;
1459
- height: 12px;
1460
- }
1461
- `;
1462
- function MemoryPanel({ memories, open, onDelete }) {
1463
- return /* @__PURE__ */ jsxs(PanelRoot, { $open: open, children: [
1464
- /* @__PURE__ */ jsx(PanelHeader, { children: /* @__PURE__ */ jsxs(Typography, { variant: "sigma", textColor: "neutral600", children: [
1465
- "MEMORIES (",
1466
- memories.length,
1467
- ")"
1468
- ] }) }),
1469
- /* @__PURE__ */ jsxs(MemoryList, { children: [
1470
- memories.map((mem) => /* @__PURE__ */ jsxs(MemoryItem, { children: [
1471
- /* @__PURE__ */ jsxs(MemoryContent, { children: [
1472
- /* @__PURE__ */ jsx(CategoryBadge, { children: mem.category }),
1473
- /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral800", style: { display: "block" }, children: mem.content })
1474
- ] }),
1475
- /* @__PURE__ */ jsx(
1476
- DeleteBtn,
1477
- {
1478
- className: "memory-delete",
1479
- onClick: () => onDelete(mem.documentId),
1480
- "aria-label": "Delete memory",
1481
- children: /* @__PURE__ */ jsx(Trash, {})
1482
- }
1483
- )
1484
- ] }, mem.documentId)),
1485
- memories.length === 0 && /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral500", children: "No memories saved yet. The AI will save facts about you as you chat." }) })
1486
- ] })
1487
- ] });
1488
- }
1489
- function buildContentManagerUrl(contentType, documentId) {
1490
- const base = `/content-manager/collection-types/${contentType}`;
1491
- return documentId ? `${base}/${documentId}` : base;
1492
- }
1493
- function extractContentLinks(toolCall) {
1494
- if (toolCall.output === void 0) return [];
1495
- const input = toolCall.input;
1496
- const output = toolCall.output;
1497
- if (!input || !output) return [];
1498
- const contentType = input.contentType;
1499
- if (!contentType) return [];
1500
- if (toolCall.toolName === "searchContent") {
1501
- const results = output.results;
1502
- const links = [
1503
- { label: contentType, to: buildContentManagerUrl(contentType) }
1504
- ];
1505
- if (results && results.length > 0) {
1506
- for (const entry of results.slice(0, 5)) {
1507
- const docId = entry.documentId;
1508
- if (!docId) continue;
1509
- const title = entry.title || entry.name || entry.slug || docId;
1510
- links.push({
1511
- label: String(title),
1512
- to: buildContentManagerUrl(contentType, docId)
1513
- });
1514
- }
1515
- }
1516
- return links;
1517
- }
1518
- if (toolCall.toolName === "writeContent") {
1519
- const doc = output.document;
1520
- const docId = doc?.documentId;
1521
- if (docId) {
1522
- const title = doc?.title || doc?.name || docId;
1523
- return [
1524
- {
1525
- label: `${input.action === "create" ? "Created" : "Updated"}: ${title}`,
1526
- to: buildContentManagerUrl(contentType, docId)
1527
- }
1528
- ];
1529
- }
1530
- return [{ label: contentType, to: buildContentManagerUrl(contentType) }];
1531
- }
1532
- return [];
1533
- }
1534
- const ToolCallBox = styled.div`
1535
- margin-top: 8px;
1536
- border: 1px solid #dcdce4;
1537
- border-radius: 8px;
1538
- overflow: hidden;
1539
- font-size: 13px;
1540
- `;
1541
- const ToolCallHeader = styled.button`
1542
- display: flex;
1543
- align-items: center;
1544
- gap: 6px;
1545
- width: 100%;
1546
- padding: 8px 12px;
1547
- background: #eaeaef;
1548
- border: none;
1549
- cursor: pointer;
1550
- font-size: 12px;
1551
- font-weight: 600;
1552
- color: #32324d;
1553
- text-align: left;
1554
-
1555
- &:hover {
1556
- background: #dcdce4;
1557
- }
1558
- `;
1559
- const ToolCallContent = styled.pre`
1560
- margin: 0;
1561
- padding: 8px 12px;
1562
- background: #fafafa;
1563
- font-size: 11px;
1564
- line-height: 1.4;
1565
- overflow-x: auto;
1566
- max-height: 200px;
1567
- overflow-y: auto;
1568
- white-space: pre-wrap;
1569
- word-break: break-word;
1570
- `;
1571
- const ContentLinksRow = styled.div`
1572
- display: flex;
1573
- flex-wrap: wrap;
1574
- gap: 4px;
1575
- padding: 6px 12px;
1576
- background: #f6f6f9;
1577
- border-top: 1px solid #eaeaef;
1578
- `;
1579
- const ContentLinkChip = styled(Link)`
1580
- display: inline-flex;
1581
- align-items: center;
1582
- gap: 4px;
1583
- padding: 2px 8px;
1584
- border-radius: 4px;
1585
- background: #dcdce4;
1586
- color: #4945ff;
1587
- font-size: 11px;
1588
- font-weight: 500;
1589
- text-decoration: none;
1590
- white-space: nowrap;
1591
- max-width: 200px;
1592
- overflow: hidden;
1593
- text-overflow: ellipsis;
1594
-
1595
- &:hover {
1596
- background: #c0c0cf;
1597
- }
1598
- `;
1599
- const HIDDEN_TOOLS = /* @__PURE__ */ new Set(["triggerAnimation"]);
1600
- function ToolCallDisplay({ toolCall }) {
1601
- const [expanded, setExpanded] = useState(false);
1602
- const contentLinks = extractContentLinks(toolCall);
1603
- return /* @__PURE__ */ jsxs(ToolCallBox, { children: [
1604
- /* @__PURE__ */ jsxs(ToolCallHeader, { onClick: () => setExpanded(!expanded), children: [
1605
- /* @__PURE__ */ jsx("span", { children: expanded ? "▼" : "▶" }),
1606
- /* @__PURE__ */ jsxs("span", { children: [
1607
- "Tool: ",
1608
- toolCall.toolName
1609
- ] }),
1610
- toolCall.output !== void 0 && /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto", fontWeight: 400, opacity: 0.6 }, children: "completed" })
1611
- ] }),
1612
- contentLinks.length > 0 && /* @__PURE__ */ jsx(ContentLinksRow, { children: contentLinks.map((link) => /* @__PURE__ */ jsx(ContentLinkChip, { to: link.to, children: link.label }, link.to)) }),
1613
- expanded && /* @__PURE__ */ jsx(ToolCallContent, { children: toolCall.output === void 0 ? "Waiting for result..." : JSON.stringify(toolCall.output, null, 2) })
1614
- ] });
1615
- }
1616
- const waifuAvatar = "data:image/png;base64,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";
1617
- const MessagesArea = styled.div`
1618
- flex: 1;
1619
- overflow-y: auto;
1620
- scroll-behavior: smooth;
1621
- padding: 24px;
1622
- display: flex;
1623
- flex-direction: column;
1624
- gap: 12px;
1625
- `;
1626
- const MessageRow = styled.div`
1627
- display: flex;
1628
- align-items: flex-end;
1629
- gap: 8px;
1630
- align-self: ${({ $isUser }) => $isUser ? "flex-end" : "flex-start"};
1631
- max-width: 80%;
1632
- `;
1633
- const Avatar = styled.img`
1634
- width: 104px;
1635
- height: 104px;
1636
- border-radius: 50%;
1637
- object-fit: cover;
1638
- flex-shrink: 0;
1639
- `;
1640
- const MessageBubble = styled.div`
1641
- min-width: 0;
1642
- background-color: ${({ $isUser }) => $isUser ? "#4945ff" : "#f6f6f9"};
1643
- color: ${({ $isUser }) => $isUser ? "#ffffff" : "#32324d"};
1644
- border-radius: ${({ $isUser }) => $isUser ? "16px 16px 4px 16px" : "16px 16px 16px 4px"};
1645
- padding: 12px 16px;
1646
- font-size: 15px;
1647
- word-break: break-word;
1648
- line-height: 1.6;
1649
- `;
1650
- const MarkdownBody = styled.div`
1651
- p { margin: 0 0 8px; &:last-child { margin-bottom: 0; } }
1652
- ul, ol { margin: 4px 0; padding-left: 20px; }
1653
- li { margin: 2px 0; }
1654
- code {
1655
- font-size: 0.85em;
1656
- padding: 1px 4px;
1657
- border-radius: 3px;
1658
- background: ${({ $isUser }) => $isUser ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.06)"};
1659
- }
1660
- pre {
1661
- margin: 8px 0;
1662
- padding: 8px 10px;
1663
- border-radius: 6px;
1664
- overflow-x: auto;
1665
- font-size: 0.85em;
1666
- background: ${({ $isUser }) => $isUser ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.04)"};
1667
- code { padding: 0; background: none; }
1668
- }
1669
- h1, h2, h3, h4 { margin: 12px 0 4px; &:first-child { margin-top: 0; } }
1670
- h1 { font-size: 1.3em; } h2 { font-size: 1.15em; } h3 { font-size: 1.05em; }
1671
- blockquote {
1672
- margin: 8px 0;
1673
- padding-left: 12px;
1674
- border-left: 3px solid ${({ $isUser }) => $isUser ? "rgba(255,255,255,0.3)" : "#dcdce4"};
1675
- opacity: 0.85;
1676
- }
1677
- a { color: ${({ $isUser }) => $isUser ? "#c0cfff" : "#4945ff"}; }
1678
- table { border-collapse: collapse; margin: 8px 0; font-size: 0.9em; }
1679
- th, td { border: 1px solid ${({ $isUser }) => $isUser ? "rgba(255,255,255,0.2)" : "#dcdce4"}; padding: 4px 8px; }
1680
- `;
1681
- const MessageRole = styled.div`
1682
- font-size: 11px;
1683
- font-weight: 600;
1684
- margin-bottom: 4px;
1685
- opacity: 0.7;
1686
- color: ${({ $isUser }) => $isUser ? "#ffffff" : "#666687"};
1687
- `;
1688
- const TypingDots = styled.span`
1689
- display: inline-flex;
1690
- gap: 4px;
1691
-
1692
- span {
1693
- width: 7px;
1694
- height: 7px;
1695
- border-radius: 50%;
1696
- background: #a5a5ba;
1697
- animation: bounce 1.4s infinite ease-in-out both;
1698
- }
1699
- span:nth-child(1) { animation-delay: 0s; }
1700
- span:nth-child(2) { animation-delay: 0.2s; }
1701
- span:nth-child(3) { animation-delay: 0.4s; }
1702
-
1703
- @keyframes bounce {
1704
- 0%, 80%, 100% { transform: scale(0.4); opacity: 0.4; }
1705
- 40% { transform: scale(1); opacity: 1; }
1706
- }
1707
- `;
1708
- const EmptyState = styled.div`
1709
- display: flex;
1710
- flex-direction: column;
1711
- align-items: center;
1712
- justify-content: center;
1713
- flex: 1;
1714
- color: #a5a5ba;
1715
- `;
1716
- const CONTENT_TYPE_UID_RE = /\b(api::\w[\w-]*\.\w[\w-]*)\b/g;
1717
- function autoLinkContentTypeUids(text) {
1718
- return text.replace(
1719
- CONTENT_TYPE_UID_RE,
1720
- (match) => `[${match}](/content-manager/collection-types/${match})`
1721
- );
1722
- }
1723
- function isInternalPath(href) {
1724
- return href.startsWith("/content-manager/");
1725
- }
1726
- function MarkdownLink({
1727
- href,
1728
- children,
1729
- ...props
1730
- }) {
1731
- if (href && isInternalPath(href)) {
1732
- return /* @__PURE__ */ jsx(Link, { to: href, ...props, children });
1733
- }
1734
- return /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer", ...props, children });
1735
- }
1736
- const markdownComponents = {
1737
- a: MarkdownLink
1738
- };
1739
- const MessageList = forwardRef(
1740
- function MessageList2({ messages, isLoading, awaitingAudio, voiceEnabled, visibleText }, ref) {
1741
- return /* @__PURE__ */ jsxs(MessagesArea, { children: [
1742
- messages.length === 0 && /* @__PURE__ */ jsxs(EmptyState, { children: [
1743
- /* @__PURE__ */ jsx(Typography, { variant: "beta", textColor: "neutral400", children: "AI Chat" }),
1744
- /* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral500", children: "Send a message to start the conversation" }) })
1745
- ] }),
1746
- messages.map((message, index) => {
1747
- const isLatestAssistant = message.role === "assistant" && index === messages.length - 1;
1748
- const rawContent = voiceEnabled && isLatestAssistant && (awaitingAudio || visibleText) ? visibleText : message.content;
1749
- const displayContent = message.role === "assistant" && rawContent ? autoLinkContentTypeUids(rawContent) : rawContent;
1750
- return /* @__PURE__ */ jsxs(MessageRow, { $isUser: message.role === "user", children: [
1751
- message.role === "assistant" && /* @__PURE__ */ jsx(Avatar, { src: waifuAvatar, alt: "Assistant" }),
1752
- /* @__PURE__ */ jsxs(MessageBubble, { $isUser: message.role === "user", children: [
1753
- /* @__PURE__ */ jsx(MessageRole, { $isUser: message.role === "user", children: message.role === "user" ? "You" : "Assistant" }),
1754
- message.role === "user" && message.content,
1755
- message.role === "assistant" && displayContent && /* @__PURE__ */ jsx(MarkdownBody, { $isUser: false, children: /* @__PURE__ */ jsx(Markdown, { components: markdownComponents, children: displayContent }) }),
1756
- message.role === "assistant" && !displayContent && (isLoading || awaitingAudio) && /* @__PURE__ */ jsxs(TypingDots, { children: [
1757
- /* @__PURE__ */ jsx("span", {}),
1758
- /* @__PURE__ */ jsx("span", {}),
1759
- /* @__PURE__ */ jsx("span", {})
1760
- ] }),
1761
- message.toolCalls?.filter((tc) => !HIDDEN_TOOLS.has(tc.toolName)).map((tc) => /* @__PURE__ */ jsx(ToolCallDisplay, { toolCall: tc }, tc.toolCallId))
1762
- ] })
1763
- ] }, message.id);
1764
- }),
1765
- /* @__PURE__ */ jsx("div", { ref })
1766
- ] });
1767
- }
1768
- );
1769
- const InputArea = styled.div`
1770
- display: flex;
1771
- gap: 8px;
1772
- align-items: flex-end;
1773
- padding: 16px;
1774
- border-top: 1px solid #eaeaef;
1775
- `;
1776
- const VoiceToggle = styled.button`
1777
- display: flex;
1778
- align-items: center;
1779
- justify-content: center;
1780
- width: 40px;
1781
- height: 40px;
1782
- border-radius: 8px;
1783
- border: 1px solid ${({ $active }) => $active ? "#4945ff" : "#dcdce4"};
1784
- background: ${({ $active }) => $active ? "#f0f0ff" : "#ffffff"};
1785
- color: ${({ $active }) => $active ? "#4945ff" : "#a5a5ba"};
1786
- cursor: pointer;
1787
- flex-shrink: 0;
1788
- transition: all 0.15s ease;
1789
-
1790
- &:hover {
1791
- border-color: #4945ff;
1792
- color: #4945ff;
1793
- }
1794
-
1795
- svg {
1796
- width: 18px;
1797
- height: 18px;
1798
- }
1799
- `;
1800
- function ChatInput({
1801
- input,
1802
- isLoading,
1803
- voiceEnabled,
1804
- onInputChange,
1805
- onSend,
1806
- onToggleVoice
1807
- }) {
1808
- return /* @__PURE__ */ jsx(
1809
- "form",
1810
- {
1811
- onSubmit: (e) => {
1812
- e.preventDefault();
1813
- onSend();
1814
- },
1815
- children: /* @__PURE__ */ jsxs(InputArea, { children: [
1816
- /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsx(
1817
- TextInput,
1818
- {
1819
- placeholder: "Type your message...",
1820
- "aria-label": "Chat message",
1821
- value: input,
1822
- onChange: (e) => onInputChange(e.target.value)
1823
- }
1824
- ) }),
1825
- /* @__PURE__ */ jsx(
1826
- VoiceToggle,
1827
- {
1828
- type: "button",
1829
- onClick: onToggleVoice,
1830
- title: voiceEnabled ? "Disable voice" : "Enable voice",
1831
- $active: voiceEnabled,
1832
- children: voiceEnabled ? /* @__PURE__ */ jsx(VolumeUp, {}) : /* @__PURE__ */ jsx(VolumeMute, {})
1833
- }
1834
- ),
1835
- /* @__PURE__ */ jsx(
1836
- Button,
1837
- {
1838
- type: "submit",
1839
- disabled: isLoading || !input.trim(),
1840
- loading: isLoading,
1841
- size: "L",
1842
- startIcon: /* @__PURE__ */ jsx(Sparkle, {}),
1843
- children: "Send"
1844
- }
1845
- )
1846
- ] })
1847
- }
1848
- );
1849
- }
1850
- const ChatLayout = styled.div`
1851
- display: flex;
1852
- flex-direction: row;
1853
- height: calc(100vh - 200px);
1854
- min-height: 400px;
1855
- border-radius: 4px;
1856
- overflow: hidden;
1857
- box-shadow: 0 1px 4px rgba(33, 33, 52, 0.1);
1858
- background: #ffffff;
1859
- `;
1860
- const ChatWrapper = styled.div`
1861
- display: flex;
1862
- flex-direction: column;
1863
- flex: 1;
1864
- min-width: 0;
1865
- `;
1866
- const ToggleSidebarBtn = styled.button`
1867
- display: flex;
1868
- align-items: center;
1869
- justify-content: center;
1870
- width: 32px;
1871
- height: 32px;
1872
- border: 1px solid #dcdce4;
1873
- border-radius: 4px;
1874
- background: #ffffff;
1875
- color: #666687;
1876
- cursor: pointer;
1877
- flex-shrink: 0;
1878
-
1879
- &:hover {
1880
- background: #f0f0ff;
1881
- color: #4945ff;
1882
- border-color: #4945ff;
1883
- }
1884
-
1885
- svg {
1886
- width: 16px;
1887
- height: 16px;
1888
- }
1889
- `;
1890
- const ChatTopBar = styled.div`
1891
- display: flex;
1892
- align-items: center;
1893
- padding: 8px 16px;
1894
- border-bottom: 1px solid #eaeaef;
1895
- gap: 8px;
1896
- `;
1897
- function Chat() {
1898
- const navigate = useNavigate();
1899
- const [input, setInput] = useState("");
1900
- const [sidebarOpen, setSidebarOpen] = useState(false);
1901
- const [memoryPanelOpen, setMemoryPanelOpen] = useState(false);
1902
- const [voiceEnabled, setVoiceEnabled] = useState(false);
1903
- const [awaitingAudio, setAwaitingAudio] = useState(false);
1904
- const messagesEndRef = useRef(null);
1905
- const fullTextRef = useRef("");
1906
- const voiceRef = useRef(voiceEnabled);
1907
- voiceRef.current = voiceEnabled;
1908
- const prevIsLoadingRef = useRef(false);
1909
- const { trigger, clearAnimation } = useAvatarAnimation();
1910
- const { visibleText, startReveal, reset: resetReveal } = useTextReveal();
1911
- const { speak: speak2, stop: stopAudio } = useAudioPlayer({
1912
- onPlayStart: (duration) => {
1913
- trigger("speak");
1914
- startReveal(fullTextRef.current, duration);
1915
- setAwaitingAudio(false);
1916
- },
1917
- onPlayEnded: () => clearAnimation()
1918
- });
1919
- const {
1920
- conversations,
1921
- activeId,
1922
- initialMessages,
1923
- selectConversation,
1924
- startNewConversation,
1925
- saveMessages,
1926
- removeConversation
1927
- } = useConversations();
1928
- const { memories, removeMemory, refresh: refreshMemories } = useMemories();
1929
- const { messages, sendMessage, isLoading, error } = useChat({
1930
- initialMessages,
1931
- conversationId: activeId,
1932
- onAnimationTrigger: trigger,
1933
- onStreamEnd: (fullText) => {
1934
- if (!voiceRef.current) return;
1935
- fullTextRef.current = fullText;
1936
- if (!fullText) {
1937
- setAwaitingAudio(false);
1938
- clearAnimation();
1939
- } else {
1940
- speak2(fullText);
1941
- }
1942
- }
1943
- });
1944
- useEffect(() => {
1945
- if (prevIsLoadingRef.current && !isLoading && messages.length > 0) {
1946
- saveMessages(messages);
1947
- refreshMemories();
1948
- }
1949
- prevIsLoadingRef.current = isLoading;
1950
- }, [isLoading, messages, saveMessages, refreshMemories]);
1951
- const handleSend = () => {
1952
- if (!input.trim() || isLoading) return;
1953
- if (voiceEnabled) {
1954
- fullTextRef.current = "";
1955
- resetReveal();
1956
- setAwaitingAudio(true);
1957
- }
1958
- sendMessage(input);
1959
- setInput("");
1960
- };
1961
- const handleToggleVoice = () => {
1962
- const next = !voiceEnabled;
1963
- setVoiceEnabled(next);
1964
- if (!next) {
1965
- stopAudio();
1966
- resetReveal();
1967
- setAwaitingAudio(false);
1968
- clearAnimation();
1969
- }
1970
- };
1971
- useEffect(() => {
1972
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
1973
- }, [messages, visibleText]);
1974
- return /* @__PURE__ */ jsxs(ChatLayout, { children: [
1975
- /* @__PURE__ */ jsx(
1976
- ConversationSidebar,
1977
- {
1978
- conversations,
1979
- activeId,
1980
- open: sidebarOpen,
1981
- onSelect: selectConversation,
1982
- onNew: startNewConversation,
1983
- onDelete: removeConversation
1984
- }
1985
- ),
1986
- /* @__PURE__ */ jsx(AvatarPanel, {}),
1987
- /* @__PURE__ */ jsxs(ChatWrapper, { children: [
1988
- /* @__PURE__ */ jsxs(ChatTopBar, { children: [
1989
- /* @__PURE__ */ jsx(
1990
- ToggleSidebarBtn,
1991
- {
1992
- onClick: () => setSidebarOpen((prev) => !prev),
1993
- "aria-label": sidebarOpen ? "Hide conversations" : "Show conversations",
1994
- children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
1995
- /* @__PURE__ */ jsx("rect", { x: "1", y: "2", width: "14", height: "12", rx: "1.5" }),
1996
- /* @__PURE__ */ jsx("line", { x1: "5.5", y1: "2", x2: "5.5", y2: "14" })
1997
- ] })
1998
- }
1999
- ),
2000
- /* @__PURE__ */ jsx(
2001
- ToggleSidebarBtn,
2002
- {
2003
- onClick: () => navigate(`/plugins/${PLUGIN_ID}/memory-store`),
2004
- "aria-label": "Memory Store",
2005
- children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M2 4h12M2 8h12M2 12h8" }) })
2006
- }
2007
- ),
2008
- /* @__PURE__ */ jsx(
2009
- ToggleSidebarBtn,
2010
- {
2011
- onClick: () => navigate(`/plugins/${PLUGIN_ID}/public-memory-store`),
2012
- "aria-label": "Public Memory Store",
2013
- children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
2014
- /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6" }),
2015
- /* @__PURE__ */ jsx("ellipse", { cx: "8", cy: "8", rx: "2.5", ry: "6" }),
2016
- /* @__PURE__ */ jsx("path", { d: "M2 8h12" })
2017
- ] })
2018
- }
2019
- ),
2020
- /* @__PURE__ */ jsx(
2021
- ToggleSidebarBtn,
2022
- {
2023
- onClick: () => navigate(`/plugins/${PLUGIN_ID}/widget-preview`),
2024
- "aria-label": "Widget Preview",
2025
- children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
2026
- /* @__PURE__ */ jsx("polyline", { points: "4 4 8 2 12 4" }),
2027
- /* @__PURE__ */ jsx("polyline", { points: "4 4 4 10 8 12 12 10 12 4" }),
2028
- /* @__PURE__ */ jsx("line", { x1: "8", y1: "2", x2: "8", y2: "12" })
2029
- ] })
2030
- }
2031
- ),
2032
- /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
2033
- /* @__PURE__ */ jsx(
2034
- ToggleSidebarBtn,
2035
- {
2036
- onClick: () => setMemoryPanelOpen((prev) => !prev),
2037
- "aria-label": memoryPanelOpen ? "Hide memories" : "Show memories",
2038
- children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
2039
- /* @__PURE__ */ jsx("circle", { cx: "8", cy: "6", r: "4" }),
2040
- /* @__PURE__ */ jsx("path", { d: "M4 10.5C4 10.5 5 14 8 14s4-3.5 4-3.5" })
2041
- ] })
2042
- }
2043
- )
2044
- ] }),
2045
- /* @__PURE__ */ jsx(
2046
- MessageList,
2047
- {
2048
- ref: messagesEndRef,
2049
- messages,
2050
- isLoading,
2051
- awaitingAudio,
2052
- voiceEnabled,
2053
- visibleText
2054
- }
2055
- ),
2056
- error && /* @__PURE__ */ jsx(Box, { padding: 3, background: "danger100", marginLeft: 4, marginRight: 4, children: /* @__PURE__ */ jsxs(Typography, { textColor: "danger600", children: [
2057
- "Error: ",
2058
- error
2059
- ] }) }),
2060
- /* @__PURE__ */ jsx(
2061
- ChatInput,
2062
- {
2063
- input,
2064
- isLoading,
2065
- voiceEnabled,
2066
- onInputChange: setInput,
2067
- onSend: handleSend,
2068
- onToggleVoice: handleToggleVoice
2069
- }
2070
- )
2071
- ] }),
2072
- /* @__PURE__ */ jsx(
2073
- MemoryPanel,
2074
- {
2075
- memories,
2076
- open: memoryPanelOpen,
2077
- onDelete: removeMemory
2078
- }
2079
- )
2080
- ] });
2081
- }
2082
- const HomePage = () => {
2083
- return /* @__PURE__ */ jsxs(Main, { children: [
2084
- /* @__PURE__ */ jsx(
2085
- Layouts.Header,
2086
- {
2087
- title: "AI Chat",
2088
- subtitle: "Chat with AI powered by Vercel AI SDK"
2089
- }
2090
- ),
2091
- /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsx(AvatarAnimationProvider, { children: /* @__PURE__ */ jsx(Chat, {}) }) })
2092
- ] });
2093
- };
2094
- const PAGE_SIZE$1 = 10;
2095
- const ActionBtn$1 = styled.button`
2096
- display: inline-flex;
2097
- align-items: center;
2098
- justify-content: center;
2099
- width: 32px;
2100
- height: 32px;
2101
- padding: 0;
2102
- border: none;
2103
- border-radius: 4px;
2104
- background: transparent;
2105
- color: ${({ theme }) => theme.colors.neutral600};
2106
- cursor: pointer;
2107
-
2108
- &:hover {
2109
- background: ${({ theme }) => theme.colors.neutral200};
2110
- color: ${({ theme }) => theme.colors.primary600};
2111
- }
2112
-
2113
- svg {
2114
- width: 16px;
2115
- height: 16px;
2116
- }
2117
- `;
2118
- const DeleteActionBtn$1 = styled(ActionBtn$1)`
2119
- &:hover {
2120
- color: ${({ theme }) => theme.colors.danger600};
2121
- }
2122
- `;
2123
- const WideModalContent$1 = styled(Modal.Content)`
2124
- max-width: 680px;
2125
- width: 100%;
2126
- `;
2127
- function formatDate$1(iso) {
2128
- return new Date(iso).toLocaleDateString(void 0, {
2129
- year: "numeric",
2130
- month: "short",
2131
- day: "numeric",
2132
- hour: "2-digit",
2133
- minute: "2-digit"
2134
- });
2135
- }
2136
- function MemoryModal({ memory, open, onClose, onSave }) {
2137
- const [content, setContent] = useState("");
2138
- const [category, setCategory] = useState("general");
2139
- const isEdit = memory !== null;
2140
- useEffect(() => {
2141
- if (open && memory) {
2142
- setContent(memory.content);
2143
- setCategory(memory.category);
2144
- } else if (open) {
2145
- setContent("");
2146
- setCategory("general");
2147
- }
2148
- }, [open, memory]);
2149
- const handleClose = () => {
2150
- setContent("");
2151
- setCategory("general");
2152
- onClose();
2153
- };
2154
- const handleSave = () => {
2155
- if (!content.trim()) return;
2156
- onSave({ content, category }, memory?.documentId);
2157
- handleClose();
2158
- };
2159
- return /* @__PURE__ */ jsx(Modal.Root, { open, onOpenChange: (isOpen) => !isOpen && handleClose(), children: /* @__PURE__ */ jsxs(WideModalContent$1, { children: [
2160
- /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: isEdit ? "Edit Memory" : "Add Memory" }) }),
2161
- /* @__PURE__ */ jsx(Modal.Body, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 5, width: "100%", children: [
2162
- /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
2163
- /* @__PURE__ */ jsx(Field.Label, { children: "Content" }),
2164
- /* @__PURE__ */ jsx(
2165
- Textarea,
2166
- {
2167
- placeholder: "A fact or preference to remember (e.g. 'User prefers dark mode')",
2168
- value: content,
2169
- onChange: (e) => setContent(e.target.value),
2170
- style: { width: "100%", minHeight: "120px" }
2171
- }
2172
- )
2173
- ] }),
2174
- /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
2175
- /* @__PURE__ */ jsx(Field.Label, { children: "Category" }),
2176
- /* @__PURE__ */ jsxs(
2177
- SingleSelect,
2178
- {
2179
- value: category,
2180
- onChange: (value) => setCategory(value),
2181
- children: [
2182
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "general", children: "General" }),
2183
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "preference", children: "Preference" }),
2184
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "personal", children: "Personal" }),
2185
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "project", children: "Project" })
2186
- ]
2187
- }
2188
- ),
2189
- /* @__PURE__ */ jsx(Field.Hint, { children: "Used to organize memories by topic" })
2190
- ] })
2191
- ] }) }),
2192
- /* @__PURE__ */ jsxs(Modal.Footer, { children: [
2193
- /* @__PURE__ */ jsx(Modal.Close, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: "Cancel" }) }),
2194
- /* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: !content.trim(), children: isEdit ? "Save changes" : "Add memory" })
2195
- ] })
2196
- ] }) });
2197
- }
2198
- const MemoryStorePage = () => {
2199
- const navigate = useNavigate();
2200
- const { memories, loading, addMemory, editMemory, removeMemory } = useMemories();
2201
- const [search, setSearch] = useState("");
2202
- const [page, setPage] = useState(1);
2203
- const [modalOpen, setModalOpen] = useState(false);
2204
- const [editingMemory, setEditingMemory] = useState(null);
2205
- const filtered = useMemo(() => {
2206
- if (!search.trim()) return memories;
2207
- const q = search.toLowerCase();
2208
- return memories.filter(
2209
- (m) => m.content.toLowerCase().includes(q) || m.category.toLowerCase().includes(q)
2210
- );
2211
- }, [memories, search]);
2212
- const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE$1));
2213
- const paginated = filtered.slice((page - 1) * PAGE_SIZE$1, page * PAGE_SIZE$1);
2214
- const handleOpenCreate = () => {
2215
- setEditingMemory(null);
2216
- setModalOpen(true);
2217
- };
2218
- const handleOpenEdit = (mem) => {
2219
- setEditingMemory(mem);
2220
- setModalOpen(true);
2221
- };
2222
- const handleCloseModal = () => {
2223
- setModalOpen(false);
2224
- setEditingMemory(null);
2225
- };
2226
- const handleSave = (data, documentId) => {
2227
- if (documentId) {
2228
- editMemory(documentId, data);
2229
- } else {
2230
- addMemory(data);
2231
- }
2232
- };
2233
- return /* @__PURE__ */ jsxs(Main, { children: [
2234
- /* @__PURE__ */ jsx(
2235
- Layouts.Header,
2236
- {
2237
- title: "Memory Store",
2238
- subtitle: `${memories.length} memories saved`,
2239
- primaryAction: /* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(Plus, {}), onClick: handleOpenCreate, children: "Add memory" }),
2240
- navigationAction: /* @__PURE__ */ jsx(Button, { variant: "ghost", startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), onClick: () => navigate(`/plugins/${PLUGIN_ID}`), children: "Back to Chat" })
2241
- }
2242
- ),
2243
- /* @__PURE__ */ jsxs(Layouts.Content, { children: [
2244
- /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(SearchForm, { children: /* @__PURE__ */ jsx(
2245
- Searchbar,
2246
- {
2247
- name: "search",
2248
- value: search,
2249
- onChange: (e) => {
2250
- setSearch(e.target.value);
2251
- setPage(1);
2252
- },
2253
- onClear: () => {
2254
- setSearch("");
2255
- setPage(1);
2256
- },
2257
- placeholder: "Search memories...",
2258
- children: "Search"
2259
- }
2260
- ) }) }),
2261
- /* @__PURE__ */ jsxs(Table, { colCount: 4, rowCount: paginated.length + 1, children: [
2262
- /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
2263
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Content" }) }),
2264
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Category" }) }),
2265
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Created" }) }),
2266
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Actions" }) })
2267
- ] }) }),
2268
- /* @__PURE__ */ jsxs(Tbody, { children: [
2269
- loading && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 4, children: /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "Loading..." }) }) }) }),
2270
- !loading && paginated.length === 0 && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 4, children: /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral500", children: search ? "No memories match your search" : "No memories saved yet" }) }) }) }),
2271
- paginated.map((mem) => /* @__PURE__ */ jsxs(Tr, { children: [
2272
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", children: mem.content }) }),
2273
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: mem.category }) }),
2274
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatDate$1(mem.createdAt) }) }),
2275
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { gap: 1, children: [
2276
- /* @__PURE__ */ jsx(
2277
- ActionBtn$1,
2278
- {
2279
- onClick: () => handleOpenEdit(mem),
2280
- "aria-label": "Edit memory",
2281
- children: /* @__PURE__ */ jsx(Pencil, {})
2282
- }
2283
- ),
2284
- /* @__PURE__ */ jsx(
2285
- DeleteActionBtn$1,
2286
- {
2287
- onClick: () => removeMemory(mem.documentId),
2288
- "aria-label": "Delete memory",
2289
- children: /* @__PURE__ */ jsx(Trash, {})
2290
- }
2291
- )
2292
- ] }) })
2293
- ] }, mem.documentId))
2294
- ] })
2295
- ] }),
2296
- pageCount > 1 && /* @__PURE__ */ jsx(Box, { paddingTop: 4, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Pagination.Root, { pageCount, activePage: page, onPageChange: setPage, children: /* @__PURE__ */ jsx(Pagination.Links, {}) }) }) }),
2297
- /* @__PURE__ */ jsx(
2298
- MemoryModal,
2299
- {
2300
- memory: editingMemory,
2301
- open: modalOpen,
2302
- onClose: handleCloseModal,
2303
- onSave: handleSave
2304
- }
2305
- )
2306
- ] })
2307
- ] });
2308
- };
2309
- const BASE = () => `${getBackendURL()}/${PLUGIN_ID}/public-memories`;
2310
- function headers() {
2311
- const token = getToken();
2312
- return {
2313
- "Content-Type": "application/json",
2314
- ...token ? { Authorization: `Bearer ${token}` } : {}
2315
- };
2316
- }
2317
- async function request(url, init) {
2318
- const res = await fetch(url, { headers: headers(), ...init });
2319
- if (!res.ok) throw new Error(`Request failed: ${res.status}`);
2320
- const json = await res.json();
2321
- return json.data;
2322
- }
2323
- function fetchPublicMemories() {
2324
- return request(BASE());
2325
- }
2326
- function createPublicMemory(data) {
2327
- return request(BASE(), {
2328
- method: "POST",
2329
- body: JSON.stringify(data)
2330
- });
2331
- }
2332
- function updatePublicMemory(documentId, data) {
2333
- return request(`${BASE()}/${documentId}`, {
2334
- method: "PUT",
2335
- body: JSON.stringify(data)
2336
- });
2337
- }
2338
- function deletePublicMemory(documentId) {
2339
- return request(`${BASE()}/${documentId}`, { method: "DELETE" });
2340
- }
2341
- function usePublicMemories() {
2342
- const [memories, setMemories] = useState([]);
2343
- const [loading, setLoading] = useState(true);
2344
- const load = useCallback(async () => {
2345
- try {
2346
- const data = await fetchPublicMemories();
2347
- setMemories(data);
2348
- } catch (err) {
2349
- console.error("Failed to load public memories:", err);
2350
- } finally {
2351
- setLoading(false);
2352
- }
2353
- }, []);
2354
- useEffect(() => {
2355
- load();
2356
- }, [load]);
2357
- const addMemory = useCallback(async (data) => {
2358
- try {
2359
- const created = await createPublicMemory(data);
2360
- setMemories((prev) => [created, ...prev]);
2361
- } catch (err) {
2362
- console.error("Failed to create public memory:", err);
2363
- }
2364
- }, []);
2365
- const editMemory = useCallback(async (documentId, data) => {
2366
- try {
2367
- const updated = await updatePublicMemory(documentId, data);
2368
- setMemories(
2369
- (prev) => prev.map((m) => m.documentId === documentId ? { ...m, ...updated } : m)
2370
- );
2371
- } catch (err) {
2372
- console.error("Failed to update public memory:", err);
2373
- }
2374
- }, []);
2375
- const removeMemory = useCallback(async (documentId) => {
2376
- try {
2377
- await deletePublicMemory(documentId);
2378
- setMemories((prev) => prev.filter((m) => m.documentId !== documentId));
2379
- } catch (err) {
2380
- console.error("Failed to delete public memory:", err);
2381
- }
2382
- }, []);
2383
- return { memories, loading, addMemory, editMemory, removeMemory, refresh: load };
2384
- }
2385
- const PAGE_SIZE = 10;
2386
- const ActionBtn = styled.button`
2387
- display: inline-flex;
2388
- align-items: center;
2389
- justify-content: center;
2390
- width: 32px;
2391
- height: 32px;
2392
- padding: 0;
2393
- border: none;
2394
- border-radius: 4px;
2395
- background: transparent;
2396
- color: ${({ theme }) => theme.colors.neutral600};
2397
- cursor: pointer;
2398
-
2399
- &:hover {
2400
- background: ${({ theme }) => theme.colors.neutral200};
2401
- color: ${({ theme }) => theme.colors.primary600};
2402
- }
2403
-
2404
- svg {
2405
- width: 16px;
2406
- height: 16px;
2407
- }
2408
- `;
2409
- const DeleteActionBtn = styled(ActionBtn)`
2410
- &:hover {
2411
- color: ${({ theme }) => theme.colors.danger600};
2412
- }
2413
- `;
2414
- const WideModalContent = styled(Modal.Content)`
2415
- max-width: 680px;
2416
- width: 100%;
2417
- `;
2418
- function formatDate(iso) {
2419
- return new Date(iso).toLocaleDateString(void 0, {
2420
- year: "numeric",
2421
- month: "short",
2422
- day: "numeric",
2423
- hour: "2-digit",
2424
- minute: "2-digit"
2425
- });
2426
- }
2427
- function PublicMemoryModal({ memory, open, onClose, onSave }) {
2428
- const [content, setContent] = useState("");
2429
- const [category, setCategory] = useState("general");
2430
- const isEdit = memory !== null;
2431
- useEffect(() => {
2432
- if (open && memory) {
2433
- setContent(memory.content);
2434
- setCategory(memory.category);
2435
- } else if (open) {
2436
- setContent("");
2437
- setCategory("general");
2438
- }
2439
- }, [open, memory]);
2440
- const handleClose = () => {
2441
- setContent("");
2442
- setCategory("general");
2443
- onClose();
2444
- };
2445
- const handleSave = () => {
2446
- if (!content.trim()) return;
2447
- onSave({ content, category }, memory?.documentId);
2448
- handleClose();
2449
- };
2450
- return /* @__PURE__ */ jsx(Modal.Root, { open, onOpenChange: (isOpen) => !isOpen && handleClose(), children: /* @__PURE__ */ jsxs(WideModalContent, { children: [
2451
- /* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: isEdit ? "Edit Public Memory" : "Add Public Memory" }) }),
2452
- /* @__PURE__ */ jsx(Modal.Body, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 5, width: "100%", children: [
2453
- /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
2454
- /* @__PURE__ */ jsx(Field.Label, { children: "Content" }),
2455
- /* @__PURE__ */ jsx(
2456
- Textarea,
2457
- {
2458
- placeholder: "Public knowledge for the chatbot (e.g. 'Our return policy is 30 days')",
2459
- value: content,
2460
- onChange: (e) => setContent(e.target.value),
2461
- style: { width: "100%", minHeight: "120px" }
2462
- }
2463
- )
2464
- ] }),
2465
- /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
2466
- /* @__PURE__ */ jsx(Field.Label, { children: "Category" }),
2467
- /* @__PURE__ */ jsxs(
2468
- SingleSelect,
2469
- {
2470
- value: category,
2471
- onChange: (value) => setCategory(value),
2472
- children: [
2473
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "general", children: "General" }),
2474
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "faq", children: "FAQ" }),
2475
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "product", children: "Product" }),
2476
- /* @__PURE__ */ jsx(SingleSelectOption, { value: "policy", children: "Policy" })
2477
- ]
2478
- }
2479
- ),
2480
- /* @__PURE__ */ jsx(Field.Hint, { children: "Used to organize public memories by topic" })
2481
- ] })
2482
- ] }) }),
2483
- /* @__PURE__ */ jsxs(Modal.Footer, { children: [
2484
- /* @__PURE__ */ jsx(Modal.Close, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: "Cancel" }) }),
2485
- /* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: !content.trim(), children: isEdit ? "Save changes" : "Add memory" })
2486
- ] })
2487
- ] }) });
2488
- }
2489
- const PublicMemoryStorePage = () => {
2490
- const navigate = useNavigate();
2491
- const { memories, loading, addMemory, editMemory, removeMemory } = usePublicMemories();
2492
- const [search, setSearch] = useState("");
2493
- const [page, setPage] = useState(1);
2494
- const [modalOpen, setModalOpen] = useState(false);
2495
- const [editingMemory, setEditingMemory] = useState(null);
2496
- const filtered = useMemo(() => {
2497
- if (!search.trim()) return memories;
2498
- const q = search.toLowerCase();
2499
- return memories.filter(
2500
- (m) => m.content.toLowerCase().includes(q) || m.category.toLowerCase().includes(q)
2501
- );
2502
- }, [memories, search]);
2503
- const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
2504
- const paginated = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
2505
- const handleOpenCreate = () => {
2506
- setEditingMemory(null);
2507
- setModalOpen(true);
2508
- };
2509
- const handleOpenEdit = (mem) => {
2510
- setEditingMemory(mem);
2511
- setModalOpen(true);
2512
- };
2513
- const handleCloseModal = () => {
2514
- setModalOpen(false);
2515
- setEditingMemory(null);
2516
- };
2517
- const handleSave = (data, documentId) => {
2518
- if (documentId) {
2519
- editMemory(documentId, data);
2520
- } else {
2521
- addMemory(data);
2522
- }
2523
- };
2524
- return /* @__PURE__ */ jsxs(Main, { children: [
2525
- /* @__PURE__ */ jsx(
2526
- Layouts.Header,
2527
- {
2528
- title: "Public Memory Store",
2529
- subtitle: `${memories.length} public memories — available to all visitors via the public chat`,
2530
- primaryAction: /* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(Plus, {}), onClick: handleOpenCreate, children: "Add memory" }),
2531
- navigationAction: /* @__PURE__ */ jsx(Button, { variant: "ghost", startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), onClick: () => navigate(`/plugins/${PLUGIN_ID}`), children: "Back to Chat" })
2532
- }
2533
- ),
2534
- /* @__PURE__ */ jsxs(Layouts.Content, { children: [
2535
- /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(SearchForm, { children: /* @__PURE__ */ jsx(
2536
- Searchbar,
2537
- {
2538
- name: "search",
2539
- value: search,
2540
- onChange: (e) => {
2541
- setSearch(e.target.value);
2542
- setPage(1);
2543
- },
2544
- onClear: () => {
2545
- setSearch("");
2546
- setPage(1);
2547
- },
2548
- placeholder: "Search public memories...",
2549
- children: "Search"
2550
- }
2551
- ) }) }),
2552
- /* @__PURE__ */ jsxs(Table, { colCount: 4, rowCount: paginated.length + 1, children: [
2553
- /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
2554
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Content" }) }),
2555
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Category" }) }),
2556
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Created" }) }),
2557
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: "Actions" }) })
2558
- ] }) }),
2559
- /* @__PURE__ */ jsxs(Tbody, { children: [
2560
- loading && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 4, children: /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: "Loading..." }) }) }) }),
2561
- !loading && paginated.length === 0 && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 4, children: /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral500", children: search ? "No public memories match your search" : "No public memories yet — add knowledge for your public chatbot" }) }) }) }),
2562
- paginated.map((mem) => /* @__PURE__ */ jsxs(Tr, { children: [
2563
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", children: mem.content }) }),
2564
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: mem.category }) }),
2565
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatDate(mem.createdAt) }) }),
2566
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { gap: 1, children: [
2567
- /* @__PURE__ */ jsx(
2568
- ActionBtn,
2569
- {
2570
- onClick: () => handleOpenEdit(mem),
2571
- "aria-label": "Edit memory",
2572
- children: /* @__PURE__ */ jsx(Pencil, {})
2573
- }
2574
- ),
2575
- /* @__PURE__ */ jsx(
2576
- DeleteActionBtn,
2577
- {
2578
- onClick: () => removeMemory(mem.documentId),
2579
- "aria-label": "Delete memory",
2580
- children: /* @__PURE__ */ jsx(Trash, {})
2581
- }
2582
- )
2583
- ] }) })
2584
- ] }, mem.documentId))
2585
- ] })
2586
- ] }),
2587
- pageCount > 1 && /* @__PURE__ */ jsx(Box, { paddingTop: 4, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Pagination.Root, { pageCount, activePage: page, onPageChange: setPage, children: /* @__PURE__ */ jsx(Pagination.Links, {}) }) }) }),
2588
- /* @__PURE__ */ jsx(
2589
- PublicMemoryModal,
2590
- {
2591
- memory: editingMemory,
2592
- open: modalOpen,
2593
- onClose: handleCloseModal,
2594
- onSave: handleSave
2595
- }
2596
- )
2597
- ] })
2598
- ] });
2599
- };
2600
- function getStrapiOrigin() {
2601
- return window.location.origin;
2602
- }
2603
- function getEmbedSnippet(origin) {
2604
- return `<script src="${origin}/api/ai-sdk/widget.js"><\/script>`;
2605
- }
2606
- function getIframeHtml(origin) {
2607
- return `<!DOCTYPE html>
2608
- <html lang="en">
2609
- <head>
2610
- <meta charset="UTF-8" />
2611
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2612
- <style>
2613
- body { margin: 0; font-family: sans-serif; background: #f5f5f5; min-height: 100vh; }
2614
- </style>
2615
- </head>
2616
- <body>
2617
- <script src="${origin}/api/ai-sdk/widget.js"><\/script>
2618
- </body>
2619
- </html>`;
2620
- }
2621
- function WidgetPreviewPage() {
2622
- const navigate = useNavigate();
2623
- const [copied, setCopied] = useState(false);
2624
- const origin = getStrapiOrigin();
2625
- const embedSnippet = getEmbedSnippet(origin);
2626
- const handleCopy = async () => {
2627
- await navigator.clipboard.writeText(embedSnippet);
2628
- setCopied(true);
2629
- setTimeout(() => setCopied(false), 2e3);
2630
- };
2631
- return /* @__PURE__ */ jsxs(Main, { children: [
2632
- /* @__PURE__ */ jsx(
2633
- Layouts.Header,
2634
- {
2635
- title: "Widget Preview",
2636
- subtitle: "Preview the embeddable chat widget as visitors will see it",
2637
- navigationAction: /* @__PURE__ */ jsx(Button, { variant: "ghost", startIcon: /* @__PURE__ */ jsx(ArrowLeft, {}), onClick: () => navigate(`/plugins/${PLUGIN_ID}`), children: "Back to Chat" })
2638
- }
2639
- ),
2640
- /* @__PURE__ */ jsxs(Layouts.Content, { children: [
2641
- /* @__PURE__ */ jsxs(Box, { padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
2642
- /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", children: "Live Preview" }),
2643
- /* @__PURE__ */ jsx(
2644
- Box,
2645
- {
2646
- marginTop: 4,
2647
- style: {
2648
- border: "1px solid #dcdce4",
2649
- borderRadius: "4px",
2650
- overflow: "hidden",
2651
- height: "500px"
2652
- },
2653
- children: /* @__PURE__ */ jsx(
2654
- "iframe",
2655
- {
2656
- title: "Widget Preview",
2657
- srcDoc: getIframeHtml(origin),
2658
- style: { width: "100%", height: "100%", border: "none" }
2659
- }
2660
- )
2661
- }
2662
- )
2663
- ] }),
2664
- /* @__PURE__ */ jsxs(Box, { marginTop: 6, padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
2665
- /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", children: "Embed Code" }),
2666
- /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", tag: "p", style: { marginTop: "8px" }, children: "Add this snippet to any HTML page to embed the chat widget:" }),
2667
- /* @__PURE__ */ jsx(
2668
- Box,
2669
- {
2670
- marginTop: 4,
2671
- padding: 4,
2672
- background: "neutral100",
2673
- hasRadius: true,
2674
- style: { fontFamily: "monospace", fontSize: "14px", wordBreak: "break-all" },
2675
- children: embedSnippet
2676
- }
2677
- ),
2678
- /* @__PURE__ */ jsx(Flex, { marginTop: 4, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: handleCopy, children: copied ? "Copied!" : "Copy Snippet" }) })
2679
- ] }),
2680
- /* @__PURE__ */ jsxs(Box, { marginTop: 6, padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
2681
- /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", children: "Production Setup" }),
2682
- /* @__PURE__ */ jsxs(Typography, { variant: "omega", textColor: "neutral600", tag: "p", style: { marginTop: "8px" }, children: [
2683
- "If the widget is embedded on a different domain, update your Strapi project's",
2684
- " ",
2685
- /* @__PURE__ */ jsx("strong", { children: "config/middlewares.ts" }),
2686
- " to allow cross-origin access:"
2687
- ] }),
2688
- /* @__PURE__ */ jsx(
2689
- Box,
2690
- {
2691
- marginTop: 4,
2692
- padding: 4,
2693
- background: "neutral100",
2694
- hasRadius: true,
2695
- style: { fontFamily: "monospace", fontSize: "13px", whiteSpace: "pre", overflowX: "auto", lineHeight: "1.5" },
2696
- children: `// config/middlewares.ts
2697
- {
2698
- name: 'strapi::security',
2699
- config: {
2700
- contentSecurityPolicy: {
2701
- directives: {
2702
- 'script-src': ["'self'", "'unsafe-inline'"],
2703
- 'connect-src': ["'self'", "blob:"],
2704
- 'img-src': ["'self'", "data:", "blob:"],
2705
- 'frame-ancestors': ["'self'", "https://your-frontend.com"],
2706
- },
2707
- },
2708
- },
2709
- },
2710
- {
2711
- name: 'strapi::cors',
2712
- config: {
2713
- origin: ['https://your-frontend.com'],
2714
- },
2715
- },`
2716
- }
2717
- ),
2718
- /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", tag: "p", style: { marginTop: "8px" }, children: [
2719
- "Replace ",
2720
- /* @__PURE__ */ jsx("strong", { children: "https://your-frontend.com" }),
2721
- " with the domain(s) where the widget will be embedded."
2722
- ] })
2723
- ] }),
2724
- /* @__PURE__ */ jsxs(Box, { marginTop: 6, padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
2725
- /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "h2", children: "Development" }),
2726
- /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", tag: "p", style: { marginTop: "8px" }, children: "To live-reload the widget during development, run:" }),
2727
- /* @__PURE__ */ jsx(
2728
- Box,
2729
- {
2730
- marginTop: 4,
2731
- padding: 4,
2732
- background: "neutral100",
2733
- hasRadius: true,
2734
- style: { fontFamily: "monospace", fontSize: "14px" },
2735
- children: "npm run dev:widget"
2736
- }
2737
- ),
2738
- /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", tag: "p", style: { marginTop: "8px" }, children: "This watches the widget source and rebuilds on changes. Refresh the iframe to see updates." })
2739
- ] })
2740
- ] })
2741
- ] });
2742
- }
2743
- const App = () => {
2744
- return /* @__PURE__ */ jsxs(Routes, { children: [
2745
- /* @__PURE__ */ jsx(Route, { index: true, element: /* @__PURE__ */ jsx(HomePage, {}) }),
2746
- /* @__PURE__ */ jsx(Route, { path: "memory-store", element: /* @__PURE__ */ jsx(MemoryStorePage, {}) }),
2747
- /* @__PURE__ */ jsx(Route, { path: "public-memory-store", element: /* @__PURE__ */ jsx(PublicMemoryStorePage, {}) }),
2748
- /* @__PURE__ */ jsx(Route, { path: "widget-preview", element: /* @__PURE__ */ jsx(WidgetPreviewPage, {}) }),
2749
- /* @__PURE__ */ jsx(Route, { path: "*", element: /* @__PURE__ */ jsx(Page.Error, {}) })
2750
- ] });
2751
- };
2752
- export {
2753
- App
2754
- };