strapi-plugin-ai-sdk 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2775 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const jsxRuntime = require("react/jsx-runtime");
4
- const admin = require("@strapi/strapi/admin");
5
- const reactRouterDom = require("react-router-dom");
6
- const designSystem = require("@strapi/design-system");
7
- const react = require("react");
8
- const styled = require("styled-components");
9
- const index = require("./index-BCq8Gjnl.js");
10
- const THREE = require("three");
11
- const OrbitControls_js = require("three/examples/jsm/controls/OrbitControls.js");
12
- const GLTFLoader_js = require("three/examples/jsm/loaders/GLTFLoader.js");
13
- const icons = require("@strapi/icons");
14
- const Markdown = require("react-markdown");
15
- const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
16
- function _interopNamespace(e) {
17
- if (e && e.__esModule) return e;
18
- const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
19
- if (e) {
20
- for (const k in e) {
21
- if (k !== "default") {
22
- const d = Object.getOwnPropertyDescriptor(e, k);
23
- Object.defineProperty(n, k, d.get ? d : {
24
- enumerable: true,
25
- get: () => e[k]
26
- });
27
- }
28
- }
29
- }
30
- n.default = e;
31
- return Object.freeze(n);
32
- }
33
- const styled__default = /* @__PURE__ */ _interopDefault(styled);
34
- const THREE__namespace = /* @__PURE__ */ _interopNamespace(THREE);
35
- const Markdown__default = /* @__PURE__ */ _interopDefault(Markdown);
36
- const COOKIE_REGEX = /(?:^|;\s*)jwtToken=([^;]*)/;
37
- function getCookieValue() {
38
- const match = COOKIE_REGEX.exec(document.cookie);
39
- return match ? decodeURIComponent(match[1]) : void 0;
40
- }
41
- function getToken() {
42
- const raw = localStorage.getItem("jwtToken");
43
- if (raw) {
44
- try {
45
- return JSON.parse(raw);
46
- } catch {
47
- return raw;
48
- }
49
- }
50
- return getCookieValue() ?? null;
51
- }
52
- function getBackendURL() {
53
- return globalThis.strapi?.backendURL ?? "";
54
- }
55
- function parseSSELine(line) {
56
- const trimmed = line.trim();
57
- if (!trimmed.startsWith("data:")) return null;
58
- const payload = trimmed.slice(5).trim();
59
- if (payload === "[DONE]") return null;
60
- try {
61
- const parsed = JSON.parse(payload);
62
- switch (parsed.type) {
63
- case "text-delta":
64
- return { type: "text-delta", delta: parsed.delta };
65
- case "tool-input-available":
66
- return {
67
- type: "tool-input-available",
68
- toolCallId: parsed.toolCallId,
69
- toolName: parsed.toolName,
70
- input: parsed.input
71
- };
72
- case "tool-output-available":
73
- return {
74
- type: "tool-output-available",
75
- toolCallId: parsed.toolCallId,
76
- output: parsed.output
77
- };
78
- default:
79
- return null;
80
- }
81
- } catch {
82
- }
83
- return null;
84
- }
85
- async function readSSEStream(reader, callbacksOrOnDelta) {
86
- const callbacks = typeof callbacksOrOnDelta === "function" ? { onTextDelta: callbacksOrOnDelta } : callbacksOrOnDelta;
87
- const decoder = new TextDecoder();
88
- let buffer = "";
89
- let accumulated = "";
90
- const toolNameMap = /* @__PURE__ */ new Map();
91
- while (true) {
92
- const { done, value } = await reader.read();
93
- if (done) break;
94
- buffer += decoder.decode(value, { stream: true });
95
- const lines = buffer.split("\n");
96
- buffer = lines.pop() ?? "";
97
- for (const line of lines) {
98
- const event = parseSSELine(line);
99
- if (!event) continue;
100
- switch (event.type) {
101
- case "text-delta":
102
- accumulated += event.delta;
103
- callbacks.onTextDelta(accumulated);
104
- break;
105
- case "tool-input-available":
106
- toolNameMap.set(event.toolCallId, event.toolName);
107
- callbacks.onToolInput?.(event.toolCallId, event.toolName, event.input);
108
- break;
109
- case "tool-output-available": {
110
- const resolvedName = toolNameMap.get(event.toolCallId) ?? "";
111
- callbacks.onToolOutput?.(event.toolCallId, resolvedName, event.output);
112
- break;
113
- }
114
- }
115
- }
116
- }
117
- return accumulated;
118
- }
119
- function generateId() {
120
- return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
121
- }
122
- function toUIMessages(messages) {
123
- return messages.map((message) => ({
124
- id: message.id,
125
- role: message.role,
126
- parts: [{ type: "text", text: message.content }]
127
- }));
128
- }
129
- async function fetchChatStream(messages) {
130
- const token = getToken();
131
- const response = await fetch(`${getBackendURL()}/${index.PLUGIN_ID}/chat`, {
132
- method: "POST",
133
- headers: {
134
- "Content-Type": "application/json",
135
- ...token ? { Authorization: `Bearer ${token}` } : {}
136
- },
137
- body: JSON.stringify({ messages: toUIMessages(messages) })
138
- });
139
- if (!response.ok) throw new Error(`Request failed: ${response.status}`);
140
- const reader = response.body?.getReader();
141
- if (!reader) throw new Error("No response stream");
142
- return reader;
143
- }
144
- function updateMessage(setMessages, id, updater) {
145
- setMessages((prev) => prev.map((message) => message.id === id ? updater(message) : message));
146
- }
147
- function removeMessage(setMessages, id) {
148
- setMessages((prev) => prev.filter((message) => message.id !== id));
149
- }
150
- function addToolCall(setMessages, assistantId, toolCallId, toolName, input) {
151
- updateMessage(setMessages, assistantId, (message) => ({
152
- ...message,
153
- toolCalls: [...message.toolCalls ?? [], { toolCallId, toolName, input }]
154
- }));
155
- }
156
- function updateToolOutput(setMessages, assistantId, toolCallId, output) {
157
- updateMessage(setMessages, assistantId, (message) => ({
158
- ...message,
159
- toolCalls: message.toolCalls?.map(
160
- (tc) => tc.toolCallId === toolCallId ? { ...tc, output } : tc
161
- )
162
- }));
163
- }
164
- function useChat(options) {
165
- const [messages, setMessages] = react.useState(options?.initialMessages ?? []);
166
- const [isLoading, setIsLoading] = react.useState(false);
167
- const [error, setError] = react.useState(null);
168
- react.useEffect(() => {
169
- setMessages(options?.initialMessages ?? []);
170
- setError(null);
171
- }, [options?.conversationId]);
172
- const sendMessage = react.useCallback(
173
- async (text) => {
174
- const trimmed = text.trim();
175
- if (!trimmed || isLoading) return;
176
- const userMessage = { id: generateId(), role: "user", content: trimmed };
177
- const assistantId = generateId();
178
- setMessages((prev) => [...prev, userMessage, { id: assistantId, role: "assistant", content: "" }]);
179
- setIsLoading(true);
180
- setError(null);
181
- try {
182
- const reader = await fetchChatStream([...messages, userMessage]);
183
- let streamStarted = false;
184
- const result = await readSSEStream(reader, {
185
- onTextDelta: (content) => {
186
- if (!streamStarted) {
187
- streamStarted = true;
188
- options?.onStreamStart?.();
189
- }
190
- updateMessage(setMessages, assistantId, (message) => ({ ...message, content }));
191
- },
192
- onToolInput: (toolCallId, toolName, input) => {
193
- if (toolName === "triggerAnimation" && input && typeof input === "object" && "animation" in input) {
194
- options?.onAnimationTrigger?.(String(input.animation));
195
- }
196
- addToolCall(setMessages, assistantId, toolCallId, toolName, input);
197
- },
198
- onToolOutput: (toolCallId, _toolName, output) => {
199
- updateToolOutput(setMessages, assistantId, toolCallId, output);
200
- }
201
- });
202
- if (result) {
203
- options?.onStreamEnd?.(result);
204
- }
205
- if (!result) {
206
- updateMessage(setMessages, assistantId, (message) => ({ ...message, content: message.content || "No response received." }));
207
- }
208
- } catch (err) {
209
- setError(err instanceof Error ? err.message : "Something went wrong");
210
- removeMessage(setMessages, assistantId);
211
- } finally {
212
- setIsLoading(false);
213
- }
214
- },
215
- [isLoading, messages, options]
216
- );
217
- const clearMessages = react.useCallback(() => {
218
- setMessages([]);
219
- setError(null);
220
- }, []);
221
- return { messages, setMessages, sendMessage, clearMessages, isLoading, error };
222
- }
223
- const BASE$2 = () => `${getBackendURL()}/${index.PLUGIN_ID}/conversations`;
224
- function headers$2() {
225
- const token = getToken();
226
- return {
227
- "Content-Type": "application/json",
228
- ...token ? { Authorization: `Bearer ${token}` } : {}
229
- };
230
- }
231
- async function request$2(url, init) {
232
- const res = await fetch(url, { headers: headers$2(), ...init });
233
- if (!res.ok) throw new Error(`Request failed: ${res.status}`);
234
- const json = await res.json();
235
- return json.data;
236
- }
237
- function fetchConversations() {
238
- return request$2(BASE$2());
239
- }
240
- function fetchConversation(documentId) {
241
- return request$2(`${BASE$2()}/${documentId}`);
242
- }
243
- function createConversation(data) {
244
- return request$2(BASE$2(), {
245
- method: "POST",
246
- body: JSON.stringify(data)
247
- });
248
- }
249
- function updateConversation(documentId, data) {
250
- return request$2(`${BASE$2()}/${documentId}`, {
251
- method: "PUT",
252
- body: JSON.stringify(data)
253
- });
254
- }
255
- function deleteConversation(documentId) {
256
- return request$2(`${BASE$2()}/${documentId}`, { method: "DELETE" });
257
- }
258
- function useConversations() {
259
- const [conversations, setConversations] = react.useState([]);
260
- const [activeId, setActiveId] = react.useState(null);
261
- const [initialMessages, setInitialMessages] = react.useState([]);
262
- react.useEffect(() => {
263
- fetchConversations().then(async (list) => {
264
- setConversations(list);
265
- if (list.length > 0) {
266
- const most_recent = list[0];
267
- const conversation = await fetchConversation(most_recent.documentId);
268
- setActiveId(most_recent.documentId);
269
- setInitialMessages(conversation.messages || []);
270
- }
271
- }).catch((err) => console.error("Failed to load conversations:", err));
272
- }, []);
273
- const selectConversation = react.useCallback(async (documentId) => {
274
- try {
275
- const conversation = await fetchConversation(documentId);
276
- setActiveId(documentId);
277
- setInitialMessages(conversation.messages || []);
278
- } catch (err) {
279
- console.error("Failed to load conversation:", err);
280
- }
281
- }, []);
282
- const startNewConversation = react.useCallback(() => {
283
- setActiveId(null);
284
- setInitialMessages([]);
285
- }, []);
286
- const saveMessages = react.useCallback(
287
- async (messages) => {
288
- if (messages.length === 0) return;
289
- const firstUserMsg = messages.find((m) => m.role === "user");
290
- const title = firstUserMsg ? firstUserMsg.content.slice(0, 80) + (firstUserMsg.content.length > 80 ? "…" : "") : "New conversation";
291
- try {
292
- if (activeId) {
293
- await updateConversation(activeId, { title, messages });
294
- setConversations(
295
- (prev) => prev.map(
296
- (c) => c.documentId === activeId ? { ...c, title, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : c
297
- )
298
- );
299
- } else {
300
- const created = await createConversation({ title, messages });
301
- setInitialMessages(messages);
302
- setActiveId(created.documentId);
303
- setConversations((prev) => [
304
- {
305
- documentId: created.documentId,
306
- title: created.title,
307
- createdAt: created.createdAt,
308
- updatedAt: created.updatedAt
309
- },
310
- ...prev
311
- ]);
312
- }
313
- } catch (err) {
314
- console.error("Failed to save conversation:", err);
315
- }
316
- },
317
- [activeId]
318
- );
319
- const removeConversation = react.useCallback(
320
- async (documentId) => {
321
- try {
322
- await deleteConversation(documentId);
323
- setConversations((prev) => prev.filter((c) => c.documentId !== documentId));
324
- if (activeId === documentId) {
325
- setActiveId(null);
326
- setInitialMessages([]);
327
- }
328
- } catch (err) {
329
- console.error("Failed to delete conversation:", err);
330
- }
331
- },
332
- [activeId]
333
- );
334
- return {
335
- conversations,
336
- activeId,
337
- initialMessages,
338
- selectConversation,
339
- startNewConversation,
340
- saveMessages,
341
- removeConversation
342
- };
343
- }
344
- function stripMarkdown(text) {
345
- 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();
346
- }
347
- function useAudioPlayer(options) {
348
- const [isPlaying, setIsPlaying] = react.useState(false);
349
- const audioRef = react.useRef(null);
350
- const optionsRef = react.useRef(options);
351
- optionsRef.current = options;
352
- const stop = react.useCallback(() => {
353
- if (audioRef.current) {
354
- audioRef.current.pause();
355
- audioRef.current = null;
356
- }
357
- setIsPlaying(false);
358
- }, []);
359
- const speak2 = react.useCallback(
360
- async (text) => {
361
- stop();
362
- const clean = stripMarkdown(text);
363
- if (clean.length < 3) {
364
- optionsRef.current?.onPlayStart?.(0);
365
- optionsRef.current?.onPlayEnded?.();
366
- return;
367
- }
368
- const token = getToken();
369
- try {
370
- const response = await fetch(`${getBackendURL()}/${index.PLUGIN_ID}/tts`, {
371
- method: "POST",
372
- headers: {
373
- "Content-Type": "application/json",
374
- ...token ? { Authorization: `Bearer ${token}` } : {}
375
- },
376
- body: JSON.stringify({ text: clean })
377
- });
378
- if (!response.ok) {
379
- optionsRef.current?.onPlayStart?.(0);
380
- optionsRef.current?.onPlayEnded?.();
381
- return;
382
- }
383
- const blob = await response.blob();
384
- const url = URL.createObjectURL(blob);
385
- const audio = new Audio(url);
386
- audioRef.current = audio;
387
- const duration = await new Promise((resolve) => {
388
- audio.onloadedmetadata = () => {
389
- resolve(Number.isFinite(audio.duration) ? audio.duration : 0);
390
- };
391
- audio.onerror = () => resolve(0);
392
- });
393
- audio.onplay = () => {
394
- setIsPlaying(true);
395
- optionsRef.current?.onPlayStart?.(duration);
396
- };
397
- audio.onended = () => {
398
- setIsPlaying(false);
399
- audioRef.current = null;
400
- URL.revokeObjectURL(url);
401
- optionsRef.current?.onPlayEnded?.();
402
- };
403
- audio.onerror = () => {
404
- setIsPlaying(false);
405
- audioRef.current = null;
406
- URL.revokeObjectURL(url);
407
- optionsRef.current?.onPlayEnded?.();
408
- };
409
- await audio.play();
410
- } catch {
411
- setIsPlaying(false);
412
- optionsRef.current?.onPlayStart?.(0);
413
- optionsRef.current?.onPlayEnded?.();
414
- }
415
- },
416
- [stop]
417
- );
418
- return { speak: speak2, stop, isPlaying };
419
- }
420
- function useTextReveal() {
421
- const [visibleText, setVisibleText] = react.useState("");
422
- const [isRevealing, setIsRevealing] = react.useState(false);
423
- const rafRef = react.useRef(null);
424
- const fullTextRef = react.useRef("");
425
- const stopReveal = react.useCallback(() => {
426
- if (rafRef.current !== null) {
427
- cancelAnimationFrame(rafRef.current);
428
- rafRef.current = null;
429
- }
430
- if (fullTextRef.current) {
431
- setVisibleText(fullTextRef.current);
432
- }
433
- setIsRevealing(false);
434
- }, []);
435
- const startReveal = react.useCallback(
436
- (text, duration) => {
437
- stopReveal();
438
- fullTextRef.current = text;
439
- if (duration <= 0 || !text) {
440
- setVisibleText(text);
441
- setIsRevealing(false);
442
- return;
443
- }
444
- const totalChars = text.length;
445
- const durationMs = duration * 1e3;
446
- const startTime = performance.now();
447
- setVisibleText("");
448
- setIsRevealing(true);
449
- const tick = (now) => {
450
- const elapsed = now - startTime;
451
- const progress = Math.min(elapsed / durationMs, 1);
452
- const targetChar = Math.floor(progress * totalChars);
453
- let snapTo = targetChar;
454
- if (snapTo < totalChars) {
455
- const nextSpace = text.indexOf(" ", snapTo);
456
- snapTo = nextSpace === -1 ? totalChars : nextSpace;
457
- }
458
- setVisibleText(text.slice(0, snapTo));
459
- if (progress < 1) {
460
- rafRef.current = requestAnimationFrame(tick);
461
- } else {
462
- setVisibleText(text);
463
- setIsRevealing(false);
464
- rafRef.current = null;
465
- }
466
- };
467
- rafRef.current = requestAnimationFrame(tick);
468
- },
469
- [stopReveal]
470
- );
471
- const reset = react.useCallback(() => {
472
- stopReveal();
473
- fullTextRef.current = "";
474
- setVisibleText("");
475
- setIsRevealing(false);
476
- }, [stopReveal]);
477
- return { visibleText, isRevealing, startReveal, stopReveal, reset };
478
- }
479
- const AvatarAnimationContext = react.createContext(null);
480
- function AvatarAnimationProvider({ children }) {
481
- const [currentAnimation, setCurrentAnimation] = react.useState("idle");
482
- const [requestId, setRequestId] = react.useState(0);
483
- const trigger = react.useCallback((animation) => {
484
- setCurrentAnimation(animation);
485
- setRequestId((prev) => prev + 1);
486
- }, []);
487
- const clearAnimation = react.useCallback(() => {
488
- setCurrentAnimation("idle");
489
- setRequestId((prev) => prev + 1);
490
- }, []);
491
- return /* @__PURE__ */ jsxRuntime.jsx(AvatarAnimationContext.Provider, { value: { currentAnimation, requestId, trigger, clearAnimation }, children });
492
- }
493
- function useAvatarAnimation() {
494
- const ctx = react.useContext(AvatarAnimationContext);
495
- if (!ctx) throw new Error("useAvatarAnimation must be used within AvatarAnimationProvider");
496
- return ctx;
497
- }
498
- const BASE$1 = () => `${getBackendURL()}/${index.PLUGIN_ID}/memories`;
499
- function headers$1() {
500
- const token = getToken();
501
- return {
502
- "Content-Type": "application/json",
503
- ...token ? { Authorization: `Bearer ${token}` } : {}
504
- };
505
- }
506
- async function request$1(url, init) {
507
- const res = await fetch(url, { headers: headers$1(), ...init });
508
- if (!res.ok) throw new Error(`Request failed: ${res.status}`);
509
- const json = await res.json();
510
- return json.data;
511
- }
512
- function fetchMemories() {
513
- return request$1(BASE$1());
514
- }
515
- function createMemory(data) {
516
- return request$1(BASE$1(), {
517
- method: "POST",
518
- body: JSON.stringify(data)
519
- });
520
- }
521
- function updateMemory(documentId, data) {
522
- return request$1(`${BASE$1()}/${documentId}`, {
523
- method: "PUT",
524
- body: JSON.stringify(data)
525
- });
526
- }
527
- function deleteMemory(documentId) {
528
- return request$1(`${BASE$1()}/${documentId}`, { method: "DELETE" });
529
- }
530
- function useMemories() {
531
- const [memories, setMemories] = react.useState([]);
532
- const [loading, setLoading] = react.useState(true);
533
- const load = react.useCallback(async () => {
534
- try {
535
- const data = await fetchMemories();
536
- setMemories(data);
537
- } catch (err) {
538
- console.error("Failed to load memories:", err);
539
- } finally {
540
- setLoading(false);
541
- }
542
- }, []);
543
- react.useEffect(() => {
544
- load();
545
- }, [load]);
546
- const addMemory = react.useCallback(async (data) => {
547
- try {
548
- const created = await createMemory(data);
549
- setMemories((prev) => [created, ...prev]);
550
- } catch (err) {
551
- console.error("Failed to create memory:", err);
552
- }
553
- }, []);
554
- const editMemory = react.useCallback(async (documentId, data) => {
555
- try {
556
- const updated = await updateMemory(documentId, data);
557
- setMemories(
558
- (prev) => prev.map((m) => m.documentId === documentId ? { ...m, ...updated } : m)
559
- );
560
- } catch (err) {
561
- console.error("Failed to update memory:", err);
562
- }
563
- }, []);
564
- const removeMemory = react.useCallback(async (documentId) => {
565
- try {
566
- await deleteMemory(documentId);
567
- setMemories((prev) => prev.filter((m) => m.documentId !== documentId));
568
- } catch (err) {
569
- console.error("Failed to delete memory:", err);
570
- }
571
- }, []);
572
- return { memories, loading, addMemory, editMemory, removeMemory, refresh: load };
573
- }
574
- function captureRestPose(refs) {
575
- return {
576
- rootY: refs.root.position.y,
577
- hips: refs.hips.quaternion.clone(),
578
- head: refs.head.quaternion.clone(),
579
- leftArm: refs.leftArm.quaternion.clone(),
580
- rightArm: refs.rightArm.quaternion.clone()
581
- };
582
- }
583
- const _euler = new THREE__namespace.Euler();
584
- const _quat = new THREE__namespace.Quaternion();
585
- function applyAdditiveRotation(target, restQ, x, y, z) {
586
- _euler.set(x, y, z);
587
- _quat.setFromEuler(_euler);
588
- target.quaternion.copy(restQ).multiply(_quat);
589
- }
590
- const idle = (refs, rest) => {
591
- let elapsed = 0;
592
- return {
593
- update(delta) {
594
- elapsed += delta;
595
- refs.root.position.y = rest.rootY + Math.sin(elapsed * 1.2) * 0.012;
596
- applyAdditiveRotation(
597
- refs.head,
598
- rest.head,
599
- Math.sin(elapsed * 0.5) * 0.06,
600
- Math.sin(elapsed * 0.3) * 0.05,
601
- Math.sin(elapsed * 0.7) * 0.05
602
- );
603
- applyAdditiveRotation(
604
- refs.leftArm,
605
- rest.leftArm,
606
- Math.sin(elapsed * 0.3) * 0.015,
607
- 0,
608
- Math.sin(elapsed * 0.4) * 0.01
609
- );
610
- applyAdditiveRotation(
611
- refs.rightArm,
612
- rest.rightArm,
613
- Math.sin(elapsed * 0.35) * 0.015,
614
- 0,
615
- Math.sin(elapsed * 0.35) * -0.01
616
- );
617
- return false;
618
- },
619
- reset() {
620
- elapsed = 0;
621
- refs.root.position.y = rest.rootY;
622
- refs.head.quaternion.copy(rest.head);
623
- refs.leftArm.quaternion.copy(rest.leftArm);
624
- refs.rightArm.quaternion.copy(rest.rightArm);
625
- }
626
- };
627
- };
628
- const speak = (refs, rest) => {
629
- let elapsed = 0;
630
- return {
631
- update(delta) {
632
- elapsed += delta;
633
- const env = Math.min(elapsed / 0.5, 1);
634
- const nod2 = Math.sin(elapsed * 3.5) * 0.12 * env;
635
- const turn = Math.sin(elapsed * 1.8) * 0.1 * env;
636
- const tilt = Math.sin(elapsed * 2.3) * 0.06 * env;
637
- applyAdditiveRotation(refs.head, rest.head, nod2, turn, tilt);
638
- const rz = Math.sin(elapsed * 0.8) * 0.06 * env;
639
- const rx = Math.sin(elapsed * 0.6) * 0.04 * env;
640
- applyAdditiveRotation(refs.rightArm, rest.rightArm, rx, 0, rz);
641
- const lz = Math.sin(elapsed * 0.7 + 1) * 0.04 * env;
642
- const lx = Math.sin(elapsed * 0.5 + 0.5) * 0.03 * env;
643
- applyAdditiveRotation(refs.leftArm, rest.leftArm, lx, 0, -lz);
644
- refs.root.position.y = rest.rootY + Math.sin(elapsed * 2.5) * 8e-3 * env;
645
- return false;
646
- },
647
- reset() {
648
- elapsed = 0;
649
- refs.root.position.y = rest.rootY;
650
- refs.head.quaternion.copy(rest.head);
651
- refs.leftArm.quaternion.copy(rest.leftArm);
652
- refs.rightArm.quaternion.copy(rest.rightArm);
653
- }
654
- };
655
- };
656
- const wave = (refs, rest) => {
657
- let elapsed = 0;
658
- const duration = 2.5;
659
- return {
660
- update(delta) {
661
- elapsed += delta;
662
- const t = Math.min(elapsed / duration, 1);
663
- let rx = 0;
664
- let ry = 0;
665
- let rz = 0;
666
- if (t < 0.2) {
667
- const f = t / 0.2;
668
- rx = -f * 1.4;
669
- rz = f * 1.3;
670
- } else if (t < 0.8) {
671
- const w = (t - 0.2) / 0.6;
672
- rx = -1.4;
673
- rz = 1.3;
674
- ry = Math.sin(w * Math.PI * 6) * 0.5;
675
- } else {
676
- const f = 1 - (t - 0.8) / 0.2;
677
- rx = -1.4 * f;
678
- rz = 1.3 * f;
679
- }
680
- applyAdditiveRotation(refs.rightArm, rest.rightArm, rx, ry, rz);
681
- const headTilt = t < 0.8 ? Math.sin(t * Math.PI * 4) * 0.08 : 0;
682
- applyAdditiveRotation(refs.head, rest.head, 0, 0, headTilt);
683
- return elapsed >= duration;
684
- },
685
- reset() {
686
- elapsed = 0;
687
- refs.rightArm.quaternion.copy(rest.rightArm);
688
- refs.head.quaternion.copy(rest.head);
689
- }
690
- };
691
- };
692
- const nod = (refs, rest) => {
693
- let elapsed = 0;
694
- const duration = 2;
695
- return {
696
- update(delta) {
697
- elapsed += delta;
698
- const t = Math.min(elapsed / duration, 1);
699
- const env = t > 0.85 ? (1 - t) / 0.15 : 1;
700
- applyAdditiveRotation(
701
- refs.head,
702
- rest.head,
703
- Math.sin(t * Math.PI * 6) * 0.18 * env,
704
- 0,
705
- 0
706
- );
707
- return elapsed >= duration;
708
- },
709
- reset() {
710
- elapsed = 0;
711
- refs.head.quaternion.copy(rest.head);
712
- }
713
- };
714
- };
715
- const think = (refs, rest) => {
716
- let elapsed = 0;
717
- const duration = 3.5;
718
- return {
719
- update(delta) {
720
- elapsed += delta;
721
- const t = Math.min(elapsed / duration, 1);
722
- let hx = 0, hz = 0, ax = 0, az = 0;
723
- if (t < 0.2) {
724
- const f = t / 0.2;
725
- hz = f * 0.2;
726
- hx = f * 0.12;
727
- } else if (t > 0.8) {
728
- const f = 1 - (t - 0.8) / 0.2;
729
- hz = 0.2 * f;
730
- hx = 0.12 * f;
731
- } else {
732
- hz = 0.2 + Math.sin(elapsed * 0.8) * 0.03;
733
- hx = 0.12;
734
- }
735
- if (t < 0.2) {
736
- const f = t / 0.2;
737
- az = -f * 0.6;
738
- ax = f * 0.35;
739
- } else if (t < 0.8) {
740
- const hold = (t - 0.2) / 0.6;
741
- az = -0.6 + Math.sin(hold * Math.PI * 2) * 0.06;
742
- ax = 0.35;
743
- } else {
744
- const f = 1 - (t - 0.8) / 0.2;
745
- az = -0.6 * f;
746
- ax = 0.35 * f;
747
- }
748
- applyAdditiveRotation(refs.head, rest.head, hx, 0, hz);
749
- applyAdditiveRotation(refs.rightArm, rest.rightArm, ax, 0, az);
750
- return elapsed >= duration;
751
- },
752
- reset() {
753
- elapsed = 0;
754
- refs.head.quaternion.copy(rest.head);
755
- refs.rightArm.quaternion.copy(rest.rightArm);
756
- }
757
- };
758
- };
759
- const celebrate = (refs, rest) => {
760
- let elapsed = 0;
761
- const duration = 3;
762
- return {
763
- update(delta) {
764
- elapsed += delta;
765
- const t = Math.min(elapsed / duration, 1);
766
- const env = t > 0.8 ? (1 - t) / 0.2 : 1;
767
- const bounce = Math.abs(Math.sin(t * Math.PI * 6)) * 0.05 * env;
768
- refs.root.position.y = rest.rootY + bounce;
769
- let lz = 0, rz = 0;
770
- if (t < 0.15) {
771
- const f = t / 0.15;
772
- lz = f * 1.2;
773
- rz = -f * 1.2;
774
- } else if (t < 0.8) {
775
- const w = (t - 0.15) / 0.65;
776
- lz = 1.2 + Math.sin(w * Math.PI * 8) * 0.25;
777
- rz = -1.2 - Math.sin(w * Math.PI * 8) * 0.25;
778
- } else {
779
- const f = 1 - (t - 0.8) / 0.2;
780
- lz = 1.2 * f;
781
- rz = -1.2 * f;
782
- }
783
- applyAdditiveRotation(refs.leftArm, rest.leftArm, 0, 0, lz);
784
- applyAdditiveRotation(refs.rightArm, rest.rightArm, 0, 0, rz);
785
- applyAdditiveRotation(
786
- refs.head,
787
- rest.head,
788
- Math.sin(elapsed * 4) * 0.1 * env,
789
- 0,
790
- Math.sin(elapsed * 3) * 0.06 * env
791
- );
792
- return elapsed >= duration;
793
- },
794
- reset() {
795
- elapsed = 0;
796
- refs.root.position.y = rest.rootY;
797
- refs.leftArm.quaternion.copy(rest.leftArm);
798
- refs.rightArm.quaternion.copy(rest.rightArm);
799
- refs.head.quaternion.copy(rest.head);
800
- }
801
- };
802
- };
803
- const shake = (refs, rest) => {
804
- let elapsed = 0;
805
- const duration = 1.5;
806
- return {
807
- update(delta) {
808
- elapsed += delta;
809
- const t = Math.min(elapsed / duration, 1);
810
- const env = t > 0.8 ? (1 - t) / 0.2 : 1;
811
- applyAdditiveRotation(
812
- refs.head,
813
- rest.head,
814
- 0,
815
- Math.sin(t * Math.PI * 6) * 0.3 * env,
816
- 0
817
- );
818
- return elapsed >= duration;
819
- },
820
- reset() {
821
- elapsed = 0;
822
- refs.head.quaternion.copy(rest.head);
823
- }
824
- };
825
- };
826
- const spin = (refs, rest) => {
827
- let elapsed = 0;
828
- const duration = 2;
829
- return {
830
- update(delta) {
831
- elapsed += delta;
832
- const t = Math.min(elapsed / duration, 1);
833
- const ease = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
834
- _euler.set(0, ease * Math.PI * 2, 0);
835
- _quat.setFromEuler(_euler);
836
- refs.hips.quaternion.copy(rest.hips).multiply(_quat);
837
- refs.root.position.y = rest.rootY + Math.sin(t * Math.PI) * 0.03;
838
- return elapsed >= duration;
839
- },
840
- reset() {
841
- elapsed = 0;
842
- refs.hips.quaternion.copy(rest.hips);
843
- refs.root.position.y = rest.rootY;
844
- }
845
- };
846
- };
847
- const animationRegistry = {
848
- idle,
849
- speak,
850
- wave,
851
- nod,
852
- think,
853
- celebrate,
854
- shake,
855
- spin
856
- };
857
- const SKIN = 16769228;
858
- const SKIN_SHADOW = 15780016;
859
- const HAIR_MAIN = 5978764;
860
- const HAIR_HIGHLIGHT = 8278709;
861
- const SHIRT = 4802047;
862
- const SHIRT_ACCENT = 7105023;
863
- const EYE_IRIS = 4491519;
864
- const EYE_IRIS_INNER = 6728447;
865
- const EYE_PUPIL = 1710638;
866
- const EYE_WHITE = 16777215;
867
- const EYE_HIGHLIGHT = 16777215;
868
- const CHEEK = 16751001;
869
- const MOUTH = 14708848;
870
- const EYEBROW = 3809894;
871
- function toon(color) {
872
- const gradientMap = new THREE__namespace.DataTexture(
873
- new Uint8Array([80, 160, 255]),
874
- 3,
875
- 1,
876
- THREE__namespace.RedFormat
877
- );
878
- gradientMap.needsUpdate = true;
879
- return new THREE__namespace.MeshToonMaterial({ color, gradientMap });
880
- }
881
- function basic(color, opts) {
882
- return new THREE__namespace.MeshBasicMaterial({ color, ...opts });
883
- }
884
- function phong(color, opts) {
885
- return new THREE__namespace.MeshPhongMaterial({ color, shininess: 20, ...opts });
886
- }
887
- function sphere(r, w = 16, h = 16) {
888
- return new THREE__namespace.SphereGeometry(r, w, h);
889
- }
890
- function add(parent, geo, mat, pos, scale, rot) {
891
- const m = new THREE__namespace.Mesh(geo, mat);
892
- if (pos) m.position.set(...pos);
893
- if (scale) m.scale.set(...scale);
894
- if (rot) m.rotation.set(...rot);
895
- parent.add(m);
896
- return m;
897
- }
898
- function buildEye(parent, x) {
899
- const eyeGroup = new THREE__namespace.Group();
900
- eyeGroup.position.set(x, 0.04, 0.26);
901
- parent.add(eyeGroup);
902
- add(eyeGroup, sphere(0.065, 16, 16), basic(EYE_WHITE), void 0, [0.8, 1, 0.5]);
903
- add(eyeGroup, sphere(0.048, 16, 16), phong(EYE_IRIS, { shininess: 60 }), [0, -5e-3, 0.025], [0.85, 1, 0.6]);
904
- add(eyeGroup, sphere(0.032, 12, 12), phong(EYE_IRIS_INNER, { shininess: 80 }), [0, 5e-3, 0.035], [0.85, 0.9, 0.5]);
905
- add(eyeGroup, sphere(0.02, 10, 10), basic(EYE_PUPIL), [0, -5e-3, 0.04], [0.8, 1, 0.5]);
906
- add(eyeGroup, sphere(0.015, 8, 8), basic(EYE_HIGHLIGHT), [0.015, 0.02, 0.05]);
907
- add(eyeGroup, sphere(8e-3, 6, 6), basic(EYE_HIGHLIGHT), [-0.01, -0.015, 0.05]);
908
- add(
909
- eyeGroup,
910
- new THREE__namespace.TorusGeometry(0.055, 8e-3, 8, 16, Math.PI),
911
- basic(EYEBROW),
912
- [0, 0.04, 0.02],
913
- void 0,
914
- [Math.PI, 0, 0]
915
- );
916
- return eyeGroup;
917
- }
918
- function buildPlaceholderModel() {
919
- const root = new THREE__namespace.Group();
920
- add(root, new THREE__namespace.CylinderGeometry(0.28, 0.24, 0.5, 16), toon(SHIRT), [0, -0.5, 0]);
921
- add(root, new THREE__namespace.CylinderGeometry(0.14, 0.28, 0.12, 16), toon(SHIRT_ACCENT), [0, -0.22, 0]);
922
- add(root, sphere(0.1, 12, 12), toon(SHIRT), [-0.28, -0.28, 0]);
923
- add(root, sphere(0.1, 12, 12), toon(SHIRT), [0.28, -0.28, 0]);
924
- add(root, new THREE__namespace.CylinderGeometry(0.07, 0.09, 0.12, 8), toon(SKIN), [0, -0.1, 0]);
925
- const head = new THREE__namespace.Group();
926
- head.position.set(0, 0.28, 0);
927
- root.add(head);
928
- add(head, sphere(0.34, 32, 32), toon(SKIN), void 0, [1, 1.05, 0.95]);
929
- add(
930
- head,
931
- sphere(0.2, 16, 16),
932
- phong(SKIN_SHADOW, { transparent: true, opacity: 0.3 }),
933
- [0, -0.18, 0.12],
934
- [1.4, 0.5, 1]
935
- );
936
- buildEye(head, -0.11);
937
- buildEye(head, 0.11);
938
- add(
939
- head,
940
- new THREE__namespace.CapsuleGeometry(0.012, 0.06, 4, 8),
941
- basic(EYEBROW),
942
- [-0.11, 0.12, 0.28],
943
- void 0,
944
- [0, 0, 0.15]
945
- );
946
- add(
947
- head,
948
- new THREE__namespace.CapsuleGeometry(0.012, 0.06, 4, 8),
949
- basic(EYEBROW),
950
- [0.11, 0.12, 0.28],
951
- void 0,
952
- [0, 0, -0.15]
953
- );
954
- add(head, sphere(0.012, 6, 6), phong(SKIN_SHADOW), [0, -0.04, 0.32]);
955
- add(
956
- head,
957
- new THREE__namespace.TorusGeometry(0.025, 6e-3, 8, 12, Math.PI),
958
- basic(MOUTH),
959
- [0, -0.1, 0.3],
960
- void 0,
961
- [0.15, 0, 0]
962
- );
963
- add(
964
- head,
965
- sphere(0.04, 8, 8),
966
- basic(CHEEK, { transparent: true, opacity: 0.35 }),
967
- [-0.19, -0.04, 0.22],
968
- [1.3, 0.7, 0.5]
969
- );
970
- add(
971
- head,
972
- sphere(0.04, 8, 8),
973
- basic(CHEEK, { transparent: true, opacity: 0.35 }),
974
- [0.19, -0.04, 0.22],
975
- [1.3, 0.7, 0.5]
976
- );
977
- const hairMat = toon(HAIR_MAIN);
978
- const hairHiMat = toon(HAIR_HIGHLIGHT);
979
- add(head, sphere(0.36, 24, 24), hairMat, [0, 0.08, -0.06], [1.05, 1, 1]);
980
- add(head, sphere(0.28, 20, 20), hairHiMat, [0, 0.22, 0], [1.1, 0.7, 0.95]);
981
- add(head, sphere(0.1, 12, 12), hairMat, [-0.18, 0.2, 0.2], [0.9, 0.7, 0.7]);
982
- add(head, sphere(0.12, 12, 12), hairHiMat, [-0.06, 0.22, 0.22], [0.8, 0.65, 0.7]);
983
- add(head, sphere(0.11, 12, 12), hairMat, [0.08, 0.23, 0.2], [0.85, 0.6, 0.7]);
984
- add(head, sphere(0.09, 12, 12), hairHiMat, [0.19, 0.19, 0.18], [0.8, 0.65, 0.65]);
985
- add(head, sphere(0.09, 12, 12), hairMat, [-0.3, 0, 0.05], [0.5, 1.4, 0.6]);
986
- add(head, sphere(0.08, 12, 12), hairHiMat, [-0.28, -0.15, 0.08], [0.45, 1, 0.55]);
987
- add(head, sphere(0.09, 12, 12), hairMat, [0.3, 0, 0.05], [0.5, 1.4, 0.6]);
988
- add(head, sphere(0.08, 12, 12), hairHiMat, [0.28, -0.15, 0.08], [0.45, 1, 0.55]);
989
- const ahoge = new THREE__namespace.Group();
990
- ahoge.position.set(0.02, 0.38, 0.1);
991
- ahoge.rotation.set(-0.3, 0, 0.2);
992
- head.add(ahoge);
993
- add(ahoge, new THREE__namespace.ConeGeometry(0.02, 0.15, 6), hairHiMat, [0, 0.07, 0]);
994
- const leftArm = new THREE__namespace.Group();
995
- leftArm.position.set(-0.35, -0.3, 0);
996
- root.add(leftArm);
997
- add(leftArm, new THREE__namespace.CapsuleGeometry(0.06, 0.18, 8, 8), toon(SHIRT), [0, -0.12, 0]);
998
- add(leftArm, sphere(0.055, 10, 10), toon(SKIN), [0, -0.28, 0]);
999
- const rightArm = new THREE__namespace.Group();
1000
- rightArm.position.set(0.35, -0.3, 0);
1001
- root.add(rightArm);
1002
- add(rightArm, new THREE__namespace.CapsuleGeometry(0.06, 0.18, 8, 8), toon(SHIRT), [0, -0.12, 0]);
1003
- add(rightArm, sphere(0.055, 10, 10), toon(SKIN), [0, -0.28, 0]);
1004
- return { scene: root, refs: { root, hips: root, head, leftArm, rightArm } };
1005
- }
1006
- const MODEL_PATH = "/models/avatar.glb";
1007
- function collectSkeletonBones(root) {
1008
- const bones = [];
1009
- const seen = /* @__PURE__ */ new Set();
1010
- root.traverse((child) => {
1011
- if (child.isSkinnedMesh) {
1012
- const skeleton = child.skeleton;
1013
- if (skeleton) {
1014
- for (const bone of skeleton.bones) {
1015
- if (!seen.has(bone.id)) {
1016
- seen.add(bone.id);
1017
- bones.push(bone);
1018
- }
1019
- }
1020
- }
1021
- }
1022
- });
1023
- return bones;
1024
- }
1025
- function findBone(bones, names) {
1026
- for (const bone of bones) {
1027
- if (names.includes(bone.name)) return bone;
1028
- }
1029
- for (const bone of bones) {
1030
- for (const name of names) {
1031
- if (bone.name.startsWith(name)) return bone;
1032
- }
1033
- }
1034
- const lower = names.map((n) => n.toLowerCase());
1035
- for (const bone of bones) {
1036
- const boneLower = bone.name.toLowerCase();
1037
- for (const name of lower) {
1038
- if (boneLower.startsWith(name)) return bone;
1039
- }
1040
- }
1041
- return null;
1042
- }
1043
- function extractRefsFromGLTF(model) {
1044
- const bones = collectSkeletonBones(model);
1045
- const hips = findBone(bones, [
1046
- "Hips",
1047
- "hips",
1048
- "J_Bip_C_Hips",
1049
- "mixamorigHips",
1050
- "Pelvis",
1051
- "pelvis",
1052
- "Root",
1053
- "root"
1054
- ]);
1055
- const head = findBone(bones, [
1056
- "Head",
1057
- "head",
1058
- "J_Bip_C_Head",
1059
- "mixamorigHead"
1060
- ]);
1061
- const leftArm = findBone(bones, [
1062
- "Left arm",
1063
- "Left_arm",
1064
- "LeftArm",
1065
- "leftArm",
1066
- "J_Bip_L_UpperArm",
1067
- "mixamorigLeftArm",
1068
- "arm_L",
1069
- "Arm.L",
1070
- "Left shoulder",
1071
- "Left_shoulder"
1072
- ]);
1073
- const rightArm = findBone(bones, [
1074
- "Right arm",
1075
- "Right_arm",
1076
- "RightArm",
1077
- "rightArm",
1078
- "J_Bip_R_UpperArm",
1079
- "mixamorigRightArm",
1080
- "arm_R",
1081
- "Arm.R",
1082
- "Right shoulder",
1083
- "Right_shoulder"
1084
- ]);
1085
- return {
1086
- root: model,
1087
- hips: hips ?? model,
1088
- head: head ?? model,
1089
- leftArm: leftArm ?? model,
1090
- rightArm: rightArm ?? model
1091
- };
1092
- }
1093
- function Avatar3D() {
1094
- const containerRef = react.useRef(null);
1095
- const { currentAnimation, requestId, clearAnimation } = useAvatarAnimation();
1096
- const stateRef = react.useRef(null);
1097
- react.useEffect(() => {
1098
- const el = containerRef.current;
1099
- if (!el) return;
1100
- const width = el.clientWidth;
1101
- const height = el.clientHeight;
1102
- const renderer = new THREE__namespace.WebGLRenderer({ antialias: true });
1103
- renderer.setSize(width, height);
1104
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
1105
- renderer.toneMapping = THREE__namespace.NoToneMapping;
1106
- renderer.outputColorSpace = THREE__namespace.SRGBColorSpace;
1107
- el.appendChild(renderer.domElement);
1108
- const scene = new THREE__namespace.Scene();
1109
- scene.background = new THREE__namespace.Color(15263472);
1110
- const camera = new THREE__namespace.PerspectiveCamera(30, width / height, 0.1, 100);
1111
- camera.position.set(0, 0.85, -1.8);
1112
- scene.add(new THREE__namespace.AmbientLight(16777215, 0.7));
1113
- const keyLight = new THREE__namespace.DirectionalLight(16777215, 0.9);
1114
- keyLight.position.set(1, 2, 3);
1115
- scene.add(keyLight);
1116
- const fillLight = new THREE__namespace.DirectionalLight(14544639, 0.3);
1117
- fillLight.position.set(-2, 1, -1);
1118
- scene.add(fillLight);
1119
- const rimLight = new THREE__namespace.DirectionalLight(11193599, 0.4);
1120
- rimLight.position.set(0, 1, -3);
1121
- scene.add(rimLight);
1122
- const controls = new OrbitControls_js.OrbitControls(camera, renderer.domElement);
1123
- controls.target.set(0, 0.8, 0);
1124
- controls.enablePan = false;
1125
- controls.enableZoom = true;
1126
- controls.minDistance = 1;
1127
- controls.maxDistance = 3.5;
1128
- controls.minPolarAngle = Math.PI / 3;
1129
- controls.maxPolarAngle = Math.PI / 1.8;
1130
- controls.minAzimuthAngle = -Math.PI / 6;
1131
- controls.maxAzimuthAngle = Math.PI / 6;
1132
- const clock = new THREE__namespace.Clock();
1133
- function setupAnimations(refs) {
1134
- const rest = captureRestPose(refs);
1135
- const idleClip = animationRegistry.idle(refs, rest);
1136
- const state = {
1137
- renderer,
1138
- scene,
1139
- camera,
1140
- controls,
1141
- refs,
1142
- rest,
1143
- idleClip,
1144
- activeClip: null,
1145
- lastRequestId: -1,
1146
- animationFrameId: 0,
1147
- clock
1148
- };
1149
- stateRef.current = state;
1150
- function animate() {
1151
- state.animationFrameId = requestAnimationFrame(animate);
1152
- const delta = Math.min(state.clock.getDelta(), 0.1);
1153
- state.idleClip.update(delta);
1154
- if (state.activeClip) {
1155
- const done = state.activeClip.update(delta);
1156
- if (done) {
1157
- state.activeClip.reset();
1158
- state.activeClip = null;
1159
- }
1160
- }
1161
- state.controls.update();
1162
- renderer.render(scene, camera);
1163
- }
1164
- animate();
1165
- }
1166
- {
1167
- const savedCreateImageBitmap = window.createImageBitmap;
1168
- window.createImageBitmap = void 0;
1169
- const loader = new GLTFLoader_js.GLTFLoader();
1170
- loader.load(
1171
- MODEL_PATH,
1172
- (gltf) => {
1173
- const model = gltf.scene;
1174
- const box = new THREE__namespace.Box3().setFromObject(model);
1175
- const size = box.getSize(new THREE__namespace.Vector3());
1176
- const maxDim = Math.max(size.x, size.y, size.z);
1177
- const scale = 1.2 / maxDim;
1178
- model.scale.setScalar(scale);
1179
- model.rotation.y = 0.45;
1180
- const scaledBox = new THREE__namespace.Box3().setFromObject(model);
1181
- const center = scaledBox.getCenter(new THREE__namespace.Vector3());
1182
- model.position.x -= center.x;
1183
- model.position.z -= center.z;
1184
- window.createImageBitmap = savedCreateImageBitmap;
1185
- const wrapper = new THREE__namespace.Group();
1186
- wrapper.add(model);
1187
- scene.add(wrapper);
1188
- const finalBox = new THREE__namespace.Box3().setFromObject(wrapper);
1189
- const min = finalBox.min;
1190
- const max = finalBox.max;
1191
- const modelHeight = max.y - min.y;
1192
- const faceY = min.y + modelHeight * 0.78;
1193
- camera.position.set(0, faceY, -modelHeight * 0.9);
1194
- controls.target.set(0, faceY, 0);
1195
- controls.update();
1196
- const refs = extractRefsFromGLTF(model);
1197
- refs.root = wrapper;
1198
- refs.hips = wrapper;
1199
- setupAnimations(refs);
1200
- },
1201
- void 0,
1202
- () => {
1203
- window.createImageBitmap = savedCreateImageBitmap;
1204
- 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");
1205
- const { scene: model, refs } = buildPlaceholderModel();
1206
- scene.add(model);
1207
- setupAnimations(refs);
1208
- }
1209
- );
1210
- }
1211
- const observer = new ResizeObserver(() => {
1212
- const w = el.clientWidth;
1213
- const h = el.clientHeight;
1214
- camera.aspect = w / h;
1215
- camera.updateProjectionMatrix();
1216
- renderer.setSize(w, h);
1217
- });
1218
- observer.observe(el);
1219
- return () => {
1220
- observer.disconnect();
1221
- const s = stateRef.current;
1222
- if (s) cancelAnimationFrame(s.animationFrameId);
1223
- controls.dispose();
1224
- renderer.dispose();
1225
- if (el.contains(renderer.domElement)) el.removeChild(renderer.domElement);
1226
- stateRef.current = null;
1227
- };
1228
- }, []);
1229
- react.useEffect(() => {
1230
- const state = stateRef.current;
1231
- if (!state || requestId === state.lastRequestId) return;
1232
- state.lastRequestId = requestId;
1233
- if (state.activeClip) {
1234
- state.activeClip.reset();
1235
- state.activeClip = null;
1236
- }
1237
- if (currentAnimation !== "idle") {
1238
- const factory = animationRegistry[currentAnimation];
1239
- if (factory) {
1240
- state.activeClip = factory(state.refs, state.rest);
1241
- }
1242
- }
1243
- }, [currentAnimation, requestId]);
1244
- react.useEffect(() => {
1245
- if (currentAnimation === "idle") return;
1246
- const interval = setInterval(() => {
1247
- const state = stateRef.current;
1248
- if (state && state.activeClip === null) clearAnimation();
1249
- }, 100);
1250
- return () => clearInterval(interval);
1251
- }, [currentAnimation, clearAnimation]);
1252
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, style: { width: "100%", height: "100%" } });
1253
- }
1254
- const Panel = styled__default.default.div`
1255
- width: 280px;
1256
- min-width: 280px;
1257
- border-right: 1px solid #eaeaef;
1258
- background: linear-gradient(180deg, #f0f0ff 0%, #f6f6f9 100%);
1259
- display: flex;
1260
- flex-direction: column;
1261
- align-items: center;
1262
- padding-top: 24px;
1263
- gap: 12px;
1264
- `;
1265
- const AvatarContainer = styled__default.default.div`
1266
- width: 250px;
1267
- height: 300px;
1268
- border-radius: 16px;
1269
- overflow: hidden;
1270
- background: linear-gradient(180deg, #e8e8ff 0%, #f0f0f8 100%);
1271
- box-shadow: 0 2px 8px rgba(73, 69, 255, 0.1);
1272
- `;
1273
- const Label = styled__default.default.div`
1274
- font-size: 13px;
1275
- font-weight: 600;
1276
- color: #666687;
1277
- letter-spacing: 0.5px;
1278
- text-transform: uppercase;
1279
- `;
1280
- function AvatarPanel() {
1281
- return /* @__PURE__ */ jsxRuntime.jsxs(Panel, { children: [
1282
- /* @__PURE__ */ jsxRuntime.jsx(AvatarContainer, { children: /* @__PURE__ */ jsxRuntime.jsx(Avatar3D, {}) }),
1283
- /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "AI Assistant" })
1284
- ] });
1285
- }
1286
- const SidebarRoot = styled__default.default.div`
1287
- width: ${({ $open }) => $open ? "260px" : "0px"};
1288
- min-width: ${({ $open }) => $open ? "260px" : "0px"};
1289
- display: flex;
1290
- flex-direction: column;
1291
- border-right: ${({ $open, theme }) => $open ? `1px solid ${theme.colors.neutral200}` : "none"};
1292
- background: ${({ theme }) => theme.colors.neutral100};
1293
- overflow: hidden;
1294
- transition: width 0.2s ease, min-width 0.2s ease;
1295
- `;
1296
- const NewChatButton = styled__default.default.button`
1297
- display: flex;
1298
- align-items: center;
1299
- gap: 8px;
1300
- width: 100%;
1301
- padding: 8px 12px;
1302
- border: 1px solid ${({ theme }) => theme.colors.neutral200};
1303
- border-radius: 4px;
1304
- background: ${({ theme }) => theme.colors.neutral0};
1305
- color: ${({ theme }) => theme.colors.neutral800};
1306
- font-size: 14px;
1307
- font-weight: 500;
1308
- cursor: pointer;
1309
-
1310
- &:hover {
1311
- background: ${({ theme }) => theme.colors.neutral100};
1312
- }
1313
-
1314
- svg {
1315
- width: 16px;
1316
- height: 16px;
1317
- }
1318
- `;
1319
- const ConversationList = styled__default.default.div`
1320
- flex: 1;
1321
- overflow-y: auto;
1322
- `;
1323
- const ConversationItem = styled__default.default.button`
1324
- width: 100%;
1325
- display: flex;
1326
- align-items: center;
1327
- justify-content: space-between;
1328
- padding: 10px 12px;
1329
- border: none;
1330
- background: ${({ $active, theme }) => $active ? theme.colors.neutral200 : "transparent"};
1331
- cursor: pointer;
1332
- text-align: left;
1333
-
1334
- &:hover {
1335
- background: ${({ theme }) => theme.colors.neutral200};
1336
- }
1337
-
1338
- &:hover .delete-btn {
1339
- opacity: 1;
1340
- }
1341
- `;
1342
- const DeleteBtn$1 = styled__default.default.button`
1343
- opacity: 0;
1344
- transition: opacity 0.15s;
1345
- flex-shrink: 0;
1346
- display: flex;
1347
- align-items: center;
1348
- justify-content: center;
1349
- width: 24px;
1350
- height: 24px;
1351
- padding: 0;
1352
- border: none;
1353
- border-radius: 4px;
1354
- background: transparent;
1355
- color: ${({ theme }) => theme.colors.neutral600};
1356
- cursor: pointer;
1357
-
1358
- &:hover {
1359
- background: ${({ theme }) => theme.colors.neutral300};
1360
- color: ${({ theme }) => theme.colors.danger600};
1361
- }
1362
-
1363
- svg {
1364
- width: 14px;
1365
- height: 14px;
1366
- }
1367
- `;
1368
- const TitleText = styled__default.default(designSystem.Typography)`
1369
- overflow: hidden;
1370
- text-overflow: ellipsis;
1371
- white-space: nowrap;
1372
- flex: 1;
1373
- min-width: 0;
1374
- `;
1375
- function ConversationSidebar({
1376
- conversations,
1377
- activeId,
1378
- open,
1379
- onSelect,
1380
- onNew,
1381
- onDelete
1382
- }) {
1383
- return /* @__PURE__ */ jsxRuntime.jsxs(SidebarRoot, { $open: open, children: [
1384
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(NewChatButton, { onClick: onNew, children: [
1385
- /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
1386
- "New Chat"
1387
- ] }) }),
1388
- /* @__PURE__ */ jsxRuntime.jsxs(ConversationList, { children: [
1389
- conversations.map((conv) => /* @__PURE__ */ jsxRuntime.jsxs(
1390
- ConversationItem,
1391
- {
1392
- $active: conv.documentId === activeId,
1393
- onClick: () => onSelect(conv.documentId),
1394
- children: [
1395
- /* @__PURE__ */ jsxRuntime.jsx(TitleText, { variant: "omega", textColor: "neutral800", children: conv.title }),
1396
- /* @__PURE__ */ jsxRuntime.jsx(
1397
- DeleteBtn$1,
1398
- {
1399
- className: "delete-btn",
1400
- onClick: (e) => {
1401
- e.stopPropagation();
1402
- onDelete(conv.documentId);
1403
- },
1404
- "aria-label": "Delete conversation",
1405
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1406
- }
1407
- )
1408
- ]
1409
- },
1410
- conv.documentId
1411
- )),
1412
- conversations.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral500", children: "No conversations yet" }) })
1413
- ] })
1414
- ] });
1415
- }
1416
- const PanelRoot = styled__default.default.div`
1417
- width: ${({ $open }) => $open ? "280px" : "0px"};
1418
- min-width: ${({ $open }) => $open ? "280px" : "0px"};
1419
- display: flex;
1420
- flex-direction: column;
1421
- border-left: ${({ $open, theme }) => $open ? `1px solid ${theme.colors.neutral200}` : "none"};
1422
- background: ${({ theme }) => theme.colors.neutral100};
1423
- overflow: hidden;
1424
- transition: width 0.2s ease, min-width 0.2s ease;
1425
- `;
1426
- const PanelHeader = styled__default.default.div`
1427
- padding: 12px 16px;
1428
- border-bottom: 1px solid ${({ theme }) => theme.colors.neutral200};
1429
- `;
1430
- const MemoryList = styled__default.default.div`
1431
- flex: 1;
1432
- overflow-y: auto;
1433
- padding: 8px 0;
1434
- `;
1435
- const MemoryItem = styled__default.default.div`
1436
- display: flex;
1437
- align-items: flex-start;
1438
- gap: 8px;
1439
- padding: 8px 16px;
1440
-
1441
- &:hover .memory-delete {
1442
- opacity: 1;
1443
- }
1444
- `;
1445
- const MemoryContent = styled__default.default.div`
1446
- flex: 1;
1447
- min-width: 0;
1448
- `;
1449
- const CategoryBadge = styled__default.default.span`
1450
- display: inline-block;
1451
- font-size: 11px;
1452
- padding: 1px 6px;
1453
- border-radius: 3px;
1454
- background: ${({ theme }) => theme.colors.neutral200};
1455
- color: ${({ theme }) => theme.colors.neutral600};
1456
- margin-bottom: 2px;
1457
- `;
1458
- const DeleteBtn = styled__default.default.button`
1459
- opacity: 0;
1460
- transition: opacity 0.15s;
1461
- flex-shrink: 0;
1462
- display: flex;
1463
- align-items: center;
1464
- justify-content: center;
1465
- width: 22px;
1466
- height: 22px;
1467
- margin-top: 2px;
1468
- padding: 0;
1469
- border: none;
1470
- border-radius: 4px;
1471
- background: transparent;
1472
- color: ${({ theme }) => theme.colors.neutral500};
1473
- cursor: pointer;
1474
-
1475
- &:hover {
1476
- background: ${({ theme }) => theme.colors.neutral300};
1477
- color: ${({ theme }) => theme.colors.danger600};
1478
- }
1479
-
1480
- svg {
1481
- width: 12px;
1482
- height: 12px;
1483
- }
1484
- `;
1485
- function MemoryPanel({ memories, open, onDelete }) {
1486
- return /* @__PURE__ */ jsxRuntime.jsxs(PanelRoot, { $open: open, children: [
1487
- /* @__PURE__ */ jsxRuntime.jsx(PanelHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: [
1488
- "MEMORIES (",
1489
- memories.length,
1490
- ")"
1491
- ] }) }),
1492
- /* @__PURE__ */ jsxRuntime.jsxs(MemoryList, { children: [
1493
- memories.map((mem) => /* @__PURE__ */ jsxRuntime.jsxs(MemoryItem, { children: [
1494
- /* @__PURE__ */ jsxRuntime.jsxs(MemoryContent, { children: [
1495
- /* @__PURE__ */ jsxRuntime.jsx(CategoryBadge, { children: mem.category }),
1496
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral800", style: { display: "block" }, children: mem.content })
1497
- ] }),
1498
- /* @__PURE__ */ jsxRuntime.jsx(
1499
- DeleteBtn,
1500
- {
1501
- className: "memory-delete",
1502
- onClick: () => onDelete(mem.documentId),
1503
- "aria-label": "Delete memory",
1504
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1505
- }
1506
- )
1507
- ] }, mem.documentId)),
1508
- memories.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral500", children: "No memories saved yet. The AI will save facts about you as you chat." }) })
1509
- ] })
1510
- ] });
1511
- }
1512
- function buildContentManagerUrl(contentType, documentId) {
1513
- const base = `/content-manager/collection-types/${contentType}`;
1514
- return documentId ? `${base}/${documentId}` : base;
1515
- }
1516
- function extractContentLinks(toolCall) {
1517
- if (toolCall.output === void 0) return [];
1518
- const input = toolCall.input;
1519
- const output = toolCall.output;
1520
- if (!input || !output) return [];
1521
- const contentType = input.contentType;
1522
- if (!contentType) return [];
1523
- if (toolCall.toolName === "searchContent") {
1524
- const results = output.results;
1525
- const links = [
1526
- { label: contentType, to: buildContentManagerUrl(contentType) }
1527
- ];
1528
- if (results && results.length > 0) {
1529
- for (const entry of results.slice(0, 5)) {
1530
- const docId = entry.documentId;
1531
- if (!docId) continue;
1532
- const title = entry.title || entry.name || entry.slug || docId;
1533
- links.push({
1534
- label: String(title),
1535
- to: buildContentManagerUrl(contentType, docId)
1536
- });
1537
- }
1538
- }
1539
- return links;
1540
- }
1541
- if (toolCall.toolName === "writeContent") {
1542
- const doc = output.document;
1543
- const docId = doc?.documentId;
1544
- if (docId) {
1545
- const title = doc?.title || doc?.name || docId;
1546
- return [
1547
- {
1548
- label: `${input.action === "create" ? "Created" : "Updated"}: ${title}`,
1549
- to: buildContentManagerUrl(contentType, docId)
1550
- }
1551
- ];
1552
- }
1553
- return [{ label: contentType, to: buildContentManagerUrl(contentType) }];
1554
- }
1555
- return [];
1556
- }
1557
- const ToolCallBox = styled__default.default.div`
1558
- margin-top: 8px;
1559
- border: 1px solid #dcdce4;
1560
- border-radius: 8px;
1561
- overflow: hidden;
1562
- font-size: 13px;
1563
- `;
1564
- const ToolCallHeader = styled__default.default.button`
1565
- display: flex;
1566
- align-items: center;
1567
- gap: 6px;
1568
- width: 100%;
1569
- padding: 8px 12px;
1570
- background: #eaeaef;
1571
- border: none;
1572
- cursor: pointer;
1573
- font-size: 12px;
1574
- font-weight: 600;
1575
- color: #32324d;
1576
- text-align: left;
1577
-
1578
- &:hover {
1579
- background: #dcdce4;
1580
- }
1581
- `;
1582
- const ToolCallContent = styled__default.default.pre`
1583
- margin: 0;
1584
- padding: 8px 12px;
1585
- background: #fafafa;
1586
- font-size: 11px;
1587
- line-height: 1.4;
1588
- overflow-x: auto;
1589
- max-height: 200px;
1590
- overflow-y: auto;
1591
- white-space: pre-wrap;
1592
- word-break: break-word;
1593
- `;
1594
- const ContentLinksRow = styled__default.default.div`
1595
- display: flex;
1596
- flex-wrap: wrap;
1597
- gap: 4px;
1598
- padding: 6px 12px;
1599
- background: #f6f6f9;
1600
- border-top: 1px solid #eaeaef;
1601
- `;
1602
- const ContentLinkChip = styled__default.default(reactRouterDom.Link)`
1603
- display: inline-flex;
1604
- align-items: center;
1605
- gap: 4px;
1606
- padding: 2px 8px;
1607
- border-radius: 4px;
1608
- background: #dcdce4;
1609
- color: #4945ff;
1610
- font-size: 11px;
1611
- font-weight: 500;
1612
- text-decoration: none;
1613
- white-space: nowrap;
1614
- max-width: 200px;
1615
- overflow: hidden;
1616
- text-overflow: ellipsis;
1617
-
1618
- &:hover {
1619
- background: #c0c0cf;
1620
- }
1621
- `;
1622
- const HIDDEN_TOOLS = /* @__PURE__ */ new Set(["triggerAnimation"]);
1623
- function ToolCallDisplay({ toolCall }) {
1624
- const [expanded, setExpanded] = react.useState(false);
1625
- const contentLinks = extractContentLinks(toolCall);
1626
- return /* @__PURE__ */ jsxRuntime.jsxs(ToolCallBox, { children: [
1627
- /* @__PURE__ */ jsxRuntime.jsxs(ToolCallHeader, { onClick: () => setExpanded(!expanded), children: [
1628
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: expanded ? "▼" : "▶" }),
1629
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1630
- "Tool: ",
1631
- toolCall.toolName
1632
- ] }),
1633
- toolCall.output !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: "auto", fontWeight: 400, opacity: 0.6 }, children: "completed" })
1634
- ] }),
1635
- contentLinks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ContentLinksRow, { children: contentLinks.map((link) => /* @__PURE__ */ jsxRuntime.jsx(ContentLinkChip, { to: link.to, children: link.label }, link.to)) }),
1636
- expanded && /* @__PURE__ */ jsxRuntime.jsx(ToolCallContent, { children: toolCall.output === void 0 ? "Waiting for result..." : JSON.stringify(toolCall.output, null, 2) })
1637
- ] });
1638
- }
1639
- const waifuAvatar = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA1IAAANSCAYAAABvJv8HAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAqt1JREFUeNrsvXucXlV97z+TTDJJIBMBQwDJRR1FjRlDpLYhhMdaowkJgtqK5cQSkSieBPoroY1cZLSmp6exlQA1RKtiUFsKWj1GRS5mQggaLgmihAoGhjvhGpIQErG6fn/IHtas2Ze1915r77XWfv/xfp0egbk8zzMz6/18P9/PahNCtNXJwtkThWnmTBsv2traBuiZ1CWuu+BY46xcOFX0TOoa9LlWLpxq5XNVxcqFUyt57MqydskMsXLh1FKvk7VLZuR6bqPHw/fnGMDF3zWtVmtt3X+PAAAA8tAWmkiph1+bEqUesn0/YPsghWuXzBDLFnSXeo0sW9CdKFELZ08c8ry2tbWJhbMncvgFsPt7tB+ZAgAARKomkZowrrMSEahK1uqcQrkmUSYEKpIoBAoAmQIAAECkkKigvx9TApUkUQgUQP0oP4fIFAAAIFJViVQVEhValM/1/S6TAhUnUQgUgNNv6iBTAACASNkWKSSq9Lu/zkX5ypZIpElUUokEAgXgnkz19vYu4g81AAAgUoZFas608ZVIVNyh22eJcjnKZ1qgZIlCoAD8lKm+vr4p/LEGAABEyqBIVSE3Ie1DxU3VXJEI0zE+WaLSasw5tAIgUwAAAI0SqTomUT4fvKsST1cEKpKouO+be6AAvHzjB5kCAABEqqxI2ZaouMmNzxIVJ4R1i8TKhVOtCVTcXWIIFAAyBQAA0GiRqkOifN2hcVEI1y6ZYU2ekvbm2IMCCEum+MMNAACIVE6Rsi1RrsbfQpEoG0USaa8PBAqAC3sBAAAaL1K2JSqkZj7Xvhebe1DRFIoYHwAyBQAAgEghUUFJVNUxPgTKvaITnhPgwl4AAECkKhYpddpgOp7mYhFDKBJVR5kEh003X5OIFFCLDgAAiFSFImVTouLuFvL1IO7aPhRTKJAPvAguIFMAAIBIVShStiUqlGlGUySqZ1IXUyhPD7pILlCLDgAAiFRFIoVE+fm9mI7y9Uzqip0+USbhz+uS1kSgFh0AABCpCkVKPjwjUe5/L6Zb+eImTyocJN1/Xfr6s4X80eQHAADgpUjZauhDotyP8sXVmE9vHyYWDR8hVo3oFKtGdBIXcxzf99aiu+SQKZr8AAAAvBKpKiWKi3bdlqjp7cPEhs4xQ5jePozdKA8OtL6KCCIVhkz19vYu4o87AAA0RqSQqGL15nUJhckonypRq0Z0xkrUhs4xYtHwEcT7HBYQ3yUXkQpjGkr5BAAANEak1IM0EtUciZJ3opKmUCoUGbg9JfU5dolI0eQHAADglUjJf/xMHWCQKH+qzaNp5KLhI7REiqkUkb4qJms8t/7LFPtSAADQGJEyJQdIlB8SFXe5ro5IMZVy8/UZwt4aIhVWzJTyCQAAaIRIIVHNk6i4WvNUgRp1wJDSCQ68RPoQKaB8AgAAGi1SSFRzJCrukt3o+42N9406QNw244/EluNaYuPBrxYbOscMqkJnKlX/cn8ozwEiRfkEAACAVyJlQnaQKPclKi7KF31f8gFWFambXz1B/OKkD4hfnPxBcevbZjCVqnECFRFapA+Rakb5BH/wAQAgOJFCopohUer3pU4xkurPNx3+mgGRuut97xebjjhySOkEUynzz//KhVNjGxpttWwiUmBZptiXAgAARAqJ8kui4qZQcc9RYg36mLHizvfME784+YPiFyd/UNwx87ghpRMcfM3JU9bzGWKkD5FiXwoAAKBRIoVEuS1RcVOotO8pLd63+c1vHRCpu048Wdw84QimUhXKU5wUhxTpQ6SaJVPsSwEAQKNFConyT6J0np/o31VLJzYe9Gpx1/z3/UGmTvqAuP2Ydwy0+DGVsitPTYj0IVLsSwEAADRCpJAotyVKN8qXdpAdEu/rHCPu+JNZA1OpO98zT2x81SFDSidCPNybPEzG7TzpUvdrFJEC7pcCAABEqoRIIVH+SVTR2mK1dOKWSVMGxft+9oY3DalCD/WAX+a5LSNPEeprtQkRMF4/RPwAAACCESkkyl2J0mnly/vYqPG+mw4cJ+6cM3dAprbMbokNY8YS7zMU3dN9bkPeReN1xP1SAAAAwYkUEuWXRJV5btJKJ25929GvTKUWnCQ2HTGR0glD0b2mFkwgUs3elyLiBwAAQYtUSBKl7pr4LlFl9qGKxPtuHn+YuOvEkwZk6va3v2NIvK9Jh2BT0T3dSF/oO2iIFJXoAAAAQYlUKBIVJ4R1vcNv4vBtYh8q66AzpHRi9IFiy/HvHFQ6cdO4gxtXOmFr+tTEgglEiogfET8AAAhSpEJ5RzwkiTK5D1Uk3vfT13a/Eu973/vFT1/3hiHxvlAP/SZ3n/KKctMO18gGET8AAABvRSpkiarroGZDomw9L0l3St009lXi5+89YUCm7jj2uKDvlLId32t6wQQiRcSPiB8AAAQlUiHtZsRJVB3fj2mJMrUPlTve1zlG3Pb2PxoQqZ+fsEBsPHj8kHif74f/OgSqiQUTiBQRPyJ+AAAQjEiFJFFxDX11fD9lI2E296F04n1q6cSmw14j7jrp/QMytflNbwnmTqk6BaqJBROIFBE/In4AABCESKl/1JCo+hv66pCorHjfhlEHiK3vmjMgUlv/9N2xd0r59PqpW6CaWjCR1KiJZBDxAwAA8EakVInyOZoVJ1F1fD+mJarq70F+HNV438+OessrpRMnniw2HfYab++UckGgmj6NkkWqqXeREfEj4gcAAB6KlCpRPr8T7kpDXxmJmjNt/BCJqiuSmDSV2jjuYHHXiScPyNStbzvauzulqmzhyzuNappMIFJE/Ij4AQCAlyKFRLkz5aiymS/PO8VxpRN3/Mmxg++UOqDLi3ifqQuRqTtHpMBexI+pFAAAOC9S8h+v0CTKt5rzqpv5SpdOvGbioDulbpn8Wqfjfa7sQVF3jkiBXsSPQwIAADgtUqEstrtSLmFSolwT1CGlE6MPFHfOmfvKnVJ/MmuISLkyXXEtxsc0KvnnF5Ei4heJFBE/AADwQqRo6KsvMuaqRMW9S6zG+25924zBd0od9Gqn4n0uxviYRqW/xhApIn5E/AAAwBuR8lmi1CV938olXJco9TFWp1I3H3r4K/G+kz4gftZ9lDOX87oY42MaxR1SQMQPAAACESmfJcqlcolQJUp9nIeUTow6QGx957teifcdO3tIvK/q78v1KRR154gU5HqDjIgfAAC4fyEv5RLVTT58kai4uI1aOvGzN7zplXjf3BPEhpj2viaXSXD5br6fZUQCiPgBAAAiRblEEBKVFe+76cBx4udz5/8h3jf/feKmroOGiJTteJ/LZRJMo/LFc5EI4G4pAABApCiXCEKidEonfvr6N77c3HfswH1SVdSg+zaFYhpF9TkUj/j19vYu4uAAAACIlOd7UU2TKFVgh1Shd44RG8aMFRtGHzjw/181wm6Rgm9TqKSCiSZPoxApoHgCAAAQqYaVS+SdgvguUZmlEwnYEAZfp1DUnVN9DtwtBQAAiBR7UQ2TKJ14Xxyma9B9nUJRd16tSCFlFE8AAAAihUh5vhcVkkRllU7EYaoG3fcpVNzrl4O+nerzaILB40vxBAAAIFKNF6m4S3frOCQ1XaLiDjRZImViT8rXKVScPDGNst/YF31MRIriCQAAQKQaLVK+7kWFKFE6d0qZ3pPybQqVNH1iGoVIAcUTAACASNX5h9OLS3dDlij18KtTOlGkBr1IK2LdAqXuPzGNqqexD1ltTvEEUykAAEQKkQpoLyp0iSpSOiGLlI5E+BTlyxKo5QtbYt3K05hGVVw0weNM8QQAACBSjRWpuL0oH8ol5EN1yBet5i2d0BEpnwoleiZ1JQrUrJ7JYvnClth5Xa/YeV0v06iaRKrp93NRPAEAAIhUA0XK172opkhUkTulsoTYlyhf2v7TrJ7JYt3K0wYEaud1vWL5whaX72r+nJt4bGTB5zFmKgUAAIhU40TKFYnKEzFrkkTFPU9ZpRNpe1I+SFSaQMnTJ5UmxDxdLJpApJhKAQAAItU4kfJxL6qJEpU33pd0n5TrEpUkUHHTJxWmUdWLlPx88Rg3qw6dqRQAACLVaJFyZS8qT6RPLZdo0mE5751S6qHZ1X2otArztOkT06ji0mPq8Yk+3oRxnWLtkhk8zg2aijOVAgBApBorUi7tRelG+posUUXulJIfKxclKqmBT2f6xDTKraKJnkldPOZMpQAAAJFqhki5IlG6UTMkqny8L4T4HtMod0RKfjOmZ1KXWLagm8eZqRQAACBSYYuUK3tReeJm3AuU/04pl0QqLb5XRqCYRrlRNBG9vnismUoBAAAiFaxIxUX66joA6U6jmlouUTbet2pE56AdFtcu0C0rUEyj3BGp6PnmsWYqBQAAiFSQIuXSXlR0EEOi7Mb75Od6zrTxQQkU06h6RUp9M2bh7IkUTjCVAgAARCpMkYqLVtUpdXn2opCoYvE+VZpDESimUe4UTcgihcgylQIAAEQqOJFyqepcdxrFpKF8vE/ek7IZ70sTqDwV5kyj/BCpuP0oRIqpFIcMAABEKjiRcmkvSrdgQj6Us3uR/pymxfumtw+LnRzYbuCzKVBMo8r9/JuQnSSR4meVqRQAACBSQYmUS3tROgUTNi4ObWK8T5Uok9G+tAY+GwK15+aLxb6tVwjx6AamUQ7sR6k/o4gUry2mUgAAiFRwIuVS1bnONEq9L4rDit4hRo73yW19Jssmqt5/igTqt/d+V4hHNwjx6Aaxb+sVTKMcLZpApHhDh6kUAAAiFYxIxUX66pQonWkUkb5y8T51CmVConomdVUuUNH0SWXf1iuYRjlaNMGeFKLOVAoAAJEKRqRcivTpTKOI9JWfNqqUKZioev9JnT7FwTTKraIJRAqYSgEAIFLBiZRLVec6dedqpI/DWPYkasK4zlTZKbITlRXfMy1Q8u6TDkyj3C6aQKSYSrW1tfVz2AAAQKS8FSnXIn06dedE+vI/t3ECWnQKVfX+076tV2ROn1R+e+93mUY5th+FSEHM66K/t7d3EQcOAABEykuRci3SlzWNUqcqHEqyJUreeZozbbzomdQleiZ15d6FyorvmRSoIvIk03vOIqZRJSfTpn+/JL3eeOyZSnHgAABApLwTqbiLd134A8vFu3Ykqmj7XlX7T2XlSca11zVFE8n3kvHYM5ViKgUAgEh5JVIuRvrSplHqYZ6oll2Jqmr/yaQ8JU2jOKy7WTSBSDGVYioFAIBIeSlSLoqJzk4U0yi7EpU2fTK1/2RDnmRaM6czjXJgP0r+mGm7eDz+yDtTKQAARMobkXIx0pc0jUqainAIMSdRadMnE/G9vG17Zei7ZhXTKAdFKq0Vcu2SGTwHvO6YSgEAIFJ+iJR6UHbhsJl3OsIhJL7i3JXLc/dtvaIyeWIa5eZ+lK5IMV3mtcdUCgAAkfJCpFyM9MVNo+Sq7untw8SGzjFievswJg0lJUqnPKKIQFU5dWIaFU7RBCIFsnBzQS8AACLlrEi5WDARN42Kk6gIpg3F4nxZ06e88b1InGzuOjGNqifWZ+p3AiIFRaZSfX19UziAAAAgUs6JlDqJcOEd+7hplHzg39A5RmwYM1bc+rajxeY3TRWLRoxk4qA8l0kSZXL65KI4MY1ydz9KPhxnTUsRKZB/TzGVAgBApJwTKfWw5Ep9eNo0atHwEWJD5xixacIR4hcnf1DcddIHxMZXHdL4qYN86Ig7pKZNn9ra2sQFnzjJe3HKmkZxOC/2ejL1e0F3PwqRgpi0BKUTAACIlFsi5WKkL24aJR/qBkTqsCPEXe97v/jFyR8Ut/YcLRYNH9HYyUOSRGVNn7LKI+oqhzAFd4y5WzSh0yLJ8wDS7y9KJwAAECl3RCruYkwXp1FJIrVhzFjx8/fM+8NUav77xMaDXt24qZS633bouM7MyVNadbnv4pR2AS8TjvpFSv451ilB4XkAplIAAIiUcyIVVzDhyh/NLJGSiyZunTZd/OKkD/xhKvW2Zk2l4p7DNNTpUxTTC0WcmEb5sx+FSAFTKQAARMpbkVLjXq68W5/WLid/vatGdIoNnWPExlcdIu6a/z7xi5M/KH4+/0Sx8aBm7ErpSpTcvBdJky/7TUyjmnkRLyIFXNALAIBIGecr3+878czlvU+UPWyoh3BX3q1PmkapjX3qVOq2GccMTKVum/724KdSOhK1fGFL3PD13mCnTXmmUVSe+3URLyIFXNALAIBIGRWoY2a1Ug/OPZO6tN91920aJTf2yUQidfP4w8RdC+SpVLi7UlkS1XvOokaKU9o0igO5XxfxyqxdMoPnA5hKAQAgUsU4c3nvE3n2YLKmS64WTOSZRg2ZSo06QNxx7HGvTKVmHDNoKhXKfkxa+x4CxQW8oe1HUYEOXNALAIBIFSZuCrV8YWsIcQfrpAOIqwv4yxZ0a+1Gqd9DtCu1acIR4ucnLBho8Lv5kEPF9PZhQezIJE2hWjOni75rViFPXMDr1X5U1kW8iFSzWL9irthy5bli/Yq5uV5HXNALAIBIaUvUrJ7JmZelzuqZnHoAUg9IrhxS1i6ZoRXpi75e+fsYtCt19NsHplJ3zDxOrBo5yvvJRJJEIVBMo6qI9dVxES8ilf1YhvT93PyFU8SWK88VN3/hlNxV6EylAAAQqUyJSrswVSXp3XhXCybSplFypE/9euMb/A4WP5/3h6nUL07+oNh0+JGDplK+RfyQKCrPQ9yP0rmIF5FK/50QmkxtufJcseXKc3NHnCmdAABApBIlSr33R4d1K0+LfUfe1YIJnQt4477epKnU5jdNHZhKbWn9qVjVOdrLGuw4iWrNnI4sUXleaazP1GNYZD8KkUp/LEP6njavWVx4KsWhBAAAkRpo58sT5dOJ+EWHEVf3RpJKJvLue0VTqQ0HdImtfzZnYCr1s+43ejeVQqKYRrEfhUhlPZ4hvcajeJ/uVIoqdAAARMqKRMVNpeIKG1yeRqVF+nSmUj99bbe4633v/0Md+rwF4qZxB3lTQIBEUTLBfhQipfs7IqTHJxIpnakUpRMAAIhUYqQvb5xPp3jCxQNmXMmEWjCR5+C3aPiIP8jU6APF1ta7BqZSt8/4I7FqRKfzsS91GoBEUTLR5P0oLuXN3hMKaSoVxfs2r1lMFToAACJVbBq1fGGrtETtvK43thbdtT+6cSUT8jRKR3ZU+YimUrdMnCzuOvHkP9Shn3iy88UTcXdEcTdUsVgfh2//749CpPSfq1CmUnnjffJjQLwPAKDBIiUfNExIVFy8z8U/uGkFE3lER/7vBqZSow4Qtx19jLhLKp7YMOoAJw/ccRJFM1/xkgkO2mHsRyFSeoIaymMU3SdF6QQAACKlzZnLe58wPY2Kq0J3bQKjlkzIkb68X6u6VzRQh37Qq8Wdc+ZKxRNHiUXDRzh16EaiKJkIOdZXZj8KkdJ/bIn3UToBANBIkbIxjcq6U8rFaVTeSF+akAyqQ3/z1FcifvPfJ24ef5gzB28kisrzJu1HIVL2SidCeZzKxPuYSgEANEykbE6j1D0p196pNzWNyppK3TT2VWLLrNkDU6mt73yXE3dLIVGUTIQe64u7dgGRsvd7JLR43/oVc5lKAQAgUvVMo+IKJ1yN9ZmSGlVOoqnUpsOPFD+ff+KATP30dW8YVDxR9WODRFF57vKB3EbtOSJF6YSNGnT1tUsVOgBAQ0TK9jTKZZGS2/rkP4ImDk2xdeidY8TmqdMGiifumHW82NA5ppaIHxJFyURTYn2qnCFSdp+7UPYD8+5JEe8DAGigSMn3RtmQKJf3pJKmUTbeBY8ifhvGjBW3H/OOganUhs4xld8tpUpUa+Z0JIqSCecugTb1c2BiP2rh7Ili7ZIZPE8Nivfl3ZNS2/uI9wEABC5S8r1Rs3omN0qk5Et4TU+jsoonNow+UPzs9W8Ut0ycMvC/VRXxi5OoOqJwvecsikXdNTJN3OekZIL9KB14bptVOpG3Bl2VduJ9AACBi5Q8jVq38jSrIiXH+1z4IyvvR9kSmKTiiSRsTzaqlqhImGzLkS3RomQirNpzRIp4n+09KeJ9AAANEinbJRMui5TtaVRW8UQcNiN+VUhUJCAuSJDJaVdr5vREuaJkwo5IuRbrQ6SaWToR7Unpxvto7wMAaIhIVVEy4YNIVTFNkD/HoIhfDDYu6rUlUXWLk4npkhozzCtWTKPcjfWpP3tzpo1HpCp8HkN4Y0Hek9KtQae9DwCgASJV5TTKNZGKYn02qpZNRPzkfamyX5dJicoT1ZNlwyRl5Sr6unTKNfJ8Xkomwq09R6SaG+8rsidFvA8AIHCRqrJkwmWRqrMpr4qInymJyhIJE6UNJidkeaVLV6xkmVQ/BrE+t/ejJozrRKRo7yu1J6Vbg057HwBA4CIlx/psl0xEzOqZ7Myhs8ppVNJUynbEr6xEJQmDS+JURLKypmnRxIq7o+qrPbchZ2X3oxCp5sb7Su5JEe8DAAhNpKqO9blWf171NCrzbinDEb8yEpUkT7qC4aNcFZlU2Yz1Ne3ALv9cmHosVTkrux+FSDW3Br3InhTxPgCAQEWq6pKJndf1inUrT3NKpFTRqPNzp8mUGvHTedyKSlTa9KkpF+smTazUC4v7rlllVcSbNuFyvfYckSr3nIYkUkX3pIj3AQAEKFJVxfrk/SgX/rDWLXV5In559qWKSFScQIU4fTKxFxYJlc1YX/QcEusz94aFif0oRIo9qTx7UurveUQKACAQkaoj1ifvR9Xd5GTr4Fbma1g0fETpiF9eiUoThSYLVNyuWNpelUkRl18XxPrc2o+KQJKauScViVSePSnldzLxPgCAkESqqrY+dT+q7nd1bV/AayPilyVTeSVKFQMEqrhQmXo9uyD4xPoQKfak7O1J9fX1TeHgAgDgsUjVsR8lx/pcm0a5JHVFK9HLSBQCVT7yF8LeXmjTYUSKPSkX9qSI9wEAIFJBTaNsxYiqqkRXZcrkZbugh40Jivo6aIpI+RbrQ6SaK1Im9qSoQQcAQKRKlUzUfUBxSepMVKIjUdXH+2y8huSfySaJVBVSaqL2HJEq/7sthKKOIntS1KADAAS6I1WFSKVdfJqGjYYsVVhcbbgqIlNIlL+xPvl12SSR8jHWh0hxMW9ekVJe59SgAwDQ2veHe6Fm9UxOLKyI/nlRkbLxx9eVkomylehqvA+J8jvWJ0tUk0SqilifqdpzRKr88+FClJo9KQAAMHqPVNnLdeOmWmqcTyU6MKok/fsm/gC7VjJRZl9KnUYhOP7G+mS5V392iPW5tx+FSFE4UUSkiPcBAAQkUl/5ft+JReN98pTpmFktbYGa1TNZ6/Lf5QtbsZOssjLlWslE0fulVIlq+qW5Psf65Oc6+jlsikj5GutDpLiYt0jhhBrvowYdAMBjkRJCtB0z65UDm47gqFG9Y2a1xM+fE5kxvqJ7WHEfs4wA2Xr3u8p9Kfaiwor1qdOoJomUrd0Z+TG1EetDpNiTKlI4QbwPACAwkVJlKm1ipE6aIon6+XMidgo1q2eysSILOUZY9A+xyyUTujKFRIUV64ubRqnlLE2Je5mM2dqO9SFSiFSRwgn1caAGHQAgAJFSZUoHWaLU/1Y3vle2Qj3vwcv1kgldmUKiwon1xU2jmiJSPsf6ECn2pNiTAgBApArJ1DGzWuLlHavYKZTNGnU55pc34ud6yUQemWInqh5aM6cb27FLmkbJIhXygd3WGxtVxPoQKUSqqEgpjwM16AAAoYhUVEAht/nJnLm894m4xr88O1amI34hx/qS3rlHosLYj0qaRjVFpGz9PFYR60OkKJxYv2KuCZFiTwoAICSRKjK5sj2FSrvcV3ey5GusT5Uo4nzuxPrKTDVlsY/7+QldpGy1Z1YV67NxUTh7Us0pnCDeBwDQUJFSJaqKKVTarpTuH2MfY31IVLj7UVk/Q6GLlPzGhsmfx6pifYgUIoVIAQAgUoXunLJZKGFDpHyM9SFRbsf6ykxRsqZRRd4oINZXbawPkSr/e83le/xsixR7UgAADRIpdR/KVKV5VSI1YVyndwdTtVwCkXGr9rzM6yhrGhW6SNmaSlQZ60OkKJxQCyfWr5hb+HGgBh0AIFCRciHKl1Y4kXUQUyc7Phx+VInqu2YVMhPIflTWNKoJImWrcEB+fmzH+hApRKpscx/xPgCAwEVK96LeOidSWYcZVUqQKChbe17mdaTzpsQFnzgpaJGyEe1S3zCxHetDpBCpsiKlvGaJ9wEAhCRSqkS5IFB5LytduXDqoFif65l87ooKez9KjZ7Fvbb33HzxoOlXaCIlHx5NikjVsT5EisKJsiJFDToAQKAi5bJE5blHKu5wRbkE1LUfpbNn+Nt7vxu0SEU/k6bf1Kg61odIIVJq4cTmNYvLvnlGvA8AwHeRclmi1GlU2h/htUtmeBPrQ6LC34/SmUbt23rFkM8XmkjZmEapPz9zpo1HpCxPYfIWK1CBrrcn1dfXN4UDDQCApyIlV5y7KFF5plHLFnR7EetDovyK9RUVcp1pVNznC+2wbuNnsY5Y38LZE8WyBd2NFKmiMTYq0In3AQAEK1KuS5Q8jeqZ1JV6wFy7ZIY3sT7KJcLfj8ozjVI/X4iNfablsOqSCRlEisIJkyJFDToAgKciFUX6XJWoPE19yxZ0exHrQ6L8i/UVOTxnTaP23HxxorixU5NPUquK9TVdpIrsA4UqUpvXLC4lUtSgAwB4LlLyNCrpHXNXIn06E4GFsyc6L1LqARCJCnM/So1uxlWe//be7yJSBt6MqKpkApEqLg2hV6CbECn2pAAAPBMpuWAiaX/DlYKJrIPsyoVTnY/1sRfVnPuj5IN+3LRXnUYhUv7E+hApRMpEBbr694A9KQAAz0TK5d2oWT2Tc0+j5kwb7+zCPhLVnP2oItMoRKr4VBeR8kek5OfO90KVsiLFnhQAgMciJU+j4g56PklUVDLhcqxP/dqQFX9EKu/Bucg0CpEqdvhEpPwVqaZfyst9UgAAnorUmct7n3A10idLlO67lssWdIuFsyc6W3tOuYTfIpXnnXN1GhX386XzOREmvWkUIoVIufCYFBUp9qQAADwTKbVgwqVplHpflErcZZjRNMrV/Sj1YI1E+Vc0YbLyXK47R6TKvymBSCFSPosUe1IAAJ6JlKsFE+okKg152hSVTKj7US7GkNiLCl+k0qZRSZE+RKrYoROR8k+k5J+RkESqzOOCSAEAeCRSLhZMqBI1q2eyWL6wJZYvbIlZPZMTJSuSKFf3o+SvCYnyU6TyHPayplFxBROIVPFpn3zHHCLln0i5FL+uU6TYkwIA8ESkXCuYiIvyJQneupWnDTo4qRdxyvtRLhx0uC+qeUUTaa/jrGlUmSlYE0smojdaECl/RSqE17kJkeJiXgAAT0TKhct3k4QoT9RQ/e/VWF/dBx32oponUqo4q29UZE2jEKn8jy8ihUj5fikvhRMAAB6KVJW7UdGBJ0meonfv807I0j4ee1FQlL5rVhV6LanR0jzTKESqWKU8IoVIuSRS61fMZU8KAACRKk+025RVGrF8YatUxDDpc7AXBVUWTWRVntsuuGhayUT0OwORqo71K+YaEyl5AhOSSBVt7kOkAAAaLlJZEyeT8qQSV5HuyoEPMWmGSKVNo9LqzhGpctM+RMqvCBsixZ4UAAAipRmvi2vhszH9ckmk2ItqZmNf2WkUIlVs2if//qm6Ah2RMiNSeS67RqQAAMAJkSpbfb5u5WmVRPeKyFxdf5iJ9DWzaEItQbj2i3+dexqFSBWrlEek/BcpLuWNfcMAkQIAcL21r+iEKK6yvGj7ng2Rqvuwh0Q1S6TUYpE8BROIVLlpHyKFSJXd+ypTEKGK1OY1i0291vvZkwIAcFykitwllTWFKtK+57tIUXUerkhlTTfTplFZdedpIhXCRaW2Ks8RKf9FSpaGOh/L6Hty5bGhcAIAwHGR+sr3+04sIlNZu1B1CJQrIiVH+nrPWYSMNKj6XP05KDqNKrqb1aRpVFwcWZ6OI1J+ilSdO1LR91QmkmfysZHfOECkAAAcFCkhRNsxs1raUTydMomy+1ZlUadktPRBFY19aSUIeadRiFT+aZRaNoNI+SVS0fPrwvfkokixJwUA4KhIJclUdBjUvQPKBYlSW/uqPuAMimIwjWqUSCVVcheZRiFS+tO+rJ9/RAqRqvp7MvVxKJwAAPBEpJJifmmcubzXOYmqU6TUgzQiEpZIpe0ppU2jqqxdb8o0Kq28BpHyU6QiWQ7hkmGTjw0iBQDgiUhFnLm894mkCdWZy3ufOHN57xPqFMsViapLpNSDNNOoZjX2JcXO8tSdI1Llp1GIFCJlqnHPRZFiTwoAwAORyhsFdEmi1Cr2qg44TKOaLVJJk9mqLwJu+jQKkfJbpFy6A6pMDbqtanhECgAgAJFSI4B1tfO50tinHvSoO2+WSCUd9MtMoxCp7N0zRAqRcvUyXUQKAACR0rp7yiWJqkuk1AtYfav37j1n0ZBShSRaM6cP/PtNiC/qvJaSDvom97OaKlJqZFZn+i3/+3OmjUekEKlcbF6z2KhIlW0ApLkPACAgkTpzee8TLkb64qrPqzjcqIdo36ZR6j1JRYkEK6RpnE5jX1LJRNlpFCIV//Ol88aN/Dugygp0RMo8a5fM8LIC3aRIUTgBABCQSLksUVUXTTStYEKeRLVmTg9erHREKqlkwvTnb+JBvcg0Sp1KI1J+i9TKhVMrv6AXkQIAQKSMTqAi5IIJ1yJ9dRRNUDChFw+MpCpEkYo76Be5fBeRMjONQqSq3yUKVaTKfF82RYo9KQAAx0TqK9/vOzGSJZ0YV1Zrliv7UTYPN+okgrrzoWIV99rx6XGSp25xr6W4komil+8iUmamUYhUeCJV9WProkhROAEA4KBI6YqTiovTKHU3oq2tzeo7mT4XTLggVa2Z052P/WVJeVzJhKlpVNNFqug0ShWpCeM6ESlECpECAABzIvWV7/edmHThrsqsnsli+cLWIFyUKDXWZ7Oxz/eCiTqlSt2tcllC00QqrmTC5DSqySJVZhqVNJlGpPwWqSrjfa6LFHtSAAA1ipR6B5R8EFRxdfKUVTJhU6TUQ57Pded5MCmLcULlYuQvTaTiSiZMTqOaLFK606g9N18s9tx8MSKFSAUvUsrfHUQKAKAOkVIlalbPZK9kSTfSZ1OkfJhG5WnXq7PmXK1edy3ul/ZaUn+OTE+jVJHqmdTVyGlU2gW8v733u2Lf1iucuZQXkQpDpNavmDvwfa1fMdcJkaK5DwCgZpGKkyjfBWrdytOGSJQ8KWjCNCqaMJkWJl1MSJW6Q+WCTKU19lUxjdJtDQyNuAKPpGmUeHQDItUQkVq2oLuW762oBNkWKfakAAAqFKlQJUo91Ed/dG0dPNXP51rNeN2UkSo17le3pKZJjDqVtDGNaqpI6U6josdIR6TmTBuPSHkuUlU/vo6LFIUTAABVipRcLBGCRKk7EHJDny2RqqvuPK1K3GWKPj6uyFSSxMSVTNiYRjVRpHSnUfu2XjHoccoSqaoq0BEp8yxb0I1I0dwHAFCfSKkV56FNoXomdQ3Kz9sSqSqnUS5PnfJSZELlgkwlFT2oh/1rv/jXtV4I3ORpFCLlhkhtXrPYqlwjUjT3AQDUJlLyNEotlli38jQv2vnidqF0Lkn1bRoVijyZuDOqbplKep2pEVlb06imiZTuNCru8Y5r7pN/XyBSbspGXpGqsnACkQIAQKQG7UbJkT55suNyc1+SQKW1mNkQKdvTqFAFKk6oispU1QUUcSKlHvYv+MRJte1pNXEalbSLFrcnJcd/ESm7FeGIVHWPDyIFAFChSMmxvnUrTxsiJi7vS8XtQSUJlc1on81pVFMEqkzcT5apukVKLZmwOY1qkkiVmUaJRzeI3977XSfukiojCogUIoVIAQA4JlJRrG9Wz+Qhk52kw4qLe1ByLHH5wlbslMpG2UTcnTYIVLUyJd81VWXEL+61VXXZSFNESmcapRZMZO1J+SJS0bQMkcoWqSofJ0QKAACRaks6xLoa5YsTpLSpmXpYUuvPy/7hNX35rnoBbdPRlRH5casi4qc+T3FTkyq+jiaIVFwLom6kL21PyheRMvF7CpFyr0ijCpGiuQ8AoGKRcjXKF7cLpbu7pU6wZPkp84fX5OW76h1JkP9xjR6/KqZScQJTx0XMTRAp9c2KPJG+rD0pRAqRqqvavQKRogIdAMCmSKm15y4XSpQVvqQ4YJk/vKYmEMT4zMlUVdMg9TlTpbqqO8RCFylT06ikPamqK9CLHoyTinOaLlJrl8wY8hivXTIDkUKkAADsi5SvUb6iX6tuPXqRP1hFJhA2plCnn/FRcfoZHxWXrb5E3LJ5Yymij3X0jOneyFRVUylVYFSprvtS4BCnUUlvnuR5vHwTKVkkEan4x0d9jKsqnHBVpLiUFwCgApGSa89dLZUoGuVLw5RIlZ1GmZxCmRKnLC5bfUntYpX1OMuPa1UC8+Hjj6wl1he6SKnTqLif/bytiOqelOsiZevycESqGSLVarXWcuABALAgUnKszxeJsvFxy8RtihycTU2hjp4xvRJ5SpOquoRKN95nM14nfz1vndxVS6wvdJHK+vnXjfSl7UlVfZdU0UMx0T5Eikt5AQAcFCnXyiWSSiVs3T1V9l3iPNMoE1Oo08/4aG3y5IpQZYlKFfG+MqJnuz0wxHuj4qZRRR4vdU+q6ua+oo+Dj2UTm9csrkWkli3oRqQQKQCAakTKpVifiVKJKkSqyMG5zBSq7umTi0JVd7yvbCmGra+lKdOoMhcdI1LVt9qtXzG3MpGq6rFCpAAAEClnRCpOomwUYJQVKfWd8qzpSJl7oarYfYr2nnRwSajSHvcq7pQqe++Vra/FxwN33mlUkUhfUryvSpEqEjkLRaRs7tEhUogUAECjo33qYcZmi6D6ufLuHuSZjBSVKJvxvUiKbJdbXLb6klob/GyLjQuxvlBFKmsaVfbxUvekECl/RcpkzTwiBQAA3rX2maw3LyptRVvEdKcidcf3yopTGemzOZ3SiVJWKVJ1xPpCE6msaVSZSF9SvK+q5j5ECpGqQ6T6+vqmcOgBALBwj9Qxs1q13SNVxT6UaZGS77RJO8jnkShbAlXlzlLW92BL5NIkyeaeVFJpSB2xvtBEKu33QdlIX1INussi5fPzikg5IVLcJQUAYEuk5KmUPJ2yKVVxrXxVRgzjPrfJaVSeZj7fBSpOqKqM+qVNgGSZrUqk6pCokERKfaNC/T1k8jGT432IVHgiVUUFetnvT241NFnGMeRvFSIFAGBHpJJkKk5yli9saREnKllUOQ2L+/ymKs91m/nShMNHgcrzvZn+GnUEA5Hy7/JdNW5sKtIXF++Tp9QTxnUiUohUJd+fzccHkQIAqEikhBCDLuetkqqLLuLihLoilTUJ0ZUoG1MoFwQqT9TP5NebFqezFbmLE6m6Yn2hiJQ6jbIV6YuL91XV3IdIIVKIFABAgCIVtyu1buVp2lOoCN3/rup9rLRYn45IZU2jdCTKxi6U7Wa8sqR9v1VMpSLhqUKkbNWsN0GksqZRth63KN6HSJlH3v+xJVJJ1eeIFCIFAFBLtK8uwakz1qcjUmklE7oSZUqe7rp7q7j/wfucm0IlUYUEJklMlSJVl0SpX4+PIqU+lrYjfRG/vfe7sZNqRAqRCkmkWq3WWg49AACWRCqaRrlwn1Qdsb4skUormdApljBxJ9T9D94nnnzmcfHib3aLH1//Qy8ESkciTclgVvGHaZFSWxnrqj0PQaTS6s5tRfri9qSqKJwoI1JViIEtkdq8ZjEiVaNIcZcUAEAFrX113iVV16W/OiKlHvR0LmU1sQ91191bB+Qp4oILz/NKonRk0na8z9b+kiv7Ub6LVNrOZBWPXRTvc1Gk5DdxfJsy2qr2Vh+fnkldsc8XIoVIAQBYFym5ZKJJsT5VjoqUTGRNo4pKlDx9kpl9/HFeSFN7e3stEb+keJ+t6J0rsb6412II0yibkb64eJ+LIiU/PohUPpGq4g0Fl0VKvZSXQw8AgGWRalKsT43rJR1w0mJ9piUqTp58ifJ1dHSIsWPHivGHjheHHXaYePX4V4sDDjhAdHR0aE2lTET8kqZCfdesslIEIe/GIVLlCybkadS+rVdU+vhVVYGOSJl/DU0Y14lIIVIAAPVH+0IWKbmtr2dS15BJU9If3aRYX9o0Kq9ExUX4fJGojo4Occghh4iet00T73vfAnH6xxaJT5y5WPyvj5wqjm/NFq973WvFAQccUMlUqup4na39q6aIVNLlu1XsRcXF+6po7muSSMmXzdoUqbQJIiKFSAEAWC+bCD3aFzeN0hWppIN6UlNfXom6/8H7YgXKB4kaNWqUeN3rXived9KJ4jOfvUh8+7/+U2zc1Cc233aLuHH9dWL1mn8Viz/+MTF9eo8YO3asaG9vtzqVqkOk6i6Z8FWk0qZRVUX61HgfImVPMmyJVPT4IFKIFABAbSIVxftCbe1T746K+0MT90c37e6osu18aVMoHySqs7NTvGXqm8WZn/y4+Pervil+ue1O8dSzj4tdLzwr9ry4U+zc/bR48OHt4robfiiWnr1EvPnNbxKjR4+2OpWqWqRsRQabIFJJ06iqI33q5bzy1zRn2nhEypBkrF8xF5FCpAAAuEcqlGmUjkgl3R0VF+vLc09U2hTKB4kaNmyYmDRpoliy9JPixvXXicd2PCT2vLhzyPfxwr7nxRNPPSK+9/3viFM+/CExYcKhYtiwYalTuzJTqdbM6aL3nEUDuCA4iJT+5bt1RPrUeJ/twommipRtIU+SXkQKkQIAsC5ScrwvtKmUPI1S/7BmiVSeu6N0I31pUyhfiiVGjRol5rzn3eL/ff874qlnnxB79+9K/H727t8lHnxku/jX1ZeKP/7jd4hRo0ZlTu5Mfq2RXDVBpnwRqaTLd117DF0QKR+bGOsQqaTH2xeRsnHPlipSfX19Uzj4AABYEim5vS+U+6TiCiZ0RSrP3VE606isKJ9piRre3iZGDm8XnR3DxMiOdtExrE0MM/SxD51wqFh+3t+JBx76dapEReze+5y4485bxdKz/rd4zZGvSY333bJ5ozj9jI9aEcDQhcqHQ7f6cxX9rqljLyqridElkYr7/eUy61fMRaRy/Pc2dsjUn7Xe3t5FHHwAACyJlBCi7ZhZLRHCZEqN8yUdQnRFSi0U0L0fSZaoLNkwdU/U8PY20TVmpHjdEYeIGUdNEm9/0yTxpomvFoeNGyXGjhouRgxrL/fxhw8X03reKr71H1eK53Y9pfV97d2/Szz17BPi6m//h2i983gxcuRIKyKlTg0jku4BQ6TcuHy3zr2orIikKyLl2yXLcvW5jWkLIpX/5w2RAgCwLFKqTEWHHZ/2ptTmrbR3ctNESjfWlxVT05WoCy48r7xEDWsXhx/SJU44/u3iwrMXiS9//kLxb/9ykfj7cxeLD7z7j8UbjzhIvGp0hxgxvL1UycR7575H3PKzjbF7UUm8sO95cd/2e8T/9zdni4MOOshKvC9r4hT67pROC6VrBRN170XFFYggUn7cIaU+Pr6JlDy1Q6QAAAIRqTiZkqVq+cKWk9G/dStPG9LOlxWHSRIpdRk+7R1rExJlItLX3tYmXt01Rvz5vNniP9b8k7hn87Xisf/+qXjsVz8Vv95yg/jelZeKRR94t3j9hC4xdtRwMay92Oc58MADxcfOOF3cu/0e8cK+57VF6sXf7BbPPv+kuOSyi8URRxxei0g1aUfKtYN3Ut25K5G+tMcRkfJXpPI+3lWKVBWPESIFAFCDSKlNfklEUrV8YavyqVX0eVV5yrNTIIuU/O+nxfrkHYo0Gchq5jMd6RvZMUy8/c1TxJc+f6F44OfrxdMP3CGe6d8inunfIp5+4A7x0C9vEl+/9O/FO49+gxg/dmThqdQhrz5EfPqiC8RjTzyktR+l7kr9+1XfEJMnT84UqSLxPkTKXZFSp1GuSpTtwommiFQVd0ghUogUAICzIqUzodJBlq040gRM/XeTpElF99ChLuNmxfrUQ1bZSZQpiWpraxNjR3WIk9/9J+LmH3xDPHX/7eKZ/i3i2ZdF6pn+LeKp+28Xt1z77+LU+ceL1xw0SnR2DCv0eY488khxyaVfEE8/90Rukdrz4k7x7e9cJV772ilad24hUmGIVFzBhGuRvqSpc50ildUq2vTGPnnKiUghUgAAzoqUPKU6c3nvExFlBMsWPZO6tP946ohU0mE1TQR0xcLEXlQU63vVmJHi1BP/VGzd8N0BeXpWkqmnH7hDbN3wXXH6B98jJh48upBItbe3ize8oVt89Yovi+d2PVVIpP4wkZqkVdKR906ppouULAAuHbzVSJ/LEqU+jhPGdRoVqbVLZiBSFV3Gi0ghUgAATomUDnKNet1CVUSk0mrPZZEqek+UjKnvtb2tTYwbPUK870//SGz84TcGxfoGJlLbbxMbf/ANccrcWeLwcaMKRfuGDRsmjjrqjeJrX/9KbpHau3+X2PXCs+LyL/2rOOyww7REKm+8D5FyT6TUSN+1X/xr5x9Hm4UTTbiMt6rqc5dEqkgzISIFAIBIaUUA4yJ9ujG9pI8TFwOM+5hpf0izRCruYJ52b1SevShT06iIMSOHibcfNUl8+fMXioe33TxkR+r+O38i1qw8X/zJ1CniVaM7CpVNDBs2THR3v16s+fIXxTM7d+QWqWd27hAXXnS+GDt2rJZI5Y33IVJuiVRcwYSre1FVFU40QaSqqj53SaSKiBAiBQCASA1E/lSJqqsyPa69L+2PqSpS8jvofdesij1gXbb6klJ7USanUREjhreLw181Rnzg3X8ivnX5P4q7f/pD8fC2m8XD224Wv7zlB+LKf10h/uK9x4rXHDRGdHYUK5pob28XRxxxhPj7FZ8Rjz7xYG6RevSJh8TCj/wv0dHRoS1SeeJ9iNQip+6SUqdRvkiUzcKJpomUzaKJ6PVVl0iV/T4RKQAARCq23U+eHMXVp9dxn1TSH1T14JkW6xOPbkjcjcoT6TM9jYrifWNGDBNHHnyAOP7obvHxU+aJz57zMbHibz8ulv7VyeJdxxwlJh1ygBgzYphoL/F5xo0bJ04/46PiF9vuFC/se15Lpvbu3yVe2Pe8uOPOW8Uxx7x94GPddfdWo/E+RModkVKnvb49N7YKJ4qKKCKV/Ls7BJGqYj8RkQIAcFCkiuxERXfI2EadTGX9oZGjSGrteUTZSJ+NadRA9K69TYweMUwccsAIMfGQMeINh48TbzziVWLSqw8UhxwwQozqaC8lUW1tbWLkyJHi2Fkzxbf/6z+19qQiiep/+Nfiwk+fL7q6Xjkc3v/gfaVEqjVz+sAlu02XKNdESn2efH4sTRZOFH0MfS2aWL9irvXX2Jxp4xEpRAoAwE+RKnIYrzL2l1VAoe5CZL2DXqalz9Y0Sp1MdQxrE50dw8SYkcPE6BHDxMjh7YUv4I2L902YcKhYsvST4s67bhd7XtypJVGf/VyvOOKIwwd9LB2RStuTioteNh0XDt9qpM/H58lW4UTT7pCqQtZN3tuFSAEAQGUiFTeNqjK+VyTipytSSQesMpE+k9Oow8d11taI2NHRIbq7Xy8u/PT54hd3bxW7XnhW7N2/awi79z4n7t1+j+j97KfF+PHjB32M2ccfJ178ze7CIsUEyk2RUgsmfJxG2SycQKTMR0cRKUQKACAIkaoqsldmKqUeSpJkIe5g9fA9N5UqmPjx9T8sLU/zp40Xi4+bKGYo7/pXzfDhw8WkSRPFJ878uLj2uh+IBx76tXjq2cfFMzt3iKeefVw88NB94kc/Xic+evpp4pBDDh7y319w4XnaIhVXOME0yk2R0vk58oXWzOnG96RCF6mqG/t8FqmoJt7m44RIAQB4JFIuSlQRkUqadqgilUeiysb6IoGKqFukopjfgQceKN42/W3irxZ9RHxuxWfEF1b9s/jcis+K0xZ9RPT0TBOjR4+O/W8jkdKJ9/3NkkXBTDmqnqIQ6XOrcCL0y3irKpoIQaSuu+BYsXnN4koKOdra2vr7+vqmcPABAECkSomU+k59UZHKWzBRNNZ3+LjOQQLlkkjJQjVy5EjR1dUlDjroIDF27FgxcuRIMWzYsMT/Jo9IqSUKxPrcE6mQIn02CycQqeqqz30QqZu/cEplIsWhBwAAkapEpJLeSY9EKm+kr+g0KkmiXBOpIkSPS5ZIPXzPTUMEAWHSi6NVeQAPKdJnc08q9Dukqm7s812kKvy5RKQAAFxv7XOpZEJXpHpihCTpUBWJVN6CiRd/s1vMPv44YxLVJJFSD7TsRulPUaoSqdAifUkilVazrcOyBd2NEqkqBAGRQqQAALwWqWNmDW7Fc12k1Ap09eLQLJEqEukrEutTd6JCFam0wolf3nnToAMtu1HuiVSIkT5be1J5DvU+3iEVlSfYFin5NYdIaf9sIlIAAC6K1Fe+33eiy1OpdStPS90bUUUqbf/m4XtuKiRReWN9WRLlu0hF+1FZIhXF+iKRYhrlnkiFGulDpPwvmmi6SCl/2xApAAAXRSpOpqq8cDfvPVLqH9Y8IvXSc/daF6msSF+TREq9IBVRynfwt30IDznSZ+Ni3iIiFXeJOCLlnki5+HwgUgAAnoiUy5OpWT2TUw+WqkilHQaLipTpaVSEryL14+t/mClS8jQK3BMpNdIXcpNinSLlU2Pf5jWLKy2aQKQQKQCAYEQqTqZm9UyudTqVFeuLE6m0A5VtkdKdRlU5lbLxOdTHJ65w4vn7kSJXL+UNfS/KVuGE7qFefnx9EqmqiyZ0KukRKUQKAMAbkYqTqUio6p5GJUVkbItUnlif7jTq8lOniovmd1ufSkVfj8mPOfv44zJFSi6ZAPdEqgmRPht7UrqHel/vkHKtsQ+RQqQAALwTqSSZqjrup0b60v6guiJSWQJ10fxu8b1PzhC3nXesuO28Y8VF87vF/GnjrUqUaZFS96PiRIpplLsilWenEJFqjkjVsR+FSOk/Vq1Way2HHgAAT0QqqRq9KqHKI1F5REo8usFarG/GpK5UiZIFSmbxcRONy5Q6GbMZ63vxN7vFk888zjTKkkiZPEg2LdJnunCiiEhRNJH82OjELBGpl9/w6O1dxKEHAMAzkUqbTkVCZXKHSt2J0j1I5okp2RKppFjf5adOjRWoiO99coZRmYr7OmzG+tTCCaZR5ssRqDp3o3BC91AvRyd9jPXZLJqQHxvTBR+IFAAAOCdSWdOpaIeq7JRKnULleTc+T1SpqljfRfO7UwUqTqbKFEMcPq4zVqJMlk3ExfpkkaKpzwytmdONi1TT9qLSHtOihRMh3yFV9X4UIoVIAQA0SqSi6VSaUBWZUsVNofLevVK3SKmxvqQYny2ZSosVmhSptMdKvTcKzOz0mBCpJlWd29yTClWk1q+YOyAUm9csdqZowrZIVSWPiBQAACKVW6iypGrdytNKTaGKitT+PY9ZFam8AqXKVNTmN2NSlzh8XGepvazFx03M/BgmplEv/mY3kT5HRaqpe1EuiBRFE8mvRUQq3xQZkQIACEikIhbOnjgkMpQlVnHyVGQKVVSk8jT35RWprH2oPEJ1+alTB2Ro/rTxYsakrgHy3FVl6xJemf17HkOAHBUp9eeT3bPie1KhNvZVdRFv3sa+pouUXH3e19c3hUMPAECAIhUxZ9p4MaHg9KOMROUVqf958g7jIqVbLFGUy0+dOuhjVy1SaSUTL/5mt/ifJ+9AgCyJVJl4mFp13rS9KETKLaFQHxtXRMp2nNGESHHgAQAIXKRkeiZ1aUuViT+Uefc/dEVq9vHHOSFScWKlI1Gm9qPSplEvPXcv8mO5rptIn3lBLVI4gUhVWzRRlUjZjDMiUgAAiFRukVKlSo0ARv+blaVcDZHSjfflbexLE6nvfXJGoRKKukUqbRpFpK+a6UnZQysSZWZPKsQ7pKraj5JfkxPGdSJS+abJiBQAQFNFqop3ZfOKlG68z6RImZ5YVRXrS5tGEelzU6SaXnWuM+nLK1LLFnQH2dhXx0W8eR57RAqRAgBApKr9o6N1qNJp7zMlUtEEyWS8L0uiTFzwmzaNItLnpkixF2VnT6rInXa+FU1U9TsakaL6HAAAkfJcpHSmUrrCEV2EmyRSaRf0Fp1SZcX7TMT6kqZRRPqqPfDrHibZi8pGvezYlkiVLdAJbT9KnpLaeNxdjjRSfQ4AgEgFJ1I6pRN5L8SNk6Loot24fxY3qbpofreWXGWJlM1pFJE+NyvQ5cMXEmW2cELnQC+LrA9FE/JFvFXtRyFSBQqUECkAAETKRZHKmkrlFam4qVNarC+6fLfILlUkaFVPo5AoN0WK+6LsFk7oHOh9a+xz+SJe23875O/d9cY+RAoAAJFyUqSydqXyiEckMWozX95JVZ5SiqqnUUT63BQpNdLHXpRebBKROsXp/ShEiqIJAABEynGRSpOpPPIRN5XSifWZFikT0ygkyr2GOV2J0mmtRKTyx8zWLpmRayrIflT87+e893eFLlI3/sNcGvsAABApv0UqKeJ3wYXnlZpK5Y31ZYnU7Z9+p9jyD/PFnSs/IO78p/eLr33iOHHmO19ndBpFpM+v5j72ospP+0z9DvOtsc/1/SibfzuqksgsNlx8itj0pcWIFAAAIuWvSCXJVF6RStuVSpomaYnU+ceJO1d+UNz7rfPFQ+u/Kh679b/Eoz/9T7HxW58X//I3fynOOfFosfi4SeLwcZ1WIn1IlJsiZeJ1T+HE+EaKlOsX8TZBpG78h7li8zfOFZu/ca7YcPEpsT/XrVZrLYcdAABEynmRipOpvCKlezlvFPtT96liW/sumC3uuvgvxYM3/pt49uG7xJ5dT4gX9j4jXnjhafHMUw+Jn2++QVz5+eXi3W9/g2hvb2cvqiEixV6UudikaZHyofrch/2oKkRq85rFtU6j4kSKxj4AAETKS5FSZaqISB0+rjOxeKIIW//xJNH/4y+KXU/dL/bu2zlIcvbu3yV2v/CM+OWdt4gP/+WHxNiusUhUoJMTtewAiTIjqbpTktCKJly/iLcqkaqz+lwWqc3fOBeRAgBApPwXKbl8oohIyRG/0jJ1wWzx318/Rzz74J1DJErm2eefEles/Yp461unFppKJe1FceB2s7mPvahq96SWLegOTqR82I9CpKg+BwBApDwUqUimiopUW1ubmD9tfGmZuuOzc0T/j78o9jz/eOp9V3v37xJ33b1VnHTy+0RHRwflEgGL1KD9CSSqkj2p0KrP5Yt416+Yi0g5JFIUTQAAIFKVi5S6L2LqsPXp5UtLlTfIk6mohEK32vy2844VW/9hvnj0Z1eLF/Y+kylSjz3xkPjfS84UnZ2dSFSAEbToQE+kz/xjmxU50xEpn6rPfdmPCl2k5LIJRAoAAJGy9sdw5cKpmYcZGyIlL6UXRd6ZipOqy0+dOjCx+t4nZwz8b5efOlVc8cnjxZbr/13sfuHZTJHa8fRj4m//bpkYPXp0KYliL8rdwgkkyl2R8rGxz7ZEuCpSVTYWZu6qSSK14eJTECkAAEQq/yFE95BStUhFMtWaOb20UKnTKR3Ofu9bxA+/eal4fvdTYu/+Xaki9fBjD4jFH/+YGDlyJBIVuEhx6a4Z5J/rJolUVRIhT+mKSJTOblpoIiU/ZuxHAQAgUt6LlCxU8l5FGaGaMalr0A5VEp9ovVZ86uzTRf9D94kX9j0fK1N79+8Se17cKX526ybxzj9tieHDhyNRAYsUe1HVF06sXTJDO1rsU9GE7c9Tdj/KxN8O30SKxj4AAETKmkilHVKqvJjUhFDFyZVM9M+6u18vvn7lV8XjTz4sdu99Tuzdv2sQu/c+J+5/8D7x2c/1isMPPwyJCnhqgkTZ+zlOi56FVDRRVaxPlsuisb4qRMp22UYWm760eECkNn1pMY19AACIlNk/hrrv9lYpUraEKo6Ojg4xa9ax4gur/llsvv0W8egTD4pndu4Qz+zcIR5+9AFx8y0bRO9nPy3eMvXNYsSIEUhUwCLFXlQ9e1IhipRtgTCxH1WFSNX9fKjNfexHAQAgUkb/GOoeUuoQqaqEauTIkeLIiUeK9859jzj375aJi1f9s/iXL6wUZ//1UtF65/FiwoRDU2vPkSj/pybsRdmPThYVKZ8a+7Zcea7YvGax9c8jPyZZ9fKIFCIFAIBINVik5EOviWKKtOnUAQccIA455BBxyKsPEQceeGDmvVFIlN9EzZFMo+oRKZ3CA98a+6qIs5Xdj7IpUpFEIVIAAIgUIuWQSJlu+isLEgWgP02Om5yE1thXBXIce8K4zsIiZbtsw0WROuP9x1I0AQCASDVbpOoWqtnHH5dYlY5EAegXTmT9/vKtsa8KTOxHIVKIFAAAItVwkapDqJAogPzRyaTpSdbvL5+KJqpC/n1UdD+qCpGqYlcsbwX66vNPEW1tbf19fX1TOOQAACBSRkWqZ1KXlyJVVTFFUpQPiQIotieVdYcUImVnP6oKkar7Dqk4kXp5T4r9KAAARMq8SKXtIPgiUjamVGlTqBd/s1v8z5N3cGAGKCBSITX2+RTra8JlvIgUAAAihUg5MKVKm0IhUQAbtO/qUg//FE0gUlWL1DcuOe/jHHAAABApRKqi+vQ4iXrpuXs5JAOULJxApOqJ9TVJpDZ9afEgkXrkruveyAEHAACRQqRKxP7yTKlUgWIKBVBMpNTCCRr7whKpzWsWO9PYl9Tct+361SdwwAEAQKQQqYqmVMgTgPk9KRr76on1NaX6PEmkNn/j3KUccAAAEClEqgKpas2czkEYoGaRsjVB8Qm5eAORQqQAABApRMrp6F/fNas4BAMYFqlIArJ+d7EfZSfWZ0uk1q+Y69wdUtHfOEQKAACRQqQAGnihbe85i3LjQ+EEIqWPvC8Wd7GxCyLlYtFE9DpCpAAAEClECiBwYTJ195mMC9PVOJGiaKKe/ShECpECAECkECmAIJvtbNGaOb02oeq7ZtWQaBpFE8X2o+ZMG++kSLnY2BcJ+RnvH3Sf1NJdO7Z3cMgBAECkKhEp+d1hRArAvmi0Zk4vHNeT44BxE666hEr+GiiaqG8/ytbj6WLRRPQ6UkSKu6QAABCp6kRK/Xc4+AL4P/GqepdK/tzLFnSzH1VTrA+RQqQAABApRAoASgpVlfX+eaJ9iJRfIuXyflRbW5uY8aaJXMoLAIBImc+6VylSv3+k7w883Cd+//D6GPoG8/K/zwEYwCxy5K8qmZIlLu13F0UT9mJ9C2dPFGuXzGicSFE4AQCASHkpUq/I03rx+4cifpKA9O/IcoVQAVjdy6oi5qc291E0UX3tua3HU471rV8x17lJHiIFAIBIeSVSQwXqJ+L3D6aQJlUPIVUQVrzOlTuhZJmyXUChxgopmsj3+9lEW18VIuXiY9fW1ia2Xb+a5j4AAETKfZF6RaASpKn/xlfQFitJsCSp4mAOvshT2Tui5CY/GzE/2xE/tZmQ/ajqY302RGr9irkDErV5zWInK+NbrZZ45K7rKJwAAECk3BapWImSxUmXTKl6WaiYUEGD74YyJVZVRfwQqWKxPhMlE00tmmhraxO9vb2IFAAAIuW2SA2RqDRReuAGA1LFdAqaK1FJYlX2663zLimKJuy19VUhUq7sR6l3H/b29i7atWN7x+ZvnLuU5j4AAETKOZFKlagHbshHLqGSiimQKXD0gl0fhKqKqVRWBTpFE3ZjfTZEyof9qN7e3kVCiDZZpCicAABApJwQqYFK84fWZ0rU/i3fFvu2fFv87oEbYtGSKmQKPKBOkYp2nvIUSFQxlZL3xOJ+f8l7LexHmW3rs1004ep+FCIFAIBIeSBS64fuRMWI0gubvil23/jVRJGKJKuQTBH1A0fvaqoTXZmqog496y4p9qPsxfpsiJQP+1FpIkVzHwAAIlWrSCVOoxJEafcNXxW7b/iqeOnudeJ3918/iJe2rRO7b/yqeGnbuvgJlfZkqg+ZgsbuSZWRKdsNfml3SbEfFS8CpkRq2YLuRu5HySL1yF3XvZHCCQAARMoxkVqfW6T23XHNEJHad8c1r/yzpMhfqkwNvciXAz3UsR/lyjQqr0zZvlcq7S4p9qPif9+yH1Xu8UOkAAAQKXdFKppGPTQ01jcgU/dfP/D/DojU7RkiFf3vikztvvFr8TL1EDIFCJQJmbIZ79MVqSZPo+T9HpOxPpsi5dJ+VGz5ymCRorkPAACRMhPVsCJS0gRpQKReZpBIbb9+ENE/233DV2NFat+Wb4vdN35N7N/y7fSpFLtS0OAYX1mZkmWwykt52Y8a+jjMmTbei4t4Xd6PkkWKwgkAAETK6DuMdYnU7hu+qidSklD9QaS+OlSkmEoBUyhjMiVLoY14X5ZIEesz39Znu2jClf2ouFgfIgUAgEjVKlLqHyUdkfpdgki9cPM3E0Vq3+3XDBap6J9F0b+XRWrvpm8N3ZdCpKBhd0WVpa54X9xdUuxH+Rfr27xmsRf7UapIbbt+9Qk09wEAIFLuipS6IyXJVLQHlSlSikTJIrX7xq8lF08Q7wPLPHzPTeKXd94kbtm80Rinn/FRcfoZHxVHz5he6T1TWbJjo70vS6SaKlFq25zJaVQT96M+fPyRsSJF4QQAACLljkjptvYVEamEaF+sSKVd0vswIgVmBMqkPKVx2epLxOlnfNS6TCVNnGxezhsnUuxH2Y31mRYpF/ejVBFFpAAAEClPREoj3veyTGmL1P2IFPghUJH0RKSJy9Ezpg/8e5etvkR7WlX1vhQiFcbdURFrl8ywth/lcm18nEjt2rG9gz0pAABEym2RiiudUCrQB4mSUjihSpTc2odIQd0CZXJiFMlVlljZiv3FTaXk/S/Tj6tczhH9Lmv6fpTNWJ+Nx9TF/ai4/bI4kaJwAgAAkapNpNSdid8/0ifF+xSRSpCpl7atEy9tWzd06nT/9eKlu78vXrr7+7H15/tlkUq9mDe+cIJdKdDh+fs3xO5AVRW3O3rG9ESpumz1JZUVT9gSKXnahUjZj/XZeExdrz1XX1eIFAAAIuWESMW9ez1oT0pTpn4X0+gXN4WS+e22dX8QqZ8gUmBPouLkpcoSCB2hMv31xP1cVyVS8tdhoxSh6bE+E/cNJomUq7XnKxdOTRUptbmPPSkAAESqXpHKkqm4Jj9NZBH77T0/EL+95wfxEoVIgUGJqkugVE4/46OV7E4lCY9NkZLjWE3dj4rb7fGhsc+ltr6411GaSFE4AQCASDkjUrlkKmtCFSNPA8R9rKRpFCIFBSXq/gfvqyTCl3c6ZTvqp5ZOVCFSccUTTKP8ECmXY32IFAAAItVWRRNTlkipS9BpF3QOFE+oMqUhVJlkSdSDSRKFSEE6zzxxr7j/wfvEi7/ZLX58/Q/F7OOPc/ICXdsyFXdnlI17pBCp5N+vc6aNNy5Sthr7XH0MI3FMEyma+wAAECkrsZKsf54mUoVkqgxJkT51GsWlvJDA/j2PiRd/s3tAolwUqCplqorHPEmkmjiNkiNpNkombMjpzV84xalpVNLfsDSRonACAACRshIrKStS8TKlCJUJqdKSKKZREI5EVSFTOj/fNkQq7ndP06ZRNmJ9TZjyJU01ESkAAESqUpGK+5iqSMVd3pkpU/J0KqKIVD34kwSBkiQqYRqFSEGSRLka5UvDVpufjShf1qW8TY312S6ZsLUf5fr9W7oipexJ0dwHAIBI2RepPIelVwooYqZTcUKly0P5JQqRgoj/efIOryUqwtbFvbpvlpgUKUomuhApw9H0nCJF4QQAACLllkglCtUgqYoRq7T/f5JAxUkU0ygIVKLSatHLRvyqiPc1PdZXxTTKRtGE6zJaQKSWIlIAAIiU0yKlL1TrU6RJZwqFREH1EjV82DAxeuQIccCokWL0yA4xvL3dzMcdPlyMHj1KHHjggeKAA8aIESNGaE+lytS3VxHva3qsr4ppVOiPa1qsT0ek2JMCAECkvBKpIUKlSlWsWCXwcBzE+aA6iRrW3i4mHPIqcewx08SHFvyZWPj+94oT3vnHYurrJ4quMZ2iveDH7ezsFJMmTRTv+rM/Fad/bJH46785S5zx8Y+J97x3jpgyZfIgoUralbpl88bSUymbkym5cKLp0ygbledNEKk8jbOIFAAAIlWLSNmsRx4qVH0JgpRGX+wUComCuHIJU+18w9rbxesmHi7O+thfiqu/erG45cdXiZ9df7X4f9+4THxm2Rnine94q3jVAfllqqurS7x7zp+Jf/7CSrHppzeJ+/vvFY8+8aDof3i7+Nltm8S/XPx5Mfv448To0aNT431lp1Jp0yoTO1RNjvXJj6etyvMm7EfleRNQV6R27djeweEHAACRKvSOXtwf3irvmRkkVkOmVX3pPIJEQTUS1dbWJg4ed6A449T3i40//JZ45J5N4qn7bxdP3X+7eOSeW8Qt1/67uPCvF4lp3UeKkcPbc0nUKR/+C3Ht9T8QO55+VOx5cafYu3/XAHte3Cke2/GQuPKbXxczZ/6xGD58eGq8r+xUylb0r++aVY2N9VVxAW8TRCrpEt68IkXhBAAAImUtGqGKVBVL6KlilQLCAFk15+Z2otrFtKNeK/7t4s+Kh7fdLJ5+4A7xTP8W8Uz/FvH0A3eIh+7eKP7jS/8kTpg9Q7xqzEitjzlixAjx3rnvEdffeK3YuftpsXf/roGvO2Lv/l3ihX3Pi0efeEh8bsVnxOFHHJ4Z7zMxlWrNnD4Q9TMxjWrNnN7YWF8VF/A2oWhC528XIgUAgEgl/pFctqA7eJECcLFcYmTHMPGuY2eIH/7nGvHk9tsGJCpix69vFTd85yviw/OPF+O7RmvF+173uteKL/3b5eLp556IlShZpva8uFOsv+kGMfPYPxHDhw9PjfeVmUrZLppoWqyvymlU6JM+nbISHZHatWN7hxzv23b96hM4/AAANECkTMQ2EClogkSZjPS1tbWJzo5h4t2zZogf/eca8eSvbx0iUk/ct1n86KrLxZ+/51hxyNhRmSI1atQo8ed/8UHxi213ihf2PZ8oUbJMbe//lTjlwx8SnZ2d4ugZ01NFquhUynbJRNNifVVOo0J+bHVifboiReEEAAAiZU2k1D9YiBQ0OdIX0TGsXRz9lteLr13yOfHQL28aEu379ZYbxZp/Ol8cN/0N4sDO4Zkf74gjjhArP/+PmdMoWaQe2/GwOPN/f0IccMABmXtSRadSNp6Xpsb61N+lNivPQ9+P0nkDEJECAECkahcp9Z+b2I8AqFKibFy6297WJg49aKw449STxHXX/Ju4/+frxeP3/kw8fu/PxL13XC/+6+urxEf//L1iyoRxYsSw9LKJYcOGibcfM0P88MfrxJ4Xd2ZKVCRSjz/5iPjkkjMHRCptT6roVMr08yKXTDQt1idPo2xewNsEkdK9g0xXpLZdv/oEWaTYkwIAQKRK3wpv6w4pgKoifRdceJ6V1rpoKvX6iRPERz74XnHx3y8TV3/l8+KqL68UKy88S5x64jvFGyeOF6NHDMv8OKNHjxZ/eeop4r/v/aXWNCoSqUce7xeLPvpXYtSoUZk16Lds3iguW31J7SIlx/pCr+aucxpFrC+fSFE4AQCASHlzGS+ATWztRSXJ1CFdY8RbXnu4mHX0UWL2jDeJnte/Rhx+0AFiVMcwrY8xYcKh4jOf/bR48unHtCQqEqn77r9HnHTyiaKjo0NLpG7ZvFEcPWN6rSKlE8diGoVImYj1FRCppYgUAAAi5c1lvAC+RfqSYn4jhrWLzo5hYtSIYWLk8HYxrF3/v3/Tm44S3/z3tWLXC8/mEqlbb/+pmHXcsaK9vV1bpPLG+yiZ8HMaZaLR1fdYXx6RYk8KAACRsi5SFE1AU1v6rElYe7s4dtaxou+mG7Xa+iL2vLhT/L91/yWOetNRAx9LR6TyxvtslUw0KdZXxzQq1MdXldI80ytECgAAkUKkADIifWVb+g4f1ylmTOoSh4/rtC5Sw4cPF+957xxx+9bN2vtRL/5mt3j2+SfFP39hpTh0wqG5RCpvex93R/k3jQr5Il5ZSnVeR2VEateO7R0cggAAECntP/KqSFF9Dr7w0nP3lo70HT6uUyw+buIAM5RJgi2Rmr/gBHHX3VtzFU08+Mj94uOfOGOgsS+PSOXZkyLW5980KuTHN0+sL69IUTgBANAwkaq6+pwDO7i+G1U00jd/2vhBElWVSA0bNky8d+4cseXO27SjfS/se17csnmjaLVmi+HDh+cWqTx7UpRM+DeNCjXWl6dkApECAECkECmAnLtRRaZRcRJVlUi1t7eLP3rHMeKGn/xY7N77nNY06tnnnxRrvrxaTJkyedDHclWk5LujmEYhUiYeT93XUR6R2rVjewd7UgAAiJQxkVIPAhzaweVpVJE7o5IkqiqRamtrE1OmTBZrvrxaPPXs46nxvr37d4k9L+4Ud951u/ir0xaKMWPGeCFSTSyZqGsa1ZRYn+7rSPkb15/19xaRAgBApAq/Y5r2h6s1czqHdnC6ZMKkRFUpUmPGjBF/ddpHxK13/FTs3vtcrEzt3b9LvLDvedH/8K/F//nHFeKoNx01UHvuukg1MdYn/26dMK6zMokKVaRkIcpTVoJIAQAgUtb+YKrvmNLYB77G+vJOo7IkqkqRamtrE697/evEp3svEHfdvVXs3P202Lt/1yB2vfCs+PUDvxKXXHaxeMc7/kh0dg5tFNSRqKpFqoklE+o0as608cT6Ki6ZKCpSyp7UUvakAAAQqUJ/nNTYHyIFLrf15ZGWGZO6MiWqapEaMWKEmPrWt4i//btl4gfXfl/86r67xaOPPygeeaxf3HPvL8S6H35PnPu354ipU98iRo4cEfsxXBQpplHVTqNCrD3Pe3eUQZGicAIAAJEyI1J916zi4A5OxvryNPWpFeeuiFRbW5vo6OgQr3nNEeKdf9oSiz/+MfGp8/5W/O3fLRMf+av/Jf5k5h+Lgw8+eEicz2WRkqdRTbk7Sv29WeU0KtSJX5GSiZIiNRDv23b96hM4CAEAIFK575CyeTEngEmRyiMruhJVh0jJQjV27IHi4IMPFq866FVi9OjRqQKVZz8qj0iVnUIXKQcIKYJW9TSqCbG+vN9jXpFiTwoAAJEq9M6p+gcKkQIfYn15dqN09qJk5k8bX4tIFcF1kWIaRayvypIJRAoAAJFKFallC7qtVZ+r0yr2o8BVkTIZ6btofre4/NSp4vJTp4qL5neLxcdNFIeP6/RCpHQlqiqRamLJRJ3TqFAf47JTzSIite361SfIIrVrx/YODkMAAIGJVNkYR5pIUTQBPiAf1vNOoyJp+t4nZ4jbzjs2lovmd3szlbIhUi5No1yPrdU9jQox1lemZKKMSFE4AQCASJW6Q4qLeMEHiu5GXTS/O1GeZL73yRleTKXyxPpu2bxRHD1julWRMj2Nii6zZRrVrFhfmZIJRAoAAJGyKlIUTTSHvmtWid5zFomd1/WK39773WC+pyJ157oSJU+lXJepy1ZfkkukdD5mmQu4TZZMRIdpXw780e9TYn31T6OKitSuHds72JMCAECkCokUsb7wJCp6Lnde1yt2Xtfb2FhfXonyRabySJSuSBX9uTdZeR79LnJZFNQDfzQ9I9ZnLipZ5vkvIlIUTgAAIFLGRIr7o8KQqHUrTxsQqRCmUq2Z03OXTBSRKNdl6oILz7MiUkV/7k1No2RBYRrVvFifqR07RAoAAJEyLlJpd0gR6wuHSDZm9UwekKid1/WKfVuvaMx+VBTru/zUqaVEqqhMHT6uc+BrsHEv1Yu/2W1FpMqKe9lplA/3T9VdMBFqrC+tCKkqkVL2pJayJwUAgEhp/aEytScB7kxsZIlqmkhFsb6yEiUXUERCNWNSVyxp91WZlKjZxx+XW6R0GvuK/tzLr7kyv5+iKU9ZGQu9YCLUWJ/6ejT48YqKFIUTAACIVLZIsR8VdqQvlGhfnv0oU9OoJKmS75zSwaRI/fj6H1oRqSI/96amUfK0nGkUJRMmXgclRGog3rft+tUncCACAECkcokUUuL3tEaN9O28rlfsufniRhVN2BSpiMtPnVq5SEXTqLwiZSvWZ2oa5cMlvuphv65pVNlL2X3YOTPxWigiUuxJAQAELlJlFoyT7pAi1sc0KiSRinaTdCZLaRfz6qAjUSYv940k6slnHteWqMtWX2Il1qdW0Yce6XOhYCLEWF/cNAqRAgBApKyIlOnGPvWPGLE+v2VKlagQ9qJsiFR06W5ZkdKJ95kqm4gifS/+Zre4/8H7ao/1ydOoor+bfIn0uVB3HmqsTxbUVqslWq2WcZHq6+ubovu3d9v1q0+QRWrXju0dHIoAABCpWJEi1hcWv733u2Lf1iuCEigbIhUJkDpdyhsH1In3mRCpCy48b0Ci8oqUjVifqWmUqba/JhRMhBrrU4Wnt7d3kckGyLa2NtHb27uIwgkAAETKePX5oHcDifVBA0QqmkbJ0hQJUd4JVVUiJUtUnv0oW7E++bko+ntJ/t3jUy13XQUTId4dJT+2rVZrrRCiLRKpsq8LRAoAAJEyJlLqYSCK0RDrg6aJVNI0Ku6/ufzUqalTKh2RMhnpyytStmJ9ZdvV5N9Hru/8uBLpCzHWFxe/q1ukdu3Y3sGeFAAAIpXZ2EesD5omUmnTqIvmd8dKV50iJbf0FSmasBHrk5+HIvEreTpOwURzSybiplEuiBSFEwAAiJSWSBHrgxAv5I1EKi6mFxfhS4r1xUlXXpEqG+uLm0bp7kfZivWVnUb5EulT49B1RvqaMo2SRKofkQIAQKScEam4W+OJ9UHIIhUnQBfN7x4yeUqL9WXtTdkUqTiJevE3u8Vdd281Fuvru2ZVpdMoWaJcn7DIX2udBRMhlkwkTaMG/gC/LFKm/ublFSllT2ope1IAAIjUkHgKsT4IWaTiono6Ub+0Xaq8ImUy0pd3P8pGrK/MPT8+RfpcKpgIsWQiq5rcMZGicAIAoMkiFdfYR6wPfES+uyiJw8d1Jk6Z8kyddGQs7R6potOoNInS3Y+yUTKh7qiVOdh6dNCvvWCiadMoh0RqKSIFAIBIxTb2lYn3ALheOBHJTFadeSRSSf/7wKTqgtni9t53iS1/P1ds+dxccXvvn4nbLphtZRqVFOnLsx/l2jSKSB/TqDwX5dYtUuxJAQAgUokiRawPQp9Kpe1JqdG+tFKK286fJe74zLvFLy75iLj33y8QD/xwlej/8RfF9u/+X7Fp1WLxNwveJhbPnjREouZPG29conT3o3SmUXmn0GWmUfLvG9dLE1wrmGjiNEoWqTIRUPlzIVIAAIhU4T+qpu+QAaiTvmtWGY33JfH/lhwjtqw4Qfzqm58Sj9/+PfHcY9vE7p2Pij3PPy52PfOguP8XN4urLusVn/qLmeLjkkzZkijd/Sidz5V3Cl10GqWKCZE+mvqyplGmKtBNi9SuHds7OBwBAHgsUkUjMerhgGkUNCHiF4mNTulEHFv+/r3iv9f+rXjy7vViz64dYu/+XWLv/l3ixd/sFnv37xJ79j4nHu6/W1y9+nPi/5vfY12idGJ9rk2j4tpCfSmYqFuimnJvVBUilfX5KJwAAECkck+kKJmAkCN+8lQqa1dqCBceL375r6eLxzZ/W+zZ9cSAQKnseXGnuG/breIL55wqTnzbYdYkSifWp3NvVNlpVJ6oVU9M2QaRPnajsqZRNkSqra2tH5ECAECkSr/LSskEIFPZ3PHZOeK+qz8rnnvkl2LvvucT5Wbv/l3i2eceE3//mfPFgWMPNNrQlzfWd/SM6canUWqMUvd3kPx7Z1bPZOdFyrWCiabuRjkoUgPxvm3Xrz6BwxEAACJFrA8aK1NZ5RMRW/9hvuj/8Wqx69mHEqdREc/veUZ88fJLxavHv9qaRGXF+nQifUXePJEfW91plDzdmdUzWSxf2HK6aMK1SF+Isb480yhJpEo195UVKQonAAAQKUomgIt6X2b+tPGDmvUuP3WquPzUqbGTqq3/+D7x4I1fFrufeyRVpPbu3yWe3/OMuPxL/yoOPXS8FYnKivXpRvqqmkbJ/826lac5LVIuRvpCK5kouquESAEAIFLOihSHb2hak180nYqq0dM458Tp4qp//ax47JF7M0XqyWceFxf1XqAd7csrUVmxPp1IX1XTKDkit27laWLndb0D0T4XBcG1SF9o0yhFVLWmUS6LFM19AAANEin13VamUcBlvUPvm4oTq6VzjhJfvOAMccvNN4hdLzybKFEv7Hte/PyXW8T8BSeI4cOHW5GotFifrkRVMY2SpWRWz2Sx87pesfO6Xmcv4nUx0hfaNEp+TeRtznNBpCicAABosEgl7Uf5UjKB8IGNy3p1GD58uHjL1LeI3s9+Wvz3vb8Uu/c+N1B/HvHCvufFI4/3i5X//H/FYYdNsCJRabE+W5G+uMcxb7lEJFHrVp7mbNGEi5E+plHmLuVVPz8iBQCASBV+N9C3yvNouoBMQV0yNXbsWPGOd/yR+If/8zlx6+0/FU889YjYuftp8fyeZ8TTzz0hfnnPz8Xn/+WfxNSpbxHt7e1WJCop1mdTotRpVNZEQJ18RxK187regf0o10TKxUhfaJXnZaZRskiVee0YEima+wAAQhApE/tRvogJIgW296WyaG9vFwceeKB481veLD78lx8Sn/uHz4qvfO3L4utXfk380+f/r/jwX54iJk+ZnBnpu+DC8wpLVFKsz0bVeZFplCpR0V5UhIv7UerX7IpEhVR5XnYaZaoCXf4aent7F1E4AQCASJXaj/Jt3wWRgjplqq2tTXR0dIgDDzxQTJhwqJg0aZI48sgjxcEHHyxGjBhh7LLdPNMomxKVdxqVJlHyfpRLIuVipC/gaVRhgTEsUsKUSFE4AQDQAJGK24/ySUoikfIlighhy5TNi3bzTKN07osq8zOTZxqVVC4Rtx/lyu6P/DX3TOpiGlXBNKro30BHRYo9KQCAJohU3H6ULyUTagMbUgA+yVSZKF/Ek888XmgvqszPeJ5pVJZEubgfJR/wXdqLYhqVLVJFRdyESFE4AQDQQJHytWQCkQJfZWr28ceVjvIlNfVlSZSJabPuNCqpoU8l2o9yRaRcjfQxjcoWqaLRUBsiReEEAEDgIhW3H+XTNEoVKd++dmieTJkSqLwS1Zo53cjPh+40KqtcwtX9KHmC5pJEMY1Kpq+vb0rZu6RsiBSFEwAAgYuUuh/l654RFwiD6zJlcgoVF+lLK5Yw+XOhM43KI1HyflTdIuVypC+kaZSJC3BNX8prQqR27djeQXMfAECDRMr3aZQqUhROgGsyZVqg8kiU6TcWdKZReSTKpf0olyUqtGmUiapxF0WKCnQAgAaJlHrg8VlC2JMC12TKhkDpSpStn2X182T9Tlm+sKUtUXWKlPp1uxbpYxpl/1JeWyJF4QQAgGcipftHV431+bxfJB94ifdB3TJloo0vay8qbh/K1B5U1psVSe1oOg19cQUTdcf6XN6LculeLVenUS6J1LbrV59Acx8AgMcipVv/qtae+37glXc3EACoWqZsTaB0JMqmQMVNo3omdRWWKHknygWRkr9uFyN9rtyr5fI0SgjR1mq11pYRKflrKyNSVKADADREpHy9gFfnHXOmUlClTNmcQMU19EXyVNXrPGsapStR6hSq7lifOpV3TaKYRlV3Ka8pyUOkAAAaIFIhxfqS3jXn8A9VyZRtkYok6uF7bqr9e1anUToStW7laakSVYdIub4XFXDduWi1WmtN/x10TKSWcpcUAEDAIhVarI9dKahTpmzvRNUhUEl15/LvF12JyopFVj15USWqZ1KXcxIV8uW7fX19U2yKVJE4pMnYIc19AACBi1RosT52paBOmbIhUi89d6946bl7nfo+5WmUjkSprXzyx5H/+6pFSv5aXNyLYhpVTqSKvJ4QKQAAREpLpNR3Y0Oc3DCVgiol46Xn7i0tTvv3PFa7OOlevqtOtPPsQ0URurpiferX7qJEhVQwUcU0CpECAECkKhMp9RAT+iE3lP0v8IP/efKOgWmSDv/z5B1eTN2iw6kqIuqFu0n7UBPGdQ7aQ6pDpNSv3cW9qNAKJqTH3HjBhExfX9+UMpfy2hSpXTu2d3BYAgAIRKRCuYRX9930kL9HgCqmUVGkT51mx0lU3BRKjc/VEevzRaJCivTZrDtPu0uqbpGiuQ8AIFCRakKsL+4wSMQvXH7/SN8QeFzMTqNWLpyaKVFJ+1BxwjJhXKcoUwxQtlzC1b2okAombNedp4lU3D1niBQAACJVWqSaEOuLuwOHw3HY4vT7hxUQK2PXCES/U9IkKmsfSqXKWJ8vEhVwpM9awUSSSBV5XZkUqV07tnfI8T5ECgAgEJFqSqwPAhaoAWFaL37/0Po//L/q/z0IhKrs5bt5JSpNVuQDdpHJQagSRcFEOCKl7kkhUgAAHolUU2vPoQkC9bIwPfSTl/9fDWKkisdVfxqVJFFppRJpwlDlfpQve1GhRfrkgomqplGIFAAAImVVpJoY64MAJOrhPkme8qII1cD/zZRKZxqVJlFJ90NlSUNVsT5fJIqCCTsV6C6J1LbrV5/AYQkAwHORGpRZJ9YH3ghUjEQ9qEmcVKkTKqZUsQUTeSVKR1TmTBtfiUj5JFEUTIQvUtwlBQAQgEgR6wOvp1CKJO3+ydfEb+/5gfh9/43JaAnV0IKKptedR8zqmVy4ma+uWJ9PEhXaNKqOgokkkcq7c4ZIAQAgUomHE2J94JdIxUiUJEj7t35H7P7J18T+rd9JFylZph5Mifw1XKjiplGqROlcspsn1mejXEGVKJ2oIQUTdoo9qp5GqSKVV9RNi5RSgY5IAQD4LFK09YFfErV+6CQqRqT23vKtbJHSEaqGy5Q6jSrbzFdHrE8VNZcb+kKsO1dfH4jUYJHatWN7BwcmAAAPRappl/CCxyKlRvpiJGpApG78mth949fE7x+4YQj7t3wnf9wvQaaa0tTXmjld9J6zSOy5+WLjEmU71udTzXmI0yh1EohIcSkvAEAwIkWsD7yeRsXI0P4t304Uqeif7d/y7T/8b4WmU82cTP323u+WrjdPYsK4TiuxPh8lKqSCCfXxd0Ck+hEpAABEyphIEesDL0UqbhqlyNLuG78qfv/ADeJ3LyP/s72bvvWKYGlPp5otU9E06tov/nWpUomqas99lKjQCibUnbQ6RUq+S6rI1BORAgBouEip7/SqB42+a1ZxaAd/REoVH2nyFE2kfpcwkRoyrcojUw2M+e3bekXpevMqY32+SlRI0yj5eVXjfXWLVM+kLhdEikt5AQB8FilifRCuSH1V7L7xqwPTqIh9W7798j/7mvjttnVDd6iQqVhsSZSNtj5fJSqkgom4KaNLIlVk8mn6ImFECgDAc5GiZKI8vecsGoDHowqR+okhkfqq2BftSeWWqeSIX6jTKFsSZbqtz2eJCqVgQn0Oou8LkUKkAACCESn1jx2H9WLE3bGjylXfNasG/W+95ywaVClNpFK3tS9FpBQZkmUpTaR+FyNSu29MuMw3bSoV8K5U3E5U3juiqoj1xRUbuH7hbhMifbFv3CFSiBQAgM8iNeiWeUomjMtUHphm5ZhKJZVNxMiQrkj9LrbV7zvJk6khMhXuVEq9Q0qWHhOYmkb5LFEhFUzIz6m6h4RIJYsUl/ICAHgmUhzk7cT8kg6eaTCRKhDvezA93rf/ZWFSReqlbesSp1VDyih0In4xU6nQJcpUXE6O9RVZ/g9FokKZRiVF+lwSqVartRaRAgBApEqJFCUT1e1OqQe86GLT3nMWIVCFp1I/yZSpSIhUWfpdSuyvkEjFTKVClqhIekwIhHx3VNFYn+8SFVLBRFZpiAsiJV/Km3cnDZECAECkhsT6mEaBl1OpB/VEKrp4t4hIJe5JxU6lwhGprKmqKZEqG+sLQaJCifQl7UW5LFJ5JRaRAgBApIb8QWMqAt7uSmnIlLw3FQnTC5u+qSVS+7d+J0cVehgiFSdRs3omD/rfTQhE2ZKJECQqlEhf2l4UIpXMtutXn4BIAQB4JlLyHz1KJiAYmUq6UFcpoZBLJ9TIn3qZb9NEKk6ili9siZ3X9RovmpBjfXkjViFIVCiRvqy9qCQR6evrm9J0kXr5Ul5ECgDAJ5GiZALCqUPXmEzFyNTvEwRqkEj9pFkiFSdR61aeJnZe1yuWL2w5E+vz+Z6o0O6MyiNRNkQEkQIAQKQqFSlKJiD4Fr8cMpXE7p/oilQYZRNpEmVDpIrG+kKRqBAifepzofM8IlKIFACA1yJFyQSEKVOaMT9Nqdp7y7fE7p98Tey95VspJRNh1J9nSZQa6zMRoSsyjYqL85majvlWMBFFtOuMB6rPRR0iUkKk+hEpAABEKrdIMY2CYGVKrUUvIlPSPx8yjYpr6/N8GqUjUetWnmZ0P6rI3VFxEmXyUmCfIn3qY1F3Q1+eHTcXREq+lBeRAgBApLRFSv7jR8kEBF1AoStTeYibRikiFZpEqbE+E1G6vCUT6qHdd5EyKTBlLjE29TXkiSm6JlJ5RRSRAgBosEjJBxgqz6FRe1MmBCpRotZ7N43Slaid1/WKWT2TjUbp8khAnDhEv8d83I8qE+mLE8qqo31xz0eeCVtIImWidVAVqUfuuu6NHJoAABwUKTlOwzQKmhP1W19cph78SSMkalbP5FiBsrEfladkQj20R5/b1/2oogUTSbHGqmN9SUUfDRUpI/dgKSIlECkAAEdFSp5GUTIBtPrdmC5XsQKVIFEeiVReiZJjfSaidLoSkCRR6n5V6JE+9TGrK9aX1pbYJJGSnw+DIrUUkQIAcFik5MMHJRPQ6OmUKlRpPNRsiTK9HyX/HkoSi7gDuzwFkwXLpwt4i0T6VJlUZbSqe6iyKucRqXIgUgAAjosU0ygAeTq1XkOaFHkaJFDNkCg11ld2AiT/Hip6R5QaDQwx0hcX5YtkpepYX9zXogosIoVIAQAELVJMowA2DL13Kor85RKo5kiUyf2orMpz3Yt2fWzsKxPlk8sc5H9WVclEUsQSkUKkAAAaKVJMpACUyN/D6weLUxyKQDVBokzuR8kHcvXgrUpUmrD5th+VJ9KXVW0u/3NXJMpXkWq1WmsRKQAARCq3SNHYB5AhVgn49v2UkShVpMqKS5Ic5JEo34omdCN9cfE5deIk/ztVlEzESZ2JC4ZdEane3t5FRXbNECkAgIaLFHdIAYRPWYkyeX9U0jRKjbFlRQd9K5oo2soXd7BPm+jZlqisaaTvIpUnJolIAQA0VKTmzZvLVAoAiSq0H2V6GiUf1nXbAOWyihAifXkut62qZCJO7LIkWleklI+NSCFSAAB+iFR3d7e47NJVVKADIFFarFt5mhGRkuN40YG7iET5VDSRFemLi/KlxfWqKplIuvgXkUKkAAAaLVJnn7VUXHbpqkFTKUonAJAo2/tRauV50XieL/tRWRKlG+WrehqVJFE60opIIVIAAEGL1GWXrtpw2aWrxNlnLWUqBYBEVbIfpV7AW2bHSf5vXRaptEhf3O5RloRUMY1KkyhECpECAECkXhapyy5dJbq7uymdAECirO9HxYlDnihf0mTL1aKJJKFIE5U8e1Q2SiaSYoZ5ni9ECpECAGiMSFE6AYBEVbEflXfXxuf9qKRIX9zjoCtStivP4yRq5cKpuad/undlIVKIFACA9yJF6QQAEmV7P0qdapSdIrkuUnEykdTKpytS8n9vI9aXdHdVnkr6ohLiiEj1I1IAAIhUbpGS432UTgAgUab3o6Ionol9Jtf3o1SJymrl0xUpmyUTcZIX97Wbui/LNZF6+VqQ3CIlPzaIFABAQ0WK0gmAcCRq3crTjEmUqf2oCeM6jUmPyyKlRvp0CiV0BMlmyUSSRMVFERGp5AuFESkAgIaKlBrvYyoFgESpu1GuxOjyRs3qkCh1tyhtr0lHpGxNo7K+xiLS6rtI5XmMbXz9iBQAgOciRekEgNv0XbOqcolyZfrj6n5UFOmLi/KlCUaWJNmaRumIXpHnH5FCpAAAGi1SVKEDuC1R6s+rSYmSd6JcEyn18O+aRBW5GypLpPJ8LJMSVWQ/CpFCpAAAECnifQBIlIOxPhf3o5Yt6M4V5csjUrJsmKo8V6d6SR+36PSvzsY7RAoAAJFyQqQonQBojkSpUb6ootw1kZIv4nVBpCKJyhPlyyNSpi/gVb/WNDkrKq2IFCIFAIBIMZUCaIREyfdERRLl6vTHZbErKjxJImX6At6kC3d1vi5EqhqReuSu694oidTSXTu2d3BoAgDwQKSiu6TkO6WYSgGELVFqlC+SKBenPy6J1Jxp44dIVFHZSRIpkxfw5pUo9d/XbUfMI5Hy9+ejSNkQwZdFSkQixYEJAMATkZo3b65oa2sT8+bNHSRTlE4AhCdRca186mGZ/ahkiSoT5dMRKVVkqpSoMvtReUTKxmW2ZWi1WmsRKQAARKq0SEX/N1XoAOFJlBrli5MoF/ejXBApE1E+HZEyNY0qIlHq55enlCGLVG9v76I8jxEiBQCASMWKlLozxaEWwH+JWrfytNQon+vteHWKnckoX5romJxGFZWoMvtRoYiUjrwiUgAAiFSiSMnxPkonAPyWqLha87QDMvtR9qJ8aRE6U9OoMhJVdD8KkTIrUtuuX30CByYAAM9Eqru7W1x26Spx9llLmUoBVExr5nSjEhUX49M5HBPrsxflSxMpU9Mo9U6rPF9zmecfkaL6HACg0SKVBFMpAH8kKkmgdPZd2I+yF+XTuRy37DSqjESV2Y9aOHuiWLtkBiKFSAEANE+koimUWjZB6QSAHxK1buVpqQKlG9Eqc5AOIdZnM8qXJVJlp1FlJarMflTex8h3kbLx9SNSAAAei1ScWMl/LKhCB7C/DzWrZ7K2RC1f2EqUp6KH4SbvR9mO8umKVF4pKbMTlTUhQ6Sq+fp37djewWW8AAABiZTa3sdUCsC+RJUVpyJTqCRpKfLf+xjrqyrKpytSZT9GUfkrK60NFan+vr6+KTT2AQAgUiJrd4oDMEB5es9ZlChRUUQvIq5xL4sywtG0/agqo3w6EpTnc6tRvrITtLivpYkipSPRskhRfQ4AgEhpTaUonQAwvw9Vlp5JXeLPesaXFiAX96PkSZHtKF/PpC6rUT4dkapLosruR4UkUjrPg2mR2nb96hOoPgcACFCkuFMKwB2JmjCuU/RM6hpy0I2koMzUxuWLeE2KXdwUqooon06Uri6JKrsfVUKkjETjfBcpiiYAAAIVKeJ9AOb3oaLDqkySMGUdatXabBOxPhdEShYeU1+PDQkxeSFvXokyNUUrG+vM+zWYFpGy9PX1TWlra+vXfU0gUgAAiBTxPoCaJMpkkYMp2XB5P8rE46VG+SaM6xTLFnTXIlGqvGRNdOKa+UxO0cruR/kuUkKINlmk0p4P5bko/fXT2AcAELhIyfE+2vsAipdKFG3Ts71DpE46QtqPSoryFREAk+hOo2xLlPrxiwh5nsdRkXbvRMr010/RBABA4CLFnVIA5fehTBc4mIy+ldmPiurDTdelm3jc4iKT8tdZl0ipccw8EmW6VdBErLOpItVqtdYiUgAAiBR3SgEYivJVIVHyxMbExy5zEa+N79FE8UVcK1/ZSJrpCVDaZKmqfS4T08imipSJxkH2owAAPBKp7u5ucfZZS3OLlFo6wVQKQL9UwvX9oaL7W7aa/soUaKhRvrRpWR0iJX9vSZ/fVqlEVsSwqAyXmIAhUtJ+FCIFAOC4SEV/ALq7u0tNpSieAKi2VMLWFEgVDxcq04t+f3ECYrIkoYppVNz3UMXXU+Y5DEGk5Ar0qkRKLZrgkAQA4IlIReSZTqm7UsT8AJLvh7IlUXJkzcTnKBPtcqmJUH5cdHe2qhaptGmU7VIJnf2oos8hIsV+FABAI0TqQ8M7xJHt7YP+eM6bNxeZAnC0VKLKu5XyfMwykyyTsT7168jzPeiK1M1fOMWotKiCVEWphK39KETKjEhtu371CRySAAAcF6llHSPFso6R4kPDOwb9Ac0T9UuSKfamoOlRPpsSZeuup6ISIh/CTX7feUo0ikyh8orU+hVzxZYrzxXrV8w1FqHLkqiqJmWmXrtFRcpE613VIiVLYFmRomgCAMBTkYpkSp5O5SmiOPuspYPul2JvCiiV6KrsXiUb+0h55cz215P2MctMofIKwM1fOEVsufLcUlOppEhfVc18NvejyoiUibKGOkWKogkAgAaLVIQa9cuzNxUnU0T9gH0oe5E+k9OfMvE8GxIpfz1pd1aZuuC4CpFKKpioslTC5n4UIkXRBABAo0XKhkxF0ynifoBETaytntxW0UQd+1FxteZlP18VIqVOm+oolcj6uso+h0Wnc00WKYomAAACEqmyMpUU9WN/CtiHcjPSV6ZowlRJQdr3mna5rgmZXLag27pIqWIYJ1FVFUukiVSZ15Xu4xj3eZssUtuuX30CRRMAAAGJVFmZSiqiYIcKfKX3nEW1SpStyU9ZSatqPyouymfqc+nuIm1es7iQSKmRvrgoX10iZTKamXenC5GiaAIAIFiRMiFTWRMqeVLVe84i5AqI8lUc6Sv78W3vR82ZNt5YoURZAdhy5bmFRCquSEKN8rkiUlU8jiGIlDJRLCxS6n7Urh3bOzggAQAEIlJx9ehFZEpXqOLkiigguBblq1qibE1iyk67qtiPUqvVbTzuNkUqKcKntvLVJVIm96OaJFKmijLYjwIACFykTMpUEaFCrKCJUb6qIn1l9pyq2I+qolLepkjFTaPiCiXqECmTteeIFPtRAACIlKZM5bm0N4158+YWkqq4fSsVZABMR/ls3w9VR6SvzIW6tiZlqrjanv7ZEqm46GPc55KFpkqRMv36KiFS/X19fVOaKFLsRwEANESkbMmULFWmxCprD4vJFrgc5asq0ld2z8mWZEbfd1XiqnvwLypSSQIVdyivUqRMxvrKipRLf5yrEil1PwqRAgAIXKRsy1TVcpU01UKymELVFeWzHZszJURq5ND0nVZVPta2RKrIZKgukTLxOkek2I8CAECkNDC1L1V0xyoSrIgqJQvpYApV9V6U7a+jyOeqUvQQKbdrz4uIlPL5fRap/qIiJe9HIVIAAA0SqSqnUkWQJ1jd3d2VxAWZYlEoUXfUrsoyi6pih7bJc/gPSaRsiHCTRMrE185+FABAQ0VqWcdIMXPY8IE/JvPmzXVapuSpWXRRcHd3t9WJFlMsplAu70WVnSzVXcJRtUitXzE3KJGy8cZBKCLVarXW2hYp7o8CAGi4SKkX9lYd8SsiU5FEtbW1aUcHmWKxC1XXXlQVUldEpKqOHrogUjd/4ZRgRMp07XneiKTrItXW1tZvW6TYjwIAQKSM3i9VhUyVnaDZlCwEq74YnwsiYFtO5kwbH/sxi1SfN3E/KiSRslWrj0gVi/VxfxQAQENFSi2ekCNzLu9M2S7BML2TRUywnEAlTaFcmqTYjMpFkhb3/cpRQt3PG8p+VFNFynTteVmRcuky3jpEiv0oAIAGi5S8K2Vq8mNLpqoqxpCnVfPmzR2QrKqaBZs+2UqTJxd3emyLSdrHLfK4hLIf5YpIqVLjY6wPkcod62M/CgAAkYqfSsUJlYuxP5sV7eqELumxif55FXdlxV1M3BR5cvXQXyRal2cSFUla0vQt7+MT0n5UE0XKpgg3RaTKxhKpPQcAQKQSp1IfGt6ROKVyMfZnizJyabvwIoQIYd81qxL3nlzcgapaSuSPnXZYzvv5Q9qPytMyJ4vU+hVzvRUpm89f0e/ZZ5Eq8rUT6wMAQKQSSydmDhs+SLDkZr/QpSorwmfie65LsrJihDYkLJIlGd2vsWdSl9PTEtuTHZ0pV5E7pJoqUpFEbbnyXKs7S7ZFylZTZZ7HsskiRawPAACRSq1Cl0VKd5fKd6nKiuZVGW2UJasu0aoTlydQSQJjOtIn71ylPRZFpCik/ai1S2Y4J1K+xvoQKWrPAQAQKUV8kqQoS5Sy/r2kKZUsHq6LlU6BhIvfgzrRCkW4XJ9AVVUuoStRiFS+yU8IImVzmlhCpPqbJFLUngMANEiksqQoqXTiQ8M7tOKAaVMqV8Xq7LOWpk6furu7gynWUKWrqkKMUCdRVUqUjuTkPViHFOtrokjZnIKWESmX/jD39vYukkUqY7LX39fXN0X3Y+/asb1DjvWxHwUA0ACROrK9PbdI5Zlk5ZUqWayqFBad6VOTmgmzIoWmSHpMk54Pl4WqKomydbFuU/ejQhAp29PEEt+ziyKV+nwU/dqJ9QEANFCkdCdMReWraPwvbsfKxtTKx/he0wTOdaGqcicqz8eWxUjnYB1SrK9oY5/p6vO6RMr0zwYiRawPAACRSrkbyuSeVJFpVR6xKitXOu17TZ4++SZUrtwT5ZJEFRGjphZNhCBS6s9FnTHJsvcw+ShSxPoAABouUke2t2dOpmyIVFoRRt49miy5atL+U6gkPX9VT6fky3BdlKi8YqQKYVMu4rUtUurzYEOiVi6calWCly3obqRI5Sma4BJeAICGipR8MM3afZLlJu+elImpVZHJlU6RAgLlX0lG3PNZxXQqTqBclKiyIuWzROU9+FclUj2TuryM9eXdN2uiSBHrAwBoqEipkqErUrJQ6e5Y2ZpcFZlesf8UhlBVNZ1KEigbEwBTpRV5vkaX4pJ1Fk2sXzHXmuTYuozXdqyvjEi1Wq21oYsUl/ACADRcpOT9kzQpShOWI9vbK51S6VwejEA1N+5nQqbmTBsveiZ1JQqUjcILk81/RUWqSUUTthv7qhYpGxJcRqRcvkMqTqSKVJ8T6wMAaLhIXXbpKq3InixSaQUAdU2pPjS8I1WiEKhmTaeKSE7PpK4hMbcqBEqdeJk4EOs+Fur326SLeH0XqSokOK9Iya8nl0UqLmopixQlEwAAiFSmSEUH0SIildWoplNggUC5yaaNG4xTh0z1TOoaIhHRpCkiaeJUVeW6jfp09WNyEW8861fM9VqkqnjuSlzG67RIxT0feUWKaRQAACI1SIbSREr+A5n3TiYbsT8Eypws7d3zfCVsv+9XYtPGDeKaq6+qdG+qKBPGdVqd0NiqT89zwFZF0qWInu1Dv+3qc9siZXs/quQdUt6JVN6SDLlkgmkUAAAipSVS3d3dhS+5NTGlQqDycc3VV1UuTHmkyjWZiiZVPten5xEpV2N9iFS+WJ+DIqW9Z+SaSOkIICUTAACIVCGR0hWVrNrxIrtUCJSeNG2/71dOSZOOVJmcUiXF/JKo8/4pG02DuiKlRgB9F6m8h/7NaxZbFSn5eTAtUnG7fK6JlHN/mA2KFJXnAAANFakPDe8YIlKR8CSJlPzf5BWWs89amjmlynOHFQLl9qSpLKaif6pM1VnrnSRQtsRFV6Rc3Y9auXBqJSJlcz9KjbnZjvUhUuVEKk/bINMoAIAGi1QkJXJEL6v+PKloIi9ZUypVqmSBi7tIt4k7TSFJk86kymeZSiqzsP116AqSy/tReUUqb6zPZ5GKi/XZEvNQLuPNIVKZkUS5ZIJpFABAQ0VKnuRkXchrSqTySFWaQJ191lIieg2SqTITKlWmqth/SqpQt11ekVekXL2ItwqRst3YZ1Okkl5fpl9byxZ0ByNSfX19U3RFKk/lOdMoAABEKlOk5N0kG+KgK1UhC1QTJ05VTadUmTK5k5R1ca/N6nQdkUo7XLu6H7V2yYzcIpVXRmwXTdgUKfnjtlota89hSJfx9vb2LlLfXCgSSaTyHACg4SIVSVEkUvIh01TRhOm2tVD3oJg61SdTukUUKrr3TiXdY1UFOoKkTjXq+DrTpAiR0pv6vCwI/YhUPpEqUjTBBbwAAIjUEDmx0diXl6RpVIh7UMhTfTJVJEpatEK9ThEpIlKuXaqbR6TK7kf5JFLK89Yv7/4gUsVEauXCqVpfN9MoAABEakhMLquxz6ZIpbX5hRbjI7YXrkxFu0+uTHV0RKqKxrcyezlVipQNibIlUkqsb61NkSrT2OeTSMn7UUlfN9MoAABEKvYOKflep6yiCVNyc/ZZSxMPtCHF+K65+iqmT47JVN47plRcisCZEinX7o/KK1J5D/xVFE2oj7GtWF9cGx3V58VFKqPynGkUAECTRUq9Q0pnP8p0Y19ajC+UKRQC5ZdM+SBHJkXK9Yt4bYuUvB+1ec1ib0QqLtaHSJUXKZ2vm2kUAAAiNUSKomjdke3t1kWqCWUSCJQ/MiULvUvV31WIlMv7UWuXzMglUkVifVUUTdgQqbhYHyKVX6R6JnXFft3yY5p2AS+HHACAhotUJC5ZsT655a9M8UPoZRLsP/kpU65OZWyLlKsX8cqHd12RisTLtaIJ+XkoIiW6sT5bIlVEUH0RKfV1llU0wb1RAACIVKpI5Wn5MzGFCiXGh0D5LVNq2UmIIhUXW3T1It4iIlVESqoomjAtUkmxvjhJoLFPX6SyiibkadS261efwAEHAKDBIiVLkXyItCFSIZdJIFDhyFSIU6ksOXT1e44a+3RFSv73XSqaMC1S8tQkTlQQKTMixTQKAACR0hIpuTUvLdZXZD8q5CkUAhWeTIU2lVKLJHy6iFc+vOuIlKv7UeoEqaxIqbG+vr6+KWn7PyZ31UIRqVartTbu+UiLIzKNAgBApBKrz3X2o/KKVKhTKATKL665+qrGTqWyiiRcLprIK1JFpGTzmsWViJT6GJv6WHEHfhsiVUL2hoieE3+UpT0y3aIJplEAAIhUbPW5PDX60PCOTJHKKoUIdQqFQDGVCk2kfNiP0hGpIrE+dT9q/Yq5zouUEuuL3eNxQaSk112/k3+UY0QqrWhCmkZRdw4AgEgNni6Z3I8KcQqFQDV7KuXzvVJ5RMq16VsekSoS66uqaMKkSKWVTNgSqSKS6nJjX19f35Q4kUormiDSBwCASCWKlE6sL0uk1HfxQ5hCIVBh0cR7pdJEyuWLeONkw3Ssr4qLeE2LVNLdUTEi1W/qOS1TfZ70Nbp4h1RS0cSuHds7omkUkT4AAESqtEipMb6Q7oW65uqrEKhA2bRxQ+GSlBBFypf9qCyRKhrrq6poQhUgG3dHJUXX6hYpnxr7kqZokUghUQAAiNQQKZJJkyh5p0rnYl3fplDXXH2V2H7frxAOIn5BlU40QaRMxPp8ECmdWJ8qUiamqUUuOfZZpFycogEAIFKOi9SR7e2pIiVf3psU4/NxFwqBongi5NIJ+fCtHqpdLpqIO7ybjPVVuR9lQqSy7o5KK1OosbHPSZFSHxsfvmYAAETKcZFKa+tb1jFSHNnenihPPsb4ECimUk0onZBFSp2q+bQflSRSJmJ9tkVKfaxt3B3lqEh5U33u+tcMAIBIOSRScftROtOoEGJ8CBTkmUrJ8VUf431JIuVb0USSSBWN9VW5H2VCpHRKJmyIVGiNfUki5XpdOwAAIuWwSCWVTHxoeEfiJMrHPShKJCBv8YTv8b4kkfJtPypJpIrKTVUX8ZoQqTwlEy6JlMONfUNEiv0oAABEqrBIqbG+NIGKLvD1LcaHPEDROnSf431JU6cQRKporK/Ki3hVEZLrtm2UTNQd7XN91yiuaCLtIl4AAECk2rL2nXRjfBRJQBOnUj7H+5JEyuWiiaSyBVOxvvUr5lZaNCGLUF4xUUom+nUP+qZEKu9jnHaprasixX4UAAAilUuk4mJ9aVMo3wSKGB+YnEr5HO/TESkf9qPiRKqo2JS5iLfI5y1zGW+RaZQQoq3Vaq01MUXNK1Ku7xrFNfaxHwUAgEgVFqnQ9qCYQoHpqZR6Oa9P8b44YXK5aCIprqeKVJlYX9H9qOjxzCtDRfej8laeJ01eyjy/ee+Q8rFogv0oAABESluk5It1Q2niYwoFthv8fL2cN+7r9nE/ShWporG+MvtR0eNWlUjlrTy3IVJFxc9FKenr65uiihT7UQAAiFQukUrbgfKxSIIpFFRxr5Qc7/NVpKJJWggiVVSiyuxHFW3dKypSeSvP6xYpn4omotIP9qMAABApbZHKivH5JlBMoaCqeJ8sUq6VM+iKlK9FE6pIlZlGlbmI18BUqZJplCmRKlM04aKUxBVNuB5FBAAAR0QqaRLla4yPKRRUXTrhY+FElkj5UDShilSZBr2iF/HKj1tRkcrztZeZRtUlUj7tR0Xfn8sNgwAA4IhIhdTGx71QgEgVFymXiybSDu6RSJWZRqn7Ua6KVNlplCmRCrloIno9MY0CAECkdP6ABCFQl126iikU1BbvC0GkfNyPsiVSVRdG6IpU2WmUKZEKaT8qrmiCtj4AAEQql0j5LFBMocClPSlfKtB9EqmsRjiTsb6iIhWVFNi6Q8rENKpmker3oWiCWB8AACKlja97UBRKgEvxPt+a+9QYn69FE5FIlbk7quxFvEUv1JUfb51pmolplCoORYQ5tP0o+YJi5U0GYn0AAIhUOpddumoDUT6AZolU3PTJx/2o6y44Nve+jq39KJt3SJmaRkki1Y9IxRdNEOsDAECkghYponzg2n1SvouUr0UTpih6EW9VImVqGpUkD03dj4ormuDuKAAARCpYkSLKBy7uSfkuUr7uR5mgzH6Uica+rP/W5DQKkUqfzkk/B8T6AAAQqbBEiigfIFKIlE2RyhPrM9XYl/Xfmp5GlRGpvLtospi4fhHvh48/0vnpGQAAIFJcsAtBi5QPFeiqOLlaNFF1rK8OkUqTReV5MiYjRUUq5P0oG48zAAAgUuxDAWQUToQkUk3aj1q/Ym7hWF+Zxj758U/6b6V9HeNV3FWIlA/FDfLj8NbJxPoAABCpgESKfShApKoXKZfuwfJlP6pM0UTSf6tOo2w21bEf1UasDwAAkQpHpIjygW8V6KGIVJP2ozavWVw41mdKpOKmPDanUVWJlOtROfU+LWJ9AACIVBAihUSBjxXoPolUkkS59LW7XHuuTozy/HeKJGV+bBuxuCIi1ZT9KA4mAACIlJciRakE+Fw44YtITRjXyX6Uwf0o0419NurOqxYp3/ajbE3+AAAAkaJUAiAQkUqL9LkmUlXuR21es9gZkZIP9rYkpIhINWA/imkUAAAi5Z9IIVHgA9vv+5XXIjVn2vhUiXLp6857X1HVtedyPM9kY586japiGsN+lNuTMwAAQKSQKAi+cMJlkdKRqCbtR5WN9dlo7FMLJmwJiDqNYT+KkgkAAETKQ5FCogCRqnwvqr/Vaq2Ne0eeWF+9IiVNqvptTkfU5579KGJ9AACIlGcihUQBIlWPRCVFm5okUmVqz8vsRyU19qnTqCpjbexHcXcUAAAi5ZFIIVEQYuGEiyKlNPQNHNBbrdZaas+LxfpsFE1UGTGTRUpXngPfjyLWBwCASPkhUkgUhChS8+bNdU5KlIa+QYfFuDt0XBGptUtmOB3rk6dHPZO6SotUVZG+oiIV2n5UzJsIxPoAABAp90WKO6IAkaqlXGLIO+6qSBHrq2c/SpayqvaJbIqUh/tR3B0FAIBIuS9SSBQgUm5IlMsiVXWsb/2Kubn/+7T68rwiVUe8LK9I5ZkQytM1FwWlr69viipSHEQAABApp0UKiYLQRaq7u9sJKVF3opIO56pINTHWV/V+lFo0IUtHlTs6eUUqpFifuh/FNAoAAJFyWqSQKGhCc58L0x1diXJZpFyP9ZksmqhyLyppRyjrtZrnYuQqmwcN7UdRMgEAgEi5K1JIFCBS7kmUWv/sSqwvz6G9rrY+WYbKFk3UtUeU57kvuh/l6qRH/t5d3eECAABECokCRMpBiYqLNzVlP6psW5/Jook6JyJ5RCpP1NL1SY+yH0XJBAAAIuWmSCFR0GSRqqlUQvsAW+RCVmJ9Q2WoRC14rbIhi5Sp58SHWJ/yuqdkAgAAkXJPpJAoaJpInX3W0spFSrkjKtfBXN4TmTCuk1hf9UUTtU5DdEUqz3PiW6yPaRQAACLlnEghUdBEkaq6+lyJ8uWebri4H1VlrK/oNMrgflS/K0Jh6jnxocBBjvVRMgEAgEg5JVJIFCBSdic8cVOoVqu1Ns+hUL1HZ8608Y2I9ZW9O6rs/VHyf1vnNER9/k3sR3kU6+sn1gcAgEg5J1KbNm7ggA2NFSnbd0jNmTY+dgpVpHXMxf0o27G+9SvmGo/1ldmPqlOk8jz/IcX65O+btj4AAETKGZG65uqrOFxDo0VKbWMzNeVJEKhSOzZ57hAKJdZnomRCvUy3zH5UnbEyWSjSpqd55Laui4WLxvrYjwIAQKScECkkChCpoSJVNt4XE+ErHOVL249pYqyv6McoU3vu0n6ULFJpIq0rtz7E+tTacw4eAACIVO0ihUQBIjW0aEKWqTyi0jOpK2n6JF4+BJZ+Jz3PfkwosT4TJRNqNK+MSNUdK9MVKWJ9AACASFkUKcolAJFKFin1wtskMv7bfpNRJN1YV0ixPhMlE2VifS7tR9kQKd/a+oj1AQAgUrWLFBIFiNTQWF8Uu4sEKEuwqhIol2vPqyqZ2LxmsZFYX97ac9dkQ+c1oCu3Lk3aiPUBACBSXogUkT5ApOJFShYfqW65P6882TpsNy3WJ5dMFJ1GlY31ubZDZFKk5JIJYn0AAIBIZVScU3MO8IpInX3W0sxpQ19f3xRFqgbR29u7yKY8xd2j04RYn6lpVNlYn2s7RDoyHUrJBLE+AABEqlaR2rRxAzE+gASRUvajnI4NuVh7XlXJRJlplLzHViTWJ4mUEztEWSKlOyWUvy9XBYVYHwAAIlWLSDF5AsgWKfkiXtdjQ02L9ZmYRpW9hFfdj/LhdaD7PfrwBgKxPgAARKpSkUKgAPRFyqU2Nt135pvW1ldmGqXc/1RKxFx4fcjxziSRWrtkRhCV58T6AAAQqcpEigIJgHwipcb6XK1+zlN5HdolvCanUUW+ZteEQ6f+PpTKc2J9AACIVCXs3fP8dA7HAHpsv+9X3u1HuRbrsz2NinakykyjypZMuLgflSXUOs+LD5Xn6k4g0ygAAEQKkQJwgE0bNxDrK4lOfKxuypZMqPXgPkwmdURK/p5cnsLK0yiXv04AAECkABolUjq158T6qi2ZWLlwqrFJlzqNKvpxXZvcZN0hledxcXkaJe+CUTIBAIBIIVIADu9H+VJ7Huo0Kjrgm9q9MjGNcrGQIS3iqSO4vkyjpNc8JRMAAIgUIgXgkkjJteeuH9Rci/XZjOGZ+PjqNKrox3RtPypLpLKmbr5MoyiZAABApBApAIeLJuRplMsiJUecXIj12SqZKCs9SdOoMh/TtYmlIhi5J4UeRlmZRgEAIFKIFIBL+1G+xvpcECkbEiVH6MqKmjqNKipSLk5vsqrPdeXSo4unmUYBACBSiBSASyLlY6zPhf0oW9Mo+ZBvehpl4P4oZ6YiaaUjac+NcimxD9OofqZRAACIFCIF4OB+lC+HStdqz23VlJe96yltGlVUpFyrPc9q7EuL9cmPhevTKLlkgsMFAAAihUgBOIRPsT6Xas9tTaPkaUlZWYubRhX9uC6KR9p0MoRplPTGAdMoAABECpECcA0p1tdPrK/+aZQsP2VkLWkaVUSkXIz1pb0ekh435THpd30aJZdMcLAAAECkECkAh7jia1/xbRrlRKzP1jTKZKxPnUb19vYWFikXY33y60EVqaRYn/KYOC8nL8f6nJ6aAQAAIgXQSM4/71M+7orUHuuzJVFy7Kzopblxk5fe3t5Fciwyrwi6KB/y9zOnZ3zm8xP3mDj/h9SDqRkAACBSAI3Ex5KJumN9VU2jynweJcrXr8bgisqdS/KRJNZJj5tv06hIFjlQAAAgUogUgGNc+6MfeFkyUXesz5ZEqTtNJqZasiAXFSlXZTtPrM/HaRQAACBSiBSAo8w+7jgvSybqjPXZnEbJE5MywpZU7V1EpFy8hDdrP8r3unMAAABECsCjWJ/Lv0xcivXZkihTJRNqfC2aIKmPoe9tfUk1+HGi61PdOQAAACIFQMmElV2YOmN9aRe8unB3VFq1tyweeT6+q7KdNKFUnyM1Lsk0CgAAEClECsBYrM/1d+jlQ/OcaeNrkahlC7qdn0bFFUyUESlZ7lwTEN1YX9KEDgAAAJFCpACCjvWl7cIwjdKPrxURKfnuKJcEJOk+MTXWR8EEAAAgUogUgLVYn+uHSxfujqpyGlWkzEInvibLqM7ncLVkIu01kWdCBwAAgEghUgDBxvrUgoS6Yn1VTaOKXsCrE1/LWzQhT6Nck+2414QquxRMAAAAIoVIATQ91ldryYTNunN1klTkc6kSlSQ9eURKjcT50OAoy25a6QYAAAAihUgBlI31cXdUzXXnsgQVmUbpCkPe6nOXp1FJtedpcskfZAAAQKQQKQCjsT5KJvydRqn/fdrzmadowuVpVFKsT37siPQBAAAihUgBGOfaH/2Au6McLJgoMvnKU+udR6RcnkYlxfqI9AEAACKFSAEQ63OkZMJmwYQiQaUlKksYVPnwdRoVF+uTp1GyBBLpAwAARAqRAmh0yUQdsT6b0yg1kpd3GqVTdV60aMLlaVRSrC8SXpcvDwYAAECkAAK5O8r1g2bdJROuFkzE7UXp7ADpiJQn06hBl/BGwqt+7exFAQAAIoVIAViZRnF3VD0FE2qkL8/nKipRammHr9OouEt4Y/bNkCgAAECkECkAOyUTrsf66iyZcDnSl6dcIm/RhOvTqLg9ryjS57oAAgAAIFIAYVSeC+6OqqdgQpaovJE+3Ut3s8Q0SaRclxE11hcJr/x1sxcFAACIFCIF0NhYX513R9mcRpWJ9OVt6EsT07jP68M0SpbBOdPGq183EgUAAIgUIgVgt2TC9ViffOivOtZXVaQvzzSqrETpFE24Po1Sd+bUxxSJAgAARAqRArA+jeLuqHojfXnujDIhUVlFE75No6IyEMolAAAAkUKkAJhGxZQiVBnrczHSJ9+JVCa6llU04bqQqHKNRAEAACKFSAFUXjLB3VHVRvoUGdKO9KmyUOZ5U/fNkr4+V18b8jTqrZO7kCgAAECkECmA6ivPfSqZqCrWZyvSV3QvyqREZRVN+PDakL9+SaSQKAAAQKQQKQCmUXWVTLgW6TMtUXElDTHTKGcb71S5RqIAAACRQqQAqDxPOfBXEeurUqJ04oOmdqLSds6SplE+vCZe/r+RKAAAQKQQKYDqSiZcn0bJezBVlUy4FOkz0c6n03bnS915zGuCe6IAAACRQqQAqDyvu2SiKonSifTZkqik/Sgf6s6VaRQSBQAAiBQiBUDled0lEzbvi8q7F2VTopL2o3yYRiFRAAAAiBQAJRMOlUzo3uFku+o8ZnJlXBjk/ajoa5E/r+N15+xDAQAAIoVIAVB57kLJhM1yibhIXx6JsvEcxd0fJU+jHL98F4kCAABECpECYBpVd8lE1RKVNPlSm/lsCoMa63N9GoVEAQAAIFIAzkyjKJmwK1G6VecrF061ug+VtXfm+uW7SBQAAAAiBUDJhEMlE1VLVNxeVFyUz7bgqrXn8uW7rsl1JFGUSgAAACBSAC5UngufplE2SiZsS5TOfVFxU6gqJi5q7bnLcl3VYwIAAIBIIVIAWtMolw+ntqdRVUuUuhdVxxQq6bH1oe4cAAAAECkAJ6ZRPpVMmJ5G1S1RdU2h4h7bt07u8uY1AQAAAIgUANOomirPbUtUWrmEGqGrawIkP7aSSFHiAAAAgEghUgBUng9l7ZIZlUtUz6Su2Ea+uiJ0aqyvioZAAAAAQKQAqDz3dBpVh0Sp0bk6Ynxp0yhf2hsBAAAAkQKofRrl+qHZxjSqLolSI3x1R+dUSaVgAgAAAJFCpACYRrkmUf2uPd4vx/q8Kh0BAAAARAqAaVTFTX01SVR/q9Va66Kwxk2jKJgAAABApBApgICmUWXvjZLrxquUKFfFJKZkgoIJAAAARAqRAvB9GiUf8stOo2q4J6r2Eom8u2cUTAAAACBSiBSA59ModVpSZhpVl0S5/Es2pmSCggkAAABECpECYBpVjUQtnD1R+Nh4p5RMEOkDAABApBApgDjOP+9T3k6jXJSomAt1vSpqUKdR/OEBAABApBApgBh82oUxUXdus1jCx32oFFGlpQ8AAACRQqQAfJ9GvVyAUGoaZbPiPG4K5dtuERIFAACASCFSAAFNo0zUnduSKN+nUDGPMXtRAAAAiBQiBaA7jfKljrtIwYQtiQphCqXG+pAoAAAARAqRAtCrOxcuH57LTKOWLei2IlGhTKFiYn2USwAAACBSiBTYZdPGDWLTxg3isktXDXDN1Vd5WXfuyzQqT8GEjWa+mEa+/hD2iSJZ5b4oAAAARAqRAuNsv+9XYtPGDeKaq68aJE8qTKOstcjVGuWLuxcqlBhcb2/vIiJ9AAAAiBQiBcYnT2ni5JtI+TSNyhvpszWFUgRKhDa9QaIAAAAQKUQKahMoH0TK17pznYIJ0/dDJV2s6/suFAAAACBSiBRYie9lRffS2LRxA3XnNRRMmIzyJQkUdysBAAAAIoVIQcz0qYxA+SBSIRZMmIzypQkUJQwAAACASCFSIGFCnnwQKV8LJtIifaYkKkGgBHcqAQAAACKFSIGF6ZNP+1GhFUyYiPItnD0xVaCI8QEAAAAihUiBgf2nLFy9Q8rXgom4SF/ZC3ZXLpwaV2OOQAEAAAAihUiByQY+36dRaqTPl4KJuEhfmShf2vQJgQIAAABECpGCmCmUbYG67NJVYvt9vyLSZzHSV6TaPGX3iRIJAAAAQKQQKagjxudDpM+ngom0SF+eKF9WdI/pEwAAACBSiBTUGONzXaLi7oxyVR7kSF/PpC6xbEF3rn2oDHli+gQAAACASIErUyjXW/p8KpiIi/Rl7UPlkSemTwAAAACIFDgwhXJ9L8qngom4SF/SPlRKYQTyBAAAAIBIgYtlEr5IlE8FE3EtfWqUL0Oe+pEnAAAAAEQKclJ1jM8HiVIjfS4XTMiRvijKpxvZa7Vaa9l5AgAAAECkQHP6tGnjhtoEynWJ8jXSd+rsIzOb9pg6AQAAACBSkEOa6th98lGifIr09fb2LpKnUUydAAAAABApKFgU4Zo0+SZRvkT65L2ouIkTUycAAAAARApipkt1R/NCuifKt0ifJFED0yakCQAAAACRarQc+TBVysumjRu8eC58uXiXmB4AAAAAIhWkAIUqRCFG+ZL2opAVAAAAAHBSpDZt3HCiroiUxacIXCj4EOVLkCjhctU5AAAAADRcpC67dNUGhCNMgfJlCpW0F8W+EQAAAAAgUsAuFBIFAAAAAIgUsAtlR6KI9AEAAACA8yKVcomoNt3d3WLevLnaNFFwsh6T7u7uxMdWJ8bn2xSKvSgAAAAAaLxIgV1Ck6ckiSLSBwAAAADeiNT5533qpfPP+5TIIubQCwaYfdxxsY/36R9dJObNmyvOPmvpwN5TRAj3diFRAAAAAOC1SJm4R0pHxM4/71PBypDO937tj34guPg4WaK4LwoAAAAAGidSAJRLAAAAAAAiBYBEAQAAAAAihUgBEgUAAAAAiBQiBUgUAAAAACBSiBQ0RKK4KwoAAAAAECmANGJaDqk5BwAAAABECoC7ogAAAAAAkQJAogAAAAAAkUKkAIkCAAAAAEQKkQIkCgAAAAAQKUQKkCgAAAAAAEQKkCgkCgAAAAAQKYA8EsVdUQAAAACASAEgUQAAAACASCFSgEQBAAAAACKFSAESBQAAAACIFCIFPklUb2/vIn6gAQAAAACRAqCdDwAAAAAQKUQKinPtj36gChQSBQAAAACIFAASBQAAAACIFCIFSBQAAAAAIFKIFCBRAAAAAACIFCBRAAAAAACIFATazIdEAQAAAAAihUhB3ot2kSgAAAAAQKQQKcghUfygAgAAAAAihUiB5j4UEgUAAAAAiBQiBTGcf96nYiWqt7d3ET+gAAAAAIBIIVKgEeWjVAIAAAAAEClEChKifOxDAQAAAAAihUhBySgfEgUAAAAAiBQiBUT5AAAAAACRQqSAVj4AAAAAAEQK6ppC0coHAAAAAIgUIgV5CyWI8gEAAAAAIoVIAXdDAQAAAAAihUgBUygAAAAAAEQKmEIBAAAAACBSwBQKAAAAAACRQkpo5AMAAAAAQKQQqWbeC8UUCgAAAAAQKUQKsmN8TKEAAAAAAJFCpCBPmQRTKAAAAABApBApYAoFAAAAAIBIAVMoAAAAAABECphCAQAAAAAgUogUUygAAAAAAEQKkQqn0pwpFAAAAAAAIgV5LtZlCgUAAAAAgEgBUygAAAAAAEQKzJdJMIUCAAAAAECkQK9MgikUAAAAAAAiBUyhAAAAAAAQKWAKBQAAAACASCFSTKEAAAAAABApRMqLSnOmUAAAAAAAiBTkqDRvtVpredEDAAAAACBSoBHja2tr6yfGBwAAAACASIFmmQRTKAAAAAAARApyxPiYQgEAAAAAIFKgGeOjTAIAAAAAAJECYnwAAAAAAIgUIsWdUAAAAAAAiBQiVcedUIIYHwAAAAAAIgU5Y3xMoQAAAAAAECnQaOPjTigAAAAAAEQKkdLbgyLGBwAAAACASCFSOfagBFMoAAAAAABECpHS24MagEkUAAAAAAAihUhpCFSr1WIaBQAAAACASCFSGUUSoq2tTcybN1dcdumqQbE+XqgAAAAAAIhUI0Uqo0hCdHd3i7PPWiouu3QVIgUAAAAAgEg1W6TyCtRll64SZ5+1VI74reWFCgAAAACASDVGpPIKVMS8eXMpmgAAAAAAQKSaJVJZRRJJAhUjUtwdBQAAAACASIUtUlkCFRVJZMF+FAAAAAAAIhW8SJkSKEQKAAAAAACRCl6kdIok8giUWjRBrA8AAAAAAJEKRqSKNPHpQtEEAAAAAAAiFZRI2RSoiO7u7oFYX19f3xRepAAAAAAAiJSXIlWFQLEfBQAAAACASAUhUlkCpVNlzn4UAAAAAAAi1QiR0hGovE18OfejuD8KAAAAAACR8kOk6hKouP0oXpwAAAAAAIiU0yJVt0Cp+1GtVmstL04AAAAAAETKSZE6/7xPOSFQ1J4DAAAAACBSzovU+ed9KlWeuru7KxMoas8BAAAAABApZ0Xq2h/9QEugTLbwUXsOAAAAAIBIeSlSOvtPdQoUsT4AAAAAAETKGZHKmj5Vuf+UZxpFrA8AAAAAAJGqVKR0yiPq2H/SvYSXWB8AAAAAACJViUjp7D5F06c643saJRPE+gAAAAAAECm7IqUjT65Nn7KmUcT6AAAAAAAQKeMipRPdc3n6lFYyQawPAAAAAACRMiZSuvLk+vQpaxpFrA8AAAAAAJEqJVKhylPSbhTTKAAAAAAARKqQSOnKk0/RPd1IH9MoAAAAAABEKhdNkaeESJ9otVpreSECAAAAACBS+b6AAGN7uhJFUx8AAAAAACJVWqTmzZsbnDylSRSRPgAAAAAARKoQoYpTyk6UaGtr6yfSBwAAAACASBXmsktXbQhVoM4+a6nazsckCgAAAAAAkUKk8k6h2IkCAAAAAECkEKlsgWIKBQAAAACASCFSumUSkUAxhQIAAAAAQKQQqfQ9qH4mUAAAAAAAiBQilaNIgjY+AAAAAABECpGKIUmgiPABAAAAACBSiBRNfAAAAAAAgEiZEyj2oAAAAAAAEClEiipzAAAAAABApIxWmRPjAwAAAAAARCpPE19bW1s/AgUAAAAAAIiUpkAR4wMAAAAAAEQqu8qcPSgAAAAAAECk8hRJsAcFAAAAAACIFAIFAAAAAACIFE18AAAAAACASG2giQ8AAAAAABApB0SKJj4AAAAAAECkECgAAAAAAECkzItUTJEEAgUAAAAAAIgUAgUAAAAAAIhUCZFCoAAAAAAAAJFCoAAAAAAAAJEyK1Jpd0HxZAIAAAAAACKV3cQnuAsKAAAAAAAQKX2BEm1tbYJJFAAAAAAAIFIZAtXd3T3on7ETBQAAAAAAjRepLIGK/r0o1odIAQAAAABAY0UqSaDmzZs7SKCUwol+nkAAAAAAAGikSCVNoDSqzxEpAAAAAABopkilRfjSJIqiCQAAAAAAaLRIZQnUZZeukqN/SBQAAAAAADRbpLIEStmf4t4oAAAAAABApLLukeLyXQAAAAAAQKQ0RQqJAgAAAAAARCqHSKn7UEgUAAAAAAAgUikiRakEAAAAAAAgUpoipZZKIFEAAAAAAIBIpYiUug+FRAEAAAAAACKVAqUSAAAAAACASOX9ApAoAAAAAABApAqJFBIFAAAAAACIVA6RQqIAAAAAAACRykNfX98YnggAAAAAAECkAAAAAAAAECkAAAAAAABApAAAAAAAABApAAAAAAAARAoAAAAAAACRAgAAAAAAQKQAAAAAAAAQKQAAAAAAAECkAAAAAAAAECkAAAAAAABECgAAAAAAAJECAAAAAABApAAAAAAAAACRAgAAAAAAQKQAAAAAAAAQKQAAAAAAAEQKAAAAAAAAkQIAAAAAAECkAAAAAAAAAJECAAAAAABApAAAAAAAABApAAAAAAAARAoAAAAAAACRAgAAAAAAAEQKAAAAAAAAkQIAAAAAAECkAAAAAAAAECkAAAAAAABECgAAAAAAoJH8/wMArqhkL7TfyOAAAAAASUVORK5CYII=";
1640
- const MessagesArea = styled__default.default.div`
1641
- flex: 1;
1642
- overflow-y: auto;
1643
- scroll-behavior: smooth;
1644
- padding: 24px;
1645
- display: flex;
1646
- flex-direction: column;
1647
- gap: 12px;
1648
- `;
1649
- const MessageRow = styled__default.default.div`
1650
- display: flex;
1651
- align-items: flex-end;
1652
- gap: 8px;
1653
- align-self: ${({ $isUser }) => $isUser ? "flex-end" : "flex-start"};
1654
- max-width: 80%;
1655
- `;
1656
- const Avatar = styled__default.default.img`
1657
- width: 104px;
1658
- height: 104px;
1659
- border-radius: 50%;
1660
- object-fit: cover;
1661
- flex-shrink: 0;
1662
- `;
1663
- const MessageBubble = styled__default.default.div`
1664
- min-width: 0;
1665
- background-color: ${({ $isUser }) => $isUser ? "#4945ff" : "#f6f6f9"};
1666
- color: ${({ $isUser }) => $isUser ? "#ffffff" : "#32324d"};
1667
- border-radius: ${({ $isUser }) => $isUser ? "16px 16px 4px 16px" : "16px 16px 16px 4px"};
1668
- padding: 12px 16px;
1669
- font-size: 15px;
1670
- word-break: break-word;
1671
- line-height: 1.6;
1672
- `;
1673
- const MarkdownBody = styled__default.default.div`
1674
- p { margin: 0 0 8px; &:last-child { margin-bottom: 0; } }
1675
- ul, ol { margin: 4px 0; padding-left: 20px; }
1676
- li { margin: 2px 0; }
1677
- code {
1678
- font-size: 0.85em;
1679
- padding: 1px 4px;
1680
- border-radius: 3px;
1681
- background: ${({ $isUser }) => $isUser ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.06)"};
1682
- }
1683
- pre {
1684
- margin: 8px 0;
1685
- padding: 8px 10px;
1686
- border-radius: 6px;
1687
- overflow-x: auto;
1688
- font-size: 0.85em;
1689
- background: ${({ $isUser }) => $isUser ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.04)"};
1690
- code { padding: 0; background: none; }
1691
- }
1692
- h1, h2, h3, h4 { margin: 12px 0 4px; &:first-child { margin-top: 0; } }
1693
- h1 { font-size: 1.3em; } h2 { font-size: 1.15em; } h3 { font-size: 1.05em; }
1694
- blockquote {
1695
- margin: 8px 0;
1696
- padding-left: 12px;
1697
- border-left: 3px solid ${({ $isUser }) => $isUser ? "rgba(255,255,255,0.3)" : "#dcdce4"};
1698
- opacity: 0.85;
1699
- }
1700
- a { color: ${({ $isUser }) => $isUser ? "#c0cfff" : "#4945ff"}; }
1701
- table { border-collapse: collapse; margin: 8px 0; font-size: 0.9em; }
1702
- th, td { border: 1px solid ${({ $isUser }) => $isUser ? "rgba(255,255,255,0.2)" : "#dcdce4"}; padding: 4px 8px; }
1703
- `;
1704
- const MessageRole = styled__default.default.div`
1705
- font-size: 11px;
1706
- font-weight: 600;
1707
- margin-bottom: 4px;
1708
- opacity: 0.7;
1709
- color: ${({ $isUser }) => $isUser ? "#ffffff" : "#666687"};
1710
- `;
1711
- const TypingDots = styled__default.default.span`
1712
- display: inline-flex;
1713
- gap: 4px;
1714
-
1715
- span {
1716
- width: 7px;
1717
- height: 7px;
1718
- border-radius: 50%;
1719
- background: #a5a5ba;
1720
- animation: bounce 1.4s infinite ease-in-out both;
1721
- }
1722
- span:nth-child(1) { animation-delay: 0s; }
1723
- span:nth-child(2) { animation-delay: 0.2s; }
1724
- span:nth-child(3) { animation-delay: 0.4s; }
1725
-
1726
- @keyframes bounce {
1727
- 0%, 80%, 100% { transform: scale(0.4); opacity: 0.4; }
1728
- 40% { transform: scale(1); opacity: 1; }
1729
- }
1730
- `;
1731
- const EmptyState = styled__default.default.div`
1732
- display: flex;
1733
- flex-direction: column;
1734
- align-items: center;
1735
- justify-content: center;
1736
- flex: 1;
1737
- color: #a5a5ba;
1738
- `;
1739
- const CONTENT_TYPE_UID_RE = /\b(api::\w[\w-]*\.\w[\w-]*)\b/g;
1740
- function autoLinkContentTypeUids(text) {
1741
- return text.replace(
1742
- CONTENT_TYPE_UID_RE,
1743
- (match) => `[${match}](/content-manager/collection-types/${match})`
1744
- );
1745
- }
1746
- function isInternalPath(href) {
1747
- return href.startsWith("/content-manager/");
1748
- }
1749
- function MarkdownLink({
1750
- href,
1751
- children,
1752
- ...props
1753
- }) {
1754
- if (href && isInternalPath(href)) {
1755
- return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: href, ...props, children });
1756
- }
1757
- return /* @__PURE__ */ jsxRuntime.jsx("a", { href, target: "_blank", rel: "noopener noreferrer", ...props, children });
1758
- }
1759
- const markdownComponents = {
1760
- a: MarkdownLink
1761
- };
1762
- const MessageList = react.forwardRef(
1763
- function MessageList2({ messages, isLoading, awaitingAudio, voiceEnabled, visibleText }, ref) {
1764
- return /* @__PURE__ */ jsxRuntime.jsxs(MessagesArea, { children: [
1765
- messages.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs(EmptyState, { children: [
1766
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", textColor: "neutral400", children: "AI Chat" }),
1767
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral500", children: "Send a message to start the conversation" }) })
1768
- ] }),
1769
- messages.map((message, index2) => {
1770
- const isLatestAssistant = message.role === "assistant" && index2 === messages.length - 1;
1771
- const rawContent = voiceEnabled && isLatestAssistant && (awaitingAudio || visibleText) ? visibleText : message.content;
1772
- const displayContent = message.role === "assistant" && rawContent ? autoLinkContentTypeUids(rawContent) : rawContent;
1773
- return /* @__PURE__ */ jsxRuntime.jsxs(MessageRow, { $isUser: message.role === "user", children: [
1774
- message.role === "assistant" && /* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: waifuAvatar, alt: "Assistant" }),
1775
- /* @__PURE__ */ jsxRuntime.jsxs(MessageBubble, { $isUser: message.role === "user", children: [
1776
- /* @__PURE__ */ jsxRuntime.jsx(MessageRole, { $isUser: message.role === "user", children: message.role === "user" ? "You" : "Assistant" }),
1777
- message.role === "user" && message.content,
1778
- message.role === "assistant" && displayContent && /* @__PURE__ */ jsxRuntime.jsx(MarkdownBody, { $isUser: false, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown__default.default, { components: markdownComponents, children: displayContent }) }),
1779
- message.role === "assistant" && !displayContent && (isLoading || awaitingAudio) && /* @__PURE__ */ jsxRuntime.jsxs(TypingDots, { children: [
1780
- /* @__PURE__ */ jsxRuntime.jsx("span", {}),
1781
- /* @__PURE__ */ jsxRuntime.jsx("span", {}),
1782
- /* @__PURE__ */ jsxRuntime.jsx("span", {})
1783
- ] }),
1784
- message.toolCalls?.filter((tc) => !HIDDEN_TOOLS.has(tc.toolName)).map((tc) => /* @__PURE__ */ jsxRuntime.jsx(ToolCallDisplay, { toolCall: tc }, tc.toolCallId))
1785
- ] })
1786
- ] }, message.id);
1787
- }),
1788
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref })
1789
- ] });
1790
- }
1791
- );
1792
- const InputArea = styled__default.default.div`
1793
- display: flex;
1794
- gap: 8px;
1795
- align-items: flex-end;
1796
- padding: 16px;
1797
- border-top: 1px solid #eaeaef;
1798
- `;
1799
- const VoiceToggle = styled__default.default.button`
1800
- display: flex;
1801
- align-items: center;
1802
- justify-content: center;
1803
- width: 40px;
1804
- height: 40px;
1805
- border-radius: 8px;
1806
- border: 1px solid ${({ $active }) => $active ? "#4945ff" : "#dcdce4"};
1807
- background: ${({ $active }) => $active ? "#f0f0ff" : "#ffffff"};
1808
- color: ${({ $active }) => $active ? "#4945ff" : "#a5a5ba"};
1809
- cursor: pointer;
1810
- flex-shrink: 0;
1811
- transition: all 0.15s ease;
1812
-
1813
- &:hover {
1814
- border-color: #4945ff;
1815
- color: #4945ff;
1816
- }
1817
-
1818
- svg {
1819
- width: 18px;
1820
- height: 18px;
1821
- }
1822
- `;
1823
- function ChatInput({
1824
- input,
1825
- isLoading,
1826
- voiceEnabled,
1827
- onInputChange,
1828
- onSend,
1829
- onToggleVoice
1830
- }) {
1831
- return /* @__PURE__ */ jsxRuntime.jsx(
1832
- "form",
1833
- {
1834
- onSubmit: (e) => {
1835
- e.preventDefault();
1836
- onSend();
1837
- },
1838
- children: /* @__PURE__ */ jsxRuntime.jsxs(InputArea, { children: [
1839
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", children: /* @__PURE__ */ jsxRuntime.jsx(
1840
- designSystem.TextInput,
1841
- {
1842
- placeholder: "Type your message...",
1843
- "aria-label": "Chat message",
1844
- value: input,
1845
- onChange: (e) => onInputChange(e.target.value)
1846
- }
1847
- ) }),
1848
- /* @__PURE__ */ jsxRuntime.jsx(
1849
- VoiceToggle,
1850
- {
1851
- type: "button",
1852
- onClick: onToggleVoice,
1853
- title: voiceEnabled ? "Disable voice" : "Enable voice",
1854
- $active: voiceEnabled,
1855
- children: voiceEnabled ? /* @__PURE__ */ jsxRuntime.jsx(icons.VolumeUp, {}) : /* @__PURE__ */ jsxRuntime.jsx(icons.VolumeMute, {})
1856
- }
1857
- ),
1858
- /* @__PURE__ */ jsxRuntime.jsx(
1859
- designSystem.Button,
1860
- {
1861
- type: "submit",
1862
- disabled: isLoading || !input.trim(),
1863
- loading: isLoading,
1864
- size: "L",
1865
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Sparkle, {}),
1866
- children: "Send"
1867
- }
1868
- )
1869
- ] })
1870
- }
1871
- );
1872
- }
1873
- const ChatLayout = styled__default.default.div`
1874
- display: flex;
1875
- flex-direction: row;
1876
- height: calc(100vh - 200px);
1877
- min-height: 400px;
1878
- border-radius: 4px;
1879
- overflow: hidden;
1880
- box-shadow: 0 1px 4px rgba(33, 33, 52, 0.1);
1881
- background: #ffffff;
1882
- `;
1883
- const ChatWrapper = styled__default.default.div`
1884
- display: flex;
1885
- flex-direction: column;
1886
- flex: 1;
1887
- min-width: 0;
1888
- `;
1889
- const ToggleSidebarBtn = styled__default.default.button`
1890
- display: flex;
1891
- align-items: center;
1892
- justify-content: center;
1893
- width: 32px;
1894
- height: 32px;
1895
- border: 1px solid #dcdce4;
1896
- border-radius: 4px;
1897
- background: #ffffff;
1898
- color: #666687;
1899
- cursor: pointer;
1900
- flex-shrink: 0;
1901
-
1902
- &:hover {
1903
- background: #f0f0ff;
1904
- color: #4945ff;
1905
- border-color: #4945ff;
1906
- }
1907
-
1908
- svg {
1909
- width: 16px;
1910
- height: 16px;
1911
- }
1912
- `;
1913
- const ChatTopBar = styled__default.default.div`
1914
- display: flex;
1915
- align-items: center;
1916
- padding: 8px 16px;
1917
- border-bottom: 1px solid #eaeaef;
1918
- gap: 8px;
1919
- `;
1920
- function Chat() {
1921
- const navigate = reactRouterDom.useNavigate();
1922
- const [input, setInput] = react.useState("");
1923
- const [sidebarOpen, setSidebarOpen] = react.useState(false);
1924
- const [memoryPanelOpen, setMemoryPanelOpen] = react.useState(false);
1925
- const [voiceEnabled, setVoiceEnabled] = react.useState(false);
1926
- const [awaitingAudio, setAwaitingAudio] = react.useState(false);
1927
- const messagesEndRef = react.useRef(null);
1928
- const fullTextRef = react.useRef("");
1929
- const voiceRef = react.useRef(voiceEnabled);
1930
- voiceRef.current = voiceEnabled;
1931
- const prevIsLoadingRef = react.useRef(false);
1932
- const { trigger, clearAnimation } = useAvatarAnimation();
1933
- const { visibleText, startReveal, reset: resetReveal } = useTextReveal();
1934
- const { speak: speak2, stop: stopAudio } = useAudioPlayer({
1935
- onPlayStart: (duration) => {
1936
- trigger("speak");
1937
- startReveal(fullTextRef.current, duration);
1938
- setAwaitingAudio(false);
1939
- },
1940
- onPlayEnded: () => clearAnimation()
1941
- });
1942
- const {
1943
- conversations,
1944
- activeId,
1945
- initialMessages,
1946
- selectConversation,
1947
- startNewConversation,
1948
- saveMessages,
1949
- removeConversation
1950
- } = useConversations();
1951
- const { memories, removeMemory, refresh: refreshMemories } = useMemories();
1952
- const { messages, sendMessage, isLoading, error } = useChat({
1953
- initialMessages,
1954
- conversationId: activeId,
1955
- onAnimationTrigger: trigger,
1956
- onStreamEnd: (fullText) => {
1957
- if (!voiceRef.current) return;
1958
- fullTextRef.current = fullText;
1959
- if (!fullText) {
1960
- setAwaitingAudio(false);
1961
- clearAnimation();
1962
- } else {
1963
- speak2(fullText);
1964
- }
1965
- }
1966
- });
1967
- react.useEffect(() => {
1968
- if (prevIsLoadingRef.current && !isLoading && messages.length > 0) {
1969
- saveMessages(messages);
1970
- refreshMemories();
1971
- }
1972
- prevIsLoadingRef.current = isLoading;
1973
- }, [isLoading, messages, saveMessages, refreshMemories]);
1974
- const handleSend = () => {
1975
- if (!input.trim() || isLoading) return;
1976
- if (voiceEnabled) {
1977
- fullTextRef.current = "";
1978
- resetReveal();
1979
- setAwaitingAudio(true);
1980
- }
1981
- sendMessage(input);
1982
- setInput("");
1983
- };
1984
- const handleToggleVoice = () => {
1985
- const next = !voiceEnabled;
1986
- setVoiceEnabled(next);
1987
- if (!next) {
1988
- stopAudio();
1989
- resetReveal();
1990
- setAwaitingAudio(false);
1991
- clearAnimation();
1992
- }
1993
- };
1994
- react.useEffect(() => {
1995
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
1996
- }, [messages, visibleText]);
1997
- return /* @__PURE__ */ jsxRuntime.jsxs(ChatLayout, { children: [
1998
- /* @__PURE__ */ jsxRuntime.jsx(
1999
- ConversationSidebar,
2000
- {
2001
- conversations,
2002
- activeId,
2003
- open: sidebarOpen,
2004
- onSelect: selectConversation,
2005
- onNew: startNewConversation,
2006
- onDelete: removeConversation
2007
- }
2008
- ),
2009
- /* @__PURE__ */ jsxRuntime.jsx(AvatarPanel, {}),
2010
- /* @__PURE__ */ jsxRuntime.jsxs(ChatWrapper, { children: [
2011
- /* @__PURE__ */ jsxRuntime.jsxs(ChatTopBar, { children: [
2012
- /* @__PURE__ */ jsxRuntime.jsx(
2013
- ToggleSidebarBtn,
2014
- {
2015
- onClick: () => setSidebarOpen((prev) => !prev),
2016
- "aria-label": sidebarOpen ? "Hide conversations" : "Show conversations",
2017
- children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
2018
- /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "2", width: "14", height: "12", rx: "1.5" }),
2019
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "5.5", y1: "2", x2: "5.5", y2: "14" })
2020
- ] })
2021
- }
2022
- ),
2023
- /* @__PURE__ */ jsxRuntime.jsx(
2024
- ToggleSidebarBtn,
2025
- {
2026
- onClick: () => navigate(`/plugins/${index.PLUGIN_ID}/memory-store`),
2027
- "aria-label": "Memory Store",
2028
- children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 4h12M2 8h12M2 12h8" }) })
2029
- }
2030
- ),
2031
- /* @__PURE__ */ jsxRuntime.jsx(
2032
- ToggleSidebarBtn,
2033
- {
2034
- onClick: () => navigate(`/plugins/${index.PLUGIN_ID}/public-memory-store`),
2035
- "aria-label": "Public Memory Store",
2036
- children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
2037
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "6" }),
2038
- /* @__PURE__ */ jsxRuntime.jsx("ellipse", { cx: "8", cy: "8", rx: "2.5", ry: "6" }),
2039
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 8h12" })
2040
- ] })
2041
- }
2042
- ),
2043
- /* @__PURE__ */ jsxRuntime.jsx(
2044
- ToggleSidebarBtn,
2045
- {
2046
- onClick: () => navigate(`/plugins/${index.PLUGIN_ID}/widget-preview`),
2047
- "aria-label": "Widget Preview",
2048
- children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
2049
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "4 4 8 2 12 4" }),
2050
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "4 4 4 10 8 12 12 10 12 4" }),
2051
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "8", y1: "2", x2: "8", y2: "12" })
2052
- ] })
2053
- }
2054
- ),
2055
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1 } }),
2056
- /* @__PURE__ */ jsxRuntime.jsx(
2057
- ToggleSidebarBtn,
2058
- {
2059
- onClick: () => setMemoryPanelOpen((prev) => !prev),
2060
- "aria-label": memoryPanelOpen ? "Hide memories" : "Show memories",
2061
- children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
2062
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "6", r: "4" }),
2063
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 10.5C4 10.5 5 14 8 14s4-3.5 4-3.5" })
2064
- ] })
2065
- }
2066
- )
2067
- ] }),
2068
- /* @__PURE__ */ jsxRuntime.jsx(
2069
- MessageList,
2070
- {
2071
- ref: messagesEndRef,
2072
- messages,
2073
- isLoading,
2074
- awaitingAudio,
2075
- voiceEnabled,
2076
- visibleText
2077
- }
2078
- ),
2079
- error && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, background: "danger100", marginLeft: 4, marginRight: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { textColor: "danger600", children: [
2080
- "Error: ",
2081
- error
2082
- ] }) }),
2083
- /* @__PURE__ */ jsxRuntime.jsx(
2084
- ChatInput,
2085
- {
2086
- input,
2087
- isLoading,
2088
- voiceEnabled,
2089
- onInputChange: setInput,
2090
- onSend: handleSend,
2091
- onToggleVoice: handleToggleVoice
2092
- }
2093
- )
2094
- ] }),
2095
- /* @__PURE__ */ jsxRuntime.jsx(
2096
- MemoryPanel,
2097
- {
2098
- memories,
2099
- open: memoryPanelOpen,
2100
- onDelete: removeMemory
2101
- }
2102
- )
2103
- ] });
2104
- }
2105
- const HomePage = () => {
2106
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
2107
- /* @__PURE__ */ jsxRuntime.jsx(
2108
- admin.Layouts.Header,
2109
- {
2110
- title: "AI Chat",
2111
- subtitle: "Chat with AI powered by Vercel AI SDK"
2112
- }
2113
- ),
2114
- /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(AvatarAnimationProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(Chat, {}) }) })
2115
- ] });
2116
- };
2117
- const PAGE_SIZE$1 = 10;
2118
- const ActionBtn$1 = styled__default.default.button`
2119
- display: inline-flex;
2120
- align-items: center;
2121
- justify-content: center;
2122
- width: 32px;
2123
- height: 32px;
2124
- padding: 0;
2125
- border: none;
2126
- border-radius: 4px;
2127
- background: transparent;
2128
- color: ${({ theme }) => theme.colors.neutral600};
2129
- cursor: pointer;
2130
-
2131
- &:hover {
2132
- background: ${({ theme }) => theme.colors.neutral200};
2133
- color: ${({ theme }) => theme.colors.primary600};
2134
- }
2135
-
2136
- svg {
2137
- width: 16px;
2138
- height: 16px;
2139
- }
2140
- `;
2141
- const DeleteActionBtn$1 = styled__default.default(ActionBtn$1)`
2142
- &:hover {
2143
- color: ${({ theme }) => theme.colors.danger600};
2144
- }
2145
- `;
2146
- const WideModalContent$1 = styled__default.default(designSystem.Modal.Content)`
2147
- max-width: 680px;
2148
- width: 100%;
2149
- `;
2150
- function formatDate$1(iso) {
2151
- return new Date(iso).toLocaleDateString(void 0, {
2152
- year: "numeric",
2153
- month: "short",
2154
- day: "numeric",
2155
- hour: "2-digit",
2156
- minute: "2-digit"
2157
- });
2158
- }
2159
- function MemoryModal({ memory, open, onClose, onSave }) {
2160
- const [content, setContent] = react.useState("");
2161
- const [category, setCategory] = react.useState("general");
2162
- const isEdit = memory !== null;
2163
- react.useEffect(() => {
2164
- if (open && memory) {
2165
- setContent(memory.content);
2166
- setCategory(memory.category);
2167
- } else if (open) {
2168
- setContent("");
2169
- setCategory("general");
2170
- }
2171
- }, [open, memory]);
2172
- const handleClose = () => {
2173
- setContent("");
2174
- setCategory("general");
2175
- onClose();
2176
- };
2177
- const handleSave = () => {
2178
- if (!content.trim()) return;
2179
- onSave({ content, category }, memory?.documentId);
2180
- handleClose();
2181
- };
2182
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open, onOpenChange: (isOpen) => !isOpen && handleClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(WideModalContent$1, { children: [
2183
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: isEdit ? "Edit Memory" : "Add Memory" }) }),
2184
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, width: "100%", children: [
2185
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
2186
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Content" }),
2187
- /* @__PURE__ */ jsxRuntime.jsx(
2188
- designSystem.Textarea,
2189
- {
2190
- placeholder: "A fact or preference to remember (e.g. 'User prefers dark mode')",
2191
- value: content,
2192
- onChange: (e) => setContent(e.target.value),
2193
- style: { width: "100%", minHeight: "120px" }
2194
- }
2195
- )
2196
- ] }),
2197
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
2198
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Category" }),
2199
- /* @__PURE__ */ jsxRuntime.jsxs(
2200
- designSystem.SingleSelect,
2201
- {
2202
- value: category,
2203
- onChange: (value) => setCategory(value),
2204
- children: [
2205
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "general", children: "General" }),
2206
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "preference", children: "Preference" }),
2207
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "personal", children: "Personal" }),
2208
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "project", children: "Project" })
2209
- ]
2210
- }
2211
- ),
2212
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: "Used to organize memories by topic" })
2213
- ] })
2214
- ] }) }),
2215
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
2216
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
2217
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleSave, disabled: !content.trim(), children: isEdit ? "Save changes" : "Add memory" })
2218
- ] })
2219
- ] }) });
2220
- }
2221
- const MemoryStorePage = () => {
2222
- const navigate = reactRouterDom.useNavigate();
2223
- const { memories, loading, addMemory, editMemory, removeMemory } = useMemories();
2224
- const [search, setSearch] = react.useState("");
2225
- const [page, setPage] = react.useState(1);
2226
- const [modalOpen, setModalOpen] = react.useState(false);
2227
- const [editingMemory, setEditingMemory] = react.useState(null);
2228
- const filtered = react.useMemo(() => {
2229
- if (!search.trim()) return memories;
2230
- const q = search.toLowerCase();
2231
- return memories.filter(
2232
- (m) => m.content.toLowerCase().includes(q) || m.category.toLowerCase().includes(q)
2233
- );
2234
- }, [memories, search]);
2235
- const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE$1));
2236
- const paginated = filtered.slice((page - 1) * PAGE_SIZE$1, page * PAGE_SIZE$1);
2237
- const handleOpenCreate = () => {
2238
- setEditingMemory(null);
2239
- setModalOpen(true);
2240
- };
2241
- const handleOpenEdit = (mem) => {
2242
- setEditingMemory(mem);
2243
- setModalOpen(true);
2244
- };
2245
- const handleCloseModal = () => {
2246
- setModalOpen(false);
2247
- setEditingMemory(null);
2248
- };
2249
- const handleSave = (data, documentId) => {
2250
- if (documentId) {
2251
- editMemory(documentId, data);
2252
- } else {
2253
- addMemory(data);
2254
- }
2255
- };
2256
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
2257
- /* @__PURE__ */ jsxRuntime.jsx(
2258
- admin.Layouts.Header,
2259
- {
2260
- title: "Memory Store",
2261
- subtitle: `${memories.length} memories saved`,
2262
- primaryAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}), onClick: handleOpenCreate, children: "Add memory" }),
2263
- navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), onClick: () => navigate(`/plugins/${index.PLUGIN_ID}`), children: "Back to Chat" })
2264
- }
2265
- ),
2266
- /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
2267
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.SearchForm, { children: /* @__PURE__ */ jsxRuntime.jsx(
2268
- designSystem.Searchbar,
2269
- {
2270
- name: "search",
2271
- value: search,
2272
- onChange: (e) => {
2273
- setSearch(e.target.value);
2274
- setPage(1);
2275
- },
2276
- onClear: () => {
2277
- setSearch("");
2278
- setPage(1);
2279
- },
2280
- placeholder: "Search memories...",
2281
- children: "Search"
2282
- }
2283
- ) }) }),
2284
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 4, rowCount: paginated.length + 1, children: [
2285
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
2286
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Content" }) }),
2287
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Category" }) }),
2288
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Created" }) }),
2289
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Actions" }) })
2290
- ] }) }),
2291
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tbody, { children: [
2292
- loading && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: "Loading..." }) }) }) }),
2293
- !loading && paginated.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: search ? "No memories match your search" : "No memories saved yet" }) }) }) }),
2294
- paginated.map((mem) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
2295
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: mem.content }) }),
2296
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: mem.category }) }),
2297
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatDate$1(mem.createdAt) }) }),
2298
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
2299
- /* @__PURE__ */ jsxRuntime.jsx(
2300
- ActionBtn$1,
2301
- {
2302
- onClick: () => handleOpenEdit(mem),
2303
- "aria-label": "Edit memory",
2304
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {})
2305
- }
2306
- ),
2307
- /* @__PURE__ */ jsxRuntime.jsx(
2308
- DeleteActionBtn$1,
2309
- {
2310
- onClick: () => removeMemory(mem.documentId),
2311
- "aria-label": "Delete memory",
2312
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
2313
- }
2314
- )
2315
- ] }) })
2316
- ] }, mem.documentId))
2317
- ] })
2318
- ] }),
2319
- pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Pagination.Root, { pageCount, activePage: page, onPageChange: setPage, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Pagination.Links, {}) }) }) }),
2320
- /* @__PURE__ */ jsxRuntime.jsx(
2321
- MemoryModal,
2322
- {
2323
- memory: editingMemory,
2324
- open: modalOpen,
2325
- onClose: handleCloseModal,
2326
- onSave: handleSave
2327
- }
2328
- )
2329
- ] })
2330
- ] });
2331
- };
2332
- const BASE = () => `${getBackendURL()}/${index.PLUGIN_ID}/public-memories`;
2333
- function headers() {
2334
- const token = getToken();
2335
- return {
2336
- "Content-Type": "application/json",
2337
- ...token ? { Authorization: `Bearer ${token}` } : {}
2338
- };
2339
- }
2340
- async function request(url, init) {
2341
- const res = await fetch(url, { headers: headers(), ...init });
2342
- if (!res.ok) throw new Error(`Request failed: ${res.status}`);
2343
- const json = await res.json();
2344
- return json.data;
2345
- }
2346
- function fetchPublicMemories() {
2347
- return request(BASE());
2348
- }
2349
- function createPublicMemory(data) {
2350
- return request(BASE(), {
2351
- method: "POST",
2352
- body: JSON.stringify(data)
2353
- });
2354
- }
2355
- function updatePublicMemory(documentId, data) {
2356
- return request(`${BASE()}/${documentId}`, {
2357
- method: "PUT",
2358
- body: JSON.stringify(data)
2359
- });
2360
- }
2361
- function deletePublicMemory(documentId) {
2362
- return request(`${BASE()}/${documentId}`, { method: "DELETE" });
2363
- }
2364
- function usePublicMemories() {
2365
- const [memories, setMemories] = react.useState([]);
2366
- const [loading, setLoading] = react.useState(true);
2367
- const load = react.useCallback(async () => {
2368
- try {
2369
- const data = await fetchPublicMemories();
2370
- setMemories(data);
2371
- } catch (err) {
2372
- console.error("Failed to load public memories:", err);
2373
- } finally {
2374
- setLoading(false);
2375
- }
2376
- }, []);
2377
- react.useEffect(() => {
2378
- load();
2379
- }, [load]);
2380
- const addMemory = react.useCallback(async (data) => {
2381
- try {
2382
- const created = await createPublicMemory(data);
2383
- setMemories((prev) => [created, ...prev]);
2384
- } catch (err) {
2385
- console.error("Failed to create public memory:", err);
2386
- }
2387
- }, []);
2388
- const editMemory = react.useCallback(async (documentId, data) => {
2389
- try {
2390
- const updated = await updatePublicMemory(documentId, data);
2391
- setMemories(
2392
- (prev) => prev.map((m) => m.documentId === documentId ? { ...m, ...updated } : m)
2393
- );
2394
- } catch (err) {
2395
- console.error("Failed to update public memory:", err);
2396
- }
2397
- }, []);
2398
- const removeMemory = react.useCallback(async (documentId) => {
2399
- try {
2400
- await deletePublicMemory(documentId);
2401
- setMemories((prev) => prev.filter((m) => m.documentId !== documentId));
2402
- } catch (err) {
2403
- console.error("Failed to delete public memory:", err);
2404
- }
2405
- }, []);
2406
- return { memories, loading, addMemory, editMemory, removeMemory, refresh: load };
2407
- }
2408
- const PAGE_SIZE = 10;
2409
- const ActionBtn = styled__default.default.button`
2410
- display: inline-flex;
2411
- align-items: center;
2412
- justify-content: center;
2413
- width: 32px;
2414
- height: 32px;
2415
- padding: 0;
2416
- border: none;
2417
- border-radius: 4px;
2418
- background: transparent;
2419
- color: ${({ theme }) => theme.colors.neutral600};
2420
- cursor: pointer;
2421
-
2422
- &:hover {
2423
- background: ${({ theme }) => theme.colors.neutral200};
2424
- color: ${({ theme }) => theme.colors.primary600};
2425
- }
2426
-
2427
- svg {
2428
- width: 16px;
2429
- height: 16px;
2430
- }
2431
- `;
2432
- const DeleteActionBtn = styled__default.default(ActionBtn)`
2433
- &:hover {
2434
- color: ${({ theme }) => theme.colors.danger600};
2435
- }
2436
- `;
2437
- const WideModalContent = styled__default.default(designSystem.Modal.Content)`
2438
- max-width: 680px;
2439
- width: 100%;
2440
- `;
2441
- function formatDate(iso) {
2442
- return new Date(iso).toLocaleDateString(void 0, {
2443
- year: "numeric",
2444
- month: "short",
2445
- day: "numeric",
2446
- hour: "2-digit",
2447
- minute: "2-digit"
2448
- });
2449
- }
2450
- function PublicMemoryModal({ memory, open, onClose, onSave }) {
2451
- const [content, setContent] = react.useState("");
2452
- const [category, setCategory] = react.useState("general");
2453
- const isEdit = memory !== null;
2454
- react.useEffect(() => {
2455
- if (open && memory) {
2456
- setContent(memory.content);
2457
- setCategory(memory.category);
2458
- } else if (open) {
2459
- setContent("");
2460
- setCategory("general");
2461
- }
2462
- }, [open, memory]);
2463
- const handleClose = () => {
2464
- setContent("");
2465
- setCategory("general");
2466
- onClose();
2467
- };
2468
- const handleSave = () => {
2469
- if (!content.trim()) return;
2470
- onSave({ content, category }, memory?.documentId);
2471
- handleClose();
2472
- };
2473
- return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open, onOpenChange: (isOpen) => !isOpen && handleClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(WideModalContent, { children: [
2474
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: isEdit ? "Edit Public Memory" : "Add Public Memory" }) }),
2475
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, width: "100%", children: [
2476
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
2477
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Content" }),
2478
- /* @__PURE__ */ jsxRuntime.jsx(
2479
- designSystem.Textarea,
2480
- {
2481
- placeholder: "Public knowledge for the chatbot (e.g. 'Our return policy is 30 days')",
2482
- value: content,
2483
- onChange: (e) => setContent(e.target.value),
2484
- style: { width: "100%", minHeight: "120px" }
2485
- }
2486
- )
2487
- ] }),
2488
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { width: "100%", children: [
2489
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Category" }),
2490
- /* @__PURE__ */ jsxRuntime.jsxs(
2491
- designSystem.SingleSelect,
2492
- {
2493
- value: category,
2494
- onChange: (value) => setCategory(value),
2495
- children: [
2496
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "general", children: "General" }),
2497
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "faq", children: "FAQ" }),
2498
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "product", children: "Product" }),
2499
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "policy", children: "Policy" })
2500
- ]
2501
- }
2502
- ),
2503
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: "Used to organize public memories by topic" })
2504
- ] })
2505
- ] }) }),
2506
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
2507
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
2508
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleSave, disabled: !content.trim(), children: isEdit ? "Save changes" : "Add memory" })
2509
- ] })
2510
- ] }) });
2511
- }
2512
- const PublicMemoryStorePage = () => {
2513
- const navigate = reactRouterDom.useNavigate();
2514
- const { memories, loading, addMemory, editMemory, removeMemory } = usePublicMemories();
2515
- const [search, setSearch] = react.useState("");
2516
- const [page, setPage] = react.useState(1);
2517
- const [modalOpen, setModalOpen] = react.useState(false);
2518
- const [editingMemory, setEditingMemory] = react.useState(null);
2519
- const filtered = react.useMemo(() => {
2520
- if (!search.trim()) return memories;
2521
- const q = search.toLowerCase();
2522
- return memories.filter(
2523
- (m) => m.content.toLowerCase().includes(q) || m.category.toLowerCase().includes(q)
2524
- );
2525
- }, [memories, search]);
2526
- const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
2527
- const paginated = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
2528
- const handleOpenCreate = () => {
2529
- setEditingMemory(null);
2530
- setModalOpen(true);
2531
- };
2532
- const handleOpenEdit = (mem) => {
2533
- setEditingMemory(mem);
2534
- setModalOpen(true);
2535
- };
2536
- const handleCloseModal = () => {
2537
- setModalOpen(false);
2538
- setEditingMemory(null);
2539
- };
2540
- const handleSave = (data, documentId) => {
2541
- if (documentId) {
2542
- editMemory(documentId, data);
2543
- } else {
2544
- addMemory(data);
2545
- }
2546
- };
2547
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
2548
- /* @__PURE__ */ jsxRuntime.jsx(
2549
- admin.Layouts.Header,
2550
- {
2551
- title: "Public Memory Store",
2552
- subtitle: `${memories.length} public memories — available to all visitors via the public chat`,
2553
- primaryAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}), onClick: handleOpenCreate, children: "Add memory" }),
2554
- navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), onClick: () => navigate(`/plugins/${index.PLUGIN_ID}`), children: "Back to Chat" })
2555
- }
2556
- ),
2557
- /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
2558
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.SearchForm, { children: /* @__PURE__ */ jsxRuntime.jsx(
2559
- designSystem.Searchbar,
2560
- {
2561
- name: "search",
2562
- value: search,
2563
- onChange: (e) => {
2564
- setSearch(e.target.value);
2565
- setPage(1);
2566
- },
2567
- onClear: () => {
2568
- setSearch("");
2569
- setPage(1);
2570
- },
2571
- placeholder: "Search public memories...",
2572
- children: "Search"
2573
- }
2574
- ) }) }),
2575
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 4, rowCount: paginated.length + 1, children: [
2576
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
2577
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Content" }) }),
2578
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Category" }) }),
2579
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Created" }) }),
2580
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Actions" }) })
2581
- ] }) }),
2582
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tbody, { children: [
2583
- loading && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: "Loading..." }) }) }) }),
2584
- !loading && paginated.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: search ? "No public memories match your search" : "No public memories yet — add knowledge for your public chatbot" }) }) }) }),
2585
- paginated.map((mem) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
2586
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: mem.content }) }),
2587
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: mem.category }) }),
2588
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatDate(mem.createdAt) }) }),
2589
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
2590
- /* @__PURE__ */ jsxRuntime.jsx(
2591
- ActionBtn,
2592
- {
2593
- onClick: () => handleOpenEdit(mem),
2594
- "aria-label": "Edit memory",
2595
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {})
2596
- }
2597
- ),
2598
- /* @__PURE__ */ jsxRuntime.jsx(
2599
- DeleteActionBtn,
2600
- {
2601
- onClick: () => removeMemory(mem.documentId),
2602
- "aria-label": "Delete memory",
2603
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
2604
- }
2605
- )
2606
- ] }) })
2607
- ] }, mem.documentId))
2608
- ] })
2609
- ] }),
2610
- pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Pagination.Root, { pageCount, activePage: page, onPageChange: setPage, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Pagination.Links, {}) }) }) }),
2611
- /* @__PURE__ */ jsxRuntime.jsx(
2612
- PublicMemoryModal,
2613
- {
2614
- memory: editingMemory,
2615
- open: modalOpen,
2616
- onClose: handleCloseModal,
2617
- onSave: handleSave
2618
- }
2619
- )
2620
- ] })
2621
- ] });
2622
- };
2623
- function getStrapiOrigin() {
2624
- return window.location.origin;
2625
- }
2626
- function getEmbedSnippet(origin) {
2627
- return `<script src="${origin}/api/ai-sdk/widget.js"><\/script>`;
2628
- }
2629
- function getIframeHtml(origin) {
2630
- return `<!DOCTYPE html>
2631
- <html lang="en">
2632
- <head>
2633
- <meta charset="UTF-8" />
2634
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2635
- <style>
2636
- body { margin: 0; font-family: sans-serif; background: #f5f5f5; min-height: 100vh; }
2637
- </style>
2638
- </head>
2639
- <body>
2640
- <script src="${origin}/api/ai-sdk/widget.js"><\/script>
2641
- </body>
2642
- </html>`;
2643
- }
2644
- function WidgetPreviewPage() {
2645
- const navigate = reactRouterDom.useNavigate();
2646
- const [copied, setCopied] = react.useState(false);
2647
- const origin = getStrapiOrigin();
2648
- const embedSnippet = getEmbedSnippet(origin);
2649
- const handleCopy = async () => {
2650
- await navigator.clipboard.writeText(embedSnippet);
2651
- setCopied(true);
2652
- setTimeout(() => setCopied(false), 2e3);
2653
- };
2654
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
2655
- /* @__PURE__ */ jsxRuntime.jsx(
2656
- admin.Layouts.Header,
2657
- {
2658
- title: "Widget Preview",
2659
- subtitle: "Preview the embeddable chat widget as visitors will see it",
2660
- navigationAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), onClick: () => navigate(`/plugins/${index.PLUGIN_ID}`), children: "Back to Chat" })
2661
- }
2662
- ),
2663
- /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
2664
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
2665
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: "Live Preview" }),
2666
- /* @__PURE__ */ jsxRuntime.jsx(
2667
- designSystem.Box,
2668
- {
2669
- marginTop: 4,
2670
- style: {
2671
- border: "1px solid #dcdce4",
2672
- borderRadius: "4px",
2673
- overflow: "hidden",
2674
- height: "500px"
2675
- },
2676
- children: /* @__PURE__ */ jsxRuntime.jsx(
2677
- "iframe",
2678
- {
2679
- title: "Widget Preview",
2680
- srcDoc: getIframeHtml(origin),
2681
- style: { width: "100%", height: "100%", border: "none" }
2682
- }
2683
- )
2684
- }
2685
- )
2686
- ] }),
2687
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 6, padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
2688
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: "Embed Code" }),
2689
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", tag: "p", style: { marginTop: "8px" }, children: "Add this snippet to any HTML page to embed the chat widget:" }),
2690
- /* @__PURE__ */ jsxRuntime.jsx(
2691
- designSystem.Box,
2692
- {
2693
- marginTop: 4,
2694
- padding: 4,
2695
- background: "neutral100",
2696
- hasRadius: true,
2697
- style: { fontFamily: "monospace", fontSize: "14px", wordBreak: "break-all" },
2698
- children: embedSnippet
2699
- }
2700
- ),
2701
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: handleCopy, children: copied ? "Copied!" : "Copy Snippet" }) })
2702
- ] }),
2703
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 6, padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
2704
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: "Production Setup" }),
2705
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", textColor: "neutral600", tag: "p", style: { marginTop: "8px" }, children: [
2706
- "If the widget is embedded on a different domain, update your Strapi project's",
2707
- " ",
2708
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "config/middlewares.ts" }),
2709
- " to allow cross-origin access:"
2710
- ] }),
2711
- /* @__PURE__ */ jsxRuntime.jsx(
2712
- designSystem.Box,
2713
- {
2714
- marginTop: 4,
2715
- padding: 4,
2716
- background: "neutral100",
2717
- hasRadius: true,
2718
- style: { fontFamily: "monospace", fontSize: "13px", whiteSpace: "pre", overflowX: "auto", lineHeight: "1.5" },
2719
- children: `// config/middlewares.ts
2720
- {
2721
- name: 'strapi::security',
2722
- config: {
2723
- contentSecurityPolicy: {
2724
- directives: {
2725
- 'script-src': ["'self'", "'unsafe-inline'"],
2726
- 'connect-src': ["'self'", "blob:"],
2727
- 'img-src': ["'self'", "data:", "blob:"],
2728
- 'frame-ancestors': ["'self'", "https://your-frontend.com"],
2729
- },
2730
- },
2731
- },
2732
- },
2733
- {
2734
- name: 'strapi::cors',
2735
- config: {
2736
- origin: ['https://your-frontend.com'],
2737
- },
2738
- },`
2739
- }
2740
- ),
2741
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", tag: "p", style: { marginTop: "8px" }, children: [
2742
- "Replace ",
2743
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "https://your-frontend.com" }),
2744
- " with the domain(s) where the widget will be embedded."
2745
- ] })
2746
- ] }),
2747
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 6, padding: 6, background: "neutral0", shadow: "filterShadow", hasRadius: true, children: [
2748
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: "Development" }),
2749
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", tag: "p", style: { marginTop: "8px" }, children: "To live-reload the widget during development, run:" }),
2750
- /* @__PURE__ */ jsxRuntime.jsx(
2751
- designSystem.Box,
2752
- {
2753
- marginTop: 4,
2754
- padding: 4,
2755
- background: "neutral100",
2756
- hasRadius: true,
2757
- style: { fontFamily: "monospace", fontSize: "14px" },
2758
- children: "npm run dev:widget"
2759
- }
2760
- ),
2761
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.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." })
2762
- ] })
2763
- ] })
2764
- ] });
2765
- }
2766
- const App = () => {
2767
- return /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
2768
- /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(HomePage, {}) }),
2769
- /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "memory-store", element: /* @__PURE__ */ jsxRuntime.jsx(MemoryStorePage, {}) }),
2770
- /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "public-memory-store", element: /* @__PURE__ */ jsxRuntime.jsx(PublicMemoryStorePage, {}) }),
2771
- /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "widget-preview", element: /* @__PURE__ */ jsxRuntime.jsx(WidgetPreviewPage, {}) }),
2772
- /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Error, {}) })
2773
- ] });
2774
- };
2775
- exports.App = App;