@tangle-network/sandbox-ui 0.58.0 → 0.60.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 +169 -2
- package/dist/chat.js +5 -1
- package/dist/chunk-V5JZOO3W.js +1383 -0
- package/dist/globals.css +96 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/dist/styles.css +96 -0
- package/package.json +1 -1
- package/dist/chunk-WLMLD2CZ.js +0 -783
package/dist/chunk-WLMLD2CZ.js
DELETED
|
@@ -1,783 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
HARNESS_OPTIONS,
|
|
3
|
-
HarnessLogo,
|
|
4
|
-
chatCapableHarnesses
|
|
5
|
-
} from "./chunk-HU4B2M4K.js";
|
|
6
|
-
import {
|
|
7
|
-
ModelPicker,
|
|
8
|
-
canonicalModelId
|
|
9
|
-
} from "./chunk-SF6LPMMN.js";
|
|
10
|
-
import {
|
|
11
|
-
cn
|
|
12
|
-
} from "./chunk-EI44GEQ5.js";
|
|
13
|
-
|
|
14
|
-
// src/chat/index.ts
|
|
15
|
-
import {
|
|
16
|
-
AgentTimeline,
|
|
17
|
-
ChatContainer,
|
|
18
|
-
ChatInput,
|
|
19
|
-
ChatMessage as ChatMessage2,
|
|
20
|
-
MessageList,
|
|
21
|
-
ThinkingIndicator,
|
|
22
|
-
UserMessage
|
|
23
|
-
} from "@tangle-network/ui/chat";
|
|
24
|
-
|
|
25
|
-
// src/chat/reasoning-level-picker.tsx
|
|
26
|
-
import { reasoningLadder } from "@tangle-network/agent-interface";
|
|
27
|
-
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
28
|
-
import { Brain, ChevronDown, Sparkles } from "lucide-react";
|
|
29
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
30
|
-
var REASONING_LADDER = reasoningLadder;
|
|
31
|
-
function ReasoningGlyph({ level }) {
|
|
32
|
-
if (level === "auto") {
|
|
33
|
-
return /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 text-muted-foreground" });
|
|
34
|
-
}
|
|
35
|
-
const BARS = 4;
|
|
36
|
-
const rank = REASONING_LADDER.indexOf(level) + 1;
|
|
37
|
-
const filled = Math.max(1, Math.ceil(rank / REASONING_LADDER.length * BARS));
|
|
38
|
-
return /* @__PURE__ */ jsx(
|
|
39
|
-
"span",
|
|
40
|
-
{
|
|
41
|
-
"aria-hidden": true,
|
|
42
|
-
className: "inline-flex h-3.5 items-end gap-px",
|
|
43
|
-
style: { width: 16 },
|
|
44
|
-
children: Array.from({ length: BARS }).map((_, i) => /* @__PURE__ */ jsx(
|
|
45
|
-
"span",
|
|
46
|
-
{
|
|
47
|
-
className: cn(
|
|
48
|
-
"w-1 rounded-[1px]",
|
|
49
|
-
i < filled ? "bg-foreground" : "bg-border"
|
|
50
|
-
),
|
|
51
|
-
style: { height: `${35 + i * 22}%` }
|
|
52
|
-
},
|
|
53
|
-
i
|
|
54
|
-
))
|
|
55
|
-
}
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
var DEFAULT_REASONING_LEVEL_OPTIONS = [
|
|
59
|
-
{ value: "auto", label: "Auto", description: "Let the agent pick the right depth." },
|
|
60
|
-
{ value: "none", label: "None", description: "No extended thinking \u2014 fastest, cheapest." },
|
|
61
|
-
{ value: "minimal", label: "Minimal", description: "Thinking on, lowest budget." },
|
|
62
|
-
{ value: "low", label: "Low", description: "Fast, direct answers." },
|
|
63
|
-
{ value: "medium", label: "Medium", description: "Inspect context before acting." },
|
|
64
|
-
{ value: "high", label: "High", description: "Deeper planning and edge-case checks." },
|
|
65
|
-
{ value: "xhigh", label: "Extra High", description: "Extended reasoning for hard problems (Codex/OpenAI)." },
|
|
66
|
-
{ value: "ultracode", label: "Ultracode", description: "Maximum extended thinking (Claude Code's ultracode)." }
|
|
67
|
-
];
|
|
68
|
-
function ReasoningLevelPicker({
|
|
69
|
-
value,
|
|
70
|
-
onChange,
|
|
71
|
-
disabled,
|
|
72
|
-
className,
|
|
73
|
-
triggerClassName,
|
|
74
|
-
options = DEFAULT_REASONING_LEVEL_OPTIONS,
|
|
75
|
-
available,
|
|
76
|
-
side,
|
|
77
|
-
avoidCollisions
|
|
78
|
-
}) {
|
|
79
|
-
const shown = available ? options.filter((o) => o.value === "auto" || available.includes(o.value)) : options;
|
|
80
|
-
const selected = shown.find((option) => option.value === value);
|
|
81
|
-
const label = selected?.label ?? "Auto";
|
|
82
|
-
return /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
|
|
83
|
-
/* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
84
|
-
"button",
|
|
85
|
-
{
|
|
86
|
-
type: "button",
|
|
87
|
-
disabled,
|
|
88
|
-
className: cn(
|
|
89
|
-
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5",
|
|
90
|
-
"text-xs font-medium text-foreground shadow-sm transition-colors",
|
|
91
|
-
"hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
92
|
-
"data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
|
|
93
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
94
|
-
className,
|
|
95
|
-
triggerClassName
|
|
96
|
-
),
|
|
97
|
-
"aria-label": "Reasoning level",
|
|
98
|
-
children: [
|
|
99
|
-
/* @__PURE__ */ jsx(Brain, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
100
|
-
/* @__PURE__ */ jsx("span", { children: label }),
|
|
101
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
102
|
-
]
|
|
103
|
-
}
|
|
104
|
-
) }),
|
|
105
|
-
/* @__PURE__ */ jsx(DropdownMenu.Portal, { children: /* @__PURE__ */ jsx(
|
|
106
|
-
DropdownMenu.Content,
|
|
107
|
-
{
|
|
108
|
-
side,
|
|
109
|
-
align: "start",
|
|
110
|
-
avoidCollisions,
|
|
111
|
-
collisionPadding: 24,
|
|
112
|
-
sideOffset: 6,
|
|
113
|
-
className: cn(
|
|
114
|
-
// Cap to the viewport space on the open side and scroll, so the menu
|
|
115
|
-
// pinned downward (floating composer) never runs off the bottom edge.
|
|
116
|
-
"z-50 w-64 max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest p-1",
|
|
117
|
-
"shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]",
|
|
118
|
-
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
119
|
-
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
120
|
-
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
|
|
121
|
-
),
|
|
122
|
-
children: shown.map((option) => /* @__PURE__ */ jsxs(
|
|
123
|
-
DropdownMenu.Item,
|
|
124
|
-
{
|
|
125
|
-
onSelect: () => onChange(option.value),
|
|
126
|
-
className: cn(
|
|
127
|
-
"flex cursor-pointer items-start gap-2.5 rounded-md px-2.5 py-2 outline-none",
|
|
128
|
-
"transition-colors hover:bg-accent/40 focus:bg-accent/40",
|
|
129
|
-
option.value === value && "bg-[var(--accent-surface-soft)] text-[var(--accent-text)]"
|
|
130
|
-
),
|
|
131
|
-
children: [
|
|
132
|
-
/* @__PURE__ */ jsx("span", { className: "mt-0.5 flex w-3.5 shrink-0 justify-center", children: /* @__PURE__ */ jsx(ReasoningGlyph, { level: option.value }) }),
|
|
133
|
-
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
|
|
134
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: option.label }),
|
|
135
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: option.description })
|
|
136
|
-
] })
|
|
137
|
-
]
|
|
138
|
-
},
|
|
139
|
-
option.value
|
|
140
|
-
))
|
|
141
|
-
}
|
|
142
|
-
) })
|
|
143
|
-
] });
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// src/chat/agent-session-controls.tsx
|
|
147
|
-
import { reasoningEffortsFor } from "@tangle-network/agent-interface";
|
|
148
|
-
import * as DropdownMenu2 from "@radix-ui/react-dropdown-menu";
|
|
149
|
-
import { ChevronDown as ChevronDown2, Lock, SlidersHorizontal } from "lucide-react";
|
|
150
|
-
|
|
151
|
-
// src/chat/harness-model-compat.ts
|
|
152
|
-
import {
|
|
153
|
-
harnessSupportsModel,
|
|
154
|
-
snapModelToHarness as snapModelIdToHarness
|
|
155
|
-
} from "@tangle-network/agent-interface";
|
|
156
|
-
import { modelProvider, snapHarnessToModel } from "@tangle-network/agent-interface";
|
|
157
|
-
function isModelCompatibleWithHarness(harness, modelId) {
|
|
158
|
-
return harnessSupportsModel(harness, modelId);
|
|
159
|
-
}
|
|
160
|
-
function snapModelToHarness(harness, modelId, models) {
|
|
161
|
-
return snapModelIdToHarness(harness, modelId, models.map(canonicalModelId));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// src/chat/agent-session-controls.tsx
|
|
165
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
166
|
-
function HarnessDropdown({
|
|
167
|
-
value,
|
|
168
|
-
onChange,
|
|
169
|
-
available,
|
|
170
|
-
disabled,
|
|
171
|
-
locked,
|
|
172
|
-
lockReason,
|
|
173
|
-
side = "bottom",
|
|
174
|
-
align = "start",
|
|
175
|
-
avoidCollisions
|
|
176
|
-
}) {
|
|
177
|
-
const allowed = new Set(
|
|
178
|
-
available ?? HARNESS_OPTIONS.map((h) => h.type)
|
|
179
|
-
);
|
|
180
|
-
const options = HARNESS_OPTIONS.filter((h) => allowed.has(h.type));
|
|
181
|
-
const selected = options.find((option) => option.type === value);
|
|
182
|
-
return /* @__PURE__ */ jsxs2(DropdownMenu2.Root, { children: [
|
|
183
|
-
/* @__PURE__ */ jsx2(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs2(
|
|
184
|
-
"button",
|
|
185
|
-
{
|
|
186
|
-
type: "button",
|
|
187
|
-
disabled: disabled || locked,
|
|
188
|
-
title: locked ? lockReason : void 0,
|
|
189
|
-
className: cn(
|
|
190
|
-
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container px-2.5",
|
|
191
|
-
"text-xs font-medium text-foreground shadow-sm transition-colors",
|
|
192
|
-
"hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
193
|
-
"data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high",
|
|
194
|
-
"disabled:cursor-not-allowed disabled:opacity-60"
|
|
195
|
-
),
|
|
196
|
-
"aria-label": "Agent harness",
|
|
197
|
-
children: [
|
|
198
|
-
locked ? /* @__PURE__ */ jsx2(Lock, { className: "h-3 w-3 text-muted-foreground" }) : /* @__PURE__ */ jsx2(HarnessLogo, { type: value, size: 16 }),
|
|
199
|
-
/* @__PURE__ */ jsx2("span", { children: selected?.label ?? value }),
|
|
200
|
-
!locked && /* @__PURE__ */ jsx2(ChevronDown2, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
201
|
-
]
|
|
202
|
-
}
|
|
203
|
-
) }),
|
|
204
|
-
/* @__PURE__ */ jsx2(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsx2(
|
|
205
|
-
DropdownMenu2.Content,
|
|
206
|
-
{
|
|
207
|
-
side,
|
|
208
|
-
align,
|
|
209
|
-
avoidCollisions,
|
|
210
|
-
collisionPadding: 24,
|
|
211
|
-
sideOffset: 6,
|
|
212
|
-
className: cn(
|
|
213
|
-
// Cap to the viewport space on the open side and scroll, so a tall
|
|
214
|
-
// backend list pinned downward (floating composer) never runs off
|
|
215
|
-
// the bottom edge.
|
|
216
|
-
"z-50 w-72 max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest p-1",
|
|
217
|
-
"shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]",
|
|
218
|
-
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
219
|
-
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
220
|
-
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
|
|
221
|
-
),
|
|
222
|
-
children: options.map((option) => /* @__PURE__ */ jsxs2(
|
|
223
|
-
DropdownMenu2.Item,
|
|
224
|
-
{
|
|
225
|
-
onSelect: (event) => {
|
|
226
|
-
event.preventDefault();
|
|
227
|
-
onChange(option.type);
|
|
228
|
-
},
|
|
229
|
-
className: cn(
|
|
230
|
-
"flex cursor-pointer items-start gap-2.5 rounded-md px-2.5 py-2 outline-none",
|
|
231
|
-
"transition-colors hover:bg-accent/50 focus:bg-accent/50",
|
|
232
|
-
option.type === value && "bg-primary/10 font-medium text-foreground ring-1 ring-inset ring-primary/25 hover:bg-primary/15 focus:bg-primary/15"
|
|
233
|
-
),
|
|
234
|
-
children: [
|
|
235
|
-
/* @__PURE__ */ jsx2(HarnessLogo, { type: option.type, size: 20, className: "mt-0.5" }),
|
|
236
|
-
/* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
|
|
237
|
-
/* @__PURE__ */ jsx2("span", { className: "text-sm font-medium", children: option.label }),
|
|
238
|
-
option.description && /* @__PURE__ */ jsx2("span", { className: "text-xs text-muted-foreground", children: option.description })
|
|
239
|
-
] })
|
|
240
|
-
]
|
|
241
|
-
},
|
|
242
|
-
option.type
|
|
243
|
-
))
|
|
244
|
-
}
|
|
245
|
-
) })
|
|
246
|
-
] });
|
|
247
|
-
}
|
|
248
|
-
function AgentSessionControls({
|
|
249
|
-
harness,
|
|
250
|
-
model,
|
|
251
|
-
reasoning,
|
|
252
|
-
trailing,
|
|
253
|
-
className,
|
|
254
|
-
context = "chat",
|
|
255
|
-
layout = "inline",
|
|
256
|
-
menuPlacement = "auto"
|
|
257
|
-
}) {
|
|
258
|
-
if (!harness && !model && !reasoning && !trailing) return null;
|
|
259
|
-
const handleHarnessChange = (next) => {
|
|
260
|
-
harness?.onChange(next);
|
|
261
|
-
if (model) {
|
|
262
|
-
const snapped = snapModelToHarness(next, model.value, model.models);
|
|
263
|
-
if (snapped !== model.value) model.onChange(snapped);
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
const handleModelChange = (nextModelId) => {
|
|
267
|
-
model?.onChange(nextModelId);
|
|
268
|
-
if (harness && !harness.locked) {
|
|
269
|
-
const snapped = snapHarnessToModel(harness.value, nextModelId);
|
|
270
|
-
if (snapped !== harness.value) harness.onChange(snapped);
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
const visibleModels = model && harness?.locked ? model.models.filter(
|
|
274
|
-
(entry) => isModelCompatibleWithHarness(
|
|
275
|
-
harness.value,
|
|
276
|
-
canonicalModelId(entry)
|
|
277
|
-
)
|
|
278
|
-
) : model?.models;
|
|
279
|
-
const harnessAvailable = harness?.available ?? (context === "chat" ? chatCapableHarnesses : void 0);
|
|
280
|
-
const menuSide = layout === "gear" ? "left" : "bottom";
|
|
281
|
-
const menuAlign = layout === "gear" ? "end" : "start";
|
|
282
|
-
const inlineForceDown = menuPlacement === "down" && layout !== "gear";
|
|
283
|
-
const avoidCollisions = inlineForceDown ? false : void 0;
|
|
284
|
-
const harnessNode = harness && /* @__PURE__ */ jsx2(
|
|
285
|
-
HarnessDropdown,
|
|
286
|
-
{
|
|
287
|
-
...harness,
|
|
288
|
-
available: harnessAvailable,
|
|
289
|
-
onChange: handleHarnessChange,
|
|
290
|
-
side: menuSide,
|
|
291
|
-
align: menuAlign,
|
|
292
|
-
avoidCollisions
|
|
293
|
-
}
|
|
294
|
-
);
|
|
295
|
-
const modelNode = model && /* @__PURE__ */ jsx2(
|
|
296
|
-
ModelPicker,
|
|
297
|
-
{
|
|
298
|
-
variant: "pill",
|
|
299
|
-
label: "",
|
|
300
|
-
value: model.value,
|
|
301
|
-
onChange: handleModelChange,
|
|
302
|
-
models: visibleModels ?? [],
|
|
303
|
-
loading: model.loading,
|
|
304
|
-
popular: model.popular,
|
|
305
|
-
recents: model.recents,
|
|
306
|
-
disabled: model.disabled || (visibleModels ?? []).length === 0,
|
|
307
|
-
side: layout === "gear" ? void 0 : "bottom",
|
|
308
|
-
avoidCollisions
|
|
309
|
-
}
|
|
310
|
-
);
|
|
311
|
-
const selectedModel = model?.models.find(
|
|
312
|
-
(entry) => canonicalModelId(entry) === model.value
|
|
313
|
-
);
|
|
314
|
-
const modelReasoning = selectedModel ? {
|
|
315
|
-
supportsReasoning: selectedModel.supportsReasoning,
|
|
316
|
-
maxEffort: selectedModel.maxReasoningEffort
|
|
317
|
-
} : null;
|
|
318
|
-
const reasoningNode = reasoning && /* @__PURE__ */ jsx2(
|
|
319
|
-
ReasoningLevelPicker,
|
|
320
|
-
{
|
|
321
|
-
value: reasoning.value,
|
|
322
|
-
onChange: reasoning.onChange,
|
|
323
|
-
options: reasoning.options,
|
|
324
|
-
disabled: reasoning.disabled,
|
|
325
|
-
available: harness ? reasoningEffortsFor(harness.value, modelReasoning) : reasoning.available,
|
|
326
|
-
side: layout === "gear" ? void 0 : "bottom",
|
|
327
|
-
avoidCollisions
|
|
328
|
-
}
|
|
329
|
-
);
|
|
330
|
-
if (layout === "gear") {
|
|
331
|
-
return /* @__PURE__ */ jsx2(
|
|
332
|
-
GearControls,
|
|
333
|
-
{
|
|
334
|
-
className,
|
|
335
|
-
harnessNode,
|
|
336
|
-
modelNode,
|
|
337
|
-
reasoningNode,
|
|
338
|
-
trailing
|
|
339
|
-
}
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
return /* @__PURE__ */ jsxs2(
|
|
343
|
-
"div",
|
|
344
|
-
{
|
|
345
|
-
className: cn("flex flex-wrap items-center gap-2", className),
|
|
346
|
-
"data-testid": "agent-session-controls",
|
|
347
|
-
children: [
|
|
348
|
-
harnessNode,
|
|
349
|
-
modelNode,
|
|
350
|
-
reasoningNode,
|
|
351
|
-
trailing && /* @__PURE__ */ jsx2("div", { className: "ml-auto flex items-center gap-2", children: trailing })
|
|
352
|
-
]
|
|
353
|
-
}
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
function GearControls({
|
|
357
|
-
className,
|
|
358
|
-
harnessNode,
|
|
359
|
-
modelNode,
|
|
360
|
-
reasoningNode,
|
|
361
|
-
trailing
|
|
362
|
-
}) {
|
|
363
|
-
return /* @__PURE__ */ jsxs2(
|
|
364
|
-
"div",
|
|
365
|
-
{
|
|
366
|
-
className: cn("flex items-center gap-2", className),
|
|
367
|
-
"data-testid": "agent-session-controls",
|
|
368
|
-
children: [
|
|
369
|
-
trailing && /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2", children: trailing }),
|
|
370
|
-
/* @__PURE__ */ jsxs2(DropdownMenu2.Root, { children: [
|
|
371
|
-
/* @__PURE__ */ jsx2(DropdownMenu2.Trigger, { asChild: true, children: /* @__PURE__ */ jsx2(
|
|
372
|
-
"button",
|
|
373
|
-
{
|
|
374
|
-
type: "button",
|
|
375
|
-
"aria-label": "Session controls",
|
|
376
|
-
className: cn(
|
|
377
|
-
"inline-flex h-8 w-8 items-center justify-center rounded-lg border border-[var(--md3-outline-variant)] bg-surface-container",
|
|
378
|
-
"text-foreground shadow-sm transition-colors",
|
|
379
|
-
"hover:border-[var(--md3-outline-variant)] hover:bg-surface-container-high focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
380
|
-
"data-[state=open]:border-[var(--md3-outline-variant)] data-[state=open]:bg-surface-container-high"
|
|
381
|
-
),
|
|
382
|
-
children: /* @__PURE__ */ jsx2(SlidersHorizontal, { className: "h-4 w-4 text-muted-foreground" })
|
|
383
|
-
}
|
|
384
|
-
) }),
|
|
385
|
-
/* @__PURE__ */ jsx2(DropdownMenu2.Portal, { children: /* @__PURE__ */ jsxs2(
|
|
386
|
-
DropdownMenu2.Content,
|
|
387
|
-
{
|
|
388
|
-
side: "top",
|
|
389
|
-
align: "end",
|
|
390
|
-
sideOffset: 8,
|
|
391
|
-
onInteractOutside: (event) => {
|
|
392
|
-
const target = event.target;
|
|
393
|
-
if (target?.closest(
|
|
394
|
-
"[data-radix-popper-content-wrapper],[data-radix-menu-content],[role=menu],[role=listbox]"
|
|
395
|
-
)) {
|
|
396
|
-
event.preventDefault();
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
className: cn(
|
|
400
|
-
"z-50 flex w-56 flex-col gap-2 rounded-[var(--radius-md)] border border-[var(--md3-outline-variant)] bg-surface-container-highest p-2",
|
|
401
|
-
"shadow-[0_8px_30px_rgba(0,0,0,0.45)] ring-1 ring-[#ffffff14]",
|
|
402
|
-
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
403
|
-
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
404
|
-
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
|
|
405
|
-
),
|
|
406
|
-
children: [
|
|
407
|
-
harnessNode && /* @__PURE__ */ jsx2(GearSection, { label: "Harness", children: harnessNode }),
|
|
408
|
-
modelNode && /* @__PURE__ */ jsx2(GearSection, { label: "Model", children: modelNode }),
|
|
409
|
-
reasoningNode && /* @__PURE__ */ jsx2(GearSection, { label: "Effort", children: reasoningNode })
|
|
410
|
-
]
|
|
411
|
-
}
|
|
412
|
-
) })
|
|
413
|
-
] })
|
|
414
|
-
]
|
|
415
|
-
}
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
function GearSection({
|
|
419
|
-
label,
|
|
420
|
-
children
|
|
421
|
-
}) {
|
|
422
|
-
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1", children: [
|
|
423
|
-
/* @__PURE__ */ jsx2("span", { className: "px-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground/70", children: label }),
|
|
424
|
-
children
|
|
425
|
-
] });
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// src/chat/artifact-agent-dock.tsx
|
|
429
|
-
import * as React from "react";
|
|
430
|
-
import { Sparkles as Sparkles2, Send, Square, X } from "lucide-react";
|
|
431
|
-
import { ChatMessage } from "@tangle-network/ui/chat";
|
|
432
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
433
|
-
function defaultScopeLabel(scope) {
|
|
434
|
-
if (scope.label) return scope.label;
|
|
435
|
-
switch (scope.kind) {
|
|
436
|
-
case "vault-file":
|
|
437
|
-
return scope.key.split("/").pop() ?? scope.key;
|
|
438
|
-
case "sequence":
|
|
439
|
-
return `Sequence ${scope.key.slice(0, 8)}`;
|
|
440
|
-
case "content":
|
|
441
|
-
return `Content ${scope.key.slice(0, 8)}`;
|
|
442
|
-
case "image":
|
|
443
|
-
return `Image ${scope.key.slice(0, 8)}`;
|
|
444
|
-
case "video":
|
|
445
|
-
return `Video ${scope.key.slice(0, 8)}`;
|
|
446
|
-
case "project":
|
|
447
|
-
return "Project chat";
|
|
448
|
-
case "workspace":
|
|
449
|
-
return "Workspace chat";
|
|
450
|
-
default:
|
|
451
|
-
return scope.key;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
function normalizeRole(role) {
|
|
455
|
-
return role === "assistant" || role === "system" ? role : "user";
|
|
456
|
-
}
|
|
457
|
-
function createDockMessageId() {
|
|
458
|
-
const c = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
|
|
459
|
-
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
460
|
-
if (c && typeof c.getRandomValues === "function") {
|
|
461
|
-
const b = c.getRandomValues(new Uint8Array(16));
|
|
462
|
-
b[6] = b[6] & 15 | 64;
|
|
463
|
-
b[8] = b[8] & 63 | 128;
|
|
464
|
-
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
465
|
-
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
466
|
-
}
|
|
467
|
-
throw new Error("crypto.getRandomValues is required for message ids");
|
|
468
|
-
}
|
|
469
|
-
function ArtifactAgentDock({
|
|
470
|
-
scope,
|
|
471
|
-
open,
|
|
472
|
-
onOpenChange,
|
|
473
|
-
transport,
|
|
474
|
-
defaultPrompt,
|
|
475
|
-
onError,
|
|
476
|
-
className
|
|
477
|
-
}) {
|
|
478
|
-
const [threadId, setThreadId] = React.useState(null);
|
|
479
|
-
const [messages, setMessages] = React.useState([]);
|
|
480
|
-
const [composer, setComposer] = React.useState(defaultPrompt ?? "");
|
|
481
|
-
const [streamContent, setStreamContent] = React.useState("");
|
|
482
|
-
const [sending, setSending] = React.useState(false);
|
|
483
|
-
const [loading, setLoading] = React.useState(false);
|
|
484
|
-
const abortRef = React.useRef(null);
|
|
485
|
-
const scrollRef = React.useRef(null);
|
|
486
|
-
const scopeTokenRef = React.useRef(0);
|
|
487
|
-
const reportError = React.useCallback(
|
|
488
|
-
(message) => {
|
|
489
|
-
if (onError) onError(message);
|
|
490
|
-
else if (typeof console !== "undefined") console.error("[ArtifactAgentDock]", message);
|
|
491
|
-
},
|
|
492
|
-
[onError]
|
|
493
|
-
);
|
|
494
|
-
React.useEffect(() => {
|
|
495
|
-
if (!open) return;
|
|
496
|
-
scopeTokenRef.current += 1;
|
|
497
|
-
abortRef.current?.abort();
|
|
498
|
-
abortRef.current = null;
|
|
499
|
-
let cancelled = false;
|
|
500
|
-
async function bootstrap() {
|
|
501
|
-
setLoading(true);
|
|
502
|
-
try {
|
|
503
|
-
const { id } = await transport.ensureThread(scope);
|
|
504
|
-
if (cancelled) return;
|
|
505
|
-
setThreadId(id);
|
|
506
|
-
if (transport.loadMessages) {
|
|
507
|
-
const prior = await transport.loadMessages(id);
|
|
508
|
-
if (cancelled) return;
|
|
509
|
-
setMessages(
|
|
510
|
-
prior.map((m) => ({
|
|
511
|
-
...m,
|
|
512
|
-
role: normalizeRole(m.role),
|
|
513
|
-
createdAt: m.createdAt instanceof Date ? m.createdAt : m.createdAt ? new Date(m.createdAt) : void 0
|
|
514
|
-
}))
|
|
515
|
-
);
|
|
516
|
-
} else {
|
|
517
|
-
setMessages([]);
|
|
518
|
-
}
|
|
519
|
-
} catch (err) {
|
|
520
|
-
reportError(err instanceof Error ? err.message : "Failed to open artifact chat");
|
|
521
|
-
} finally {
|
|
522
|
-
if (!cancelled) setLoading(false);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
void bootstrap();
|
|
526
|
-
return () => {
|
|
527
|
-
cancelled = true;
|
|
528
|
-
};
|
|
529
|
-
}, [open, scope.kind, scope.key, transport, reportError]);
|
|
530
|
-
React.useEffect(() => {
|
|
531
|
-
if (!scrollRef.current) return;
|
|
532
|
-
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
533
|
-
}, [messages.length, streamContent]);
|
|
534
|
-
const send = React.useCallback(async () => {
|
|
535
|
-
const text = composer.trim();
|
|
536
|
-
if (!text || !threadId || sending) return;
|
|
537
|
-
const token = scopeTokenRef.current;
|
|
538
|
-
const userMsg = {
|
|
539
|
-
id: createDockMessageId(),
|
|
540
|
-
role: "user",
|
|
541
|
-
content: text,
|
|
542
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
543
|
-
};
|
|
544
|
-
setMessages((prev) => [...prev, userMsg]);
|
|
545
|
-
setComposer("");
|
|
546
|
-
setSending(true);
|
|
547
|
-
setStreamContent("");
|
|
548
|
-
const controller = new AbortController();
|
|
549
|
-
abortRef.current = controller;
|
|
550
|
-
let assistantContent = "";
|
|
551
|
-
let streamFailed = false;
|
|
552
|
-
try {
|
|
553
|
-
for await (const event of transport.sendStream({ threadId, content: text, signal: controller.signal })) {
|
|
554
|
-
if (scopeTokenRef.current !== token) return;
|
|
555
|
-
if (event.type === "delta") {
|
|
556
|
-
assistantContent += event.text;
|
|
557
|
-
setStreamContent(assistantContent);
|
|
558
|
-
} else if (event.type === "error") {
|
|
559
|
-
streamFailed = true;
|
|
560
|
-
reportError(event.message);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
if (scopeTokenRef.current !== token) return;
|
|
564
|
-
if (assistantContent && !streamFailed) {
|
|
565
|
-
setMessages((prev) => [
|
|
566
|
-
...prev,
|
|
567
|
-
{
|
|
568
|
-
id: createDockMessageId(),
|
|
569
|
-
role: "assistant",
|
|
570
|
-
content: assistantContent,
|
|
571
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
572
|
-
}
|
|
573
|
-
]);
|
|
574
|
-
}
|
|
575
|
-
setStreamContent("");
|
|
576
|
-
} catch (err) {
|
|
577
|
-
if (err?.name === "AbortError") return;
|
|
578
|
-
if (scopeTokenRef.current === token) reportError(err instanceof Error ? err.message : "Send failed");
|
|
579
|
-
} finally {
|
|
580
|
-
if (scopeTokenRef.current === token) setSending(false);
|
|
581
|
-
if (abortRef.current === controller) abortRef.current = null;
|
|
582
|
-
}
|
|
583
|
-
}, [composer, threadId, sending, transport, reportError]);
|
|
584
|
-
const cancel = React.useCallback(() => {
|
|
585
|
-
if (threadId && transport.cancel) {
|
|
586
|
-
try {
|
|
587
|
-
void Promise.resolve(transport.cancel({ threadId })).catch(() => {
|
|
588
|
-
});
|
|
589
|
-
} catch {
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
abortRef.current?.abort();
|
|
593
|
-
setSending(false);
|
|
594
|
-
setStreamContent("");
|
|
595
|
-
}, [threadId, transport]);
|
|
596
|
-
if (!open) return null;
|
|
597
|
-
const heading = defaultScopeLabel(scope);
|
|
598
|
-
return /* @__PURE__ */ jsxs3(
|
|
599
|
-
"aside",
|
|
600
|
-
{
|
|
601
|
-
className: cn(
|
|
602
|
-
"flex h-full w-[420px] min-w-[360px] max-w-[480px] flex-col border-l border-[var(--md3-outline-variant)] bg-surface-container shadow-[var(--shadow-dropdown)]",
|
|
603
|
-
className
|
|
604
|
-
),
|
|
605
|
-
"aria-label": `Agent chat about ${heading}`,
|
|
606
|
-
children: [
|
|
607
|
-
/* @__PURE__ */ jsxs3("header", { className: "flex items-center justify-between border-b border-[var(--md3-outline-variant)] bg-surface-container-low px-4 py-3", children: [
|
|
608
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
609
|
-
/* @__PURE__ */ jsx3(Sparkles2, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
610
|
-
/* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
|
|
611
|
-
/* @__PURE__ */ jsx3("div", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground", children: "Discussing" }),
|
|
612
|
-
/* @__PURE__ */ jsx3("div", { className: "truncate text-sm font-semibold text-foreground", children: heading })
|
|
613
|
-
] })
|
|
614
|
-
] }),
|
|
615
|
-
/* @__PURE__ */ jsx3(
|
|
616
|
-
"button",
|
|
617
|
-
{
|
|
618
|
-
type: "button",
|
|
619
|
-
onClick: () => onOpenChange(false),
|
|
620
|
-
"aria-label": "Close artifact chat",
|
|
621
|
-
className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground",
|
|
622
|
-
children: /* @__PURE__ */ jsx3(X, { className: "h-4 w-4" })
|
|
623
|
-
}
|
|
624
|
-
)
|
|
625
|
-
] }),
|
|
626
|
-
/* @__PURE__ */ jsx3("div", { ref: scrollRef, className: "flex-1 overflow-y-auto px-3 py-4", children: loading ? /* @__PURE__ */ jsx3("div", { className: "px-3 py-8 text-center text-xs text-muted-foreground", children: "Loading conversation\u2026" }) : messages.length === 0 && !streamContent ? /* @__PURE__ */ jsxs3("div", { className: "px-3 py-12 text-center", children: [
|
|
627
|
-
/* @__PURE__ */ jsx3(Sparkles2, { className: "mx-auto mb-3 h-5 w-5 text-muted-foreground/40" }),
|
|
628
|
-
/* @__PURE__ */ jsxs3("p", { className: "text-sm text-foreground", children: [
|
|
629
|
-
"Ask the agent about this ",
|
|
630
|
-
scope.kind === "vault-file" ? "document" : scope.kind,
|
|
631
|
-
"."
|
|
632
|
-
] }),
|
|
633
|
-
/* @__PURE__ */ jsx3("p", { className: "mt-1 text-xs text-muted-foreground", children: "It can read the current contents and propose edits, rewrites, or follow-ups." })
|
|
634
|
-
] }) : /* @__PURE__ */ jsxs3("div", { className: "space-y-3", children: [
|
|
635
|
-
messages.map((m) => /* @__PURE__ */ jsx3(ChatMessage, { role: m.role, content: m.content, timestamp: m.createdAt }, m.id)),
|
|
636
|
-
streamContent && /* @__PURE__ */ jsx3(ChatMessage, { role: "assistant", content: streamContent, isStreaming: true })
|
|
637
|
-
] }) }),
|
|
638
|
-
/* @__PURE__ */ jsx3(
|
|
639
|
-
"form",
|
|
640
|
-
{
|
|
641
|
-
className: "border-t border-[var(--md3-outline-variant)] bg-surface-container-low p-3",
|
|
642
|
-
onSubmit: (e) => {
|
|
643
|
-
e.preventDefault();
|
|
644
|
-
void send();
|
|
645
|
-
},
|
|
646
|
-
children: /* @__PURE__ */ jsxs3("div", { className: "flex items-end gap-2", children: [
|
|
647
|
-
/* @__PURE__ */ jsx3(
|
|
648
|
-
"textarea",
|
|
649
|
-
{
|
|
650
|
-
value: composer,
|
|
651
|
-
onChange: (e) => setComposer(e.target.value),
|
|
652
|
-
onKeyDown: (e) => {
|
|
653
|
-
if (e.key === "Enter" && !e.shiftKey && !sending) {
|
|
654
|
-
e.preventDefault();
|
|
655
|
-
void send();
|
|
656
|
-
}
|
|
657
|
-
},
|
|
658
|
-
placeholder: `Ask about ${heading}\u2026`,
|
|
659
|
-
rows: 2,
|
|
660
|
-
disabled: !threadId,
|
|
661
|
-
className: "min-h-[44px] flex-1 resize-none rounded-[20px] border border-[var(--md3-outline-variant)] bg-surface-dim px-3.5 py-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:outline-none focus:border-[var(--border-accent)] disabled:opacity-50"
|
|
662
|
-
}
|
|
663
|
-
),
|
|
664
|
-
sending ? /* @__PURE__ */ jsx3(
|
|
665
|
-
"button",
|
|
666
|
-
{
|
|
667
|
-
type: "button",
|
|
668
|
-
onClick: cancel,
|
|
669
|
-
"aria-label": "Cancel",
|
|
670
|
-
className: "inline-flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-surface-container-high hover:text-foreground",
|
|
671
|
-
children: /* @__PURE__ */ jsx3(Square, { className: "h-4 w-4" })
|
|
672
|
-
}
|
|
673
|
-
) : /* @__PURE__ */ jsx3(
|
|
674
|
-
"button",
|
|
675
|
-
{
|
|
676
|
-
type: "submit",
|
|
677
|
-
disabled: !composer.trim() || !threadId,
|
|
678
|
-
"aria-label": "Send",
|
|
679
|
-
className: "inline-flex h-9 w-9 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50",
|
|
680
|
-
children: /* @__PURE__ */ jsx3(Send, { className: "h-4 w-4" })
|
|
681
|
-
}
|
|
682
|
-
)
|
|
683
|
-
] })
|
|
684
|
-
}
|
|
685
|
-
)
|
|
686
|
-
]
|
|
687
|
-
}
|
|
688
|
-
);
|
|
689
|
-
}
|
|
690
|
-
function createFetchTransport(opts) {
|
|
691
|
-
const threadsUrl = opts.threadsUrl ?? "/api/threads";
|
|
692
|
-
const chatUrl = opts.chatUrl ?? "/api/chat";
|
|
693
|
-
const f = opts.fetchImpl ?? ((...args) => fetch(...args));
|
|
694
|
-
const workspaceId = opts.workspaceId;
|
|
695
|
-
return {
|
|
696
|
-
async ensureThread(scope) {
|
|
697
|
-
const url = threadsUrl.includes("?") ? `${threadsUrl}&workspaceId=${encodeURIComponent(workspaceId)}` : `${threadsUrl}?workspaceId=${encodeURIComponent(workspaceId)}`;
|
|
698
|
-
const res = await f(url, {
|
|
699
|
-
method: "POST",
|
|
700
|
-
headers: { "Content-Type": "application/json" },
|
|
701
|
-
body: JSON.stringify({ workspaceId, scope, ensureScoped: true })
|
|
702
|
-
});
|
|
703
|
-
if (!res.ok) throw new Error(`Thread ensure failed (${res.status})`);
|
|
704
|
-
const data = await res.json();
|
|
705
|
-
const id = data.thread?.id ?? data.id;
|
|
706
|
-
if (!id) throw new Error("Malformed ensure response");
|
|
707
|
-
return { id };
|
|
708
|
-
},
|
|
709
|
-
async loadMessages(threadId) {
|
|
710
|
-
const res = await f(`${threadsUrl}/${encodeURIComponent(threadId)}/messages`);
|
|
711
|
-
if (!res.ok) return [];
|
|
712
|
-
const data = await res.json();
|
|
713
|
-
return (data.messages ?? []).map((m) => ({
|
|
714
|
-
id: m.id,
|
|
715
|
-
role: normalizeRole(m.role),
|
|
716
|
-
content: m.content,
|
|
717
|
-
createdAt: new Date(m.createdAt)
|
|
718
|
-
}));
|
|
719
|
-
},
|
|
720
|
-
sendStream({ threadId, content, signal }) {
|
|
721
|
-
return (async function* () {
|
|
722
|
-
const res = await f(chatUrl, {
|
|
723
|
-
method: "POST",
|
|
724
|
-
headers: { "Content-Type": "application/json" },
|
|
725
|
-
body: JSON.stringify({ workspaceId, threadId, content }),
|
|
726
|
-
signal
|
|
727
|
-
});
|
|
728
|
-
if (!res.ok || !res.body) {
|
|
729
|
-
const text = await res.text().catch(() => "");
|
|
730
|
-
yield { type: "error", message: text || `Send failed (${res.status})` };
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
const reader = res.body.getReader();
|
|
734
|
-
const decoder = new TextDecoder();
|
|
735
|
-
let buffer = "";
|
|
736
|
-
while (true) {
|
|
737
|
-
const { done, value } = await reader.read();
|
|
738
|
-
if (done) break;
|
|
739
|
-
buffer += decoder.decode(value, { stream: true });
|
|
740
|
-
const lines = buffer.split("\n");
|
|
741
|
-
buffer = lines.pop() ?? "";
|
|
742
|
-
for (const line of lines) {
|
|
743
|
-
if (!line.trim()) continue;
|
|
744
|
-
try {
|
|
745
|
-
const event = JSON.parse(line);
|
|
746
|
-
if (event.type === "message.part.updated" && event.data) {
|
|
747
|
-
const delta = event.data.delta;
|
|
748
|
-
const part = event.data.part;
|
|
749
|
-
const isText = !part || typeof part !== "object" || part.type === void 0 || part.type === "text";
|
|
750
|
-
if (isText && typeof delta === "string" && delta.length > 0) {
|
|
751
|
-
yield { type: "delta", text: delta };
|
|
752
|
-
}
|
|
753
|
-
} else if (event.type === "error" && event.data) {
|
|
754
|
-
const message = typeof event.data.message === "string" ? event.data.message : "Stream error";
|
|
755
|
-
yield { type: "error", message };
|
|
756
|
-
}
|
|
757
|
-
} catch {
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
})();
|
|
762
|
-
}
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
export {
|
|
767
|
-
DEFAULT_REASONING_LEVEL_OPTIONS,
|
|
768
|
-
ReasoningLevelPicker,
|
|
769
|
-
isModelCompatibleWithHarness,
|
|
770
|
-
snapModelToHarness,
|
|
771
|
-
modelProvider,
|
|
772
|
-
snapHarnessToModel,
|
|
773
|
-
AgentSessionControls,
|
|
774
|
-
ArtifactAgentDock,
|
|
775
|
-
createFetchTransport,
|
|
776
|
-
AgentTimeline,
|
|
777
|
-
ChatContainer,
|
|
778
|
-
ChatInput,
|
|
779
|
-
ChatMessage2 as ChatMessage,
|
|
780
|
-
MessageList,
|
|
781
|
-
ThinkingIndicator,
|
|
782
|
-
UserMessage
|
|
783
|
-
};
|