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.
- package/dist/_chunks/App-BhTbl60k.mjs +2755 -0
- package/dist/_chunks/App-CdOD0qmW.js +2776 -0
- package/dist/_chunks/{index-DNcK7AKT.mjs → index-5GDOg82N.mjs} +1 -1
- package/dist/_chunks/{index-BCq8Gjnl.js → index-HDnO9h6E.js} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +39 -19
- package/dist/server/index.mjs +39 -19
- package/dist/server/src/config/index.d.ts +7 -0
- package/dist/server/src/index.d.ts +4 -0
- package/dist/server/src/lib/ai-provider.d.ts +1 -1
- package/dist/server/src/lib/types.d.ts +17 -1
- package/dist/server/src/tool-logic/list-content-types.d.ts +1 -0
- package/dist/server/src/tool-logic/search-content.d.ts +1 -1
- package/dist/widget/widget.js +1 -1
- package/package.json +1 -1
- package/dist/_chunks/App-BcZSDAZc.js +0 -2775
- package/dist/_chunks/App-tiKFYaQx.mjs +0 -2754
|
@@ -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
|
-
};
|