@datum-cloud/datum-ui 3.5.0 → 3.7.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 +466 -120
- package/dist/components/features/assistant/components/assistant-workspace.d.ts +8 -2
- package/dist/components/features/assistant/components/composer/prompt-card.d.ts +7 -2
- package/dist/components/features/assistant/components/conversation/conversation.d.ts +23 -1
- package/dist/components/features/assistant/components/message/assistant-message.d.ts +27 -1
- package/dist/components/features/assistant/components/message/choice-chips.d.ts +17 -0
- package/dist/components/features/assistant/components/message/index.d.ts +2 -0
- package/dist/components/features/assistant/components/message/plan-card.d.ts +20 -0
- package/dist/components/features/assistant/components/message/user-message.d.ts +8 -3
- package/dist/components/features/assistant/index.d.ts +2 -1
- package/dist/components/features/assistant/types.d.ts +69 -59
- package/dist/components/features/assistant/utils/format-relative-time.d.ts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/rich-text-editor/index.mjs +1 -1
- package/dist/{rich-text-editor-DP5JnAoO.mjs → rich-text-editor-CP1YyxMR.mjs} +2 -2
- package/dist/styles/themes/alpha.css +1 -1
- package/dist/typography/index.mjs +1 -1
- package/dist/{typography-q71iTG72.mjs → typography-B1bpHKmV.mjs} +3 -3
- package/package.json +3 -2
package/dist/assistant/index.mjs
CHANGED
|
@@ -5,12 +5,12 @@ import { t as Dialog } from "../dialog-Gu70fban.mjs";
|
|
|
5
5
|
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";
|
|
6
6
|
import { t as Tooltip } from "../tooltip-DhbN1BDK.mjs";
|
|
7
7
|
import { n as EASE, t as DURATION } from "../motion-DvGRWdsm.mjs";
|
|
8
|
-
import { Archive, ArchiveRestore, ArrowDown, ArrowRight, ArrowUp, Brain, Check, ChevronDown, Copy, Download, MessagesSquare, Mic, MicOff, PanelLeft, Plus, RotateCw, Square, SquarePen, Trash2, X } from "lucide-react";
|
|
8
|
+
import { Archive, ArchiveRestore, ArrowDown, ArrowRight, ArrowUp, Box, Brain, Check, ChevronDown, ClipboardList, Clock, Code, Copy, Database, Download, Globe, Laptop, Link, MapPin, MessagesSquare, Mic, MicOff, Network, PanelLeft, Plus, RotateCw, Send, Server, Shield, Split, Square, SquarePen, Trash2, X } from "lucide-react";
|
|
9
9
|
import { Fragment, createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
|
10
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
11
|
import { AnimatePresence, motion } from "motion/react";
|
|
12
12
|
import { EditorContent } from "@tiptap/react";
|
|
13
|
-
import {
|
|
13
|
+
import { DEFAULT_ASSISTANT_CONFIG as DEFAULT_ASSISTANT_CONFIG$1, activeToolPart, assistantErrorMessage, chatFileName, chatToMarkdown, deriveTurns, formatRelativeTime, hasText, isReasoningUIPart, isStreamingMessage, isTextUIPart, isToolUIPart, messageText, showInitialLoading, showToolIndicator, thinkingLabel, toolLabel } from "@datum-cloud/datum-ui-assistant-core";
|
|
14
14
|
import { code } from "@streamdown/code";
|
|
15
15
|
import { Streamdown } from "streamdown";
|
|
16
16
|
import "streamdown/styles.css";
|
|
@@ -40,11 +40,7 @@ function defaultRenderLink({ href, children }) {
|
|
|
40
40
|
* config. Host apps override via `<AssistantConfigProvider config={…}>`.
|
|
41
41
|
*/
|
|
42
42
|
const DEFAULT_ASSISTANT_CONFIG = {
|
|
43
|
-
|
|
44
|
-
suggestions: [],
|
|
45
|
-
showReasoning: true,
|
|
46
|
-
modelSelector: false,
|
|
47
|
-
toolLabels: {},
|
|
43
|
+
...DEFAULT_ASSISTANT_CONFIG$1,
|
|
48
44
|
renderLink: defaultRenderLink
|
|
49
45
|
};
|
|
50
46
|
const AssistantConfigContext = createContext(DEFAULT_ASSISTANT_CONFIG);
|
|
@@ -147,15 +143,17 @@ function ModelSelector({ modelId, effortId, onModelChange, onEffortChange, disab
|
|
|
147
143
|
}
|
|
148
144
|
//#endregion
|
|
149
145
|
//#region src/components/features/assistant/components/composer/prompt-card.tsx
|
|
150
|
-
function PromptCard({ editor, isReady, canRetry, onSend, onStop, onRetry, modelId, effortId, onModelChange, onEffortChange, speechSupported, isListening, frequencyData, onMicToggle, className }) {
|
|
146
|
+
function PromptCard({ editor, isReady, canRetry, onSend, onStop, onRetry, modelId, effortId, onModelChange, onEffortChange, speechSupported, isListening, frequencyData, onMicToggle, density = "default", className }) {
|
|
151
147
|
const { modelSelector } = useAssistantConfig();
|
|
148
|
+
const compact = density === "compact";
|
|
149
|
+
const sendClasses = compact ? "text-muted-foreground hover:text-foreground hover:bg-accent shrink-0 rounded-full p-1.5 transition-colors" : "bg-primary text-primary-foreground hover:bg-primary/90 shrink-0 rounded-full p-1.5 transition-colors";
|
|
152
150
|
return /* @__PURE__ */ jsxs("div", {
|
|
153
|
-
className: cn("bg-card border-primary/30 rounded-2xl border p-3 shadow-sm transition-shadow", className),
|
|
151
|
+
className: cn(compact ? "bg-card border-input flex items-end gap-2 rounded-lg border py-1 pr-1.5 pl-3" : "bg-card border-primary/30 rounded-2xl border p-3 shadow-sm transition-shadow", className),
|
|
154
152
|
children: [/* @__PURE__ */ jsx(EditorContent, {
|
|
155
153
|
editor,
|
|
156
|
-
className: "max-h-52 min-
|
|
154
|
+
className: cn("max-h-52 overflow-y-auto", compact ? "min-w-0 flex-1 [&_.ProseMirror]:py-0.5" : "min-h-9 px-1 py-1")
|
|
157
155
|
}), /* @__PURE__ */ jsx("div", {
|
|
158
|
-
className: "
|
|
156
|
+
className: cn("flex items-center justify-end gap-2", compact ? "pb-0.5" : "mt-1"),
|
|
159
157
|
children: /* @__PURE__ */ jsxs("div", {
|
|
160
158
|
className: "flex items-center gap-1",
|
|
161
159
|
children: [
|
|
@@ -223,9 +221,9 @@ function PromptCard({ editor, isReady, canRetry, onSend, onStop, onRetry, modelI
|
|
|
223
221
|
type: "button",
|
|
224
222
|
onClick: onSend,
|
|
225
223
|
"aria-label": "Send message",
|
|
226
|
-
className:
|
|
224
|
+
className: sendClasses,
|
|
227
225
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
228
|
-
icon: ArrowUp,
|
|
226
|
+
icon: compact ? Send : ArrowUp,
|
|
229
227
|
className: "size-4"
|
|
230
228
|
})
|
|
231
229
|
})
|
|
@@ -236,7 +234,7 @@ function PromptCard({ editor, isReady, canRetry, onSend, onStop, onRetry, modelI
|
|
|
236
234
|
type: "button",
|
|
237
235
|
onClick: onStop,
|
|
238
236
|
"aria-label": "Stop generating",
|
|
239
|
-
className:
|
|
237
|
+
className: sendClasses,
|
|
240
238
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
241
239
|
icon: Square,
|
|
242
240
|
className: "size-4 fill-current"
|
|
@@ -260,10 +258,7 @@ function useTurnRail(messages, containerRef, userScrolledUpRef) {
|
|
|
260
258
|
const [activeTurn, setActiveTurn] = useState(null);
|
|
261
259
|
const scrollElRef = useRef(null);
|
|
262
260
|
const programmaticUntilRef = useRef(0);
|
|
263
|
-
const turns = useMemo(() => messages
|
|
264
|
-
id: m.id,
|
|
265
|
-
text: m.parts.find(isTextUIPart)?.text ?? ""
|
|
266
|
-
})), [messages]);
|
|
261
|
+
const turns = useMemo(() => deriveTurns(messages), [messages]);
|
|
267
262
|
const activeTurnId = activeTurn ?? turns.at(-1)?.id ?? null;
|
|
268
263
|
const setScrollEl = useCallback((node) => {
|
|
269
264
|
scrollElRef.current = node;
|
|
@@ -315,20 +310,11 @@ function useTurnRail(messages, containerRef, userScrolledUpRef) {
|
|
|
315
310
|
};
|
|
316
311
|
}
|
|
317
312
|
//#endregion
|
|
318
|
-
//#region src/components/features/assistant/
|
|
319
|
-
/**
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const minutes = Math.floor(seconds / 60);
|
|
324
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
325
|
-
const hours = Math.floor(minutes / 60);
|
|
326
|
-
if (hours < 24) return `${hours}h ago`;
|
|
327
|
-
const days = Math.floor(hours / 24);
|
|
328
|
-
if (days === 1) return "Yesterday";
|
|
329
|
-
if (days < 7) return `${days}d ago`;
|
|
330
|
-
return new Date(timestamp).toLocaleDateString();
|
|
331
|
-
}
|
|
313
|
+
//#region src/components/features/assistant/types.ts
|
|
314
|
+
/** The message part type that carries a pick-one question (see {@link AssistantChoicesData}). */
|
|
315
|
+
const CHOICES_PART_TYPE = "data-choices";
|
|
316
|
+
/** The message part type that carries a plan the person can approve (see {@link AssistantPlanData}). */
|
|
317
|
+
const PLAN_PART_TYPE = "data-plan";
|
|
332
318
|
//#endregion
|
|
333
319
|
//#region src/components/features/assistant/utils/sanitize-html.ts
|
|
334
320
|
const HTML_ESCAPE_MAP = {
|
|
@@ -365,6 +351,59 @@ function sanitizeUserHtml(raw) {
|
|
|
365
351
|
return result.startsWith("<p>") ? result : `<p>${result}</p>`;
|
|
366
352
|
}
|
|
367
353
|
//#endregion
|
|
354
|
+
//#region src/components/features/assistant/components/message/choice-chips.tsx
|
|
355
|
+
const CHOICE_ICONS = {
|
|
356
|
+
clock: Clock,
|
|
357
|
+
container: Box,
|
|
358
|
+
database: Database,
|
|
359
|
+
domain: Link,
|
|
360
|
+
globe: Globe,
|
|
361
|
+
laptop: Laptop,
|
|
362
|
+
location: MapPin,
|
|
363
|
+
network: Network,
|
|
364
|
+
server: Server,
|
|
365
|
+
shield: Shield
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* A pick-one question as a row of buttons. Once an option is picked it stays
|
|
369
|
+
* marked with a check and the others fade, so a scrolled-back conversation
|
|
370
|
+
* still shows what was chosen.
|
|
371
|
+
*/
|
|
372
|
+
function AssistantChoiceChips({ data, onChoose, disabled = false }) {
|
|
373
|
+
const answered = data.selectedId !== void 0;
|
|
374
|
+
const interactive = !answered && !disabled && onChoose !== void 0;
|
|
375
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
376
|
+
className: "my-1 space-y-2",
|
|
377
|
+
"data-slot": "assistant-choices",
|
|
378
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
379
|
+
className: "font-medium",
|
|
380
|
+
children: data.question
|
|
381
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
382
|
+
role: "group",
|
|
383
|
+
"aria-label": data.question,
|
|
384
|
+
className: "flex flex-wrap gap-2",
|
|
385
|
+
children: data.options.map((option) => {
|
|
386
|
+
const selected = option.id === data.selectedId;
|
|
387
|
+
const icon = option.icon ? CHOICE_ICONS[option.icon] : void 0;
|
|
388
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
389
|
+
type: "button",
|
|
390
|
+
disabled: !interactive,
|
|
391
|
+
"aria-pressed": answered ? selected : void 0,
|
|
392
|
+
onClick: () => onChoose?.(option),
|
|
393
|
+
className: cn("inline-flex items-center gap-1.5 rounded-full border px-3.5 py-1.5 text-sm transition-colors", selected && "border-btn-primary text-btn-primary bg-btn-primary/5", !selected && answered && "text-muted-foreground/60 border-border/60", !selected && !answered && "text-foreground border-border", interactive ? "hover:bg-accent cursor-pointer" : "cursor-default", !answered && !interactive && "opacity-60"),
|
|
394
|
+
children: [selected ? /* @__PURE__ */ jsx(Icon, {
|
|
395
|
+
icon: Check,
|
|
396
|
+
className: "size-3.5 shrink-0"
|
|
397
|
+
}) : icon && /* @__PURE__ */ jsx(Icon, {
|
|
398
|
+
icon,
|
|
399
|
+
className: "size-3.5 shrink-0"
|
|
400
|
+
}), option.label]
|
|
401
|
+
}, option.id);
|
|
402
|
+
})
|
|
403
|
+
})]
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
//#endregion
|
|
368
407
|
//#region src/components/features/assistant/components/message/loading-dots.tsx
|
|
369
408
|
/** Three bouncing dots — the assistant's "thinking" indicator. */
|
|
370
409
|
function LoadingDots({ className }) {
|
|
@@ -378,6 +417,238 @@ function LoadingDots({ className }) {
|
|
|
378
417
|
});
|
|
379
418
|
}
|
|
380
419
|
//#endregion
|
|
420
|
+
//#region src/components/features/assistant/components/message/plan-card.tsx
|
|
421
|
+
/** How a few common kinds read to someone who doesn't know their API names. */
|
|
422
|
+
const KIND_LABELS = {
|
|
423
|
+
HTTPProxy: "Load balancer",
|
|
424
|
+
NetworkService: "Network service",
|
|
425
|
+
Workload: "Workload",
|
|
426
|
+
Domain: "Domain",
|
|
427
|
+
Network: "Network"
|
|
428
|
+
};
|
|
429
|
+
const KIND_ICONS = {
|
|
430
|
+
HTTPProxy: Split,
|
|
431
|
+
NetworkService: Network,
|
|
432
|
+
Workload: Server,
|
|
433
|
+
Domain: Globe,
|
|
434
|
+
Network
|
|
435
|
+
};
|
|
436
|
+
const BADGES = {
|
|
437
|
+
pending: {
|
|
438
|
+
label: "Nothing created yet",
|
|
439
|
+
className: "bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300"
|
|
440
|
+
},
|
|
441
|
+
applying: {
|
|
442
|
+
label: "Applying",
|
|
443
|
+
className: "bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300"
|
|
444
|
+
},
|
|
445
|
+
applied: {
|
|
446
|
+
label: "Applied",
|
|
447
|
+
className: "bg-emerald-50 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300"
|
|
448
|
+
},
|
|
449
|
+
failed: {
|
|
450
|
+
label: "Not applied",
|
|
451
|
+
className: "bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300"
|
|
452
|
+
},
|
|
453
|
+
expired: {
|
|
454
|
+
label: "Expired",
|
|
455
|
+
className: "bg-muted text-muted-foreground"
|
|
456
|
+
},
|
|
457
|
+
replaced: {
|
|
458
|
+
label: "Replaced",
|
|
459
|
+
className: "bg-muted text-muted-foreground"
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
function cardState(data, latest, now) {
|
|
463
|
+
if (data.status !== "pending") return data.status;
|
|
464
|
+
if (!latest) return "replaced";
|
|
465
|
+
const expires = Date.parse(data.expiresAt);
|
|
466
|
+
if (!Number.isNaN(expires) && expires <= now) return "expired";
|
|
467
|
+
return "pending";
|
|
468
|
+
}
|
|
469
|
+
/** "3 resources to create", "1 resource to change", "2 resources to create or change". */
|
|
470
|
+
function headline(resources) {
|
|
471
|
+
const creates = resources.some((r) => r.action === "create");
|
|
472
|
+
const updates = resources.some((r) => r.action === "update");
|
|
473
|
+
const verb = creates && updates ? "create or change" : updates ? "change" : "create";
|
|
474
|
+
return `${resources.length} ${resources.length === 1 ? "resource" : "resources"} to ${verb}`;
|
|
475
|
+
}
|
|
476
|
+
function approveLabel(resources) {
|
|
477
|
+
const updates = resources.some((r) => r.action === "update");
|
|
478
|
+
const creates = resources.some((r) => r.action === "create");
|
|
479
|
+
return creates && !updates ? "Approve and create" : updates && !creates ? "Approve and update" : "Approve and apply";
|
|
480
|
+
}
|
|
481
|
+
function timeOf(iso) {
|
|
482
|
+
if (!iso) return void 0;
|
|
483
|
+
const t = new Date(iso);
|
|
484
|
+
return Number.isNaN(t.getTime()) ? void 0 : t.toLocaleTimeString([], {
|
|
485
|
+
hour: "2-digit",
|
|
486
|
+
minute: "2-digit"
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* A plan the assistant settled, as a card: one row per resource with what it
|
|
491
|
+
* does, the YAML and differences on request, and the person's two answers.
|
|
492
|
+
* Nothing is created until they click Approve, and the card then shows how it
|
|
493
|
+
* went.
|
|
494
|
+
*/
|
|
495
|
+
function AssistantPlanCard({ data, latest = true, canApprove = false, onApprove, onChange, now = Date.now() }) {
|
|
496
|
+
const [showYaml, setShowYaml] = useState(false);
|
|
497
|
+
const state = cardState(data, latest, now);
|
|
498
|
+
const badge = BADGES[state];
|
|
499
|
+
const approvedAt = timeOf(data.approvedAt);
|
|
500
|
+
const appliedNames = new Set((data.applied ?? []).map((a) => `${a.kind}/${a.name}`));
|
|
501
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
502
|
+
"aria-label": `Plan: ${headline(data.resources)}`,
|
|
503
|
+
"data-slot": "assistant-plan",
|
|
504
|
+
"data-state": state,
|
|
505
|
+
className: "bg-card my-2 overflow-hidden rounded-xl border",
|
|
506
|
+
children: [
|
|
507
|
+
/* @__PURE__ */ jsxs("header", {
|
|
508
|
+
className: "bg-muted/40 flex items-center gap-2 border-b px-4 py-3",
|
|
509
|
+
children: [
|
|
510
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
511
|
+
icon: ClipboardList,
|
|
512
|
+
className: "size-4 shrink-0"
|
|
513
|
+
}),
|
|
514
|
+
/* @__PURE__ */ jsxs("span", {
|
|
515
|
+
className: "flex-1 font-medium",
|
|
516
|
+
children: [
|
|
517
|
+
"Plan ·",
|
|
518
|
+
" ",
|
|
519
|
+
headline(data.resources)
|
|
520
|
+
]
|
|
521
|
+
}),
|
|
522
|
+
/* @__PURE__ */ jsx("span", {
|
|
523
|
+
className: cn("rounded-full px-2.5 py-0.5 text-xs", badge.className),
|
|
524
|
+
children: badge.label
|
|
525
|
+
})
|
|
526
|
+
]
|
|
527
|
+
}),
|
|
528
|
+
/* @__PURE__ */ jsx("ul", {
|
|
529
|
+
className: "divide-y",
|
|
530
|
+
children: data.resources.map((r) => {
|
|
531
|
+
const kindIcon = KIND_ICONS[r.kind] ?? Box;
|
|
532
|
+
const done = state === "applied" && appliedNames.has(`${r.kind}/${r.name}`);
|
|
533
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
534
|
+
className: "flex gap-3 px-4 py-3",
|
|
535
|
+
children: [
|
|
536
|
+
/* @__PURE__ */ jsx("span", {
|
|
537
|
+
className: "bg-btn-primary/10 text-btn-primary flex size-8 shrink-0 items-center justify-center rounded-md",
|
|
538
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
539
|
+
icon: kindIcon,
|
|
540
|
+
className: "size-4"
|
|
541
|
+
})
|
|
542
|
+
}),
|
|
543
|
+
/* @__PURE__ */ jsxs("div", {
|
|
544
|
+
className: "min-w-0 flex-1",
|
|
545
|
+
children: [
|
|
546
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
547
|
+
/* @__PURE__ */ jsx("span", {
|
|
548
|
+
className: "font-medium",
|
|
549
|
+
children: KIND_LABELS[r.kind] ?? r.kind
|
|
550
|
+
}),
|
|
551
|
+
" ",
|
|
552
|
+
/* @__PURE__ */ jsx("span", {
|
|
553
|
+
className: "text-muted-foreground font-mono text-[0.9em]",
|
|
554
|
+
children: r.name
|
|
555
|
+
})
|
|
556
|
+
] }),
|
|
557
|
+
r.summary && /* @__PURE__ */ jsx("p", {
|
|
558
|
+
className: "text-muted-foreground",
|
|
559
|
+
children: r.summary
|
|
560
|
+
}),
|
|
561
|
+
r.diff && r.diff.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
562
|
+
className: "mt-1 flex flex-wrap gap-1",
|
|
563
|
+
children: r.diff.map((line) => /* @__PURE__ */ jsx("code", {
|
|
564
|
+
className: "rounded bg-emerald-50 px-1.5 py-0.5 font-mono text-xs text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200",
|
|
565
|
+
children: line
|
|
566
|
+
}, line))
|
|
567
|
+
})
|
|
568
|
+
]
|
|
569
|
+
}),
|
|
570
|
+
/* @__PURE__ */ jsx("span", {
|
|
571
|
+
className: cn("h-fit shrink-0 rounded-full px-2.5 py-0.5 text-xs", r.action === "update" ? "bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300" : "bg-emerald-50 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300"),
|
|
572
|
+
children: done ? r.action === "update" ? "Updated" : "Created" : r.action === "update" ? "Update" : "Create"
|
|
573
|
+
})
|
|
574
|
+
]
|
|
575
|
+
}, `${r.kind}/${r.namespace ?? ""}/${r.name}`);
|
|
576
|
+
})
|
|
577
|
+
}),
|
|
578
|
+
showYaml && /* @__PURE__ */ jsx("div", {
|
|
579
|
+
className: "bg-muted/30 space-y-3 border-t px-4 py-3",
|
|
580
|
+
children: data.resources.map((r) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("p", {
|
|
581
|
+
className: "text-muted-foreground mb-1 text-xs",
|
|
582
|
+
children: [
|
|
583
|
+
r.kind,
|
|
584
|
+
" ",
|
|
585
|
+
r.name
|
|
586
|
+
]
|
|
587
|
+
}), /* @__PURE__ */ jsx("pre", {
|
|
588
|
+
className: "bg-card overflow-x-auto rounded-md border p-2 font-mono text-xs",
|
|
589
|
+
children: r.manifest
|
|
590
|
+
})] }, `yaml-${r.kind}/${r.name}`))
|
|
591
|
+
}),
|
|
592
|
+
/* @__PURE__ */ jsxs("footer", {
|
|
593
|
+
className: "flex flex-wrap items-center gap-2 border-t px-4 py-3",
|
|
594
|
+
children: [
|
|
595
|
+
/* @__PURE__ */ jsxs("button", {
|
|
596
|
+
type: "button",
|
|
597
|
+
onClick: () => setShowYaml((v) => !v),
|
|
598
|
+
"aria-expanded": showYaml,
|
|
599
|
+
className: "text-muted-foreground hover:text-foreground mr-auto inline-flex items-center gap-1.5 text-sm transition-colors",
|
|
600
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
601
|
+
icon: Code,
|
|
602
|
+
className: "size-4"
|
|
603
|
+
}), showYaml ? "Hide YAML and diff" : "View YAML and diff"]
|
|
604
|
+
}),
|
|
605
|
+
state === "pending" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
606
|
+
type: "tertiary",
|
|
607
|
+
theme: "outline",
|
|
608
|
+
onClick: onChange,
|
|
609
|
+
disabled: !canApprove,
|
|
610
|
+
children: "Change something"
|
|
611
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
612
|
+
type: "primary",
|
|
613
|
+
theme: "solid",
|
|
614
|
+
icon: /* @__PURE__ */ jsx(Icon, {
|
|
615
|
+
icon: Check,
|
|
616
|
+
className: "size-4"
|
|
617
|
+
}),
|
|
618
|
+
onClick: onApprove,
|
|
619
|
+
disabled: !canApprove,
|
|
620
|
+
children: approveLabel(data.resources)
|
|
621
|
+
})] }),
|
|
622
|
+
state === "applying" && /* @__PURE__ */ jsx(Button, {
|
|
623
|
+
type: "primary",
|
|
624
|
+
theme: "solid",
|
|
625
|
+
loading: true,
|
|
626
|
+
disabled: true,
|
|
627
|
+
children: "Applying"
|
|
628
|
+
}),
|
|
629
|
+
(state === "applied" || state === "failed") && approvedAt && /* @__PURE__ */ jsxs("span", {
|
|
630
|
+
className: "text-muted-foreground text-sm",
|
|
631
|
+
children: [
|
|
632
|
+
"Approved by you at",
|
|
633
|
+
" ",
|
|
634
|
+
approvedAt
|
|
635
|
+
]
|
|
636
|
+
}),
|
|
637
|
+
state === "expired" && /* @__PURE__ */ jsx("span", {
|
|
638
|
+
className: "text-muted-foreground text-sm",
|
|
639
|
+
children: "This plan expired. Ask for a new one to go ahead."
|
|
640
|
+
})
|
|
641
|
+
]
|
|
642
|
+
}),
|
|
643
|
+
state === "failed" && data.error && /* @__PURE__ */ jsx("p", {
|
|
644
|
+
role: "alert",
|
|
645
|
+
className: "text-destructive border-t px-4 py-3 text-sm",
|
|
646
|
+
children: data.error
|
|
647
|
+
})
|
|
648
|
+
]
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
//#endregion
|
|
381
652
|
//#region src/components/features/assistant/components/message/thinking-block.tsx
|
|
382
653
|
function ThinkingBlock({ text, isStreaming = false }) {
|
|
383
654
|
const [open, setOpen] = useState(false);
|
|
@@ -385,7 +656,7 @@ function ThinkingBlock({ text, isStreaming = false }) {
|
|
|
385
656
|
const duration = useRef(null);
|
|
386
657
|
if (isStreaming && startedAt.current === null) startedAt.current = Date.now();
|
|
387
658
|
if (!isStreaming && startedAt.current !== null && duration.current === null) duration.current = Math.round((Date.now() - startedAt.current) / 1e3);
|
|
388
|
-
const label = isStreaming
|
|
659
|
+
const label = thinkingLabel(isStreaming, duration.current);
|
|
389
660
|
return /* @__PURE__ */ jsxs("div", {
|
|
390
661
|
className: "mb-3",
|
|
391
662
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
@@ -417,74 +688,127 @@ function ThinkingBlock({ text, isStreaming = false }) {
|
|
|
417
688
|
}
|
|
418
689
|
//#endregion
|
|
419
690
|
//#region src/components/features/assistant/components/message/assistant-message.tsx
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
691
|
+
/** Streamdown overrides shared by both densities (code surfaces). */
|
|
692
|
+
const STREAMDOWN_BASE_CLASSES = "**: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";
|
|
693
|
+
/**
|
|
694
|
+
* Markdown overrides for `compact`: tighter blocks, headings at body size, and
|
|
695
|
+
* inline code that reads as part of the sentence (a long image reference wraps
|
|
696
|
+
* anywhere instead of breaking the line around a bordered pill).
|
|
697
|
+
*/
|
|
698
|
+
const COMPACT_MARKDOWN_CLASSES = cn("space-y-2 leading-relaxed", "[&_li]:py-0.5", "[&_:is(h1,h2,h3,h4,h5,h6)]:mt-3 [&_:is(h1,h2,h3,h4,h5,h6)]:mb-1", "[&_:is(h1,h2,h3,h4,h5,h6)]:text-sm [&_:is(h1,h2,h3,h4,h5,h6)]:font-semibold", "**:data-[streamdown='code-block-body']:bg-card", "**:data-[streamdown='inline-code']:bg-transparent **:data-[streamdown='inline-code']:p-0", "**:data-[streamdown='inline-code']:text-[0.95em] **:data-[streamdown='inline-code']:[overflow-wrap:anywhere]");
|
|
699
|
+
function AssistantMessage({ msg, isLastMessage, status, density = "default", onChoose, canChoose = false, onApprovePlan, onChangePlan, latestPlanId, canApprovePlan = false }) {
|
|
424
700
|
const { toolLabels, showReasoning, messageActions, renderLink, renderToolOutput } = useAssistantConfig();
|
|
425
|
-
const isStreaming =
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
easing: "ease-out",
|
|
450
|
-
sep: "word",
|
|
451
|
-
stagger: 30
|
|
452
|
-
},
|
|
453
|
-
components: {
|
|
454
|
-
a: ({ href, children }) => renderLink({
|
|
455
|
-
href,
|
|
456
|
-
children
|
|
457
|
-
}),
|
|
458
|
-
img: ({ src, alt }) => /* @__PURE__ */ jsx("img", {
|
|
459
|
-
src,
|
|
460
|
-
alt: alt ?? "",
|
|
461
|
-
className: "my-2 inline-block max-h-16 rounded-full",
|
|
462
|
-
referrerPolicy: "no-referrer"
|
|
463
|
-
})
|
|
464
|
-
},
|
|
465
|
-
children: part.text
|
|
701
|
+
const isStreaming = isStreamingMessage({
|
|
702
|
+
isLastMessage,
|
|
703
|
+
status
|
|
704
|
+
});
|
|
705
|
+
const hasText$1 = hasText(msg);
|
|
706
|
+
const activeToolPart$1 = activeToolPart(msg);
|
|
707
|
+
const showInitialLoading$1 = showInitialLoading({
|
|
708
|
+
msg,
|
|
709
|
+
isLastMessage,
|
|
710
|
+
status
|
|
711
|
+
});
|
|
712
|
+
const showToolIndicator$1 = showToolIndicator({
|
|
713
|
+
msg,
|
|
714
|
+
isLastMessage
|
|
715
|
+
});
|
|
716
|
+
const compact = density === "compact";
|
|
717
|
+
const body = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
718
|
+
msg.parts.map((part, i) => {
|
|
719
|
+
if (isReasoningUIPart(part)) {
|
|
720
|
+
if (!showReasoning) return null;
|
|
721
|
+
const isThinkingStreaming = isStreaming && i === msg.parts.length - 1;
|
|
722
|
+
return /* @__PURE__ */ jsx(ThinkingBlock, {
|
|
723
|
+
text: part.text,
|
|
724
|
+
isStreaming: isThinkingStreaming
|
|
466
725
|
}, i);
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
726
|
+
}
|
|
727
|
+
if (isTextUIPart(part) && part.text) return /* @__PURE__ */ jsx(Streamdown, {
|
|
728
|
+
className: cn(compact ? COMPACT_MARKDOWN_CLASSES : `${STREAMDOWN_BASE_CLASSES} [&_h2]:text-xl [&_h3]:text-lg`, isStreaming && "**:data-[streamdown='inline-code']:animate-[sd-blurIn_300ms_ease-out_both]"),
|
|
729
|
+
isAnimating: isStreaming,
|
|
730
|
+
plugins: { code },
|
|
731
|
+
animated: {
|
|
732
|
+
animation: "blurIn",
|
|
733
|
+
duration: 300,
|
|
734
|
+
easing: "ease-out",
|
|
735
|
+
sep: "word",
|
|
736
|
+
stagger: 30
|
|
737
|
+
},
|
|
738
|
+
components: {
|
|
739
|
+
a: ({ href, children }) => renderLink({
|
|
740
|
+
href,
|
|
741
|
+
children
|
|
742
|
+
}),
|
|
743
|
+
img: ({ src, alt }) => /* @__PURE__ */ jsx("img", {
|
|
744
|
+
src,
|
|
745
|
+
alt: alt ?? "",
|
|
746
|
+
className: "my-2 inline-block max-h-16 rounded-full",
|
|
747
|
+
referrerPolicy: "no-referrer"
|
|
748
|
+
})
|
|
749
|
+
},
|
|
750
|
+
children: part.text
|
|
751
|
+
}, i);
|
|
752
|
+
if (part.type === "data-choices") return /* @__PURE__ */ jsx(AssistantChoiceChips, {
|
|
753
|
+
data: part.data,
|
|
754
|
+
onChoose,
|
|
755
|
+
disabled: !canChoose
|
|
756
|
+
}, i);
|
|
757
|
+
if (part.type === "data-plan") {
|
|
758
|
+
const { id, data } = part;
|
|
759
|
+
const latest = latestPlanId === void 0 || latestPlanId === id;
|
|
760
|
+
return /* @__PURE__ */ jsx(AssistantPlanCard, {
|
|
761
|
+
data,
|
|
762
|
+
latest,
|
|
763
|
+
canApprove: canApprovePlan && latest && onApprovePlan !== void 0,
|
|
764
|
+
onApprove: () => onApprovePlan?.(id),
|
|
765
|
+
onChange: () => onChangePlan?.(id)
|
|
766
|
+
}, i);
|
|
767
|
+
}
|
|
768
|
+
if (isToolUIPart(part) && part.state === "output-available" && renderToolOutput) {
|
|
769
|
+
const rendered = renderToolOutput(part, msg);
|
|
770
|
+
if (rendered) return /* @__PURE__ */ jsx(Fragment, { children: rendered }, i);
|
|
771
|
+
}
|
|
772
|
+
return null;
|
|
773
|
+
}),
|
|
774
|
+
showInitialLoading$1 && /* @__PURE__ */ jsx("div", {
|
|
775
|
+
className: "py-1",
|
|
776
|
+
children: /* @__PURE__ */ jsx(LoadingDots, {})
|
|
777
|
+
}),
|
|
778
|
+
showToolIndicator$1 && activeToolPart$1 && /* @__PURE__ */ jsxs("div", {
|
|
779
|
+
className: "text-muted-foreground flex items-center gap-1.5 py-1 text-xs",
|
|
780
|
+
children: [
|
|
781
|
+
/* @__PURE__ */ jsx("span", { className: "bg-muted-foreground/50 h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.3s]" }),
|
|
782
|
+
/* @__PURE__ */ jsx("span", { className: "bg-muted-foreground/50 h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.15s]" }),
|
|
783
|
+
/* @__PURE__ */ jsx("span", { className: "bg-muted-foreground/50 h-1.5 w-1.5 animate-bounce rounded-full" }),
|
|
784
|
+
/* @__PURE__ */ jsx("span", { children: toolLabel(activeToolPart$1, toolLabels) })
|
|
785
|
+
]
|
|
786
|
+
}),
|
|
787
|
+
!isStreaming && hasText$1 && (messageActions ? messageActions(msg) : !compact && /* @__PURE__ */ jsx(MessageActions, { msg }))
|
|
788
|
+
] });
|
|
789
|
+
if (!compact) return /* @__PURE__ */ jsx("div", {
|
|
790
|
+
className: "text-foreground w-full text-sm",
|
|
791
|
+
children: body
|
|
792
|
+
});
|
|
793
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
794
|
+
className: "text-foreground flex w-full gap-3 text-sm",
|
|
795
|
+
"data-density": "compact",
|
|
796
|
+
children: [/* @__PURE__ */ jsx(AssistantAvatar, {}), /* @__PURE__ */ jsx("div", {
|
|
797
|
+
className: "min-w-0 flex-1 pt-1",
|
|
798
|
+
children: body
|
|
799
|
+
})]
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
/** The assistant's mark beside each compact turn. Decorative: the turn itself carries the content. */
|
|
803
|
+
function AssistantAvatar() {
|
|
804
|
+
return /* @__PURE__ */ jsx("span", {
|
|
805
|
+
"aria-hidden": "true",
|
|
806
|
+
"data-slot": "assistant-avatar",
|
|
807
|
+
className: "bg-btn-primary flex size-7 shrink-0 items-center justify-center rounded-full text-white",
|
|
808
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
809
|
+
icon: Brain,
|
|
810
|
+
className: "size-3.5"
|
|
811
|
+
})
|
|
488
812
|
});
|
|
489
813
|
}
|
|
490
814
|
function MessageActions({ msg }) {
|
|
@@ -506,6 +830,7 @@ function MessageActions({ msg }) {
|
|
|
506
830
|
URL.revokeObjectURL(url);
|
|
507
831
|
};
|
|
508
832
|
return /* @__PURE__ */ jsxs("div", {
|
|
833
|
+
"data-slot": "message-actions",
|
|
509
834
|
className: "text-muted-foreground/60 mt-1.5 flex items-center gap-0.5",
|
|
510
835
|
children: [/* @__PURE__ */ jsx(Tooltip, {
|
|
511
836
|
message: copied ? "Copied" : "Copy",
|
|
@@ -542,11 +867,11 @@ function MessageActions({ msg }) {
|
|
|
542
867
|
//#endregion
|
|
543
868
|
//#region src/components/features/assistant/components/message/user-message.tsx
|
|
544
869
|
/** A user's message — a right-aligned bubble rendering the sanitized input HTML. */
|
|
545
|
-
function UserMessage({ html }) {
|
|
870
|
+
function UserMessage({ html, density = "default" }) {
|
|
546
871
|
return /* @__PURE__ */ jsx("div", {
|
|
547
872
|
className: "flex justify-end",
|
|
548
873
|
children: /* @__PURE__ */ jsx("div", {
|
|
549
|
-
className: "
|
|
874
|
+
className: cn("text-foreground max-w-[80%] rounded-2xl px-4 py-2.5 text-sm", density === "compact" ? "bg-muted" : "bg-border rounded-br-sm"),
|
|
550
875
|
children: /* @__PURE__ */ jsx("div", {
|
|
551
876
|
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",
|
|
552
877
|
dangerouslySetInnerHTML: { __html: html }
|
|
@@ -614,10 +939,12 @@ function TurnRail({ turns, activeId, onJump }) {
|
|
|
614
939
|
}
|
|
615
940
|
//#endregion
|
|
616
941
|
//#region src/components/features/assistant/components/conversation/conversation.tsx
|
|
617
|
-
function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRef, bottomRef, userScrolledUpRef, footer }) {
|
|
942
|
+
function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRef, bottomRef, userScrolledUpRef, footer, density = "default", onChoose, onApprovePlan, onChangePlan }) {
|
|
943
|
+
const compact = density === "compact";
|
|
618
944
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
619
945
|
const { turns, activeTurnId, setScrollEl, handleScroll, scrollToTurn } = useTurnRail(messages, containerRef, userScrolledUpRef);
|
|
620
946
|
let userMsgIdx = 0;
|
|
947
|
+
const latestPlanId = newestPlanId(messages);
|
|
621
948
|
return /* @__PURE__ */ jsxs("div", {
|
|
622
949
|
className: "relative flex min-h-0 flex-1 flex-col",
|
|
623
950
|
children: [
|
|
@@ -626,7 +953,7 @@ function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRe
|
|
|
626
953
|
onScroll: (e) => setShowScrollButton(handleScroll(e.currentTarget)),
|
|
627
954
|
className: "flex-1 overflow-y-auto scroll-smooth",
|
|
628
955
|
children: /* @__PURE__ */ jsxs("div", {
|
|
629
|
-
className: "mx-auto flex w-full max-w-3xl flex-col gap-6 px-4 py-6",
|
|
956
|
+
className: compact ? "flex w-full flex-col gap-5 px-7 py-4" : "mx-auto flex w-full max-w-3xl flex-col gap-6 px-4 py-6",
|
|
630
957
|
children: [
|
|
631
958
|
messages.map((msg, msgIdx) => {
|
|
632
959
|
const isLastMessage = msgIdx === messages.length - 1;
|
|
@@ -648,7 +975,10 @@ function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRe
|
|
|
648
975
|
stiffness: 500,
|
|
649
976
|
damping: 30
|
|
650
977
|
},
|
|
651
|
-
children: /* @__PURE__ */ jsx(UserMessage, {
|
|
978
|
+
children: /* @__PURE__ */ jsx(UserMessage, {
|
|
979
|
+
html: html ?? sanitizeUserHtml(fallbackText),
|
|
980
|
+
density
|
|
981
|
+
})
|
|
652
982
|
}, msg.id);
|
|
653
983
|
}
|
|
654
984
|
return /* @__PURE__ */ jsx(motion.div, {
|
|
@@ -668,7 +998,14 @@ function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRe
|
|
|
668
998
|
children: /* @__PURE__ */ jsx(AssistantMessage, {
|
|
669
999
|
msg,
|
|
670
1000
|
isLastMessage,
|
|
671
|
-
status
|
|
1001
|
+
status,
|
|
1002
|
+
density,
|
|
1003
|
+
onChoose: onChoose && ((option) => onChoose(msg.id, option)),
|
|
1004
|
+
canChoose: isLastMessage && status === "ready",
|
|
1005
|
+
onApprovePlan: onApprovePlan && ((planId) => onApprovePlan(msg.id, planId)),
|
|
1006
|
+
onChangePlan: onChangePlan && ((planId) => onChangePlan(msg.id, planId)),
|
|
1007
|
+
latestPlanId,
|
|
1008
|
+
canApprovePlan: status === "ready"
|
|
672
1009
|
})
|
|
673
1010
|
}, msg.id);
|
|
674
1011
|
}),
|
|
@@ -690,8 +1027,8 @@ function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRe
|
|
|
690
1027
|
stiffness: 500,
|
|
691
1028
|
damping: 30
|
|
692
1029
|
},
|
|
693
|
-
className: "text-destructive bg-destructive/10 w-fit rounded-lg px-3 py-2 text-sm",
|
|
694
|
-
children:
|
|
1030
|
+
className: cn("text-destructive bg-destructive/10 w-fit rounded-lg px-3 py-2 text-sm", compact && "ml-10"),
|
|
1031
|
+
children: assistantErrorMessage(error)
|
|
695
1032
|
}, "error") }),
|
|
696
1033
|
/* @__PURE__ */ jsx(AnimatePresence, { children: status === "submitted" && /* @__PURE__ */ jsx(motion.div, {
|
|
697
1034
|
initial: {
|
|
@@ -711,7 +1048,7 @@ function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRe
|
|
|
711
1048
|
stiffness: 500,
|
|
712
1049
|
damping: 30
|
|
713
1050
|
},
|
|
714
|
-
className: "flex justify-start",
|
|
1051
|
+
className: cn("flex justify-start", compact && "pl-10"),
|
|
715
1052
|
children: /* @__PURE__ */ jsx(LoadingDots, {})
|
|
716
1053
|
}, "loading") }),
|
|
717
1054
|
/* @__PURE__ */ jsx("div", { ref: bottomRef })
|
|
@@ -724,7 +1061,7 @@ function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRe
|
|
|
724
1061
|
onJump: scrollToTurn
|
|
725
1062
|
}),
|
|
726
1063
|
/* @__PURE__ */ jsxs("div", {
|
|
727
|
-
className: "relative shrink-0 px-4 pb-4",
|
|
1064
|
+
className: cn("relative shrink-0", compact ? "px-7 pb-6" : "px-4 pb-4"),
|
|
728
1065
|
children: [/* @__PURE__ */ jsx(ScrollToBottomButton, {
|
|
729
1066
|
show: showScrollButton && messages.length > 0,
|
|
730
1067
|
onClick: () => {
|
|
@@ -733,13 +1070,23 @@ function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRe
|
|
|
733
1070
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
734
1071
|
}
|
|
735
1072
|
}), /* @__PURE__ */ jsx("div", {
|
|
736
|
-
className: "mx-auto w-full max-w-3xl",
|
|
1073
|
+
className: compact ? "w-full" : "mx-auto w-full max-w-3xl",
|
|
737
1074
|
children: footer
|
|
738
1075
|
})]
|
|
739
1076
|
})
|
|
740
1077
|
]
|
|
741
1078
|
});
|
|
742
1079
|
}
|
|
1080
|
+
/** The id of the newest plan part in the conversation, if any. */
|
|
1081
|
+
function newestPlanId(messages) {
|
|
1082
|
+
for (let m = messages.length - 1; m >= 0; m--) {
|
|
1083
|
+
const parts = messages[m]?.parts ?? [];
|
|
1084
|
+
for (let p = parts.length - 1; p >= 0; p--) {
|
|
1085
|
+
const part = parts[p];
|
|
1086
|
+
if (part.type === "data-plan" && part.id) return part.id;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
743
1090
|
//#endregion
|
|
744
1091
|
//#region src/components/features/assistant/components/brain-glyph.tsx
|
|
745
1092
|
/**
|
|
@@ -872,15 +1219,11 @@ function ChatRail({ historyOpen, onNewChat, onToggleHistory }) {
|
|
|
872
1219
|
//#endregion
|
|
873
1220
|
//#region src/components/features/assistant/components/sidebar/history-panel.tsx
|
|
874
1221
|
function downloadChat(chat) {
|
|
875
|
-
const
|
|
876
|
-
return `## ${msg.role === "user" ? "You" : "Assistant"}\n\n${msg.parts.filter(isTextUIPart).map((p) => p.text).join("\n")}`;
|
|
877
|
-
});
|
|
878
|
-
const markdown = `# ${chat.title}\n\n${lines.join("\n\n---\n\n")}\n`;
|
|
879
|
-
const blob = new Blob([markdown], { type: "text/markdown" });
|
|
1222
|
+
const blob = new Blob([chatToMarkdown(chat)], { type: "text/markdown" });
|
|
880
1223
|
const url = URL.createObjectURL(blob);
|
|
881
1224
|
const a = document.createElement("a");
|
|
882
1225
|
a.href = url;
|
|
883
|
-
a.download =
|
|
1226
|
+
a.download = chatFileName(chat.title);
|
|
884
1227
|
a.click();
|
|
885
1228
|
URL.revokeObjectURL(url);
|
|
886
1229
|
}
|
|
@@ -1068,7 +1411,7 @@ const PANEL_SPRING = {
|
|
|
1068
1411
|
stiffness: 400,
|
|
1069
1412
|
damping: 32
|
|
1070
1413
|
};
|
|
1071
|
-
function AssistantWorkspace({ orientation = "horizontal", onClose, config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory }) {
|
|
1414
|
+
function AssistantWorkspace({ orientation = "horizontal", onClose, config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, onSuggestion, onChoose, onApprovePlan, onChangePlan, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory }) {
|
|
1072
1415
|
const hasMessages = messages.length > 0;
|
|
1073
1416
|
const isVertical = orientation === "vertical";
|
|
1074
1417
|
const promptCard = /* @__PURE__ */ jsx(PromptCard, {
|
|
@@ -1137,7 +1480,10 @@ function AssistantWorkspace({ orientation = "horizontal", onClose, config, userN
|
|
|
1137
1480
|
containerRef,
|
|
1138
1481
|
bottomRef,
|
|
1139
1482
|
userScrolledUpRef,
|
|
1140
|
-
footer: promptCard
|
|
1483
|
+
footer: promptCard,
|
|
1484
|
+
onChoose,
|
|
1485
|
+
onApprovePlan,
|
|
1486
|
+
onChangePlan
|
|
1141
1487
|
}) : /* @__PURE__ */ jsx(EmptyState, {
|
|
1142
1488
|
name: userName,
|
|
1143
1489
|
isReady,
|
|
@@ -1245,4 +1591,4 @@ function AssistantWorkspace({ orientation = "horizontal", onClose, config, userN
|
|
|
1245
1591
|
});
|
|
1246
1592
|
}
|
|
1247
1593
|
//#endregion
|
|
1248
|
-
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 };
|
|
1594
|
+
export { AssistantChoiceChips, AssistantConfigProvider, AssistantMessage, AssistantPlanCard, AssistantWorkspace, BrainGlyph, CHOICES_PART_TYPE, ChatRail, Conversation, DEFAULT_ASSISTANT_CONFIG, EmptyState, Equalizer, HistoryPanel, LoadingDots, ModelSelector, PLAN_PART_TYPE, PromptCard, ScrollToBottomButton, ThinkingBlock, TurnRail, UserMessage, defaultRenderLink, formatRelativeTime, sanitizeUserHtml, useAssistantConfig, useTurnRail };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Editor } from '@tiptap/react';
|
|
2
2
|
import type { UIMessage } from 'ai';
|
|
3
3
|
import type { MouseEvent as ReactMouseEvent, ReactNode, RefObject } from 'react';
|
|
4
|
-
import type { AssistantConfig, ChatSummary, EffortId } from '../types';
|
|
4
|
+
import type { AssistantChoiceOption, AssistantConfig, ChatSummary, EffortId } from '../types';
|
|
5
5
|
export type AssistantWorkspaceOrientation = 'horizontal' | 'vertical';
|
|
6
6
|
/**
|
|
7
7
|
* Presentational, props-driven assistant layout: left rail + collapsible
|
|
@@ -66,6 +66,12 @@ export interface AssistantWorkspaceProps {
|
|
|
66
66
|
/** Confirm (deletion can't be undone) before calling `onDeleteChat`. */
|
|
67
67
|
confirmDelete?: boolean;
|
|
68
68
|
onSuggestion: (text: string) => void;
|
|
69
|
+
/** Called when the person picks an option of a question asked as buttons (see `Conversation`). */
|
|
70
|
+
onChoose?: (messageId: string, option: AssistantChoiceOption) => void;
|
|
71
|
+
/** Called when the person approves a plan card (see `Conversation`). */
|
|
72
|
+
onApprovePlan?: (messageId: string, planId: string) => void;
|
|
73
|
+
/** Called when the person clicks "Change something" on a plan card. */
|
|
74
|
+
onChangePlan?: (messageId: string, planId: string) => void;
|
|
69
75
|
modelId: string;
|
|
70
76
|
effortId: EffortId;
|
|
71
77
|
onModelChange: (id: string) => void;
|
|
@@ -77,4 +83,4 @@ export interface AssistantWorkspaceProps {
|
|
|
77
83
|
historyOpen: boolean;
|
|
78
84
|
onToggleHistory: () => void;
|
|
79
85
|
}
|
|
80
|
-
export declare function AssistantWorkspace({ orientation, onClose, config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, onSuggestion, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory, }: AssistantWorkspaceProps): import("react").JSX.Element;
|
|
86
|
+
export declare function AssistantWorkspace({ orientation, onClose, config, userName, title, messages, status, error, isReady, chatList, currentChatId, sidebarHeader, editor, htmlByUserMsgIndex, bottomRef, containerRef, userScrolledUpRef, onSend, onStop, onRetry, onNewChat, onLoadChat, onDeleteChat, onArchiveChat, onUnarchiveChat, confirmDelete, onSuggestion, onChoose, onApprovePlan, onChangePlan, modelId, effortId, onModelChange, onEffortChange, micSupported, micListening, micFrequencyData, onMicToggle, historyOpen, onToggleHistory, }: AssistantWorkspaceProps): import("react").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Editor } from '@tiptap/react';
|
|
2
|
-
import type { EffortId } from '../../types';
|
|
2
|
+
import type { ConversationDensity, EffortId } from '../../types';
|
|
3
3
|
interface PromptCardProps {
|
|
4
4
|
editor: Editor | null;
|
|
5
5
|
isReady: boolean;
|
|
@@ -15,7 +15,12 @@ interface PromptCardProps {
|
|
|
15
15
|
isListening?: boolean;
|
|
16
16
|
frequencyData?: number[];
|
|
17
17
|
onMicToggle?: () => void;
|
|
18
|
+
/**
|
|
19
|
+
* `compact` is one row: the input and its buttons side by side in a plain
|
|
20
|
+
* bordered field, with an outline send button. For a chat embedded in a page.
|
|
21
|
+
*/
|
|
22
|
+
density?: ConversationDensity;
|
|
18
23
|
className?: string;
|
|
19
24
|
}
|
|
20
|
-
export declare function PromptCard({ editor, isReady, canRetry, onSend, onStop, onRetry, modelId, effortId, onModelChange, onEffortChange, speechSupported, isListening, frequencyData, onMicToggle, className, }: PromptCardProps): import("react").JSX.Element;
|
|
25
|
+
export declare function PromptCard({ editor, isReady, canRetry, onSend, onStop, onRetry, modelId, effortId, onModelChange, onEffortChange, speechSupported, isListening, frequencyData, onMicToggle, density, className, }: PromptCardProps): import("react").JSX.Element;
|
|
21
26
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { UIMessage } from 'ai';
|
|
2
2
|
import type { RefObject } from 'react';
|
|
3
|
+
import type { AssistantChoiceOption, ConversationDensity } from '../../types';
|
|
3
4
|
interface ConversationProps {
|
|
4
5
|
messages: UIMessage[];
|
|
5
6
|
status: string;
|
|
@@ -10,6 +11,27 @@ interface ConversationProps {
|
|
|
10
11
|
userScrolledUpRef: RefObject<boolean>;
|
|
11
12
|
/** The docked prompt card, pinned to the bottom. */
|
|
12
13
|
footer: React.ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* `default` is the full workspace layout. `compact` is for a conversation
|
|
16
|
+
* embedded in another page: it fills the host's width with 1.75rem side
|
|
17
|
+
* padding, puts an avatar beside each assistant turn and tightens markdown
|
|
18
|
+
* spacing.
|
|
19
|
+
*/
|
|
20
|
+
density?: ConversationDensity;
|
|
21
|
+
/**
|
|
22
|
+
* Called when the person picks an option of a question the assistant asked
|
|
23
|
+
* as buttons, with the message it belongs to. Only the latest turn's
|
|
24
|
+
* question can be answered, and only once it has finished streaming.
|
|
25
|
+
*/
|
|
26
|
+
onChoose?: (messageId: string, option: AssistantChoiceOption) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Called when the person approves a plan card, with the message it is in
|
|
29
|
+
* and the plan's id. Only the newest plan can be approved, and only while
|
|
30
|
+
* the conversation is idle.
|
|
31
|
+
*/
|
|
32
|
+
onApprovePlan?: (messageId: string, planId: string) => void;
|
|
33
|
+
/** Called when the person clicks "Change something" on a plan card. */
|
|
34
|
+
onChangePlan?: (messageId: string, planId: string) => void;
|
|
13
35
|
}
|
|
14
|
-
export declare function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRef, bottomRef, userScrolledUpRef, footer, }: ConversationProps): import("react").JSX.Element;
|
|
36
|
+
export declare function Conversation({ messages, status, error, htmlByUserMsgIndex, containerRef, bottomRef, userScrolledUpRef, footer, density, onChoose, onApprovePlan, onChangePlan, }: ConversationProps): import("react").JSX.Element;
|
|
15
37
|
export {};
|
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
import type { UIMessage } from 'ai';
|
|
2
|
+
import type { AssistantChoiceOption, ConversationDensity } from '../../types';
|
|
2
3
|
import 'streamdown/styles.css';
|
|
3
4
|
interface AssistantMessageProps {
|
|
4
5
|
msg: UIMessage;
|
|
5
6
|
isLastMessage: boolean;
|
|
6
7
|
status: string;
|
|
8
|
+
/**
|
|
9
|
+
* `compact` puts the assistant avatar beside the turn, tightens markdown
|
|
10
|
+
* spacing (headings render at body size), sets inline code as plain mono
|
|
11
|
+
* text, and leaves out the copy and download actions (a host's own
|
|
12
|
+
* `messageActions` still render).
|
|
13
|
+
*/
|
|
14
|
+
density?: ConversationDensity;
|
|
15
|
+
/**
|
|
16
|
+
* Called when the person picks an option of a question in this turn (a
|
|
17
|
+
* `data-choices` part). The host sends the option's label as their reply.
|
|
18
|
+
*/
|
|
19
|
+
onChoose?: (option: AssistantChoiceOption) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Whether this turn's question can still be answered: the host passes true
|
|
22
|
+
* only for the latest turn once it has finished streaming.
|
|
23
|
+
*/
|
|
24
|
+
canChoose?: boolean;
|
|
25
|
+
/** Called when the person approves a plan in this turn (a `data-plan` part), with its id. */
|
|
26
|
+
onApprovePlan?: (planId: string) => void;
|
|
27
|
+
/** Called when the person asks to change a plan in this turn. */
|
|
28
|
+
onChangePlan?: (planId: string) => void;
|
|
29
|
+
/** The id of the newest plan in the conversation; any other reads as replaced. */
|
|
30
|
+
latestPlanId?: string;
|
|
31
|
+
/** Whether a plan can be approved now: the conversation is idle. */
|
|
32
|
+
canApprovePlan?: boolean;
|
|
7
33
|
}
|
|
8
|
-
export declare function AssistantMessage({ msg, isLastMessage, status }: AssistantMessageProps): import("react").JSX.Element;
|
|
34
|
+
export declare function AssistantMessage({ msg, isLastMessage, status, density, onChoose, canChoose, onApprovePlan, onChangePlan, latestPlanId, canApprovePlan, }: AssistantMessageProps): import("react").JSX.Element;
|
|
9
35
|
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AssistantChoiceOption, AssistantChoicesData } from '../../types';
|
|
2
|
+
export interface AssistantChoiceChipsProps {
|
|
3
|
+
data: AssistantChoicesData;
|
|
4
|
+
/**
|
|
5
|
+
* Called with the option the person picked. Omit it, or pass `disabled`,
|
|
6
|
+
* for a question that can no longer be answered (an earlier turn, or one
|
|
7
|
+
* answered by typing instead).
|
|
8
|
+
*/
|
|
9
|
+
onChoose?: (option: AssistantChoiceOption) => void;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A pick-one question as a row of buttons. Once an option is picked it stays
|
|
14
|
+
* marked with a check and the others fade, so a scrolled-back conversation
|
|
15
|
+
* still shows what was chosen.
|
|
16
|
+
*/
|
|
17
|
+
export declare function AssistantChoiceChips({ data, onChoose, disabled }: AssistantChoiceChipsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AssistantPlanData } from '../../types';
|
|
2
|
+
export interface AssistantPlanCardProps {
|
|
3
|
+
data: AssistantPlanData;
|
|
4
|
+
/** Whether this is the newest plan in the conversation; an older one reads as replaced. */
|
|
5
|
+
latest?: boolean;
|
|
6
|
+
/** Whether Approve can be clicked now (the conversation is idle). */
|
|
7
|
+
canApprove?: boolean;
|
|
8
|
+
onApprove?: () => void;
|
|
9
|
+
/** "Change something": the host puts the person in the composer to say what. */
|
|
10
|
+
onChange?: () => void;
|
|
11
|
+
/** The clock, for tests. */
|
|
12
|
+
now?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A plan the assistant settled, as a card: one row per resource with what it
|
|
16
|
+
* does, the YAML and differences on request, and the person's two answers.
|
|
17
|
+
* Nothing is created until they click Approve, and the card then shows how it
|
|
18
|
+
* went.
|
|
19
|
+
*/
|
|
20
|
+
export declare function AssistantPlanCard({ data, latest, canApprove, onApprove, onChange, now, }: AssistantPlanCardProps): import("react").JSX.Element;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { ConversationDensity } from '../../types';
|
|
2
|
+
interface UserMessageProps {
|
|
3
3
|
html: string;
|
|
4
|
-
|
|
4
|
+
/** `compact` uses a lighter bubble with even corners. */
|
|
5
|
+
density?: ConversationDensity;
|
|
6
|
+
}
|
|
7
|
+
/** A user's message — a right-aligned bubble rendering the sanitized input HTML. */
|
|
8
|
+
export declare function UserMessage({ html, density }: UserMessageProps): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -7,5 +7,6 @@ export * from './components/message';
|
|
|
7
7
|
export * from './components/sidebar';
|
|
8
8
|
export { AssistantConfigProvider, DEFAULT_ASSISTANT_CONFIG, defaultRenderLink, useAssistantConfig, } from './context';
|
|
9
9
|
export { useTurnRail } from './hooks';
|
|
10
|
-
export
|
|
10
|
+
export { CHOICES_PART_TYPE, PLAN_PART_TYPE } from './types';
|
|
11
|
+
export type { AssistantAppliedResource, AssistantChoiceIcon, AssistantChoiceOption, AssistantChoicesData, AssistantConfig, AssistantPlanData, AssistantPlanResource, AssistantPlanStatus, ChatSummary, ConversationDensity, EffortId, EffortOption, LinkRenderProps, ModelOption, ModelSelectorConfig, Turn, } from './types';
|
|
11
12
|
export { formatRelativeTime, sanitizeUserHtml } from './utils';
|
|
@@ -1,71 +1,81 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AssistantConfig as CoreAssistantConfig, LinkRenderProps as CoreLinkRenderProps } from '@datum-cloud/datum-ui-assistant-core';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
export type EffortId
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export type { ChatSummary, EffortId, EffortOption, ModelOption, ModelSelectorConfig, Turn, } from '@datum-cloud/datum-ui-assistant-core';
|
|
4
|
+
/** Props passed to a `renderLink` implementation (a link in the assistant's markdown). */
|
|
5
|
+
export type LinkRenderProps = CoreLinkRenderProps<ReactNode>;
|
|
6
|
+
/**
|
|
7
|
+
* Everything that differs between host apps (staff vs cloud). Supplied once at
|
|
8
|
+
* the workspace root and read by leaf components via `useAssistantConfig()`, so
|
|
9
|
+
* the presentational pieces stay generic and shell-agnostic.
|
|
10
|
+
*/
|
|
11
|
+
export type AssistantConfig = CoreAssistantConfig<ReactNode>;
|
|
12
|
+
/**
|
|
13
|
+
* How tightly a conversation is laid out. `default` is the full assistant
|
|
14
|
+
* workspace; `compact` suits a conversation embedded in another page: it fills
|
|
15
|
+
* the host's width, puts an avatar beside each assistant turn, tightens
|
|
16
|
+
* markdown spacing and uses a one-line composer.
|
|
17
|
+
*/
|
|
18
|
+
export type ConversationDensity = 'default' | 'compact';
|
|
19
|
+
/** The message part type that carries a pick-one question (see {@link AssistantChoicesData}). */
|
|
20
|
+
export declare const CHOICES_PART_TYPE = "data-choices";
|
|
21
|
+
/** Icon names a choice option may carry. Anything else renders with no icon. */
|
|
22
|
+
export type AssistantChoiceIcon = 'clock' | 'container' | 'database' | 'domain' | 'globe' | 'laptop' | 'location' | 'network' | 'server' | 'shield';
|
|
23
|
+
/** One answer to a pick-one question. `label` is sent back as the person's reply. */
|
|
24
|
+
export interface AssistantChoiceOption {
|
|
6
25
|
id: string;
|
|
7
26
|
label: string;
|
|
27
|
+
icon?: AssistantChoiceIcon | string;
|
|
8
28
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The data of a `data-choices` message part: a question the assistant put to
|
|
31
|
+
* the person as buttons. `selectedId` is set once they pick one.
|
|
32
|
+
*/
|
|
33
|
+
export interface AssistantChoicesData {
|
|
34
|
+
question: string;
|
|
35
|
+
options: AssistantChoiceOption[];
|
|
36
|
+
selectedId?: string;
|
|
17
37
|
}
|
|
18
|
-
/**
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
/** The message part type that carries a plan the person can approve (see {@link AssistantPlanData}). */
|
|
39
|
+
export declare const PLAN_PART_TYPE = "data-plan";
|
|
40
|
+
/** One resource a plan creates or changes, in apply order. */
|
|
41
|
+
export interface AssistantPlanResource {
|
|
42
|
+
kind: string;
|
|
43
|
+
name: string;
|
|
44
|
+
namespace?: string;
|
|
45
|
+
action: 'create' | 'update' | string;
|
|
46
|
+
/** One plain-language line on what it does for the person. */
|
|
47
|
+
summary?: string;
|
|
48
|
+
/** What an update changes, one line per change. */
|
|
49
|
+
diff?: string[];
|
|
50
|
+
/** The manifest the plan covers, as YAML. */
|
|
51
|
+
manifest: string;
|
|
22
52
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
53
|
+
/** One resource applying a plan created or changed. */
|
|
54
|
+
export interface AssistantAppliedResource {
|
|
55
|
+
action: string;
|
|
56
|
+
kind: string;
|
|
57
|
+
name: string;
|
|
58
|
+
namespace?: string;
|
|
28
59
|
}
|
|
29
60
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* the
|
|
61
|
+
* Where a plan is. `pending` waits for the person; `applying` is their
|
|
62
|
+
* approval in flight; `applied` and `failed` are its outcome. Expired and
|
|
63
|
+
* replaced are not stored: the card works them out from `expiresAt` and from
|
|
64
|
+
* whether a newer plan exists.
|
|
33
65
|
*/
|
|
34
|
-
export
|
|
35
|
-
/** Greeting line on the empty state, e.g. "Hey there, Jacob". */
|
|
36
|
-
greeting: (name?: string) => string;
|
|
37
|
-
/** Starter prompts on the empty state. */
|
|
38
|
-
suggestions: string[];
|
|
39
|
-
/** Whether to render the assistant's reasoning ("thinking") blocks. */
|
|
40
|
-
showReasoning: boolean;
|
|
41
|
-
/** Model + effort picker config, or `false` to hide the control. */
|
|
42
|
-
modelSelector: ModelSelectorConfig | false;
|
|
43
|
-
/** Tool name → progress label ("Searching users…"). Host-specific tool set. */
|
|
44
|
-
toolLabels: Record<string, string>;
|
|
45
|
-
/** Renders links inside the assistant's markdown (internal routes, external, host-specific icons). */
|
|
46
|
-
renderLink: (props: LinkRenderProps) => ReactNode;
|
|
47
|
-
/** Override the per-message action toolbar (defaults to copy + download). */
|
|
48
|
-
messageActions?: (msg: UIMessage) => ReactNode;
|
|
49
|
-
/**
|
|
50
|
-
* Renders a completed tool call inline, in message order. Return a falsy value
|
|
51
|
-
* to leave that tool invisible — the default for every tool, since most only
|
|
52
|
-
* feed the model. Hosts use this to surface a result as UI (cloud-portal turns
|
|
53
|
-
* an `openSupportTicket` result into a button).
|
|
54
|
-
*/
|
|
55
|
-
renderToolOutput?: (part: ToolUIPart | DynamicToolUIPart, msg: UIMessage) => ReactNode;
|
|
56
|
-
}
|
|
66
|
+
export type AssistantPlanStatus = 'pending' | 'applying' | 'applied' | 'failed';
|
|
57
67
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
68
|
+
* The data of a `data-plan` message part: a change the assistant settled and
|
|
69
|
+
* the token that applies exactly it. The part's `id` names the plan.
|
|
60
70
|
*/
|
|
61
|
-
export interface
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
export interface AssistantPlanData {
|
|
72
|
+
token: string;
|
|
73
|
+
/** When the plan stops being accepted, RFC 3339. */
|
|
74
|
+
expiresAt: string;
|
|
75
|
+
resources: AssistantPlanResource[];
|
|
76
|
+
status: AssistantPlanStatus;
|
|
77
|
+
/** When the person approved it, RFC 3339. */
|
|
78
|
+
approvedAt?: string;
|
|
79
|
+
applied?: AssistantAppliedResource[];
|
|
80
|
+
error?: string;
|
|
71
81
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/** Human-friendly relative timestamp for the chat history list. */
|
|
2
|
-
export
|
|
1
|
+
/** Human-friendly relative timestamp for the chat history list (shared with React Native). */
|
|
2
|
+
export { formatRelativeTime } from '@datum-cloud/datum-ui-assistant-core';
|
package/dist/index.mjs
CHANGED
|
@@ -41,7 +41,7 @@ import { t as Switch } from "./switch-BaR-Vna4.mjs";
|
|
|
41
41
|
import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "./table/index.mjs";
|
|
42
42
|
import { Tabs, TabsContent, TabsLinkTrigger, TabsList, TabsTrigger } from "./tabs/index.mjs";
|
|
43
43
|
import { t as Textarea } from "./textarea-DiSGxCBO.mjs";
|
|
44
|
-
import { a as ListItem, c as Title, d as titleVariants, i as List, l as paragraphVariants, n as Code, o as Paragraph, r as Link, s as Text, t as Blockquote, u as textVariants } from "./typography-
|
|
44
|
+
import { a as ListItem, c as Title, d as titleVariants, i as List, l as paragraphVariants, n as Code, o as Paragraph, r as Link, s as Text, t as Blockquote, u as textVariants } from "./typography-B1bpHKmV.mjs";
|
|
45
45
|
import { t as VisuallyHidden } from "./visuallyhidden-D7L_qs8b.mjs";
|
|
46
46
|
import { a as hasActiveDescendant, i as findItemByHref, n as NavMenu, o as isNavItemActive, r as NavMenuItem, s as normalizePath, t as AppNavigation } from "./app-navigation-BB_NLEWv.mjs";
|
|
47
47
|
import { n as EASE, t as DURATION } from "./motion-DvGRWdsm.mjs";
|
|
@@ -79,7 +79,7 @@ import { t as MoreActions } from "./more-actions-C3DYWO0a.mjs";
|
|
|
79
79
|
import { configureProgress, startProgress, stopProgress } from "./nprogress/index.mjs";
|
|
80
80
|
import { t as PageTitle } from "./page-title-ipRWEOeM.mjs";
|
|
81
81
|
import { t as RichTextContent } from "./rich-text-content-DNonBD39.mjs";
|
|
82
|
-
import { n as useRichTextEditor, t as RichTextEditor } from "./rich-text-editor-
|
|
82
|
+
import { n as useRichTextEditor, t as RichTextEditor } from "./rich-text-editor-CP1YyxMR.mjs";
|
|
83
83
|
import { t as defineStepper } from "./stepper-dNvkipop.mjs";
|
|
84
84
|
import { t as TagsInput } from "./tag-input-c9OvWTSe.mjs";
|
|
85
85
|
import { _ as RedisTaskStorage, a as TaskPanelItem, b as createProjectMetadata, c as TaskPanelHeader, d as useCurrentScope, f as useTasksWithLabels, g as detectStorage, h as TaskQueue, i as TaskPanel, l as getContextLabel, m as TaskQueueProvider, n as TaskSummaryDialog, o as TaskPanelCounter, p as useTaskQueue, r as TaskQueueTrigger, s as TaskPanelActions, t as TaskQueueDropdown, u as matchesCurrentScope, v as LocalTaskStorage, x as createUserMetadata, y as createOrgMetadata } from "./task-queue-dropdown-CHPP5WIm.mjs";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { t as RichTextContent } from "../rich-text-content-DNonBD39.mjs";
|
|
2
|
-
import { n as useRichTextEditor, t as RichTextEditor } from "../rich-text-editor-
|
|
2
|
+
import { n as useRichTextEditor, t as RichTextEditor } from "../rich-text-editor-CP1YyxMR.mjs";
|
|
3
3
|
export { RichTextContent, RichTextEditor, useRichTextEditor };
|
|
@@ -8,7 +8,7 @@ import { BoldIcon, ItalicIcon, LinkIcon, StrikethroughIcon, UnderlineIcon, Unlin
|
|
|
8
8
|
import { createContext, use, useCallback, useEffect, useState } from "react";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
import CharacterCount from "@tiptap/extension-character-count";
|
|
11
|
-
import Link from "@tiptap/extension-link";
|
|
11
|
+
import Link$1 from "@tiptap/extension-link";
|
|
12
12
|
import Placeholder from "@tiptap/extension-placeholder";
|
|
13
13
|
import Underline from "@tiptap/extension-underline";
|
|
14
14
|
import { EditorContent, useEditor } from "@tiptap/react";
|
|
@@ -191,7 +191,7 @@ function RichTextEditorRoot({ content, onChange, onBlur, autoFocus = false, plac
|
|
|
191
191
|
horizontalRule: false
|
|
192
192
|
}),
|
|
193
193
|
Underline,
|
|
194
|
-
Link.configure({
|
|
194
|
+
Link$1.configure({
|
|
195
195
|
autolink: true,
|
|
196
196
|
openOnClick: false,
|
|
197
197
|
validate: (url) => /^https?:\/\/|^mailto:/.test(url),
|
|
@@ -589,7 +589,7 @@ TAILWIND CUSTOM UTILITIES FOR THEME
|
|
|
589
589
|
/* ========================================
|
|
590
590
|
TYPE SCALE
|
|
591
591
|
One named scale shared by raw `text-*` utilities and the Typography
|
|
592
|
-
components. See Storybook "
|
|
592
|
+
components. See Storybook "Foundations/Type Scale" for the rules.
|
|
593
593
|
|
|
594
594
|
Deliberately a plain `@theme`, not `@theme inline`: utilities must read
|
|
595
595
|
`var(--text-*)` at runtime so the display steps below can shrink per
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ListItem, c as Title, d as titleVariants, i as List, l as paragraphVariants, n as Code, o as Paragraph, r as Link, s as Text, t as Blockquote, u as textVariants } from "../typography-
|
|
1
|
+
import { a as ListItem, c as Title, d as titleVariants, i as List, l as paragraphVariants, n as Code, o as Paragraph, r as Link, s as Text, t as Blockquote, u as textVariants } from "../typography-B1bpHKmV.mjs";
|
|
2
2
|
export { Blockquote, Code, Link, List, ListItem, Paragraph, Text, Title, paragraphVariants, textVariants, titleVariants };
|
|
@@ -162,7 +162,7 @@ function Paragraph({ className, size, spacing, as = "p", children, ...props }) {
|
|
|
162
162
|
...props
|
|
163
163
|
}, children);
|
|
164
164
|
}
|
|
165
|
-
function Link({ className, children, href, target = "_self", rel, ...props }) {
|
|
165
|
+
function Link$1({ className, children, href, target = "_self", rel, ...props }) {
|
|
166
166
|
return /* @__PURE__ */ jsx("a", {
|
|
167
167
|
href,
|
|
168
168
|
target,
|
|
@@ -193,7 +193,7 @@ function Blockquote({ className, children, ...props }) {
|
|
|
193
193
|
children
|
|
194
194
|
});
|
|
195
195
|
}
|
|
196
|
-
function Code({ className, as = "code", children, ...props }) {
|
|
196
|
+
function Code$1({ className, as = "code", children, ...props }) {
|
|
197
197
|
if (as === "pre") return /* @__PURE__ */ jsx("pre", {
|
|
198
198
|
className: cn("bg-muted relative overflow-x-auto rounded p-4 font-mono text-sm", className),
|
|
199
199
|
...props,
|
|
@@ -206,4 +206,4 @@ function Code({ className, as = "code", children, ...props }) {
|
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
208
|
//#endregion
|
|
209
|
-
export { ListItem as a, Title as c, titleVariants as d, List as i, paragraphVariants as l, Code as n, Paragraph as o, Link as r, Text as s, Blockquote as t, textVariants as u };
|
|
209
|
+
export { ListItem as a, Title as c, titleVariants as d, List as i, paragraphVariants as l, Code$1 as n, Paragraph as o, Link$1 as r, Text as s, Blockquote as t, textVariants as u };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datum-cloud/datum-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.7.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://github.com/datum-cloud/datum-ui"
|
|
@@ -621,7 +621,8 @@
|
|
|
621
621
|
"cmdk": "^1.1.1",
|
|
622
622
|
"isomorphic-dompurify": "^3.22.0",
|
|
623
623
|
"tailwind-merge": "^3.6.0",
|
|
624
|
-
"tw-animate-css": "^1.4.0"
|
|
624
|
+
"tw-animate-css": "^1.4.0",
|
|
625
|
+
"@datum-cloud/datum-ui-assistant-core": "^0.1.0"
|
|
625
626
|
},
|
|
626
627
|
"devDependencies": {
|
|
627
628
|
"@conform-to/react": "^1.21.0",
|