@tangle-network/sandbox-ui 0.17.0 → 0.18.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/chat.d.ts +111 -0
- package/dist/chat.js +12 -3
- package/dist/chunk-CNVE6KOM.js +417 -0
- package/dist/chunk-N5RYCDLD.js +1187 -0
- package/dist/dashboard.d.ts +1 -1
- package/dist/dashboard.js +1 -1
- package/dist/globals.css +34 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +11 -3
- package/dist/openui.d.ts +0 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1 -1
- package/dist/styles.css +34 -0
- package/dist/{template-card-Dufxl4hV.d.ts → template-card-gf-InrfN.d.ts} +12 -0
- package/package.json +2 -1
- package/dist/chunk-27X3DWMO.js +0 -815
- package/dist/chunk-QPAJR74X.js +0 -20
package/dist/chat.d.ts
CHANGED
|
@@ -1 +1,112 @@
|
|
|
1
|
+
import { MessageRole } from '@tangle-network/ui/chat';
|
|
1
2
|
export { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineCustomItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, ChatContainer, ChatContainerProps, ChatInput, ChatInputProps, ChatMessage, ChatMessageProps, MessageList, MessageListProps, MessageRole, PendingFile, ThinkingIndicator, ThinkingIndicatorProps, UserMessage, UserMessageProps } from '@tangle-network/ui/chat';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
type ReasoningLevel = "auto" | "low" | "medium" | "high";
|
|
6
|
+
interface ReasoningLevelOption {
|
|
7
|
+
value: ReasoningLevel;
|
|
8
|
+
label: string;
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
interface ReasoningLevelPickerProps {
|
|
12
|
+
value: ReasoningLevel;
|
|
13
|
+
onChange: (value: ReasoningLevel) => void;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
triggerClassName?: string;
|
|
17
|
+
options?: ReadonlyArray<ReasoningLevelOption>;
|
|
18
|
+
}
|
|
19
|
+
declare const DEFAULT_REASONING_LEVEL_OPTIONS: ReadonlyArray<ReasoningLevelOption>;
|
|
20
|
+
declare function ReasoningLevelPicker({ value, onChange, disabled, className, triggerClassName, options, }: ReasoningLevelPickerProps): react_jsx_runtime.JSX.Element;
|
|
21
|
+
|
|
22
|
+
type ArtifactKind = string;
|
|
23
|
+
interface ArtifactScope {
|
|
24
|
+
kind: ArtifactKind;
|
|
25
|
+
key: string;
|
|
26
|
+
/** Short label shown in the dock header. Defaults to a generated one from kind/key. */
|
|
27
|
+
label?: string;
|
|
28
|
+
}
|
|
29
|
+
interface ArtifactDockMessage {
|
|
30
|
+
id: string;
|
|
31
|
+
role: MessageRole;
|
|
32
|
+
content: string;
|
|
33
|
+
createdAt?: Date;
|
|
34
|
+
}
|
|
35
|
+
type ArtifactDockStreamEvent = {
|
|
36
|
+
type: "delta";
|
|
37
|
+
text: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: "error";
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Consumer-supplied adapter. Keeps the dock decoupled from any specific app's
|
|
44
|
+
* API surface — every consumer wires its own thread-ensure, message-load, and
|
|
45
|
+
* NDJSON stream-send.
|
|
46
|
+
*/
|
|
47
|
+
interface ArtifactAgentDockTransport {
|
|
48
|
+
/** Get-or-create the canonical thread for this scope. */
|
|
49
|
+
ensureThread(scope: ArtifactScope): Promise<{
|
|
50
|
+
id: string;
|
|
51
|
+
}>;
|
|
52
|
+
/** Load prior messages, in chronological order. Optional — returns []. */
|
|
53
|
+
loadMessages?(threadId: string): Promise<ArtifactDockMessage[]>;
|
|
54
|
+
/** Stream a user turn. Yield delta events; the dock concatenates them. */
|
|
55
|
+
sendStream(args: {
|
|
56
|
+
threadId: string;
|
|
57
|
+
content: string;
|
|
58
|
+
signal: AbortSignal;
|
|
59
|
+
}): AsyncIterable<ArtifactDockStreamEvent>;
|
|
60
|
+
}
|
|
61
|
+
interface ArtifactAgentDockProps {
|
|
62
|
+
scope: ArtifactScope;
|
|
63
|
+
open: boolean;
|
|
64
|
+
onOpenChange: (open: boolean) => void;
|
|
65
|
+
transport: ArtifactAgentDockTransport;
|
|
66
|
+
defaultPrompt?: string;
|
|
67
|
+
/** Toast handler for non-fatal errors. Falls back to console.error. */
|
|
68
|
+
onError?: (message: string) => void;
|
|
69
|
+
className?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* ArtifactAgentDock — slide-in right-rail chat scoped to a single artifact.
|
|
73
|
+
*
|
|
74
|
+
* Renders a persistent, per-artifact conversation surface that streams via a
|
|
75
|
+
* consumer-supplied transport. Mount alongside any artifact view (file editor,
|
|
76
|
+
* sequence editor, asset preview) to give every artifact its own dedicated
|
|
77
|
+
* agent conversation without forking the chat UI per app.
|
|
78
|
+
*
|
|
79
|
+
* Behavior:
|
|
80
|
+
* - On open, calls `transport.ensureThread(scope)` to get the canonical thread
|
|
81
|
+
* id and `transport.loadMessages(threadId)` to populate prior turns.
|
|
82
|
+
* - On submit, calls `transport.sendStream(...)` and concatenates delta events
|
|
83
|
+
* into a single streaming assistant message; renders inline.
|
|
84
|
+
* - Composer: Enter sends, Shift+Enter newline, Cancel during stream supported.
|
|
85
|
+
* - Accessibility: scope `aria-label` on the panel, `aria-pressed` is the
|
|
86
|
+
* consumer's job on whatever button toggles `open`.
|
|
87
|
+
*/
|
|
88
|
+
declare function ArtifactAgentDock({ scope, open, onOpenChange, transport, defaultPrompt, onError, className, }: ArtifactAgentDockProps): react_jsx_runtime.JSX.Element | null;
|
|
89
|
+
/**
|
|
90
|
+
* Build a transport that hits the Tangle product convention: POST
|
|
91
|
+
* /api/threads with body.scope = ArtifactScope to ensure the canonical
|
|
92
|
+
* thread; GET /api/threads/:id/messages to load history; POST /api/chat
|
|
93
|
+
* with body = {workspaceId, threadId, content} and an NDJSON event stream
|
|
94
|
+
* to send. Most consumers can use this as-is; advanced ones can supply
|
|
95
|
+
* their own transport object.
|
|
96
|
+
*
|
|
97
|
+
* The expected NDJSON event shape:
|
|
98
|
+
* { type: "message.part.delta", data: { text: string } } // assistant chunk
|
|
99
|
+
* { type: "error", data: { message: string, ... } } // surface error
|
|
100
|
+
*
|
|
101
|
+
* Other event types are ignored — consumers that need tool-call rendering
|
|
102
|
+
* or other event semantics should ship a richer transport.
|
|
103
|
+
*/
|
|
104
|
+
declare function createFetchTransport(opts: {
|
|
105
|
+
workspaceId: string;
|
|
106
|
+
threadsUrl?: string;
|
|
107
|
+
chatUrl?: string;
|
|
108
|
+
/** Optional fetch override (auth headers, baseURL, etc.). */
|
|
109
|
+
fetchImpl?: typeof fetch;
|
|
110
|
+
}): ArtifactAgentDockTransport;
|
|
111
|
+
|
|
112
|
+
export { ArtifactAgentDock, type ArtifactAgentDockProps, type ArtifactAgentDockTransport, type ArtifactDockMessage, type ArtifactDockStreamEvent, type ArtifactKind, type ArtifactScope, DEFAULT_REASONING_LEVEL_OPTIONS, type ReasoningLevel, type ReasoningLevelOption, ReasoningLevelPicker, type ReasoningLevelPickerProps, createFetchTransport };
|
package/dist/chat.js
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentTimeline,
|
|
3
|
+
ArtifactAgentDock,
|
|
3
4
|
ChatContainer,
|
|
4
5
|
ChatInput,
|
|
5
6
|
ChatMessage,
|
|
7
|
+
DEFAULT_REASONING_LEVEL_OPTIONS,
|
|
6
8
|
MessageList,
|
|
9
|
+
ReasoningLevelPicker,
|
|
7
10
|
ThinkingIndicator,
|
|
8
|
-
UserMessage
|
|
9
|
-
|
|
11
|
+
UserMessage,
|
|
12
|
+
createFetchTransport
|
|
13
|
+
} from "./chunk-CNVE6KOM.js";
|
|
14
|
+
import "./chunk-EI44GEQ5.js";
|
|
10
15
|
export {
|
|
11
16
|
AgentTimeline,
|
|
17
|
+
ArtifactAgentDock,
|
|
12
18
|
ChatContainer,
|
|
13
19
|
ChatInput,
|
|
14
20
|
ChatMessage,
|
|
21
|
+
DEFAULT_REASONING_LEVEL_OPTIONS,
|
|
15
22
|
MessageList,
|
|
23
|
+
ReasoningLevelPicker,
|
|
16
24
|
ThinkingIndicator,
|
|
17
|
-
UserMessage
|
|
25
|
+
UserMessage,
|
|
26
|
+
createFetchTransport
|
|
18
27
|
};
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-EI44GEQ5.js";
|
|
4
|
+
|
|
5
|
+
// src/chat/index.ts
|
|
6
|
+
import {
|
|
7
|
+
AgentTimeline,
|
|
8
|
+
ChatContainer,
|
|
9
|
+
ChatInput,
|
|
10
|
+
ChatMessage as ChatMessage2,
|
|
11
|
+
MessageList,
|
|
12
|
+
ThinkingIndicator,
|
|
13
|
+
UserMessage
|
|
14
|
+
} from "@tangle-network/ui/chat";
|
|
15
|
+
|
|
16
|
+
// src/chat/reasoning-level-picker.tsx
|
|
17
|
+
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
18
|
+
import { Brain, ChevronDown } from "lucide-react";
|
|
19
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
20
|
+
var DEFAULT_REASONING_LEVEL_OPTIONS = [
|
|
21
|
+
{ value: "auto", label: "Auto", description: "Let the agent pick the right depth." },
|
|
22
|
+
{ value: "low", label: "Low", description: "Fast, direct answers." },
|
|
23
|
+
{ value: "medium", label: "Medium", description: "Inspect context before acting." },
|
|
24
|
+
{ value: "high", label: "High", description: "Deeper planning and edge-case checks." }
|
|
25
|
+
];
|
|
26
|
+
function ReasoningLevelPicker({
|
|
27
|
+
value,
|
|
28
|
+
onChange,
|
|
29
|
+
disabled,
|
|
30
|
+
className,
|
|
31
|
+
triggerClassName,
|
|
32
|
+
options = DEFAULT_REASONING_LEVEL_OPTIONS
|
|
33
|
+
}) {
|
|
34
|
+
const selected = options.find((option) => option.value === value);
|
|
35
|
+
const label = selected?.label ?? "Auto";
|
|
36
|
+
return /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
|
|
37
|
+
/* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
38
|
+
"button",
|
|
39
|
+
{
|
|
40
|
+
type: "button",
|
|
41
|
+
disabled,
|
|
42
|
+
className: cn(
|
|
43
|
+
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-border bg-card px-2.5",
|
|
44
|
+
"text-xs font-medium text-foreground shadow-sm transition-colors",
|
|
45
|
+
"hover:border-primary/30 hover:bg-accent/30 focus:outline-none focus:border-primary/40",
|
|
46
|
+
"data-[state=open]:border-primary/40 data-[state=open]:bg-accent/30",
|
|
47
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
48
|
+
className,
|
|
49
|
+
triggerClassName
|
|
50
|
+
),
|
|
51
|
+
"aria-label": "Reasoning level",
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx(Brain, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
54
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
55
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
) }),
|
|
59
|
+
/* @__PURE__ */ jsx(DropdownMenu.Portal, { children: /* @__PURE__ */ jsx(
|
|
60
|
+
DropdownMenu.Content,
|
|
61
|
+
{
|
|
62
|
+
align: "start",
|
|
63
|
+
sideOffset: 6,
|
|
64
|
+
className: cn(
|
|
65
|
+
"z-50 w-64 overflow-hidden rounded-[var(--radius-md)] border border-border bg-card p-1",
|
|
66
|
+
"shadow-[var(--shadow-dropdown)]",
|
|
67
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
68
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
69
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
|
|
70
|
+
),
|
|
71
|
+
children: options.map((option) => /* @__PURE__ */ jsxs(
|
|
72
|
+
DropdownMenu.Item,
|
|
73
|
+
{
|
|
74
|
+
onSelect: (event) => {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
onChange(option.value);
|
|
77
|
+
},
|
|
78
|
+
className: cn(
|
|
79
|
+
"flex cursor-pointer flex-col gap-0.5 rounded-md px-2.5 py-2 outline-none",
|
|
80
|
+
"transition-colors hover:bg-accent/40 focus:bg-accent/40",
|
|
81
|
+
option.value === value && "bg-[var(--accent-surface-soft)] text-[var(--accent-text)]"
|
|
82
|
+
),
|
|
83
|
+
children: [
|
|
84
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: option.label }),
|
|
85
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: option.description })
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
option.value
|
|
89
|
+
))
|
|
90
|
+
}
|
|
91
|
+
) })
|
|
92
|
+
] });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/chat/artifact-agent-dock.tsx
|
|
96
|
+
import * as React from "react";
|
|
97
|
+
import { Sparkles, Send, Square, X } from "lucide-react";
|
|
98
|
+
import { ChatMessage } from "@tangle-network/ui/chat";
|
|
99
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
100
|
+
function defaultScopeLabel(scope) {
|
|
101
|
+
if (scope.label) return scope.label;
|
|
102
|
+
switch (scope.kind) {
|
|
103
|
+
case "vault-file":
|
|
104
|
+
return scope.key.split("/").pop() ?? scope.key;
|
|
105
|
+
case "sequence":
|
|
106
|
+
return `Sequence ${scope.key.slice(0, 8)}`;
|
|
107
|
+
case "content":
|
|
108
|
+
return `Content ${scope.key.slice(0, 8)}`;
|
|
109
|
+
case "image":
|
|
110
|
+
return `Image ${scope.key.slice(0, 8)}`;
|
|
111
|
+
case "video":
|
|
112
|
+
return `Video ${scope.key.slice(0, 8)}`;
|
|
113
|
+
case "project":
|
|
114
|
+
return "Project chat";
|
|
115
|
+
case "workspace":
|
|
116
|
+
return "Workspace chat";
|
|
117
|
+
default:
|
|
118
|
+
return scope.key;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function normalizeRole(role) {
|
|
122
|
+
return role === "assistant" || role === "system" ? role : "user";
|
|
123
|
+
}
|
|
124
|
+
function ArtifactAgentDock({
|
|
125
|
+
scope,
|
|
126
|
+
open,
|
|
127
|
+
onOpenChange,
|
|
128
|
+
transport,
|
|
129
|
+
defaultPrompt,
|
|
130
|
+
onError,
|
|
131
|
+
className
|
|
132
|
+
}) {
|
|
133
|
+
const [threadId, setThreadId] = React.useState(null);
|
|
134
|
+
const [messages, setMessages] = React.useState([]);
|
|
135
|
+
const [composer, setComposer] = React.useState(defaultPrompt ?? "");
|
|
136
|
+
const [streamContent, setStreamContent] = React.useState("");
|
|
137
|
+
const [sending, setSending] = React.useState(false);
|
|
138
|
+
const [loading, setLoading] = React.useState(false);
|
|
139
|
+
const abortRef = React.useRef(null);
|
|
140
|
+
const scrollRef = React.useRef(null);
|
|
141
|
+
const reportError = React.useCallback(
|
|
142
|
+
(message) => {
|
|
143
|
+
if (onError) onError(message);
|
|
144
|
+
else if (typeof console !== "undefined") console.error("[ArtifactAgentDock]", message);
|
|
145
|
+
},
|
|
146
|
+
[onError]
|
|
147
|
+
);
|
|
148
|
+
React.useEffect(() => {
|
|
149
|
+
if (!open) return;
|
|
150
|
+
let cancelled = false;
|
|
151
|
+
async function bootstrap() {
|
|
152
|
+
setLoading(true);
|
|
153
|
+
try {
|
|
154
|
+
const { id } = await transport.ensureThread(scope);
|
|
155
|
+
if (cancelled) return;
|
|
156
|
+
setThreadId(id);
|
|
157
|
+
if (transport.loadMessages) {
|
|
158
|
+
const prior = await transport.loadMessages(id);
|
|
159
|
+
if (cancelled) return;
|
|
160
|
+
setMessages(
|
|
161
|
+
prior.map((m) => ({
|
|
162
|
+
...m,
|
|
163
|
+
role: normalizeRole(m.role),
|
|
164
|
+
createdAt: m.createdAt instanceof Date ? m.createdAt : m.createdAt ? new Date(m.createdAt) : void 0
|
|
165
|
+
}))
|
|
166
|
+
);
|
|
167
|
+
} else {
|
|
168
|
+
setMessages([]);
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
reportError(err instanceof Error ? err.message : "Failed to open artifact chat");
|
|
172
|
+
} finally {
|
|
173
|
+
if (!cancelled) setLoading(false);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
void bootstrap();
|
|
177
|
+
return () => {
|
|
178
|
+
cancelled = true;
|
|
179
|
+
};
|
|
180
|
+
}, [open, scope.kind, scope.key, transport, reportError]);
|
|
181
|
+
React.useEffect(() => {
|
|
182
|
+
if (!scrollRef.current) return;
|
|
183
|
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
184
|
+
}, [messages.length, streamContent]);
|
|
185
|
+
const send = React.useCallback(async () => {
|
|
186
|
+
const text = composer.trim();
|
|
187
|
+
if (!text || !threadId || sending) return;
|
|
188
|
+
const userMsg = {
|
|
189
|
+
id: `local-${Date.now()}`,
|
|
190
|
+
role: "user",
|
|
191
|
+
content: text,
|
|
192
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
193
|
+
};
|
|
194
|
+
setMessages((prev) => [...prev, userMsg]);
|
|
195
|
+
setComposer("");
|
|
196
|
+
setSending(true);
|
|
197
|
+
setStreamContent("");
|
|
198
|
+
const controller = new AbortController();
|
|
199
|
+
abortRef.current = controller;
|
|
200
|
+
let assistantContent = "";
|
|
201
|
+
let streamFailed = false;
|
|
202
|
+
try {
|
|
203
|
+
for await (const event of transport.sendStream({ threadId, content: text, signal: controller.signal })) {
|
|
204
|
+
if (event.type === "delta") {
|
|
205
|
+
assistantContent += event.text;
|
|
206
|
+
setStreamContent(assistantContent);
|
|
207
|
+
} else if (event.type === "error") {
|
|
208
|
+
streamFailed = true;
|
|
209
|
+
reportError(event.message);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (assistantContent && !streamFailed) {
|
|
213
|
+
setMessages((prev) => [
|
|
214
|
+
...prev,
|
|
215
|
+
{
|
|
216
|
+
id: `assist-${Date.now()}`,
|
|
217
|
+
role: "assistant",
|
|
218
|
+
content: assistantContent,
|
|
219
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
220
|
+
}
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
setStreamContent("");
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (err?.name === "AbortError") return;
|
|
226
|
+
reportError(err instanceof Error ? err.message : "Send failed");
|
|
227
|
+
} finally {
|
|
228
|
+
setSending(false);
|
|
229
|
+
abortRef.current = null;
|
|
230
|
+
}
|
|
231
|
+
}, [composer, threadId, sending, transport, reportError]);
|
|
232
|
+
const cancel = React.useCallback(() => {
|
|
233
|
+
abortRef.current?.abort();
|
|
234
|
+
setSending(false);
|
|
235
|
+
setStreamContent("");
|
|
236
|
+
}, []);
|
|
237
|
+
if (!open) return null;
|
|
238
|
+
const heading = defaultScopeLabel(scope);
|
|
239
|
+
return /* @__PURE__ */ jsxs2(
|
|
240
|
+
"aside",
|
|
241
|
+
{
|
|
242
|
+
className: cn(
|
|
243
|
+
"flex h-full w-[420px] min-w-[360px] max-w-[480px] flex-col border-l border-border bg-card/95 shadow-xl",
|
|
244
|
+
className
|
|
245
|
+
),
|
|
246
|
+
"aria-label": `Agent chat about ${heading}`,
|
|
247
|
+
children: [
|
|
248
|
+
/* @__PURE__ */ jsxs2("header", { className: "flex items-center justify-between border-b border-border bg-background/60 px-4 py-3", children: [
|
|
249
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
250
|
+
/* @__PURE__ */ jsx2(Sparkles, { className: "h-4 w-4 shrink-0 text-primary" }),
|
|
251
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
|
|
252
|
+
/* @__PURE__ */ jsx2("div", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground", children: "Discussing" }),
|
|
253
|
+
/* @__PURE__ */ jsx2("div", { className: "truncate text-sm font-semibold text-foreground", children: heading })
|
|
254
|
+
] })
|
|
255
|
+
] }),
|
|
256
|
+
/* @__PURE__ */ jsx2(
|
|
257
|
+
"button",
|
|
258
|
+
{
|
|
259
|
+
type: "button",
|
|
260
|
+
onClick: () => onOpenChange(false),
|
|
261
|
+
"aria-label": "Close artifact chat",
|
|
262
|
+
className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
263
|
+
children: /* @__PURE__ */ jsx2(X, { className: "h-4 w-4" })
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
] }),
|
|
267
|
+
/* @__PURE__ */ jsx2("div", { ref: scrollRef, className: "flex-1 overflow-y-auto px-3 py-4", children: loading ? /* @__PURE__ */ jsx2("div", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: "Loading conversation\u2026" }) : messages.length === 0 && !streamContent ? /* @__PURE__ */ jsxs2("div", { className: "px-3 py-12 text-center", children: [
|
|
268
|
+
/* @__PURE__ */ jsx2(Sparkles, { className: "mx-auto mb-3 h-5 w-5 text-muted-foreground/40" }),
|
|
269
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-sm text-foreground", children: [
|
|
270
|
+
"Ask the agent about this ",
|
|
271
|
+
scope.kind === "vault-file" ? "document" : scope.kind,
|
|
272
|
+
"."
|
|
273
|
+
] }),
|
|
274
|
+
/* @__PURE__ */ jsx2("p", { className: "mt-1 text-xs text-muted-foreground", children: "It can read the current contents and propose edits, rewrites, or follow-ups." })
|
|
275
|
+
] }) : /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
|
|
276
|
+
messages.map((m) => /* @__PURE__ */ jsx2(ChatMessage, { role: m.role, content: m.content, timestamp: m.createdAt }, m.id)),
|
|
277
|
+
streamContent && /* @__PURE__ */ jsx2(ChatMessage, { role: "assistant", content: streamContent, isStreaming: true })
|
|
278
|
+
] }) }),
|
|
279
|
+
/* @__PURE__ */ jsx2(
|
|
280
|
+
"form",
|
|
281
|
+
{
|
|
282
|
+
className: "border-t border-border bg-background/60 p-3",
|
|
283
|
+
onSubmit: (e) => {
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
void send();
|
|
286
|
+
},
|
|
287
|
+
children: /* @__PURE__ */ jsxs2("div", { className: "flex items-end gap-2", children: [
|
|
288
|
+
/* @__PURE__ */ jsx2(
|
|
289
|
+
"textarea",
|
|
290
|
+
{
|
|
291
|
+
value: composer,
|
|
292
|
+
onChange: (e) => setComposer(e.target.value),
|
|
293
|
+
onKeyDown: (e) => {
|
|
294
|
+
if (e.key === "Enter" && !e.shiftKey && !sending) {
|
|
295
|
+
e.preventDefault();
|
|
296
|
+
void send();
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
placeholder: `Ask about ${heading}\u2026`,
|
|
300
|
+
rows: 2,
|
|
301
|
+
disabled: !threadId,
|
|
302
|
+
className: "min-h-[44px] flex-1 resize-none rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary/40 disabled:opacity-50"
|
|
303
|
+
}
|
|
304
|
+
),
|
|
305
|
+
sending ? /* @__PURE__ */ jsx2(
|
|
306
|
+
"button",
|
|
307
|
+
{
|
|
308
|
+
type: "button",
|
|
309
|
+
onClick: cancel,
|
|
310
|
+
"aria-label": "Cancel",
|
|
311
|
+
className: "inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
312
|
+
children: /* @__PURE__ */ jsx2(Square, { className: "h-4 w-4" })
|
|
313
|
+
}
|
|
314
|
+
) : /* @__PURE__ */ jsx2(
|
|
315
|
+
"button",
|
|
316
|
+
{
|
|
317
|
+
type: "submit",
|
|
318
|
+
disabled: !composer.trim() || !threadId,
|
|
319
|
+
"aria-label": "Send",
|
|
320
|
+
className: "inline-flex h-9 w-9 items-center justify-center rounded-md bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50",
|
|
321
|
+
children: /* @__PURE__ */ jsx2(Send, { className: "h-4 w-4" })
|
|
322
|
+
}
|
|
323
|
+
)
|
|
324
|
+
] })
|
|
325
|
+
}
|
|
326
|
+
)
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
function createFetchTransport(opts) {
|
|
332
|
+
const threadsUrl = opts.threadsUrl ?? "/api/threads";
|
|
333
|
+
const chatUrl = opts.chatUrl ?? "/api/chat";
|
|
334
|
+
const f = opts.fetchImpl ?? ((...args) => fetch(...args));
|
|
335
|
+
const workspaceId = opts.workspaceId;
|
|
336
|
+
return {
|
|
337
|
+
async ensureThread(scope) {
|
|
338
|
+
const url = threadsUrl.includes("?") ? `${threadsUrl}&workspaceId=${encodeURIComponent(workspaceId)}` : `${threadsUrl}?workspaceId=${encodeURIComponent(workspaceId)}`;
|
|
339
|
+
const res = await f(url, {
|
|
340
|
+
method: "POST",
|
|
341
|
+
headers: { "Content-Type": "application/json" },
|
|
342
|
+
body: JSON.stringify({ workspaceId, scope, ensureScoped: true })
|
|
343
|
+
});
|
|
344
|
+
if (!res.ok) throw new Error(`Thread ensure failed (${res.status})`);
|
|
345
|
+
const data = await res.json();
|
|
346
|
+
const id = data.thread?.id ?? data.id;
|
|
347
|
+
if (!id) throw new Error("Malformed ensure response");
|
|
348
|
+
return { id };
|
|
349
|
+
},
|
|
350
|
+
async loadMessages(threadId) {
|
|
351
|
+
const res = await f(`${threadsUrl}/${encodeURIComponent(threadId)}/messages`);
|
|
352
|
+
if (!res.ok) return [];
|
|
353
|
+
const data = await res.json();
|
|
354
|
+
return (data.messages ?? []).map((m) => ({
|
|
355
|
+
id: m.id,
|
|
356
|
+
role: normalizeRole(m.role),
|
|
357
|
+
content: m.content,
|
|
358
|
+
createdAt: new Date(m.createdAt)
|
|
359
|
+
}));
|
|
360
|
+
},
|
|
361
|
+
sendStream({ threadId, content, signal }) {
|
|
362
|
+
return (async function* () {
|
|
363
|
+
const res = await f(chatUrl, {
|
|
364
|
+
method: "POST",
|
|
365
|
+
headers: { "Content-Type": "application/json" },
|
|
366
|
+
body: JSON.stringify({ workspaceId, threadId, content }),
|
|
367
|
+
signal
|
|
368
|
+
});
|
|
369
|
+
if (!res.ok || !res.body) {
|
|
370
|
+
const text = await res.text().catch(() => "");
|
|
371
|
+
yield { type: "error", message: text || `Send failed (${res.status})` };
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const reader = res.body.getReader();
|
|
375
|
+
const decoder = new TextDecoder();
|
|
376
|
+
let buffer = "";
|
|
377
|
+
while (true) {
|
|
378
|
+
const { done, value } = await reader.read();
|
|
379
|
+
if (done) break;
|
|
380
|
+
buffer += decoder.decode(value, { stream: true });
|
|
381
|
+
const lines = buffer.split("\n");
|
|
382
|
+
buffer = lines.pop() ?? "";
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
if (!line.trim()) continue;
|
|
385
|
+
try {
|
|
386
|
+
const event = JSON.parse(line);
|
|
387
|
+
if (event.type === "message.part.delta" && event.data) {
|
|
388
|
+
const text = event.data.text;
|
|
389
|
+
if (typeof text === "string" && text.length > 0) {
|
|
390
|
+
yield { type: "delta", text };
|
|
391
|
+
}
|
|
392
|
+
} else if (event.type === "error" && event.data) {
|
|
393
|
+
const message = typeof event.data.message === "string" ? event.data.message : "Stream error";
|
|
394
|
+
yield { type: "error", message };
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
})();
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export {
|
|
406
|
+
DEFAULT_REASONING_LEVEL_OPTIONS,
|
|
407
|
+
ReasoningLevelPicker,
|
|
408
|
+
ArtifactAgentDock,
|
|
409
|
+
createFetchTransport,
|
|
410
|
+
AgentTimeline,
|
|
411
|
+
ChatContainer,
|
|
412
|
+
ChatInput,
|
|
413
|
+
ChatMessage2 as ChatMessage,
|
|
414
|
+
MessageList,
|
|
415
|
+
ThinkingIndicator,
|
|
416
|
+
UserMessage
|
|
417
|
+
};
|