@datum-cloud/datum-ui 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant/index.mjs +996 -0
- package/dist/components/features/assistant/components/assistant-workspace.d.ts +53 -0
- package/dist/components/features/assistant/components/brain-glyph.d.ts +8 -0
- package/dist/components/features/assistant/components/composer/equalizer.d.ts +6 -0
- package/dist/components/features/assistant/components/composer/index.d.ts +3 -0
- package/dist/components/features/assistant/components/composer/model-selector.d.ts +10 -0
- package/dist/components/features/assistant/components/composer/prompt-card.d.ts +21 -0
- package/dist/components/features/assistant/components/conversation/conversation.d.ts +15 -0
- package/dist/components/features/assistant/components/conversation/index.d.ts +3 -0
- package/dist/components/features/assistant/components/conversation/scroll-to-bottom-button.d.ts +5 -0
- package/dist/components/features/assistant/components/conversation/turn-rail.d.ts +12 -0
- package/dist/components/features/assistant/components/empty-state.d.ts +10 -0
- package/dist/components/features/assistant/components/message/assistant-message.d.ts +9 -0
- package/dist/components/features/assistant/components/message/index.d.ts +4 -0
- package/dist/components/features/assistant/components/message/loading-dots.d.ts +4 -0
- package/dist/components/features/assistant/components/message/thinking-block.d.ts +4 -0
- package/dist/components/features/assistant/components/message/user-message.d.ts +4 -0
- package/dist/components/features/assistant/components/sidebar/chat-rail.d.ts +7 -0
- package/dist/components/features/assistant/components/sidebar/history-panel.d.ts +16 -0
- package/dist/components/features/assistant/components/sidebar/index.d.ts +2 -0
- package/dist/components/features/assistant/context/assistant-config.d.ts +19 -0
- package/dist/components/features/assistant/context/index.d.ts +1 -0
- package/dist/components/features/assistant/hooks/index.d.ts +1 -0
- package/dist/components/features/assistant/hooks/use-turn-rail.d.ts +16 -0
- package/dist/components/features/assistant/index.d.ts +11 -0
- package/dist/components/features/assistant/types.d.ts +66 -0
- package/dist/components/features/assistant/utils/format-relative-time.d.ts +2 -0
- package/dist/components/features/assistant/utils/index.d.ts +2 -0
- package/dist/components/features/assistant/utils/sanitize-html.d.ts +2 -0
- package/dist/dropdown-menu-b8zY5Cs6.mjs +82 -0
- package/dist/index.mjs +1 -1
- package/dist/{layers-CGp0ceSG.mjs → layers-DJYz6fyE.mjs} +2 -78
- package/dist/map/index.mjs +1 -1
- package/package.json +21 -1
|
@@ -0,0 +1,996 @@
|
|
|
1
|
+
import { t as cn } from "../utils-Bu32wN-x.mjs";
|
|
2
|
+
import { t as Icon } from "../icon-wrapper-DKfJlJd0.mjs";
|
|
3
|
+
import { a as DropdownMenuRadioGroup, c as DropdownMenuTrigger, i as DropdownMenuLabel, o as DropdownMenuRadioItem, r as DropdownMenuContent, s as DropdownMenuSeparator, t as DropdownMenu } from "../dropdown-menu-b8zY5Cs6.mjs";
|
|
4
|
+
import { t as Tooltip } from "../tooltip-BhatUaar.mjs";
|
|
5
|
+
import { ArrowDown, ArrowRight, ArrowUp, Brain, Check, ChevronDown, Copy, Download, MessagesSquare, Mic, MicOff, PanelLeft, Plus, RotateCw, Square, Trash2 } from "lucide-react";
|
|
6
|
+
import { Fragment, createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
9
|
+
import { EditorContent } from "@tiptap/react";
|
|
10
|
+
import { getToolName, isReasoningUIPart, isTextUIPart, isToolUIPart } from "ai";
|
|
11
|
+
import { code } from "@streamdown/code";
|
|
12
|
+
import { Streamdown } from "streamdown";
|
|
13
|
+
import "streamdown/styles.css";
|
|
14
|
+
//#region src/components/features/assistant/context/assistant-config.tsx
|
|
15
|
+
/**
|
|
16
|
+
* Framework-agnostic link renderer: internal routes (`/…`) render as a plain
|
|
17
|
+
* same-tab anchor, everything else opens in a new tab. Hosts override
|
|
18
|
+
* `renderLink` to add routing (e.g. a router `<Link>`) or icons (e.g. a Sentry
|
|
19
|
+
* glyph) — see the host config.
|
|
20
|
+
*/
|
|
21
|
+
function defaultRenderLink({ href, children }) {
|
|
22
|
+
if (href?.startsWith("/")) return /* @__PURE__ */ jsx("a", {
|
|
23
|
+
href,
|
|
24
|
+
className: "underline",
|
|
25
|
+
children
|
|
26
|
+
});
|
|
27
|
+
return /* @__PURE__ */ jsx("a", {
|
|
28
|
+
href,
|
|
29
|
+
className: "underline",
|
|
30
|
+
target: "_blank",
|
|
31
|
+
rel: "noopener noreferrer",
|
|
32
|
+
children
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Neutral fallback so leaf components render without a host having wired a full
|
|
37
|
+
* config. Host apps override via `<AssistantConfigProvider config={…}>`.
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_ASSISTANT_CONFIG = {
|
|
40
|
+
greeting: (name) => `Hey there${name ? `, ${name}` : ""}`,
|
|
41
|
+
suggestions: [],
|
|
42
|
+
showReasoning: true,
|
|
43
|
+
modelSelector: false,
|
|
44
|
+
toolLabels: {},
|
|
45
|
+
renderLink: defaultRenderLink
|
|
46
|
+
};
|
|
47
|
+
const AssistantConfigContext = createContext(DEFAULT_ASSISTANT_CONFIG);
|
|
48
|
+
function AssistantConfigProvider({ config, children }) {
|
|
49
|
+
const value = config ? {
|
|
50
|
+
...DEFAULT_ASSISTANT_CONFIG,
|
|
51
|
+
...config
|
|
52
|
+
} : DEFAULT_ASSISTANT_CONFIG;
|
|
53
|
+
return /* @__PURE__ */ jsx(AssistantConfigContext.Provider, {
|
|
54
|
+
value,
|
|
55
|
+
children
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function useAssistantConfig() {
|
|
59
|
+
return useContext(AssistantConfigContext);
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/components/features/assistant/components/composer/equalizer.tsx
|
|
63
|
+
function Equalizer({ frequencyData, className }) {
|
|
64
|
+
return /* @__PURE__ */ jsx("div", {
|
|
65
|
+
className: `flex h-4 items-center justify-center gap-[3px] ${className ?? ""}`,
|
|
66
|
+
children: frequencyData.map((level, i) => /* @__PURE__ */ jsx(motion.div, {
|
|
67
|
+
className: "bg-foreground w-[3px] rounded-full",
|
|
68
|
+
animate: { height: Math.max(3, level * 24) },
|
|
69
|
+
transition: {
|
|
70
|
+
type: "spring",
|
|
71
|
+
stiffness: 400,
|
|
72
|
+
damping: 15,
|
|
73
|
+
mass: .3
|
|
74
|
+
}
|
|
75
|
+
}, i))
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/components/features/assistant/components/composer/model-selector.tsx
|
|
80
|
+
function ModelSelector({ modelId, effortId, onModelChange, onEffortChange, disabled }) {
|
|
81
|
+
const { modelSelector } = useAssistantConfig();
|
|
82
|
+
if (!modelSelector) return null;
|
|
83
|
+
const { models, efforts } = modelSelector;
|
|
84
|
+
const model = models.find((m) => m.id === modelId) ?? models[0];
|
|
85
|
+
const effort = efforts.find((e) => e.id === effortId) ?? efforts[0];
|
|
86
|
+
if (!model || !effort) return null;
|
|
87
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
88
|
+
asChild: true,
|
|
89
|
+
disabled,
|
|
90
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
91
|
+
type: "button",
|
|
92
|
+
"aria-label": "Select model and effort",
|
|
93
|
+
className: "text-muted-foreground hover:text-foreground flex items-center gap-1.5 rounded-md px-1.5 py-1 text-xs transition-colors disabled:opacity-50",
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsx("span", {
|
|
96
|
+
className: "text-foreground font-medium",
|
|
97
|
+
children: model.label
|
|
98
|
+
}),
|
|
99
|
+
/* @__PURE__ */ jsx("span", { children: effort.label }),
|
|
100
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5 shrink-0" })
|
|
101
|
+
]
|
|
102
|
+
})
|
|
103
|
+
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
104
|
+
align: "end",
|
|
105
|
+
className: "w-44",
|
|
106
|
+
children: [
|
|
107
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, {
|
|
108
|
+
className: "text-muted-foreground text-[10px] tracking-wide uppercase",
|
|
109
|
+
children: "Model"
|
|
110
|
+
}),
|
|
111
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
|
|
112
|
+
value: modelId,
|
|
113
|
+
onValueChange: onModelChange,
|
|
114
|
+
children: models.map((m) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
115
|
+
value: m.id,
|
|
116
|
+
className: "text-sm",
|
|
117
|
+
children: m.label
|
|
118
|
+
}, m.id))
|
|
119
|
+
}),
|
|
120
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
121
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, {
|
|
122
|
+
className: "text-muted-foreground text-[10px] tracking-wide uppercase",
|
|
123
|
+
children: "Effort"
|
|
124
|
+
}),
|
|
125
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
|
|
126
|
+
value: effortId,
|
|
127
|
+
onValueChange: (v) => onEffortChange(v),
|
|
128
|
+
children: efforts.map((e) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
129
|
+
value: e.id,
|
|
130
|
+
className: "text-sm",
|
|
131
|
+
children: e.label
|
|
132
|
+
}, e.id))
|
|
133
|
+
})
|
|
134
|
+
]
|
|
135
|
+
})] });
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/components/features/assistant/components/composer/prompt-card.tsx
|
|
139
|
+
function PromptCard({ editor, isReady, canRetry, onSend, onStop, onRetry, modelId, effortId, onModelChange, onEffortChange, speechSupported, isListening, frequencyData, onMicToggle, className }) {
|
|
140
|
+
const { modelSelector } = useAssistantConfig();
|
|
141
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
142
|
+
className: cn("bg-card border-primary/30 rounded-2xl border p-3 shadow-sm transition-shadow", className),
|
|
143
|
+
children: [/* @__PURE__ */ jsx(EditorContent, {
|
|
144
|
+
editor,
|
|
145
|
+
className: "max-h-52 min-h-9 overflow-y-auto px-1 py-1"
|
|
146
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
147
|
+
className: "mt-1 flex items-center justify-end gap-2",
|
|
148
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
149
|
+
className: "flex items-center gap-1",
|
|
150
|
+
children: [
|
|
151
|
+
modelSelector && /* @__PURE__ */ jsx(ModelSelector, {
|
|
152
|
+
modelId,
|
|
153
|
+
effortId,
|
|
154
|
+
onModelChange,
|
|
155
|
+
onEffortChange
|
|
156
|
+
}),
|
|
157
|
+
speechSupported && /* @__PURE__ */ jsx(Tooltip, {
|
|
158
|
+
message: isListening ? "Stop dictating" : "Dictate",
|
|
159
|
+
side: "top",
|
|
160
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
161
|
+
type: "button",
|
|
162
|
+
onClick: onMicToggle,
|
|
163
|
+
"aria-label": isListening ? "Stop listening" : "Start voice input",
|
|
164
|
+
className: cn("hover:bg-accent shrink-0 rounded-full p-1.5 transition-colors", isListening ? "text-destructive hover:text-destructive/80" : "text-muted-foreground hover:text-foreground"),
|
|
165
|
+
children: isListening ? frequencyData ? /* @__PURE__ */ jsx(Equalizer, { frequencyData }) : /* @__PURE__ */ jsx(MicOff, { className: "size-4" }) : /* @__PURE__ */ jsx(Mic, { className: "size-4" })
|
|
166
|
+
})
|
|
167
|
+
}),
|
|
168
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: canRetry && /* @__PURE__ */ jsx(motion.div, {
|
|
169
|
+
className: "flex items-center",
|
|
170
|
+
initial: {
|
|
171
|
+
opacity: 0,
|
|
172
|
+
scale: .8
|
|
173
|
+
},
|
|
174
|
+
animate: {
|
|
175
|
+
opacity: 1,
|
|
176
|
+
scale: 1
|
|
177
|
+
},
|
|
178
|
+
exit: {
|
|
179
|
+
opacity: 0,
|
|
180
|
+
scale: .8
|
|
181
|
+
},
|
|
182
|
+
transition: {
|
|
183
|
+
type: "spring",
|
|
184
|
+
stiffness: 500,
|
|
185
|
+
damping: 30
|
|
186
|
+
},
|
|
187
|
+
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
188
|
+
message: "Retry last message",
|
|
189
|
+
side: "top",
|
|
190
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
191
|
+
type: "button",
|
|
192
|
+
onClick: onRetry,
|
|
193
|
+
"aria-label": "Retry last message",
|
|
194
|
+
className: "text-muted-foreground hover:text-foreground hover:bg-accent shrink-0 rounded-full p-1.5 transition-colors",
|
|
195
|
+
children: /* @__PURE__ */ jsx(RotateCw, { className: "size-4" })
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
}) }),
|
|
199
|
+
isReady ? /* @__PURE__ */ jsx(Tooltip, {
|
|
200
|
+
message: "Send message",
|
|
201
|
+
side: "top",
|
|
202
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
203
|
+
type: "button",
|
|
204
|
+
onClick: onSend,
|
|
205
|
+
"aria-label": "Send message",
|
|
206
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 shrink-0 rounded-full p-1.5 transition-colors",
|
|
207
|
+
children: /* @__PURE__ */ jsx(ArrowUp, { className: "size-4" })
|
|
208
|
+
})
|
|
209
|
+
}) : /* @__PURE__ */ jsx(Tooltip, {
|
|
210
|
+
message: "Stop generating",
|
|
211
|
+
side: "top",
|
|
212
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
213
|
+
type: "button",
|
|
214
|
+
onClick: onStop,
|
|
215
|
+
"aria-label": "Stop generating",
|
|
216
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 shrink-0 rounded-full p-1.5 transition-colors",
|
|
217
|
+
children: /* @__PURE__ */ jsx(Square, { className: "size-4 fill-current" })
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
]
|
|
221
|
+
})
|
|
222
|
+
})]
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/components/features/assistant/hooks/use-turn-rail.ts
|
|
227
|
+
/**
|
|
228
|
+
* Tracks conversation "turns" (submitted prompts) for the right-edge rail:
|
|
229
|
+
* derives the turn list, follows which turn is in view as you scroll, and
|
|
230
|
+
* scrolls a chosen turn to the top. Owns a node ref to the scroll container,
|
|
231
|
+
* composed with the caller's own container ref callback.
|
|
232
|
+
*/
|
|
233
|
+
function useTurnRail(messages, containerRef, userScrolledUpRef) {
|
|
234
|
+
const [activeTurn, setActiveTurn] = useState(null);
|
|
235
|
+
const scrollElRef = useRef(null);
|
|
236
|
+
const programmaticUntilRef = useRef(0);
|
|
237
|
+
const turns = useMemo(() => messages.filter((m) => m.role === "user").map((m) => ({
|
|
238
|
+
id: m.id,
|
|
239
|
+
text: m.parts.find(isTextUIPart)?.text ?? ""
|
|
240
|
+
})), [messages]);
|
|
241
|
+
const activeTurnId = activeTurn ?? turns.at(-1)?.id ?? null;
|
|
242
|
+
const setScrollEl = useCallback((node) => {
|
|
243
|
+
scrollElRef.current = node;
|
|
244
|
+
containerRef(node);
|
|
245
|
+
}, [containerRef]);
|
|
246
|
+
const refineActiveTurn = (el) => {
|
|
247
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight < 8) {
|
|
248
|
+
const last = turns.at(-1)?.id;
|
|
249
|
+
if (last) setActiveTurn(last);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const cTop = el.getBoundingClientRect().top;
|
|
253
|
+
let active = null;
|
|
254
|
+
el.querySelectorAll("[data-turn-id]").forEach((t) => {
|
|
255
|
+
if (t.getBoundingClientRect().top - cTop <= 120) active = t.dataset.turnId ?? null;
|
|
256
|
+
});
|
|
257
|
+
if (active) setActiveTurn(active);
|
|
258
|
+
};
|
|
259
|
+
/** Handle a scroll event. Returns whether the user is scrolled up (for the button). */
|
|
260
|
+
const handleScroll = (el) => {
|
|
261
|
+
const scrolledUp = el.scrollHeight - el.scrollTop - el.clientHeight > 50;
|
|
262
|
+
if (Date.now() < programmaticUntilRef.current) {
|
|
263
|
+
userScrolledUpRef.current = true;
|
|
264
|
+
return scrolledUp;
|
|
265
|
+
}
|
|
266
|
+
userScrolledUpRef.current = scrolledUp;
|
|
267
|
+
refineActiveTurn(el);
|
|
268
|
+
return scrolledUp;
|
|
269
|
+
};
|
|
270
|
+
const scrollToTurn = (id) => {
|
|
271
|
+
const container = scrollElRef.current;
|
|
272
|
+
const target = container?.querySelector(`[data-turn-id="${id}"]`);
|
|
273
|
+
if (!container || !target) return;
|
|
274
|
+
const top = target.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
|
|
275
|
+
programmaticUntilRef.current = Date.now() + 700;
|
|
276
|
+
userScrolledUpRef.current = true;
|
|
277
|
+
setActiveTurn(id);
|
|
278
|
+
container.scrollTo({
|
|
279
|
+
top: Math.max(0, top - 16),
|
|
280
|
+
behavior: "smooth"
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
turns,
|
|
285
|
+
activeTurnId,
|
|
286
|
+
setScrollEl,
|
|
287
|
+
handleScroll,
|
|
288
|
+
scrollToTurn
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/components/features/assistant/utils/format-relative-time.ts
|
|
293
|
+
/** Human-friendly relative timestamp for the chat history list. */
|
|
294
|
+
function formatRelativeTime(timestamp) {
|
|
295
|
+
const seconds = Math.floor((Date.now() - timestamp) / 1e3);
|
|
296
|
+
if (seconds < 60) return "just now";
|
|
297
|
+
const minutes = Math.floor(seconds / 60);
|
|
298
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
299
|
+
const hours = Math.floor(minutes / 60);
|
|
300
|
+
if (hours < 24) return `${hours}h ago`;
|
|
301
|
+
const days = Math.floor(hours / 24);
|
|
302
|
+
if (days === 1) return "Yesterday";
|
|
303
|
+
if (days < 7) return `${days}d ago`;
|
|
304
|
+
return new Date(timestamp).toLocaleDateString();
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/components/features/assistant/utils/sanitize-html.ts
|
|
308
|
+
const HTML_ESCAPE_MAP = {
|
|
309
|
+
"&": "&",
|
|
310
|
+
"<": "<",
|
|
311
|
+
">": ">",
|
|
312
|
+
"\"": """,
|
|
313
|
+
"'": "'"
|
|
314
|
+
};
|
|
315
|
+
function escapeHtml(s) {
|
|
316
|
+
return s.replace(/[&<>"']/g, (c) => HTML_ESCAPE_MAP[c]);
|
|
317
|
+
}
|
|
318
|
+
const ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
319
|
+
"p",
|
|
320
|
+
"strong",
|
|
321
|
+
"em",
|
|
322
|
+
"u",
|
|
323
|
+
"s",
|
|
324
|
+
"br"
|
|
325
|
+
]);
|
|
326
|
+
/** Whitelist-sanitize the tiptap-authored HTML of a user message before render. */
|
|
327
|
+
function sanitizeUserHtml(raw) {
|
|
328
|
+
const doc = new DOMParser().parseFromString(raw, "text/html");
|
|
329
|
+
function walk(node) {
|
|
330
|
+
if (node.nodeType === Node.TEXT_NODE) return escapeHtml(node.textContent ?? "");
|
|
331
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return "";
|
|
332
|
+
const el = node;
|
|
333
|
+
const tag = el.tagName.toLowerCase();
|
|
334
|
+
const children = Array.from(el.childNodes).map(walk).join("");
|
|
335
|
+
if (ALLOWED_TAGS.has(tag)) return tag === "br" ? "<br>" : `<${tag}>${children}</${tag}>`;
|
|
336
|
+
return children;
|
|
337
|
+
}
|
|
338
|
+
const result = Array.from(doc.body.childNodes).map(walk).join("");
|
|
339
|
+
return result.startsWith("<p>") ? result : `<p>${result}</p>`;
|
|
340
|
+
}
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/components/features/assistant/components/message/loading-dots.tsx
|
|
343
|
+
/** Three bouncing dots — the assistant's "thinking" indicator. */
|
|
344
|
+
function LoadingDots({ className }) {
|
|
345
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
346
|
+
className: cn("flex gap-1", className),
|
|
347
|
+
children: [
|
|
348
|
+
/* @__PURE__ */ jsx("span", { className: "bg-muted-foreground/50 h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.3s]" }),
|
|
349
|
+
/* @__PURE__ */ jsx("span", { className: "bg-muted-foreground/50 h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.15s]" }),
|
|
350
|
+
/* @__PURE__ */ jsx("span", { className: "bg-muted-foreground/50 h-1.5 w-1.5 animate-bounce rounded-full" })
|
|
351
|
+
]
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/components/features/assistant/components/message/thinking-block.tsx
|
|
356
|
+
function ThinkingBlock({ text, isStreaming = false }) {
|
|
357
|
+
const [open, setOpen] = useState(false);
|
|
358
|
+
const startedAt = useRef(null);
|
|
359
|
+
const duration = useRef(null);
|
|
360
|
+
if (isStreaming && startedAt.current === null) startedAt.current = Date.now();
|
|
361
|
+
if (!isStreaming && startedAt.current !== null && duration.current === null) duration.current = Math.round((Date.now() - startedAt.current) / 1e3);
|
|
362
|
+
const label = isStreaming ? "Thinking…" : duration.current !== null ? duration.current < 1 ? "Thought for less than 1 second" : `Thought for ${duration.current}s` : "Thought";
|
|
363
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
364
|
+
className: "mb-3",
|
|
365
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
366
|
+
onClick: () => !isStreaming && setOpen((o) => !o),
|
|
367
|
+
disabled: isStreaming,
|
|
368
|
+
className: cn("text-muted-foreground flex items-center gap-1.5 text-xs transition-colors", !isStreaming && "hover:text-foreground cursor-pointer", isStreaming && "cursor-default"),
|
|
369
|
+
children: [
|
|
370
|
+
/* @__PURE__ */ jsx(Brain, { className: cn("size-3.5 shrink-0", isStreaming && "animate-pulse") }),
|
|
371
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
372
|
+
!isStreaming && /* @__PURE__ */ jsx(ChevronDown, { className: cn("size-3.5 shrink-0 transition-transform", open && "rotate-180") })
|
|
373
|
+
]
|
|
374
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
375
|
+
className: cn("grid transition-[grid-template-rows] duration-200", open && !isStreaming ? "grid-rows-[1fr]" : "grid-rows-[0fr]"),
|
|
376
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
377
|
+
className: "overflow-hidden",
|
|
378
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
379
|
+
className: "text-muted-foreground/75 border-muted-foreground/20 mt-2 border-l-2 pl-3 text-xs leading-relaxed whitespace-pre-wrap",
|
|
380
|
+
children: text
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
})]
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/components/features/assistant/components/message/assistant-message.tsx
|
|
388
|
+
function messageText(msg) {
|
|
389
|
+
return msg.parts.filter(isTextUIPart).map((p) => p.text).join("\n\n");
|
|
390
|
+
}
|
|
391
|
+
function AssistantMessage({ msg, isLastMessage, status }) {
|
|
392
|
+
const { toolLabels, showReasoning, messageActions, renderLink, renderToolOutput } = useAssistantConfig();
|
|
393
|
+
const isStreaming = isLastMessage && status === "streaming";
|
|
394
|
+
const hasText = msg.parts.some((p) => isTextUIPart(p) && p.text);
|
|
395
|
+
const activeToolPart = msg.parts.find((p) => isToolUIPart(p) && (p.state === "input-streaming" || p.state === "input-available"));
|
|
396
|
+
const showInitialLoading = isStreaming && !hasText && !activeToolPart;
|
|
397
|
+
const showToolIndicator = isLastMessage && activeToolPart != null;
|
|
398
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
399
|
+
className: "text-foreground w-full text-sm",
|
|
400
|
+
children: [
|
|
401
|
+
msg.parts.map((part, i) => {
|
|
402
|
+
if (isReasoningUIPart(part)) {
|
|
403
|
+
if (!showReasoning) return null;
|
|
404
|
+
const isThinkingStreaming = isStreaming && i === msg.parts.length - 1;
|
|
405
|
+
return /* @__PURE__ */ jsx(ThinkingBlock, {
|
|
406
|
+
text: part.text,
|
|
407
|
+
isStreaming: isThinkingStreaming
|
|
408
|
+
}, i);
|
|
409
|
+
}
|
|
410
|
+
if (isTextUIPart(part) && part.text) return /* @__PURE__ */ jsx(Streamdown, {
|
|
411
|
+
className: cn("**:data-[streamdown='code-block-body']:bg-card **:data-[streamdown='inline-code']:bg-card dark:**:data-[streamdown='inline-code']:bg-accent **:data-[streamdown='inline-code']:border [&_h2]:text-xl [&_h3]:text-lg", isStreaming && "**:data-[streamdown='inline-code']:animate-[sd-blurIn_300ms_ease-out_both]"),
|
|
412
|
+
isAnimating: isStreaming,
|
|
413
|
+
plugins: { code },
|
|
414
|
+
animated: {
|
|
415
|
+
animation: "blurIn",
|
|
416
|
+
duration: 300,
|
|
417
|
+
easing: "ease-out",
|
|
418
|
+
sep: "word",
|
|
419
|
+
stagger: 30
|
|
420
|
+
},
|
|
421
|
+
components: {
|
|
422
|
+
a: ({ href, children }) => renderLink({
|
|
423
|
+
href,
|
|
424
|
+
children
|
|
425
|
+
}),
|
|
426
|
+
img: ({ src, alt }) => /* @__PURE__ */ jsx("img", {
|
|
427
|
+
src,
|
|
428
|
+
alt: alt ?? "",
|
|
429
|
+
className: "my-2 inline-block max-h-16 rounded-full",
|
|
430
|
+
referrerPolicy: "no-referrer"
|
|
431
|
+
})
|
|
432
|
+
},
|
|
433
|
+
children: part.text
|
|
434
|
+
}, i);
|
|
435
|
+
if (isToolUIPart(part) && part.state === "output-available" && renderToolOutput) {
|
|
436
|
+
const rendered = renderToolOutput(part, msg);
|
|
437
|
+
if (rendered) return /* @__PURE__ */ jsx(Fragment, { children: rendered }, i);
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
}),
|
|
441
|
+
showInitialLoading && /* @__PURE__ */ jsx("div", {
|
|
442
|
+
className: "py-1",
|
|
443
|
+
children: /* @__PURE__ */ jsx(LoadingDots, {})
|
|
444
|
+
}),
|
|
445
|
+
showToolIndicator && /* @__PURE__ */ jsxs("div", {
|
|
446
|
+
className: "text-muted-foreground flex items-center gap-1.5 py-1 text-xs",
|
|
447
|
+
children: [
|
|
448
|
+
/* @__PURE__ */ jsx("span", { className: "bg-muted-foreground/50 h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.3s]" }),
|
|
449
|
+
/* @__PURE__ */ jsx("span", { className: "bg-muted-foreground/50 h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.15s]" }),
|
|
450
|
+
/* @__PURE__ */ jsx("span", { className: "bg-muted-foreground/50 h-1.5 w-1.5 animate-bounce rounded-full" }),
|
|
451
|
+
/* @__PURE__ */ jsx("span", { children: toolLabels[getToolName(activeToolPart)] ?? "Working…" })
|
|
452
|
+
]
|
|
453
|
+
}),
|
|
454
|
+
!isStreaming && hasText && (messageActions ? messageActions(msg) : /* @__PURE__ */ jsx(MessageActions, { msg }))
|
|
455
|
+
]
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
function MessageActions({ msg }) {
|
|
459
|
+
const [copied, setCopied] = useState(false);
|
|
460
|
+
const handleCopy = async () => {
|
|
461
|
+
try {
|
|
462
|
+
await navigator.clipboard.writeText(messageText(msg));
|
|
463
|
+
setCopied(true);
|
|
464
|
+
setTimeout(setCopied, 1500, false);
|
|
465
|
+
} catch {}
|
|
466
|
+
};
|
|
467
|
+
const handleDownload = () => {
|
|
468
|
+
const blob = new Blob([messageText(msg)], { type: "text/markdown" });
|
|
469
|
+
const url = URL.createObjectURL(blob);
|
|
470
|
+
const a = document.createElement("a");
|
|
471
|
+
a.href = url;
|
|
472
|
+
a.download = "assistant-message.md";
|
|
473
|
+
a.click();
|
|
474
|
+
URL.revokeObjectURL(url);
|
|
475
|
+
};
|
|
476
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
477
|
+
className: "text-muted-foreground/60 mt-1.5 flex items-center gap-0.5",
|
|
478
|
+
children: [/* @__PURE__ */ jsx(Tooltip, {
|
|
479
|
+
message: copied ? "Copied" : "Copy",
|
|
480
|
+
side: "top",
|
|
481
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
482
|
+
type: "button",
|
|
483
|
+
onClick: handleCopy,
|
|
484
|
+
"aria-label": "Copy message",
|
|
485
|
+
className: "hover:text-foreground rounded p-1 transition-colors",
|
|
486
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "size-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "size-3.5" })
|
|
487
|
+
})
|
|
488
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
489
|
+
message: "Download as Markdown",
|
|
490
|
+
side: "top",
|
|
491
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
492
|
+
type: "button",
|
|
493
|
+
onClick: handleDownload,
|
|
494
|
+
"aria-label": "Download message",
|
|
495
|
+
className: "hover:text-foreground rounded p-1 transition-colors",
|
|
496
|
+
children: /* @__PURE__ */ jsx(Download, { className: "size-3.5" })
|
|
497
|
+
})
|
|
498
|
+
})]
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
//#endregion
|
|
502
|
+
//#region src/components/features/assistant/components/message/user-message.tsx
|
|
503
|
+
/** A user's message — a right-aligned bubble rendering the sanitized input HTML. */
|
|
504
|
+
function UserMessage({ html }) {
|
|
505
|
+
return /* @__PURE__ */ jsx("div", {
|
|
506
|
+
className: "flex justify-end",
|
|
507
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
508
|
+
className: "bg-border text-foreground max-w-[80%] rounded-2xl rounded-br-sm px-4 py-2.5 text-sm",
|
|
509
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
510
|
+
className: "[&_em]:italic [&_p]:my-0.5 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0 [&_s]:line-through [&_strong]:font-semibold [&_u]:underline",
|
|
511
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
512
|
+
})
|
|
513
|
+
})
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/components/features/assistant/components/conversation/scroll-to-bottom-button.tsx
|
|
518
|
+
/** Floating chevron above the composer; jumps to the latest message. */
|
|
519
|
+
function ScrollToBottomButton({ show, onClick }) {
|
|
520
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { children: show && /* @__PURE__ */ jsx(motion.button, {
|
|
521
|
+
type: "button",
|
|
522
|
+
initial: {
|
|
523
|
+
opacity: 0,
|
|
524
|
+
y: 8
|
|
525
|
+
},
|
|
526
|
+
animate: {
|
|
527
|
+
opacity: 1,
|
|
528
|
+
y: 0
|
|
529
|
+
},
|
|
530
|
+
exit: {
|
|
531
|
+
opacity: 0,
|
|
532
|
+
y: 8
|
|
533
|
+
},
|
|
534
|
+
transition: { duration: .15 },
|
|
535
|
+
onClick,
|
|
536
|
+
"aria-label": "Scroll to bottom",
|
|
537
|
+
className: "ring-border bg-card text-muted-foreground hover:text-foreground absolute -top-10 left-1/2 z-10 -translate-x-1/2 rounded-full p-1.5 ring-1 transition-colors",
|
|
538
|
+
children: /* @__PURE__ */ jsx(ArrowDown, { className: "size-3.5" })
|
|
539
|
+
}) });
|
|
540
|
+
}
|
|
541
|
+
//#endregion
|
|
542
|
+
//#region src/components/features/assistant/components/conversation/turn-rail.tsx
|
|
543
|
+
/**
|
|
544
|
+
* Right-edge rail: ticks (one per submitted prompt) when idle; morphs into a
|
|
545
|
+
* compact, clickable prompt list on hover. The in-view turn is highlighted.
|
|
546
|
+
*/
|
|
547
|
+
function TurnRail({ turns, activeId, onJump }) {
|
|
548
|
+
const [open, setOpen] = useState(false);
|
|
549
|
+
if (turns.length <= 1) return null;
|
|
550
|
+
return /* @__PURE__ */ jsx("div", {
|
|
551
|
+
className: "absolute top-1/2 right-6 z-20 -translate-y-1/2",
|
|
552
|
+
onMouseEnter: () => setOpen(true),
|
|
553
|
+
onMouseLeave: () => setOpen(false),
|
|
554
|
+
children: open ? /* @__PURE__ */ jsx("div", {
|
|
555
|
+
className: "bg-card ring-border max-h-[65vh] w-64 overflow-y-auto rounded-xl p-1 shadow-lg ring-1",
|
|
556
|
+
children: turns.map((turn) => /* @__PURE__ */ jsx("button", {
|
|
557
|
+
type: "button",
|
|
558
|
+
onClick: () => onJump(turn.id),
|
|
559
|
+
className: cn("block w-full truncate rounded-md px-2.5 py-1.5 text-left text-xs transition-colors", activeId === turn.id ? "bg-accent text-foreground" : "text-muted-foreground hover:bg-accent/60 hover:text-foreground"),
|
|
560
|
+
children: turn.text || "Untitled prompt"
|
|
561
|
+
}, turn.id))
|
|
562
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
563
|
+
className: "flex max-h-[55vh] flex-col items-end gap-2 overflow-hidden",
|
|
564
|
+
children: turns.map((turn) => /* @__PURE__ */ jsx("span", { className: cn("h-[3px] rounded-full transition-all", activeId === turn.id ? "bg-primary w-5" : "bg-muted-foreground/30 w-3.5") }, turn.id))
|
|
565
|
+
})
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
//#endregion
|
|
569
|
+
//#region src/components/features/assistant/components/conversation/conversation.tsx
|
|
570
|
+
function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRef, bottomRef, userScrolledUpRef, footer }) {
|
|
571
|
+
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
572
|
+
const { turns, activeTurnId, setScrollEl, handleScroll, scrollToTurn } = useTurnRail(messages, containerRef, userScrolledUpRef);
|
|
573
|
+
let userMsgIdx = 0;
|
|
574
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
575
|
+
className: "relative flex min-h-0 flex-1 flex-col",
|
|
576
|
+
children: [
|
|
577
|
+
/* @__PURE__ */ jsx("div", {
|
|
578
|
+
ref: setScrollEl,
|
|
579
|
+
onScroll: (e) => setShowScrollButton(handleScroll(e.currentTarget)),
|
|
580
|
+
className: "flex-1 overflow-y-auto scroll-smooth",
|
|
581
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
582
|
+
className: "mx-auto flex w-full max-w-3xl flex-col gap-6 px-4 py-6",
|
|
583
|
+
children: [
|
|
584
|
+
messages.map((msg, msgIdx) => {
|
|
585
|
+
const isLastMessage = msgIdx === messages.length - 1;
|
|
586
|
+
if (msg.role === "user") {
|
|
587
|
+
const html = htmlByUserMsgIndex.current[userMsgIdx++];
|
|
588
|
+
const fallbackText = msg.parts.find(isTextUIPart)?.text ?? "";
|
|
589
|
+
return /* @__PURE__ */ jsx(motion.div, {
|
|
590
|
+
"data-turn-id": msg.id,
|
|
591
|
+
initial: {
|
|
592
|
+
opacity: 0,
|
|
593
|
+
y: 8
|
|
594
|
+
},
|
|
595
|
+
animate: {
|
|
596
|
+
opacity: 1,
|
|
597
|
+
y: 0
|
|
598
|
+
},
|
|
599
|
+
transition: {
|
|
600
|
+
type: "spring",
|
|
601
|
+
stiffness: 500,
|
|
602
|
+
damping: 30
|
|
603
|
+
},
|
|
604
|
+
children: /* @__PURE__ */ jsx(UserMessage, { html: html ?? sanitizeUserHtml(fallbackText) })
|
|
605
|
+
}, msg.id);
|
|
606
|
+
}
|
|
607
|
+
return /* @__PURE__ */ jsx(motion.div, {
|
|
608
|
+
initial: {
|
|
609
|
+
opacity: 0,
|
|
610
|
+
y: 8
|
|
611
|
+
},
|
|
612
|
+
animate: {
|
|
613
|
+
opacity: 1,
|
|
614
|
+
y: 0
|
|
615
|
+
},
|
|
616
|
+
transition: {
|
|
617
|
+
type: "spring",
|
|
618
|
+
stiffness: 500,
|
|
619
|
+
damping: 30
|
|
620
|
+
},
|
|
621
|
+
children: /* @__PURE__ */ jsx(AssistantMessage, {
|
|
622
|
+
msg,
|
|
623
|
+
isLastMessage,
|
|
624
|
+
status
|
|
625
|
+
})
|
|
626
|
+
}, msg.id);
|
|
627
|
+
}),
|
|
628
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: error && /* @__PURE__ */ jsx(motion.div, {
|
|
629
|
+
initial: {
|
|
630
|
+
opacity: 0,
|
|
631
|
+
y: 8
|
|
632
|
+
},
|
|
633
|
+
animate: {
|
|
634
|
+
opacity: 1,
|
|
635
|
+
y: 0
|
|
636
|
+
},
|
|
637
|
+
exit: {
|
|
638
|
+
opacity: 0,
|
|
639
|
+
y: -8
|
|
640
|
+
},
|
|
641
|
+
transition: {
|
|
642
|
+
type: "spring",
|
|
643
|
+
stiffness: 500,
|
|
644
|
+
damping: 30
|
|
645
|
+
},
|
|
646
|
+
className: "text-destructive bg-destructive/10 w-fit rounded-lg px-3 py-2 text-sm",
|
|
647
|
+
children: error.message.includes("429") || error.message.includes("Too Many") ? "Easy there, speed racer! You've hit the rate limit. Give it a minute and try again." : error.message.includes("503") || error.message.includes("not configured") ? "AI assistant is not configured." : `Something went wrong — ${error.message}`
|
|
648
|
+
}, "error") }),
|
|
649
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: status === "submitted" && /* @__PURE__ */ jsx(motion.div, {
|
|
650
|
+
initial: {
|
|
651
|
+
opacity: 0,
|
|
652
|
+
scale: .9
|
|
653
|
+
},
|
|
654
|
+
animate: {
|
|
655
|
+
opacity: 1,
|
|
656
|
+
scale: 1
|
|
657
|
+
},
|
|
658
|
+
exit: {
|
|
659
|
+
opacity: 0,
|
|
660
|
+
scale: .9
|
|
661
|
+
},
|
|
662
|
+
transition: {
|
|
663
|
+
type: "spring",
|
|
664
|
+
stiffness: 500,
|
|
665
|
+
damping: 30
|
|
666
|
+
},
|
|
667
|
+
className: "flex justify-start",
|
|
668
|
+
children: /* @__PURE__ */ jsx(LoadingDots, {})
|
|
669
|
+
}, "loading") }),
|
|
670
|
+
/* @__PURE__ */ jsx("div", { ref: bottomRef })
|
|
671
|
+
]
|
|
672
|
+
})
|
|
673
|
+
}),
|
|
674
|
+
/* @__PURE__ */ jsx(TurnRail, {
|
|
675
|
+
turns,
|
|
676
|
+
activeId: activeTurnId,
|
|
677
|
+
onJump: scrollToTurn
|
|
678
|
+
}),
|
|
679
|
+
/* @__PURE__ */ jsxs("div", {
|
|
680
|
+
className: "relative shrink-0 px-4 pb-4",
|
|
681
|
+
children: [/* @__PURE__ */ jsx(ScrollToBottomButton, {
|
|
682
|
+
show: showScrollButton && messages.length > 0,
|
|
683
|
+
onClick: () => {
|
|
684
|
+
userScrolledUpRef.current = false;
|
|
685
|
+
setShowScrollButton(false);
|
|
686
|
+
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
687
|
+
}
|
|
688
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
689
|
+
className: "mx-auto w-full max-w-3xl",
|
|
690
|
+
children: footer
|
|
691
|
+
})]
|
|
692
|
+
})
|
|
693
|
+
]
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region src/components/features/assistant/components/brain-glyph.tsx
|
|
698
|
+
/**
|
|
699
|
+
* Decorative brain/sunburst glyph for the assistant empty-state greeting (per
|
|
700
|
+
* Figma). Follows the Datum logo's theming: canyon-clay (#bf9595) in light,
|
|
701
|
+
* aurora-moss (#e6f59f) in dark. Size is driven by the passed `className`.
|
|
702
|
+
*/
|
|
703
|
+
function BrainGlyph({ className }) {
|
|
704
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
705
|
+
className,
|
|
706
|
+
viewBox: "0 0 61 61",
|
|
707
|
+
fill: "none",
|
|
708
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
709
|
+
"aria-hidden": "true",
|
|
710
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
711
|
+
className: "fill-[#bf9595] dark:fill-[#e6f59f]",
|
|
712
|
+
fillRule: "evenodd",
|
|
713
|
+
clipRule: "evenodd",
|
|
714
|
+
d: "M30.7567 48.9374C29.3703 50.1334 26.6771 50.463 25.3874 47.9318C24.9868 47.1477 24.822 46.2528 24.7453 45.5766C23.7766 45.4687 22.1459 45.1533 20.8362 44.2187C20.7936 44.1874 20.2311 43.784 19.7794 43.1079C19.2169 42.267 18.9612 41.2301 19.1658 40.0454C19.251 39.5568 19.4073 39.0852 19.5976 38.6505C18.7709 38.6619 17.8533 38.5284 16.9897 38.0823C15.1004 37.1108 14.1999 34.9261 15.5124 32.5937C14.0039 32.3153 12.7255 31.2557 11.9329 29.6022C11.3363 28.3607 10.5692 25.4971 14.9982 23.0794C15.1715 22.9857 15.3391 22.8976 15.4982 22.8209C14.8192 21.909 14.1061 20.5425 14.3647 19.0056C14.7908 16.4573 17.8249 14.6789 22.2905 16.1675C21.9865 14.6277 21.9439 12.6221 23.37 11.4488C23.8217 11.0766 25.0206 10.409 26.2706 10.3891C28.4865 10.355 29.8445 12.034 30.9922 13.1732C31.0547 13.1135 31.1399 13.0255 31.2564 12.9062C32.4979 11.6391 34.3558 9.59365 37.3047 10.7613C37.4638 10.8238 38.0689 11.0823 38.4837 11.4005C40.0093 12.5681 39.9269 14.6761 39.5945 16.2585C40.8814 15.8096 42.5888 15.4459 44.2961 15.8295C46.5404 16.3323 48.066 18.2073 47.5262 20.5994C47.3245 21.4886 46.8785 22.2499 46.441 22.8295C48.0774 23.6392 50.5802 25.2301 50.478 27.8465C50.4751 27.9516 50.4836 29.0965 49.4638 30.5482C48.6598 31.6902 47.603 32.3692 46.4297 32.5766C48.0404 35.409 46.424 38.0823 43.4438 38.5568C43.0632 38.6164 42.691 38.6363 42.3388 38.6306C42.8757 39.8465 43.1654 41.3749 42.3388 42.8351C41.9581 43.5084 41.3444 44.142 40.4467 44.659C39.3842 45.2726 38.2222 45.5539 37.4182 45.6846C37.3018 46.9545 36.8501 48.9971 35.0887 49.8295C33.5489 50.5567 31.7336 49.9601 30.7535 48.9345L30.7567 48.9374ZM30.5947 14.6763C30.4328 14.682 30.3135 14.6366 30.2822 14.6252C30.0862 14.5513 30.0947 14.5343 29.9413 14.3894C28.8589 13.3553 27.6828 11.8752 26.089 12.1423C25.2935 12.2758 24.6543 12.7417 24.5606 12.8241C23.2907 13.9463 24.7538 17.3696 24.8333 17.6281C25.0095 18.1906 24.6799 18.5599 24.6259 18.6224C24.2623 19.0315 23.7964 18.969 23.7197 18.9577C23.4356 18.9179 23.2168 18.7304 22.3589 18.3952C18.9213 17.04 16.4015 17.9264 16.464 19.7048C16.5123 21.0855 17.9838 22.3497 18.0293 22.398C18.0719 22.4434 18.1145 22.4861 18.1514 22.5344C18.4469 22.9264 18.3645 23.3355 18.3503 23.4065C18.3389 23.4633 18.2736 23.7815 17.9781 24.0258C17.6685 24.2815 16.7224 24.5627 15.6117 25.2503C13.2935 26.6849 13.6373 27.8553 13.785 28.3099C13.8645 28.5514 14.5094 30.1735 15.9185 30.4207C16.7339 30.5627 17.5151 30.2388 18.0379 29.932C18.2282 29.7928 18.4299 29.6564 18.643 29.5201C18.7964 29.4206 18.8646 29.3922 18.8844 29.3837C18.9526 29.3553 19.3475 29.1877 19.802 29.4064C19.8674 29.4377 20.2566 29.6252 20.3901 30.111C20.41 30.182 20.5236 30.5968 20.2481 31.0172C20.214 31.0712 20.1742 31.1195 20.1316 31.165C20.0691 31.2332 19.7537 31.4917 19.2878 31.7729C18.5349 32.3326 17.3503 33.3752 17.1628 34.5115C16.9128 36.003 18.7224 36.7843 20.8616 36.2161C21.3105 36.0968 21.5804 35.9718 21.7309 35.9434C22.2451 35.8468 22.5917 36.1309 22.6514 36.1792C23.0576 36.5087 23.0661 36.9576 23.069 37.0343C23.0747 37.3355 22.9525 37.5599 22.9298 37.5997C22.8417 37.7616 22.4724 38.1678 22.0775 38.8184C20.8104 40.9122 21.5718 41.8213 21.9979 42.2275C22.0746 42.3014 22.1684 42.3781 22.1854 42.3923C22.1911 42.398 23.0661 43.2985 25.1144 43.6593C25.6456 43.7531 25.8132 43.7304 26.0093 43.8156C26.0775 43.844 26.4866 44.0173 26.5746 44.5287C26.6116 44.7389 26.5405 45.2332 26.6513 46.0599C26.8758 47.7133 27.6655 48.0429 27.6911 48.0543C28.6002 48.5486 29.8502 47.844 30.0263 47.3355C30.032 47.3241 30.0377 47.3128 30.0434 47.3014C30.0093 46.6622 29.9809 45.1992 29.9752 41.6594C29.9723 40.4435 29.9723 39.2957 29.978 37.8639C29.6627 38.0542 29.3019 38.2162 28.8871 38.3355C28.549 38.4321 28.1797 38.4576 28.1172 38.4633C27.6939 38.4917 27.1257 38.4264 27.0405 38.415C26.8814 38.3951 26.7053 38.3781 26.6768 38.3753C26.5178 38.3582 26.5178 38.3582 26.3558 38.3667V38.3639C26.3388 38.3639 26.3161 38.3667 26.3018 38.3639C26.299 38.3639 26.2962 38.361 26.2933 38.3554C26.2024 38.4008 26.1172 38.4605 26.0178 38.4832C25.9723 38.4946 25.6996 38.5628 25.4183 38.398C25.3757 38.3724 25.1285 38.2275 25.0462 37.9008C25.0263 37.8213 24.9041 37.3355 25.4695 36.9747C25.9638 36.6565 26.7592 36.5372 27.4041 36.3895C27.4382 36.381 27.4382 36.3838 27.8161 36.3071C28.5888 36.1509 29.3814 36.1963 29.9894 35.327C29.9922 34.9434 29.9951 34.54 29.9979 34.1139C29.9951 34.057 29.9951 34.0173 29.9979 33.9832C30.0008 33.7161 30.0008 33.4406 30.0036 33.1537C30.0093 32.4605 30.032 32.3838 30.1627 28.8241C30.174 28.5003 30.1854 28.1792 30.1968 27.8582C30.1513 27.7702 30.1286 27.6707 30.103 27.577C30.0632 27.4236 29.9354 25.9435 29.336 25.2475C29.2138 25.1054 29.1911 25.0742 28.6542 24.6963C28.5263 24.6054 28.5235 24.6111 28.3672 24.5088L28.3814 24.5173C28.1627 24.3583 27.4439 24.077 27.282 24.0173C27.0718 23.9406 25.8729 23.7361 25.7877 23.7588C25.6882 23.7844 25.5803 23.7531 25.4837 23.719C25.4382 23.702 25.1684 23.6111 25.0235 23.31C24.8814 23.0173 24.9723 22.7531 24.9894 22.7077C25.1627 22.2048 25.7167 22.1168 25.7962 22.1026C26.4553 21.9946 28.2366 22.2475 28.9041 22.452C29.1059 22.5145 29.4269 22.6082 29.9837 22.9776C30.0348 23.0117 30.1883 23.1281 30.3473 23.273C30.4667 19.3866 30.549 16.148 30.5917 14.6847L30.5947 14.6763ZM32.2567 37.9345V37.9857C32.251 39.4459 32.2112 45.8802 32.2965 47.2729C32.5465 47.6735 33.788 48.3525 34.5266 47.4974C35.2709 46.6366 35.2226 44.7673 35.2283 44.6763C35.234 44.6138 35.234 44.5513 35.2453 44.4917C35.2595 44.4065 35.3504 43.8923 35.9101 43.6565C35.9669 43.6338 36.0266 43.6139 36.0862 43.5997C36.2624 43.5571 36.7851 43.5627 37.5834 43.3838C37.8163 43.3326 40.2766 42.807 40.6004 41.2275C40.8987 39.7588 39.2936 37.9037 39.1828 37.7446C39.1459 37.6934 39.1061 37.6423 39.0777 37.5855C39.0408 37.5201 38.8334 37.1366 39.0152 36.6565C39.0408 36.5883 39.1999 36.1792 39.6828 36.0088C39.7254 35.9946 39.9612 35.9122 40.251 35.9662C40.4612 36.006 40.9186 36.2418 41.7538 36.3696C43.9186 36.702 45.7879 35.5372 44.376 33.415C43.9044 32.7077 43.2084 32.1225 42.6942 31.7446C42.484 31.6167 42.2737 31.4747 42.0664 31.3184C41.9357 31.2218 41.8845 31.165 41.8703 31.1508C41.5323 30.79 41.5749 30.3696 41.5834 30.2985C41.6317 29.8071 41.9754 29.5627 42.0351 29.5201C42.4385 29.2332 42.8476 29.3298 42.9186 29.3468C42.9783 29.361 43.0379 29.3809 43.0947 29.4037C43.1913 29.4434 43.5379 29.6593 43.9612 29.9662C44.9271 30.5173 46.7879 31.1423 48.0265 28.7843C48.4242 28.0286 48.7538 26.9377 46.9299 25.5912C45.7992 24.7588 44.4072 24.2645 44.1771 24.1082C44.0265 24.0059 44.018 24.0173 43.9015 23.8809C43.856 23.8269 43.589 23.5173 43.6543 23.0457C43.66 23.0031 43.6913 22.773 43.8589 22.5542C43.9668 22.4094 44.3759 22.0883 44.8021 21.5201C46.0691 19.8355 45.7112 18.3497 43.9526 17.8582C41.6032 17.2019 38.6941 18.736 38.5549 18.7957C38.5066 18.8156 38.4611 18.8383 38.41 18.8525C38.3418 18.8752 37.9242 19.0031 37.5265 18.6821C37.4697 18.6366 37.1316 18.361 37.1799 17.8525C37.2083 17.5599 37.4782 17.0826 37.7424 16.1026C37.9612 15.2901 38.3418 13.4832 37.4129 12.7531C37.2538 12.6309 36.5919 12.2105 35.9044 12.1111C34.4015 11.8895 33.3561 13.094 32.2084 14.2276C32.1459 16.2503 32.1118 19.29 32.1288 22.9578C32.3731 22.7873 32.6913 22.6112 33.0266 22.4947C33.1999 22.435 33.7908 22.2305 35.268 22.097C36.1998 22.0117 36.626 22.0799 36.9356 22.4095C37.1658 22.6538 37.1487 22.9492 37.1459 23.0004C37.1402 23.1026 37.1146 23.2049 37.0692 23.2987C36.9243 23.5998 36.6572 23.6907 36.6089 23.7078C36.4129 23.7759 36.3305 23.7532 36.305 23.7475C36.1856 23.7163 35.0124 23.935 34.8106 24.0061C34.6601 24.06 33.9328 24.347 33.7112 24.5061L33.7254 24.4975C33.2908 24.7759 32.9697 25.0146 32.8533 25.1282C32.6999 25.2816 32.3987 25.5998 32.1573 26.6339C32.1828 28.4777 32.2198 30.4265 32.2737 32.4436C32.2879 33.0061 32.2794 33.1367 32.2652 35.5203C32.7879 36.0998 33.4328 36.1538 33.7822 36.2135C34.0947 36.2646 34.0947 36.2618 34.4072 36.3243C34.7709 36.3981 35.8618 36.597 36.3448 36.8129C36.3732 36.8271 36.5578 36.9067 36.7283 37.0345C37.0181 37.2532 37.0578 37.5317 37.0664 37.5828C37.0806 37.6851 37.0777 37.7902 37.0521 37.8896C36.9698 38.2163 36.7226 38.3612 36.68 38.3868C36.3987 38.5515 36.1289 38.4834 36.0806 38.472C35.9556 38.4407 35.43 38.3754 34.5607 38.4464C34.5465 38.4464 34.1857 38.4606 33.9811 38.4493C33.7056 38.4322 33.0749 38.3782 32.2596 37.9294L32.2567 37.9345ZM12.5352 10.0509C12.5068 10.0907 12.3506 10.3208 12.0239 10.3804C11.9244 10.3975 11.8165 10.3975 11.7171 10.3776C11.6148 10.3549 11.5182 10.3066 11.4273 10.2554C11.271 10.1674 11.2881 10.1418 9.54946 9.01965C8.4699 8.32078 7.56932 7.74692 6.78808 7.06794C5.92161 6.31507 4.95855 5.28387 4.43863 4.79805C4.26249 4.63328 4.08635 4.46851 3.90738 4.30089C3.6517 4.06226 3.63465 3.77248 3.63181 3.72134C3.62897 3.65032 3.6034 3.19294 4.07499 2.96847C4.14033 2.93722 4.54374 2.74688 4.9699 3.11336C5.89601 3.90594 5.90168 3.89743 7.56365 5.22129C8.75681 6.17016 9.18015 6.61619 10.7767 7.8207C11.7796 8.57925 12.308 8.92579 12.3279 8.94572C12.4017 9.01958 12.487 9.08493 12.5438 9.17299C12.5693 9.21277 12.7199 9.44572 12.6517 9.7639C12.6176 9.92015 12.6205 9.92299 12.5296 10.0537L12.5352 10.0509ZM20.6771 10.3265C20.6401 10.3577 20.4299 10.5395 20.1004 10.514C19.9981 10.5054 19.8958 10.4799 19.8049 10.4344C19.7111 10.389 19.6316 10.3151 19.5549 10.2412C19.4469 10.139 19.4725 10.1162 18.3163 8.88043C17.3447 7.84065 15.8702 5.24974 15.339 4.27534C15.2566 4.12762 15.2424 4.13045 15.214 3.96284C15.16 3.64466 15.3333 3.41738 15.3645 3.37761C15.5748 3.10204 15.8589 3.06227 15.9071 3.05658C15.9782 3.04522 16.4157 2.98556 16.7168 3.45715C17.2197 4.24122 17.2452 4.22421 18.1572 5.51395C18.6742 6.2469 18.9214 6.83781 19.9271 8.16733C20.3305 8.69858 20.7566 9.19293 20.7623 9.20144C20.8162 9.29235 20.8844 9.37474 20.9185 9.47417C21.0208 9.78099 20.8958 10.0281 20.8731 10.0708C20.7992 10.2156 20.7992 10.2156 20.677 10.3208L20.6771 10.3265ZM31.0807 7.87759C31.0324 7.88043 30.754 7.89179 30.5097 7.66736C30.4358 7.59918 30.3705 7.51395 30.3279 7.42021C30.2824 7.32645 30.2625 7.2185 30.2511 7.11338C30.2284 6.93725 30.2511 6.38044 30.004 3.84633C29.8705 2.46567 29.8989 2.36051 29.8989 2.33498C29.9074 1.63328 29.933 1.63328 29.9415 0.931566C29.9443 0.718497 29.9273 0.715653 30.0068 0.519631C30.0239 0.474177 30.1318 0.212809 30.4216 0.0764451C30.8904 -0.142304 31.2284 0.173035 31.2796 0.221333C31.3563 0.292355 31.4216 0.37758 31.4671 0.471333C31.575 0.690082 31.5381 0.698605 31.5722 0.940081C31.7029 1.86052 31.7881 2.0651 31.8051 2.86619C31.8279 4.10197 31.771 4.69288 31.8449 7.01957C31.8563 7.37469 31.6659 7.58207 31.6318 7.61901C31.4074 7.86333 31.129 7.87469 31.0807 7.87469L31.0807 7.87759ZM42.3251 11.2753C42.2824 11.2526 42.0268 11.1276 41.9245 10.8123C41.8932 10.7157 41.879 10.6106 41.8847 10.5083C41.9103 10.1731 42.0438 10.2185 43.612 6.67876C44.2341 5.26967 44.4926 4.6589 45.433 3.66741C45.4444 3.65321 45.5864 3.50548 45.5864 3.50548C45.6716 3.44298 45.7455 3.36059 45.8421 3.31514C45.8847 3.29525 46.1376 3.17309 46.4501 3.2782C46.4984 3.29525 46.7711 3.389 46.9188 3.69582C46.9387 3.73843 47.0609 3.99127 46.9557 4.30378C46.879 4.51969 46.8506 4.50548 46.7086 4.68446C46.2086 5.31514 46.1603 5.60206 45.737 6.41741C44.5524 8.69864 44.5353 8.6873 43.4132 10.8946C43.3336 11.0509 43.3365 11.0566 43.2029 11.173C43.166 11.2043 42.9558 11.3833 42.6319 11.3634C42.4728 11.352 42.47 11.352 42.3279 11.281L42.3251 11.2753ZM57.4218 4.22137C57.4161 4.22421 56.6263 5.31228 56.5099 5.45431C55.6747 6.47417 54.5269 7.53955 52.0866 9.76399C51.669 10.1447 51.2571 9.95433 51.1917 9.92592C51.1462 9.90604 50.8877 9.78672 50.7712 9.47138C50.7542 9.42592 50.6604 9.16455 50.7968 8.87194C50.8394 8.77535 50.9133 8.69865 50.9815 8.61626C51.0013 8.5907 52.7627 7.01117 54.1746 5.1447C54.2428 5.05379 54.6377 4.53106 55.0098 3.99415C55.2002 3.71858 55.5041 3.49131 55.6149 3.39757C55.9729 3.10211 56.1973 2.99415 56.6234 2.86631C56.7854 2.81802 56.7854 2.81234 56.953 2.8237C57.0013 2.82654 57.2768 2.84643 57.4928 3.09075C57.5268 3.12768 57.7144 3.34359 57.6916 3.67881C57.6888 3.72711 57.6689 4.00268 57.4246 4.21859L57.4218 4.22137ZM56.7769 16.193C56.7996 16.2356 56.9275 16.4856 56.828 16.801C56.7968 16.8976 56.7485 16.9913 56.6831 17.0708C56.615 17.1504 56.5269 17.2129 56.4388 17.2697C56.3508 17.3237 54.1206 18.4742 53.919 18.5765C51.1548 19.9998 50.9474 20.0624 50.74 20.1618C50.2428 20.4033 49.8991 20.1135 49.8451 20.0652C49.8082 20.0339 49.5894 19.8464 49.5638 19.5112C49.561 19.4629 49.5383 19.1874 49.7513 18.9374C49.9218 18.7357 49.9587 18.7811 51.0695 18.2271C52.5638 17.4857 55.2059 16.1078 55.7002 15.8521C55.99 15.7016 56.2627 15.7868 56.3082 15.8039C56.6235 15.9033 56.7513 16.1476 56.7741 16.1931L56.7769 16.193ZM60.4786 30.4883C60.4729 30.5366 60.4416 30.8178 60.1831 31.0252C60.1036 31.0877 60.0098 31.1388 59.9132 31.1673C59.8138 31.1957 59.7058 31.1928 59.6007 31.1928C59.5752 31.1928 59.0922 31.1275 58.2371 31.136C57.3252 31.1445 54.3593 31.0423 52.9559 31.0565C52.3934 31.0621 52.2059 30.6616 52.1746 30.5934C52.1547 30.5479 52.0326 30.2894 52.1491 29.9684C52.1661 29.9229 52.2627 29.6587 52.5581 29.5252C52.7144 29.4542 52.7201 29.4655 52.8905 29.4513C54.7798 29.315 58.6462 29.4542 59.4502 29.5678C59.7513 29.6104 59.7513 29.6019 59.7769 29.6104C59.8763 29.6417 59.9843 29.6559 60.0752 29.707C60.1178 29.7298 60.3593 29.8633 60.4502 30.1758C60.4956 30.3292 60.4956 30.3292 60.4786 30.4883ZM57.2229 45.7639C57.1973 45.8037 57.0439 46.0395 56.7229 46.1105C56.6234 46.1332 56.5183 46.1361 56.416 46.1162C56.2399 46.0849 56.2428 46.0764 56.0893 45.9798C54.578 45.0309 50.6575 43.139 49.4303 42.2241C49.1405 42.0082 49.0951 41.7298 49.0865 41.6787C49.0695 41.5764 49.0723 41.4713 49.0951 41.3718C49.1689 41.048 49.4132 40.8974 49.4559 40.8718C49.7314 40.7042 50.0042 40.7639 50.0524 40.7752C50.2541 40.8207 50.2485 40.8377 50.4161 40.9542C52.1149 42.1332 55.7967 43.886 56.9473 44.6389C57.0979 44.7383 57.1036 44.7326 57.2059 44.8804C57.2343 44.9201 57.3905 45.1474 57.3337 45.4656C57.3053 45.6218 57.3053 45.6247 57.2201 45.7582L57.2229 45.7639ZM56.6376 56.3574C56.6035 56.3915 56.4047 56.5932 56.0751 56.596C55.9729 56.596 55.8677 56.579 55.774 56.5392C55.6774 56.4994 55.595 56.4312 55.5098 56.3659C55.4075 56.2864 50.791 52.221 49.953 51.0108C49.9161 50.9568 49.7001 50.6471 49.5212 50.3034C49.2598 49.8062 49.5467 49.4539 49.5922 49.3971C49.8053 49.1357 50.0894 49.1102 50.1405 49.1045C50.4644 49.0733 50.6803 49.2494 50.7172 49.2806C50.8962 49.4284 50.8649 49.4539 50.9956 49.6471C51.3763 50.2039 52.3251 50.8516 52.9643 51.4227C53.2143 51.6443 55.0411 53.6358 56.6064 55.2068C56.7285 55.3318 56.7371 55.3289 56.8024 55.4909C56.8223 55.5363 56.9274 55.792 56.8081 56.0931C56.7484 56.2437 56.6547 56.3403 56.6376 56.3574ZM44.8449 54.5903C44.7 54.6585 44.6972 54.6585 44.5381 54.6642C44.4671 54.667 44.0409 54.6841 43.7852 54.2068C43.7369 54.1187 41.6716 49.6698 41.4244 49.221C41.3193 49.0278 41.2994 49.0335 41.2738 48.8147C41.2682 48.7664 41.2369 48.4852 41.4443 48.2267C41.4756 48.1869 41.6574 47.9596 41.9954 47.9227C42.0437 47.917 42.325 47.8857 42.5835 48.0931C42.7454 48.221 42.7341 48.2323 42.8335 48.4142C43.038 48.7948 43.021 48.8034 45.2256 53.5307C45.2455 53.5761 45.3648 53.829 45.2511 54.1386C45.1375 54.4511 44.8847 54.5676 44.8421 54.5875L44.8449 54.5903ZM31.0071 60.0676C30.9588 60.0732 30.6832 60.113 30.4162 59.917C30.3338 59.8545 30.2628 59.7778 30.2116 59.6897C30.1577 59.5988 30.1349 59.4937 30.1065 59.3943C30.1065 59.3943 30.5952 52.4567 30.8651 52.2579C30.9474 52.1954 31.044 52.1499 31.1435 52.1244C31.1918 52.113 31.473 52.042 31.7628 52.2181C31.8054 52.2437 32.044 52.3886 32.1236 52.7068C32.1491 52.809 32.1406 52.917 32.1406 53.0249C32.1406 53.0903 31.7287 59.4965 31.5327 59.7579C31.3338 60.0221 31.0582 60.0619 31.0099 60.0704L31.0071 60.0676ZM15.3736 56.9369C15.331 56.917 15.0782 56.7948 14.9674 56.4852C14.9333 56.3886 14.9162 56.2863 14.9219 56.184C14.9276 56.0789 14.9617 55.9766 14.9958 55.88C15.0753 55.6585 15.1861 55.5988 16.4503 52.5789C17.4645 50.1528 18.9475 48.5448 19.0952 48.3999C19.3424 48.1584 19.6236 48.1414 19.6719 48.1385C19.7742 48.1329 19.8765 48.1442 19.9731 48.1755C20.456 48.3346 20.4816 48.7948 20.4873 48.863C20.4929 48.9652 20.4816 49.0732 20.4503 49.1726C20.385 49.3857 20.3566 49.3715 20.2316 49.559C20.1179 49.7266 19.4418 51.2891 18.4588 52.8772C17.1435 55.005 16.4447 56.5505 16.4304 56.5703C16.3707 56.6556 16.3196 56.7521 16.2429 56.8203C16.0043 57.0362 15.7259 57.022 15.6776 57.0192C15.5185 57.0107 15.5156 57.0107 15.3708 56.9425L15.3736 56.9369ZM3.57 56.2437C3.58704 56.1982 9.75465 51.0334 10.9053 50.1641C11.053 50.0533 11.0502 50.042 11.2263 49.9965C11.2717 49.9851 11.5388 49.9113 11.8229 50.0732C12.1127 50.238 12.1865 50.5164 12.2007 50.5619C12.286 50.8744 12.1467 51.1158 12.124 51.1584C12.0331 51.3175 12.0246 51.3119 11.8797 51.4255C10.5189 52.5022 6.77461 55.6869 5.28595 57.0448C5.03311 57.275 4.64674 57.6812 4.11264 57.5193C3.85127 57.4397 3.53309 57.1926 3.51037 56.6869C3.49901 56.4198 3.55867 56.2778 3.57287 56.2437L3.57 56.2437ZM12.8202 40.1273C12.8486 40.1671 13.0105 40.3887 12.9565 40.7182C12.9395 40.8205 12.9054 40.9199 12.8514 41.008C12.7975 41.0989 12.7179 41.1728 12.6412 41.2438C12.485 41.3858 12.4367 41.3262 10.6327 42.9313C9.87415 43.6046 9.51909 43.8631 9.07589 44.1472C8.2236 44.6984 6.12135 45.8205 5.71226 46.133C5.56169 46.2467 5.56737 46.258 5.38555 46.3092C5.3401 46.3234 5.07021 46.4001 4.78328 46.2495C4.74067 46.2268 4.48783 46.0961 4.39408 45.7751C4.30032 45.4654 4.43101 45.2154 4.45373 45.1728C4.54464 44.9967 4.55317 45.0024 4.70658 44.8774C5.13555 44.5194 5.27476 44.5024 6.72643 43.2865C8.22076 42.0336 8.32869 41.991 9.23494 41.4541C10.0105 40.9939 10.5588 40.7353 11.6838 39.9598C11.8259 39.8632 11.8259 39.8575 11.9935 39.8177C12.0418 39.8064 12.3117 39.7439 12.5872 39.9115C12.7236 39.9939 12.7236 39.9939 12.8173 40.1217L12.8202 40.1273ZM9.04461 29.4764C9.05314 29.5247 9.09859 29.7974 8.90541 30.0673C8.84575 30.1497 8.76904 30.2236 8.68381 30.2804C8.59575 30.3372 8.49063 30.3685 8.3912 30.3969C8.15541 30.4594 8.1412 30.3969 6.22924 30.8798C4.47069 31.3258 4.41106 31.3628 2.45367 31.4565C1.69513 31.4935 1.69513 31.4963 0.94233 31.573C0.382672 31.6298 0.15542 31.2577 0.115638 31.1952C0.0900703 31.1526 -0.0576562 30.9111 0.0247292 30.5901C0.104278 30.2776 0.342911 30.1298 0.382686 30.1043C0.584388 29.9793 0.598599 30.019 1.32305 29.8628C2.15258 29.681 3.43098 29.2918 4.35716 29.1866C5.34298 29.073 6.23498 29.0986 8.10145 28.8287C8.27191 28.8031 8.27474 28.8003 8.44236 28.84C8.48781 28.8514 8.76054 28.9168 8.931 29.1895C9.01623 29.323 9.01623 29.3258 9.04463 29.4821L9.04461 29.4764ZM6.72927 17.5215C6.10995 17.192 4.5844 16.1749 4.55312 16.1465C4.30597 15.8965 4.30312 15.6352 4.30312 15.5869C4.30312 15.4761 4.30028 14.9789 4.85994 14.7602C5.20937 14.6238 5.52755 14.7914 6.20939 15.0215C6.64689 15.1693 7.07586 15.3596 7.15259 15.3909C7.53611 15.5585 7.71509 15.6607 8.26052 15.9874C8.56734 16.1721 10.3514 17.3426 11.823 18.1949C12.0077 18.3028 12.1923 18.4051 12.3798 18.5102C12.5332 18.5983 12.5389 18.5926 12.6469 18.7318C12.6753 18.7687 12.8486 18.9875 12.8145 19.3085C12.7775 19.6409 12.5559 19.817 12.5162 19.8483C12.2633 20.05 11.9878 20.0187 11.9395 20.0159C11.8344 20.0045 11.7349 19.9676 11.6383 19.925C11.6355 19.9221 10.7804 19.4505 9.44233 18.8028C8.34291 18.2715 7.12985 17.7345 6.72927 17.5215Z"
|
|
715
|
+
})
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
//#endregion
|
|
719
|
+
//#region src/components/features/assistant/components/empty-state.tsx
|
|
720
|
+
function EmptyState({ name, isReady, onSuggestion, children }) {
|
|
721
|
+
const { greeting, suggestions } = useAssistantConfig();
|
|
722
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
723
|
+
className: "mx-auto flex w-full max-w-2xl flex-1 flex-col justify-center px-4 py-8",
|
|
724
|
+
children: [
|
|
725
|
+
/* @__PURE__ */ jsxs(motion.div, {
|
|
726
|
+
initial: {
|
|
727
|
+
opacity: 0,
|
|
728
|
+
y: 8
|
|
729
|
+
},
|
|
730
|
+
animate: {
|
|
731
|
+
opacity: 1,
|
|
732
|
+
y: 0
|
|
733
|
+
},
|
|
734
|
+
transition: { duration: .3 },
|
|
735
|
+
className: "mb-8 flex flex-col items-center text-center",
|
|
736
|
+
children: [/* @__PURE__ */ jsx(BrainGlyph, { className: "mb-4 size-16" }), /* @__PURE__ */ jsx("h1", {
|
|
737
|
+
className: "text-foreground font-title text-3xl leading-[34px] font-normal tracking-[-1.2px]",
|
|
738
|
+
children: greeting(name)
|
|
739
|
+
})]
|
|
740
|
+
}),
|
|
741
|
+
/* @__PURE__ */ jsx(motion.div, {
|
|
742
|
+
initial: {
|
|
743
|
+
opacity: 0,
|
|
744
|
+
y: 8
|
|
745
|
+
},
|
|
746
|
+
animate: {
|
|
747
|
+
opacity: 1,
|
|
748
|
+
y: 0
|
|
749
|
+
},
|
|
750
|
+
transition: {
|
|
751
|
+
duration: .3,
|
|
752
|
+
delay: .05
|
|
753
|
+
},
|
|
754
|
+
children
|
|
755
|
+
}),
|
|
756
|
+
/* @__PURE__ */ jsx("div", {
|
|
757
|
+
className: "mt-4 flex flex-col gap-1",
|
|
758
|
+
children: suggestions.map((suggestion, i) => /* @__PURE__ */ jsxs(motion.button, {
|
|
759
|
+
type: "button",
|
|
760
|
+
initial: {
|
|
761
|
+
opacity: 0,
|
|
762
|
+
y: 8
|
|
763
|
+
},
|
|
764
|
+
animate: {
|
|
765
|
+
opacity: 1,
|
|
766
|
+
y: 0
|
|
767
|
+
},
|
|
768
|
+
transition: {
|
|
769
|
+
duration: .25,
|
|
770
|
+
delay: .1 + .05 * i
|
|
771
|
+
},
|
|
772
|
+
disabled: !isReady,
|
|
773
|
+
onClick: () => onSuggestion(suggestion),
|
|
774
|
+
className: "group bg-sidebar-accent text-foreground flex items-center justify-between rounded-xl px-4 py-3 text-left text-sm transition-[filter] hover:brightness-95 disabled:cursor-not-allowed disabled:opacity-50",
|
|
775
|
+
children: [/* @__PURE__ */ jsx("span", { children: suggestion }), /* @__PURE__ */ jsx(ArrowRight, { className: "text-muted-foreground group-hover:text-foreground size-4 shrink-0 transition-colors" })]
|
|
776
|
+
}, suggestion))
|
|
777
|
+
})
|
|
778
|
+
]
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
//#endregion
|
|
782
|
+
//#region src/components/features/assistant/components/sidebar/chat-rail.tsx
|
|
783
|
+
function ChatRail({ historyOpen, onNewChat, onToggleHistory }) {
|
|
784
|
+
const iconClass = "text-foreground hover:bg-card hover:text-primary flex size-9 items-center justify-center rounded-md border border-transparent transition-colors";
|
|
785
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
786
|
+
className: "bg-background flex w-12 shrink-0 flex-col items-center gap-1 border-r py-3",
|
|
787
|
+
children: [/* @__PURE__ */ jsx(Tooltip, {
|
|
788
|
+
message: "New chat",
|
|
789
|
+
side: "right",
|
|
790
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
791
|
+
type: "button",
|
|
792
|
+
"aria-label": "New chat",
|
|
793
|
+
onClick: onNewChat,
|
|
794
|
+
className: cn(iconClass, "group"),
|
|
795
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
796
|
+
className: "bg-foreground text-background group-hover:bg-primary flex size-4 items-center justify-center rounded-full transition-colors",
|
|
797
|
+
children: /* @__PURE__ */ jsx(Plus, {
|
|
798
|
+
className: "size-3",
|
|
799
|
+
strokeWidth: 2.5
|
|
800
|
+
})
|
|
801
|
+
})
|
|
802
|
+
})
|
|
803
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
804
|
+
message: "Chats",
|
|
805
|
+
side: "right",
|
|
806
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
807
|
+
type: "button",
|
|
808
|
+
"aria-label": "Chats",
|
|
809
|
+
onClick: onToggleHistory,
|
|
810
|
+
className: cn(iconClass, historyOpen && "bg-card text-primary"),
|
|
811
|
+
children: /* @__PURE__ */ jsx(Icon, { icon: MessagesSquare })
|
|
812
|
+
})
|
|
813
|
+
})]
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
//#endregion
|
|
817
|
+
//#region src/components/features/assistant/components/sidebar/history-panel.tsx
|
|
818
|
+
function downloadChat(chat) {
|
|
819
|
+
const lines = chat.messages.map((msg) => {
|
|
820
|
+
return `## ${msg.role === "user" ? "You" : "Assistant"}\n\n${msg.parts.filter(isTextUIPart).map((p) => p.text).join("\n")}`;
|
|
821
|
+
});
|
|
822
|
+
const markdown = `# ${chat.title}\n\n${lines.join("\n\n---\n\n")}\n`;
|
|
823
|
+
const blob = new Blob([markdown], { type: "text/markdown" });
|
|
824
|
+
const url = URL.createObjectURL(blob);
|
|
825
|
+
const a = document.createElement("a");
|
|
826
|
+
a.href = url;
|
|
827
|
+
a.download = `${chat.title.replace(/[^a-z0-9]+/gi, "-").toLowerCase()}.md`;
|
|
828
|
+
a.click();
|
|
829
|
+
URL.revokeObjectURL(url);
|
|
830
|
+
}
|
|
831
|
+
function HistoryPanel({ chatList, currentChatId, onLoadChat, onDeleteChat, header }) {
|
|
832
|
+
const [query, setQuery] = useState("");
|
|
833
|
+
const filtered = useMemo(() => {
|
|
834
|
+
const q = query.trim().toLowerCase();
|
|
835
|
+
return q ? chatList.filter((c) => c.title.toLowerCase().includes(q)) : chatList;
|
|
836
|
+
}, [chatList, query]);
|
|
837
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
838
|
+
className: "bg-background flex h-full w-64 shrink-0 flex-col border-r",
|
|
839
|
+
children: [
|
|
840
|
+
header && /* @__PURE__ */ jsx("div", {
|
|
841
|
+
className: "shrink-0 border-b px-3 py-2",
|
|
842
|
+
children: header
|
|
843
|
+
}),
|
|
844
|
+
/* @__PURE__ */ jsx("div", {
|
|
845
|
+
className: "flex h-12 items-center border-b px-2",
|
|
846
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
847
|
+
value: query,
|
|
848
|
+
onChange: (e) => setQuery(e.target.value),
|
|
849
|
+
placeholder: "Search chats…",
|
|
850
|
+
className: "bg-muted text-foreground placeholder:text-muted-foreground/70 focus:ring-primary/40 w-full rounded-md px-2.5 py-1.5 text-xs focus:ring-1 focus:outline-none"
|
|
851
|
+
})
|
|
852
|
+
}),
|
|
853
|
+
/* @__PURE__ */ jsx("div", {
|
|
854
|
+
className: "flex flex-1 flex-col gap-0.5 overflow-y-auto p-2",
|
|
855
|
+
children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
856
|
+
className: "text-muted-foreground/60 px-2 py-1 text-xs",
|
|
857
|
+
children: query ? "No matching chats" : "No saved chats"
|
|
858
|
+
}) : filtered.map((chat) => /* @__PURE__ */ jsxs("button", {
|
|
859
|
+
type: "button",
|
|
860
|
+
onClick: () => onLoadChat(chat),
|
|
861
|
+
className: cn("group w-full rounded-lg px-2 py-1.5 text-left transition-colors", chat.id === currentChatId ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground hover:bg-accent/50"),
|
|
862
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
863
|
+
className: "flex items-center gap-1",
|
|
864
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
865
|
+
className: "min-w-0 flex-1 truncate text-xs font-medium",
|
|
866
|
+
children: chat.title
|
|
867
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
868
|
+
role: "button",
|
|
869
|
+
onClick: (e) => onDeleteChat(e, chat.id),
|
|
870
|
+
"aria-label": "Delete chat",
|
|
871
|
+
className: "text-muted-foreground/40 hover:text-destructive shrink-0 rounded p-0.5 opacity-0 transition-colors group-hover:opacity-100",
|
|
872
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "size-3" })
|
|
873
|
+
})]
|
|
874
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
875
|
+
className: "flex items-center gap-1",
|
|
876
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
877
|
+
className: "text-muted-foreground/60 text-[10px]",
|
|
878
|
+
children: formatRelativeTime(chat.updatedAt)
|
|
879
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
880
|
+
message: "Download as Markdown",
|
|
881
|
+
side: "top",
|
|
882
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
883
|
+
role: "button",
|
|
884
|
+
onClick: (e) => {
|
|
885
|
+
e.stopPropagation();
|
|
886
|
+
downloadChat(chat);
|
|
887
|
+
},
|
|
888
|
+
"aria-label": "Download chat as Markdown",
|
|
889
|
+
className: "text-muted-foreground/40 hover:text-foreground shrink-0 rounded p-0.5 opacity-0 transition-colors group-hover:opacity-100",
|
|
890
|
+
children: /* @__PURE__ */ jsx(Download, { className: "size-3" })
|
|
891
|
+
})
|
|
892
|
+
})]
|
|
893
|
+
})]
|
|
894
|
+
}, chat.id))
|
|
895
|
+
}),
|
|
896
|
+
/* @__PURE__ */ jsx("p", {
|
|
897
|
+
className: "text-muted-foreground mt-auto shrink-0 border-t px-3 py-2 text-[10px]",
|
|
898
|
+
children: "Chats are saved to your browser's local storage."
|
|
899
|
+
})
|
|
900
|
+
]
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
//#endregion
|
|
904
|
+
//#region src/components/features/assistant/components/assistant-workspace.tsx
|
|
905
|
+
function AssistantWorkspace({ config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory }) {
|
|
906
|
+
const hasMessages = messages.length > 0;
|
|
907
|
+
const promptCard = /* @__PURE__ */ jsx(PromptCard, {
|
|
908
|
+
editor,
|
|
909
|
+
isReady,
|
|
910
|
+
canRetry: status === "error",
|
|
911
|
+
onSend,
|
|
912
|
+
onStop,
|
|
913
|
+
onRetry,
|
|
914
|
+
modelId,
|
|
915
|
+
effortId,
|
|
916
|
+
onModelChange,
|
|
917
|
+
onEffortChange,
|
|
918
|
+
speechSupported: micSupported,
|
|
919
|
+
isListening: micListening,
|
|
920
|
+
frequencyData: micFrequencyData,
|
|
921
|
+
onMicToggle
|
|
922
|
+
});
|
|
923
|
+
return /* @__PURE__ */ jsx(AssistantConfigProvider, {
|
|
924
|
+
config,
|
|
925
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
926
|
+
className: "bg-background flex h-full w-full overflow-hidden",
|
|
927
|
+
children: [
|
|
928
|
+
/* @__PURE__ */ jsx(ChatRail, {
|
|
929
|
+
historyOpen,
|
|
930
|
+
onNewChat,
|
|
931
|
+
onToggleHistory
|
|
932
|
+
}),
|
|
933
|
+
/* @__PURE__ */ jsx(AnimatePresence, {
|
|
934
|
+
initial: false,
|
|
935
|
+
children: historyOpen && /* @__PURE__ */ jsx(motion.div, {
|
|
936
|
+
className: "h-full shrink-0 overflow-hidden",
|
|
937
|
+
initial: { width: 0 },
|
|
938
|
+
animate: { width: 256 },
|
|
939
|
+
exit: { width: 0 },
|
|
940
|
+
transition: {
|
|
941
|
+
type: "spring",
|
|
942
|
+
stiffness: 400,
|
|
943
|
+
damping: 32
|
|
944
|
+
},
|
|
945
|
+
children: /* @__PURE__ */ jsx(HistoryPanel, {
|
|
946
|
+
chatList,
|
|
947
|
+
currentChatId,
|
|
948
|
+
header: sidebarHeader,
|
|
949
|
+
onLoadChat,
|
|
950
|
+
onDeleteChat
|
|
951
|
+
})
|
|
952
|
+
})
|
|
953
|
+
}),
|
|
954
|
+
/* @__PURE__ */ jsx("div", {
|
|
955
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
956
|
+
children: hasMessages ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("header", {
|
|
957
|
+
className: "flex h-12 shrink-0 items-center justify-between border-b px-4",
|
|
958
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
959
|
+
className: "text-foreground min-w-0 truncate text-sm font-medium",
|
|
960
|
+
children: title
|
|
961
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
962
|
+
className: "flex items-center gap-1",
|
|
963
|
+
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
964
|
+
message: historyOpen ? "Close sidebar" : "Open sidebar",
|
|
965
|
+
side: "bottom",
|
|
966
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
967
|
+
type: "button",
|
|
968
|
+
"aria-label": "Toggle sidebar",
|
|
969
|
+
onClick: onToggleHistory,
|
|
970
|
+
className: "text-muted-foreground hover:text-foreground hover:bg-accent rounded-md p-1.5 transition-colors",
|
|
971
|
+
children: /* @__PURE__ */ jsx(PanelLeft, { className: "size-4" })
|
|
972
|
+
})
|
|
973
|
+
})
|
|
974
|
+
})]
|
|
975
|
+
}), /* @__PURE__ */ jsx(Conversation, {
|
|
976
|
+
messages,
|
|
977
|
+
status,
|
|
978
|
+
error,
|
|
979
|
+
htmlByUserMsgIndex,
|
|
980
|
+
containerRef,
|
|
981
|
+
bottomRef,
|
|
982
|
+
userScrolledUpRef,
|
|
983
|
+
footer: promptCard
|
|
984
|
+
})] }) : /* @__PURE__ */ jsx(EmptyState, {
|
|
985
|
+
name: userName,
|
|
986
|
+
isReady,
|
|
987
|
+
onSuggestion,
|
|
988
|
+
children: promptCard
|
|
989
|
+
})
|
|
990
|
+
})
|
|
991
|
+
]
|
|
992
|
+
})
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
//#endregion
|
|
996
|
+
export { AssistantConfigProvider, AssistantMessage, AssistantWorkspace, BrainGlyph, ChatRail, Conversation, DEFAULT_ASSISTANT_CONFIG, EmptyState, Equalizer, HistoryPanel, LoadingDots, ModelSelector, PromptCard, ScrollToBottomButton, ThinkingBlock, TurnRail, UserMessage, defaultRenderLink, formatRelativeTime, sanitizeUserHtml, useAssistantConfig, useTurnRail };
|