@tangle-network/agent-app 0.5.1 → 0.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/{chunk-DXAMBUDL.js → chunk-OLCVUGGI.js} +2 -2
- package/dist/{chunk-LT2YIMEB.js → chunk-QAQBR6KQ.js} +19 -5
- package/dist/chunk-QAQBR6KQ.js.map +1 -0
- package/dist/{chunk-GMFPCCQZ.js → chunk-SDOT7RNB.js} +152 -2
- package/dist/chunk-SDOT7RNB.js.map +1 -0
- package/dist/{chunk-7PXRACS2.js → chunk-TH2AOJJM.js} +174 -2
- package/dist/chunk-TH2AOJJM.js.map +1 -0
- package/dist/eval/index.d.ts +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +24 -4
- package/dist/model-catalog-BEAEVDaa.d.ts +85 -0
- package/dist/preset-cloudflare/index.d.ts +1 -1
- package/dist/runtime/index.d.ts +23 -3
- package/dist/runtime/index.js +10 -2
- package/dist/stream/index.d.ts +88 -1
- package/dist/stream/index.js +13 -1
- package/dist/tools/index.d.ts +10 -2
- package/dist/tools/index.js +2 -2
- package/dist/{types-CeWor4bQ.d.ts → types-By4B3K37.d.ts} +12 -1
- package/dist/web-react/index.d.ts +117 -0
- package/dist/web-react/index.js +361 -0
- package/dist/web-react/index.js.map +1 -0
- package/package.json +14 -3
- package/dist/chunk-7PXRACS2.js.map +0 -1
- package/dist/chunk-GMFPCCQZ.js.map +0 -1
- package/dist/chunk-LT2YIMEB.js.map +0 -1
- /package/dist/{chunk-DXAMBUDL.js.map → chunk-OLCVUGGI.js.map} +0 -0
package/dist/tools/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
handleAppToolRequest,
|
|
9
9
|
readToolArgs,
|
|
10
10
|
verifyCapabilityToken
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-OLCVUGGI.js";
|
|
12
12
|
import {
|
|
13
13
|
APP_TOOL_NAMES,
|
|
14
14
|
ToolInputError,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
dispatchAppTool,
|
|
18
18
|
isAppToolName,
|
|
19
19
|
outcomeStatus
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-QAQBR6KQ.js";
|
|
21
21
|
export {
|
|
22
22
|
APP_TOOL_NAMES,
|
|
23
23
|
DEFAULT_APP_TOOL_PATHS,
|
|
@@ -41,11 +41,22 @@ interface SubmitProposalArgs {
|
|
|
41
41
|
type: string;
|
|
42
42
|
title: string;
|
|
43
43
|
description?: string | null;
|
|
44
|
+
/** Stamped by dispatch from the approval policy (needsApproval predicate or
|
|
45
|
+
* taxonomy.regulatedTypes). Handlers MUST queue (never auto-execute) when
|
|
46
|
+
* true. Products don't set this; dispatch owns it — fail-closed. */
|
|
47
|
+
regulated?: boolean;
|
|
44
48
|
}
|
|
45
49
|
interface SubmitProposalResult {
|
|
46
50
|
proposalId: string;
|
|
47
51
|
/** True when an identical (workspace, title) proposal already existed. */
|
|
48
52
|
deduped: boolean;
|
|
53
|
+
/** Handlers that execute a proposal type immediately (an unregulated
|
|
54
|
+
* generate-style action) return 'executed'; omitted/'queued_for_approval'
|
|
55
|
+
* means a human gate. Dispatch reports this verbatim to the model. */
|
|
56
|
+
status?: 'queued_for_approval' | 'executed';
|
|
57
|
+
/** Product-specific result fields (e.g. datasetId) — passed through to the
|
|
58
|
+
* tool outcome so the model and UI see what the handler actually did. */
|
|
59
|
+
[extra: string]: unknown;
|
|
49
60
|
}
|
|
50
61
|
interface ScheduleFollowupArgs {
|
|
51
62
|
title: string;
|
|
@@ -98,7 +109,7 @@ type AppToolProducedEvent = {
|
|
|
98
109
|
type: 'proposal_created';
|
|
99
110
|
proposalId: string;
|
|
100
111
|
title: string;
|
|
101
|
-
status: 'pending';
|
|
112
|
+
status: 'pending' | 'executed';
|
|
102
113
|
} | {
|
|
103
114
|
type: 'artifact';
|
|
104
115
|
path: string;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { C as CatalogModel } from '../model-catalog-BEAEVDaa.js';
|
|
4
|
+
|
|
5
|
+
interface ChatMessageMetrics {
|
|
6
|
+
modelUsed?: string;
|
|
7
|
+
promptTokens?: number;
|
|
8
|
+
completionTokens?: number;
|
|
9
|
+
durationMs?: number;
|
|
10
|
+
}
|
|
11
|
+
/** "$0.0042" from token counts × catalogue per-token pricing; null when unknown. */
|
|
12
|
+
declare function formatModelCost(msg: ChatMessageMetrics, models: CatalogModel[]): string | null;
|
|
13
|
+
/** "38 tok/s" from completion tokens over first-token→end duration; null when unknown. */
|
|
14
|
+
declare function formatTokensPerSecond(msg: ChatMessageMetrics): string | null;
|
|
15
|
+
interface ModelPickerProps {
|
|
16
|
+
value: string;
|
|
17
|
+
onChange: (id: string) => void;
|
|
18
|
+
/** Catalogue models — from `GET`ing the app's catalogue route (see
|
|
19
|
+
* `runtime/model-catalog`), plus any product-specific entries appended. */
|
|
20
|
+
models: CatalogModel[];
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
/** Render a provider logo/badge; default is a generic sparkle. */
|
|
23
|
+
renderProviderBadge?: (provider: string) => ReactNode;
|
|
24
|
+
/** Section label for `featured` models. */
|
|
25
|
+
recommendedLabel?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Searchable model picker pill + popover: a featured/recommended section
|
|
29
|
+
* first, then per-provider groups in catalogue order (the server already
|
|
30
|
+
* sorts providers by tier).
|
|
31
|
+
*/
|
|
32
|
+
declare function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel }: ModelPickerProps): react.JSX.Element;
|
|
33
|
+
interface EffortPickerProps {
|
|
34
|
+
value: string;
|
|
35
|
+
onChange: (id: string) => void;
|
|
36
|
+
}
|
|
37
|
+
/** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show
|
|
38
|
+
* it only when the selected model `supportsReasoning`. */
|
|
39
|
+
declare function EffortPicker({ value, onChange }: EffortPickerProps): react.JSX.Element;
|
|
40
|
+
/** One step of a retained tool run (e.g. a sandbox command + its output). */
|
|
41
|
+
interface ToolRunStep {
|
|
42
|
+
at: string;
|
|
43
|
+
label: string;
|
|
44
|
+
detail?: string;
|
|
45
|
+
status?: 'ok' | 'error';
|
|
46
|
+
}
|
|
47
|
+
/** A retained tool run keyed by the parent message's toolCallId. The product
|
|
48
|
+
* persists these server-side (fail-closed: only ids its own loop created)
|
|
49
|
+
* and serves them to the drill-in panel. */
|
|
50
|
+
interface ToolRunRecord {
|
|
51
|
+
toolCallId: string;
|
|
52
|
+
toolName: string;
|
|
53
|
+
title: string;
|
|
54
|
+
status: 'running' | 'complete' | 'error';
|
|
55
|
+
steps: ToolRunStep[];
|
|
56
|
+
}
|
|
57
|
+
interface RunDrillInProps {
|
|
58
|
+
run: ToolRunRecord;
|
|
59
|
+
onClose: () => void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Readonly side panel showing a retained tool run's transcript — the
|
|
63
|
+
* "drill into what the sandbox actually did" view. Follow-ups happen in the
|
|
64
|
+
* main chat, never here.
|
|
65
|
+
*/
|
|
66
|
+
declare function RunDrillIn({ run, onClose }: RunDrillInProps): react.JSX.Element;
|
|
67
|
+
interface ChatToolCallInfo {
|
|
68
|
+
id: string;
|
|
69
|
+
name: string;
|
|
70
|
+
status: 'running' | 'done' | 'error';
|
|
71
|
+
/** The tool outcome (`{ok, result}` shape). When `result.status` is
|
|
72
|
+
* 'queued_for_approval' the chip renders the approval state. */
|
|
73
|
+
result?: unknown;
|
|
74
|
+
}
|
|
75
|
+
/** Extract `{proposalId, status}` from a tool outcome when it is a proposal
|
|
76
|
+
* awaiting human approval; null otherwise. */
|
|
77
|
+
declare function pendingApprovalOf(call: ChatToolCallInfo): {
|
|
78
|
+
proposalId: string;
|
|
79
|
+
} | null;
|
|
80
|
+
interface ChatUiMessage extends ChatMessageMetrics {
|
|
81
|
+
id: string;
|
|
82
|
+
role: 'user' | 'assistant' | 'system';
|
|
83
|
+
content: string;
|
|
84
|
+
reasoning?: string;
|
|
85
|
+
toolCalls?: ChatToolCallInfo[];
|
|
86
|
+
}
|
|
87
|
+
interface ChatMessagesProps {
|
|
88
|
+
messages: ChatUiMessage[];
|
|
89
|
+
/** Catalogue models, for per-message cost from pricing. Pass [] to skip cost. */
|
|
90
|
+
models?: CatalogModel[];
|
|
91
|
+
/** Markdown renderer for assistant content; default renders pre-wrapped text. */
|
|
92
|
+
renderMarkdown?: (content: string) => ReactNode;
|
|
93
|
+
/** Extra per-message content (artifacts, custom panels) appended after the body. */
|
|
94
|
+
renderExtras?: (message: ChatUiMessage) => ReactNode;
|
|
95
|
+
userLabel?: string;
|
|
96
|
+
agentLabel?: string;
|
|
97
|
+
/** Render the trailing "agent is thinking" row. */
|
|
98
|
+
loading?: boolean;
|
|
99
|
+
/** Approve/Reject handlers for proposals awaiting approval. When omitted the
|
|
100
|
+
* chip still shows "awaiting approval" but without action buttons. */
|
|
101
|
+
approval?: ProposalApprovalHandlers;
|
|
102
|
+
/** Make tool chips clickable (e.g. open a {@link RunDrillIn} panel). */
|
|
103
|
+
onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void;
|
|
104
|
+
}
|
|
105
|
+
interface ProposalApprovalHandlers {
|
|
106
|
+
onApprove: (proposalId: string, toolCallId: string) => void | Promise<void>;
|
|
107
|
+
onReject: (proposalId: string, toolCallId: string) => void | Promise<void>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The message thread: one centered column; user messages are right-aligned
|
|
111
|
+
* bubbles with a User label; agent messages carry an Agent meta line with
|
|
112
|
+
* model id, tokens/sec, and cost, plus a collapsible thinking section and
|
|
113
|
+
* tool-call chips.
|
|
114
|
+
*/
|
|
115
|
+
declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, approval, onToolCallClick, }: ChatMessagesProps): react.JSX.Element;
|
|
116
|
+
|
|
117
|
+
export { type ChatMessageMetrics, ChatMessages, type ChatMessagesProps, type ChatToolCallInfo, type ChatUiMessage, EffortPicker, type EffortPickerProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, RunDrillIn, type RunDrillInProps, type ToolRunRecord, type ToolRunStep, formatModelCost, formatTokensPerSecond, pendingApprovalOf };
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// src/web-react/index.tsx
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
function ChevronDown({ className }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) });
|
|
6
|
+
}
|
|
7
|
+
function SearchGlyph({ className }) {
|
|
8
|
+
return /* @__PURE__ */ jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
9
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
10
|
+
/* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3" })
|
|
11
|
+
] });
|
|
12
|
+
}
|
|
13
|
+
function SparkleGlyph({ className }) {
|
|
14
|
+
return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1" }) });
|
|
15
|
+
}
|
|
16
|
+
function useClickOutside(onOutside) {
|
|
17
|
+
const ref = useRef(null);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
function handler(e) {
|
|
20
|
+
if (ref.current && !ref.current.contains(e.target)) onOutside();
|
|
21
|
+
}
|
|
22
|
+
document.addEventListener("mousedown", handler);
|
|
23
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
24
|
+
});
|
|
25
|
+
return ref;
|
|
26
|
+
}
|
|
27
|
+
function formatModelCost(msg, models) {
|
|
28
|
+
if (msg.promptTokens == null && msg.completionTokens == null) return null;
|
|
29
|
+
const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing;
|
|
30
|
+
if (!pricing) return null;
|
|
31
|
+
const cost = (msg.promptTokens ?? 0) * Number(pricing.prompt ?? 0) + (msg.completionTokens ?? 0) * Number(pricing.completion ?? 0);
|
|
32
|
+
if (!isFinite(cost) || cost <= 0) return null;
|
|
33
|
+
return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`;
|
|
34
|
+
}
|
|
35
|
+
function formatTokensPerSecond(msg) {
|
|
36
|
+
if (msg.completionTokens == null || !msg.durationMs) return null;
|
|
37
|
+
return `${Math.round(msg.completionTokens / (msg.durationMs / 1e3))} tok/s`;
|
|
38
|
+
}
|
|
39
|
+
function formatPrice(p) {
|
|
40
|
+
if (!p) return void 0;
|
|
41
|
+
const n = Number(p);
|
|
42
|
+
if (isNaN(n) || n === 0) return void 0;
|
|
43
|
+
const perM = n * 1e6;
|
|
44
|
+
return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`;
|
|
45
|
+
}
|
|
46
|
+
function formatContext(len) {
|
|
47
|
+
if (!len) return void 0;
|
|
48
|
+
if (len >= 1e6) return `${(len / 1e6).toFixed(1)}M ctx`;
|
|
49
|
+
if (len >= 1e3) return `${Math.round(len / 1e3)}K ctx`;
|
|
50
|
+
return `${len} ctx`;
|
|
51
|
+
}
|
|
52
|
+
function SectionHeader({ children }) {
|
|
53
|
+
return /* @__PURE__ */ jsx("div", { className: "px-3 pb-1 pt-3 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70", children });
|
|
54
|
+
}
|
|
55
|
+
function ModelRow({
|
|
56
|
+
model,
|
|
57
|
+
selected,
|
|
58
|
+
onSelect,
|
|
59
|
+
renderProviderBadge
|
|
60
|
+
}) {
|
|
61
|
+
const price = formatPrice(model.pricing?.prompt);
|
|
62
|
+
const ctx = formatContext(model.contextLength);
|
|
63
|
+
return /* @__PURE__ */ jsxs(
|
|
64
|
+
"button",
|
|
65
|
+
{
|
|
66
|
+
type: "button",
|
|
67
|
+
onClick: onSelect,
|
|
68
|
+
className: `flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition ${selected ? "bg-primary/10 font-medium" : "hover:bg-accent/30"}`,
|
|
69
|
+
children: [
|
|
70
|
+
renderProviderBadge ? renderProviderBadge(model.provider) : /* @__PURE__ */ jsx(SparkleGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
71
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: model.name }),
|
|
72
|
+
!model.supportsTools && /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground", children: "no tools" }),
|
|
73
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground", children: [
|
|
74
|
+
ctx && /* @__PURE__ */ jsx("span", { children: ctx }),
|
|
75
|
+
price && /* @__PURE__ */ jsx("span", { children: price })
|
|
76
|
+
] })
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = "Recommended" }) {
|
|
82
|
+
const [open, setOpen] = useState(false);
|
|
83
|
+
const [query, setQuery] = useState("");
|
|
84
|
+
const containerRef = useClickOutside(() => setOpen(false));
|
|
85
|
+
const inputRef = useRef(null);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (open) inputRef.current?.focus();
|
|
88
|
+
}, [open]);
|
|
89
|
+
const selected = models.find((m) => m.id === value);
|
|
90
|
+
const filtered = useMemo(() => {
|
|
91
|
+
const q = query.trim().toLowerCase();
|
|
92
|
+
if (!q) return null;
|
|
93
|
+
return models.filter(
|
|
94
|
+
(m) => m.id.toLowerCase().includes(q) || m.name.toLowerCase().includes(q) || (m.description?.toLowerCase() ?? "").includes(q) || m.provider.toLowerCase().includes(q)
|
|
95
|
+
);
|
|
96
|
+
}, [models, query]);
|
|
97
|
+
const sections = useMemo(() => {
|
|
98
|
+
const recommended = models.filter((m) => m.featured);
|
|
99
|
+
const byProvider = [];
|
|
100
|
+
for (const m of models) {
|
|
101
|
+
if (m.featured) continue;
|
|
102
|
+
const last = byProvider[byProvider.length - 1];
|
|
103
|
+
if (last && last.provider === m.provider) last.items.push(m);
|
|
104
|
+
else byProvider.push({ provider: m.provider, items: [m] });
|
|
105
|
+
}
|
|
106
|
+
return { recommended, byProvider };
|
|
107
|
+
}, [models]);
|
|
108
|
+
const select = (id) => {
|
|
109
|
+
onChange(id);
|
|
110
|
+
setOpen(false);
|
|
111
|
+
setQuery("");
|
|
112
|
+
};
|
|
113
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative inline-flex", children: [
|
|
114
|
+
/* @__PURE__ */ jsxs(
|
|
115
|
+
"button",
|
|
116
|
+
{
|
|
117
|
+
type: "button",
|
|
118
|
+
onClick: () => setOpen((v) => !v),
|
|
119
|
+
className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30",
|
|
120
|
+
children: [
|
|
121
|
+
selected && renderProviderBadge ? renderProviderBadge(selected.provider) : /* @__PURE__ */ jsx(SparkleGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
122
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-[160px] truncate", children: selected?.name ?? value }),
|
|
123
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
),
|
|
127
|
+
open && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg", children: [
|
|
128
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border px-3 py-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2", children: [
|
|
129
|
+
/* @__PURE__ */ jsx(SearchGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
130
|
+
/* @__PURE__ */ jsx(
|
|
131
|
+
"input",
|
|
132
|
+
{
|
|
133
|
+
ref: inputRef,
|
|
134
|
+
type: "text",
|
|
135
|
+
value: query,
|
|
136
|
+
onChange: (e) => setQuery(e.target.value),
|
|
137
|
+
placeholder: "Search models...",
|
|
138
|
+
className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
] }) }),
|
|
142
|
+
/* @__PURE__ */ jsxs("div", { className: "max-h-[400px] overflow-y-auto p-1 pb-2", children: [
|
|
143
|
+
loading && /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "Loading models..." }),
|
|
144
|
+
!loading && filtered && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
145
|
+
filtered.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "No models match your search" }),
|
|
146
|
+
filtered.map((m) => /* @__PURE__ */ jsx(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
|
|
147
|
+
] }),
|
|
148
|
+
!loading && !filtered && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
149
|
+
sections.recommended.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
150
|
+
/* @__PURE__ */ jsx(SectionHeader, { children: recommendedLabel }),
|
|
151
|
+
sections.recommended.map((m) => /* @__PURE__ */ jsx(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
|
|
152
|
+
] }),
|
|
153
|
+
sections.byProvider.map((g) => /* @__PURE__ */ jsxs("div", { children: [
|
|
154
|
+
/* @__PURE__ */ jsx(SectionHeader, { children: g.provider }),
|
|
155
|
+
g.items.map((m) => /* @__PURE__ */ jsx(ModelRow, { model: m, selected: m.id === value, onSelect: () => select(m.id), renderProviderBadge }, m.id))
|
|
156
|
+
] }, g.provider))
|
|
157
|
+
] })
|
|
158
|
+
] })
|
|
159
|
+
] })
|
|
160
|
+
] });
|
|
161
|
+
}
|
|
162
|
+
var EFFORT_LEVELS = [
|
|
163
|
+
{ id: "off", label: "Off" },
|
|
164
|
+
{ id: "low", label: "Low" },
|
|
165
|
+
{ id: "medium", label: "Medium" },
|
|
166
|
+
{ id: "high", label: "High" }
|
|
167
|
+
];
|
|
168
|
+
function EffortPicker({ value, onChange }) {
|
|
169
|
+
const [open, setOpen] = useState(false);
|
|
170
|
+
const containerRef = useClickOutside(() => setOpen(false));
|
|
171
|
+
const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2];
|
|
172
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative inline-flex", children: [
|
|
173
|
+
/* @__PURE__ */ jsxs(
|
|
174
|
+
"button",
|
|
175
|
+
{
|
|
176
|
+
type: "button",
|
|
177
|
+
onClick: () => setOpen((v) => !v),
|
|
178
|
+
title: "Reasoning effort",
|
|
179
|
+
className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30",
|
|
180
|
+
children: [
|
|
181
|
+
/* @__PURE__ */ jsx(SparkleGlyph, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
182
|
+
/* @__PURE__ */ jsx("span", { children: selected.label }),
|
|
183
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
open && /* @__PURE__ */ jsx("div", { className: "absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg", children: EFFORT_LEVELS.map((l) => /* @__PURE__ */ jsx(
|
|
188
|
+
"button",
|
|
189
|
+
{
|
|
190
|
+
type: "button",
|
|
191
|
+
onClick: () => {
|
|
192
|
+
onChange(l.id);
|
|
193
|
+
setOpen(false);
|
|
194
|
+
},
|
|
195
|
+
className: `flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${l.id === value ? "bg-primary/10 font-medium" : "hover:bg-accent/30"}`,
|
|
196
|
+
children: l.label
|
|
197
|
+
},
|
|
198
|
+
l.id
|
|
199
|
+
)) })
|
|
200
|
+
] });
|
|
201
|
+
}
|
|
202
|
+
function RunDrillIn({ run, onClose }) {
|
|
203
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl", children: [
|
|
204
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
|
|
205
|
+
/* @__PURE__ */ jsx(
|
|
206
|
+
"span",
|
|
207
|
+
{
|
|
208
|
+
className: `h-2 w-2 shrink-0 rounded-full ${run.status === "running" ? "bg-yellow-500" : run.status === "error" ? "bg-red-500" : "bg-green-500"}`
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
212
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: run.title }),
|
|
213
|
+
/* @__PURE__ */ jsx("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: run.toolName })
|
|
214
|
+
] }),
|
|
215
|
+
/* @__PURE__ */ jsx(
|
|
216
|
+
"button",
|
|
217
|
+
{
|
|
218
|
+
type: "button",
|
|
219
|
+
onClick: onClose,
|
|
220
|
+
"aria-label": "Close",
|
|
221
|
+
className: "rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground",
|
|
222
|
+
children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
] }),
|
|
226
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
|
|
227
|
+
run.steps.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No steps recorded yet." }),
|
|
228
|
+
run.steps.map((step, i) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border/60 bg-background", children: [
|
|
229
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5", children: [
|
|
230
|
+
/* @__PURE__ */ jsx("span", { className: `font-mono text-[11px] ${step.status === "error" ? "text-red-600" : "text-muted-foreground"}`, children: step.status === "error" ? "\u2717" : "$" }),
|
|
231
|
+
/* @__PURE__ */ jsx("code", { className: "min-w-0 flex-1 truncate font-mono text-xs", children: step.label }),
|
|
232
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] text-muted-foreground/60", children: new Date(step.at).toLocaleTimeString() })
|
|
233
|
+
] }),
|
|
234
|
+
step.detail && /* @__PURE__ */ jsx("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground", children: step.detail })
|
|
235
|
+
] }, i))
|
|
236
|
+
] }),
|
|
237
|
+
/* @__PURE__ */ jsx("p", { className: "border-t border-border px-4 py-2 text-[11px] text-muted-foreground/60", children: "Readonly drill-in. Follow up in the main chat." })
|
|
238
|
+
] });
|
|
239
|
+
}
|
|
240
|
+
function pendingApprovalOf(call) {
|
|
241
|
+
const outcome = call.result;
|
|
242
|
+
if (!outcome?.ok || outcome.result?.status !== "queued_for_approval" || !outcome.result.proposalId) return null;
|
|
243
|
+
return { proposalId: outcome.result.proposalId };
|
|
244
|
+
}
|
|
245
|
+
function ToolChips({
|
|
246
|
+
toolCalls,
|
|
247
|
+
approval,
|
|
248
|
+
onClick
|
|
249
|
+
}) {
|
|
250
|
+
return /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-col gap-1", children: toolCalls.map((tc) => {
|
|
251
|
+
const pending = tc.status === "done" ? pendingApprovalOf(tc) : null;
|
|
252
|
+
if (pending) {
|
|
253
|
+
return /* @__PURE__ */ jsxs(
|
|
254
|
+
"div",
|
|
255
|
+
{
|
|
256
|
+
className: "inline-flex w-fit items-center gap-2 rounded-md bg-amber-500/10 px-2.5 py-1 text-xs text-amber-700",
|
|
257
|
+
children: [
|
|
258
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono opacity-70", children: "\u23F8" }),
|
|
259
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: tc.name }),
|
|
260
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-60", children: "awaiting approval" }),
|
|
261
|
+
approval && /* @__PURE__ */ jsxs("span", { className: "ml-1 inline-flex items-center gap-1", children: [
|
|
262
|
+
/* @__PURE__ */ jsx(
|
|
263
|
+
"button",
|
|
264
|
+
{
|
|
265
|
+
type: "button",
|
|
266
|
+
onClick: () => approval.onApprove(pending.proposalId, tc.id),
|
|
267
|
+
className: "rounded bg-green-600/90 px-2 py-0.5 text-[11px] font-semibold text-white transition hover:bg-green-600",
|
|
268
|
+
children: "Approve"
|
|
269
|
+
}
|
|
270
|
+
),
|
|
271
|
+
/* @__PURE__ */ jsx(
|
|
272
|
+
"button",
|
|
273
|
+
{
|
|
274
|
+
type: "button",
|
|
275
|
+
onClick: () => approval.onReject(pending.proposalId, tc.id),
|
|
276
|
+
className: "rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30",
|
|
277
|
+
children: "Reject"
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
] })
|
|
281
|
+
]
|
|
282
|
+
},
|
|
283
|
+
tc.id
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
const Tag = onClick ? "button" : "div";
|
|
287
|
+
return /* @__PURE__ */ jsxs(
|
|
288
|
+
Tag,
|
|
289
|
+
{
|
|
290
|
+
...onClick ? { type: "button", onClick: () => onClick(tc) } : {},
|
|
291
|
+
className: `inline-flex w-fit items-center gap-2 rounded-md px-2.5 py-1 text-xs ${tc.status === "running" ? "bg-yellow-500/10 text-yellow-700" : tc.status === "error" ? "bg-red-500/10 text-red-700" : "bg-green-500/10 text-green-700"} ${onClick ? "cursor-pointer transition hover:ring-1 hover:ring-border" : ""}`,
|
|
292
|
+
children: [
|
|
293
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono opacity-70", children: tc.status === "running" ? "\u26A1" : tc.status === "error" ? "\u2717" : "\u2713" }),
|
|
294
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: tc.name }),
|
|
295
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-60", children: tc.status === "running" ? "running\u2026" : tc.status === "error" ? "failed" : "done" })
|
|
296
|
+
]
|
|
297
|
+
},
|
|
298
|
+
tc.id
|
|
299
|
+
);
|
|
300
|
+
}) });
|
|
301
|
+
}
|
|
302
|
+
function ChatMessages({
|
|
303
|
+
messages,
|
|
304
|
+
models = [],
|
|
305
|
+
renderMarkdown,
|
|
306
|
+
renderExtras,
|
|
307
|
+
userLabel = "User",
|
|
308
|
+
agentLabel = "Agent",
|
|
309
|
+
loading,
|
|
310
|
+
approval,
|
|
311
|
+
onToolCallClick
|
|
312
|
+
}) {
|
|
313
|
+
const renderBody = renderMarkdown ?? ((content) => /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: content }));
|
|
314
|
+
const lastIsUser = messages[messages.length - 1]?.role === "user";
|
|
315
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
316
|
+
messages.map(
|
|
317
|
+
(msg) => msg.role === "user" ? /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: /* @__PURE__ */ jsxs("div", { className: "ml-auto w-fit max-w-[85%]", children: [
|
|
318
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60", children: userLabel }),
|
|
319
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed", children: /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: msg.content }) })
|
|
320
|
+
] }) }, msg.id) : /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
|
|
321
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60", children: [
|
|
322
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold uppercase", children: agentLabel }),
|
|
323
|
+
msg.modelUsed && /* @__PURE__ */ jsx("span", { className: "font-mono normal-case", children: msg.modelUsed }),
|
|
324
|
+
formatTokensPerSecond(msg) && /* @__PURE__ */ jsx("span", { children: formatTokensPerSecond(msg) }),
|
|
325
|
+
formatModelCost(msg, models) && /* @__PURE__ */ jsx("span", { children: formatModelCost(msg, models) })
|
|
326
|
+
] }),
|
|
327
|
+
msg.reasoning && /* @__PURE__ */ jsxs("details", { className: "mb-2 rounded-md border border-border/40 bg-muted/30 px-3 py-2", children: [
|
|
328
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: "Thinking\u2026" }),
|
|
329
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 whitespace-pre-wrap text-sm text-muted-foreground/80", children: msg.reasoning })
|
|
330
|
+
] }),
|
|
331
|
+
/* @__PURE__ */ jsx("div", { className: "text-base leading-[1.75]", children: renderBody(msg.content) }),
|
|
332
|
+
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx(
|
|
333
|
+
ToolChips,
|
|
334
|
+
{
|
|
335
|
+
toolCalls: msg.toolCalls,
|
|
336
|
+
approval,
|
|
337
|
+
onClick: onToolCallClick ? (tc) => onToolCallClick(tc, msg) : void 0
|
|
338
|
+
}
|
|
339
|
+
),
|
|
340
|
+
renderExtras?.(msg)
|
|
341
|
+
] }, msg.id)
|
|
342
|
+
),
|
|
343
|
+
loading && lastIsUser && /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
|
|
344
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60", children: agentLabel }),
|
|
345
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
|
|
346
|
+
/* @__PURE__ */ jsx("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56", strokeLinecap: "round" }) }),
|
|
347
|
+
"Thinking..."
|
|
348
|
+
] })
|
|
349
|
+
] })
|
|
350
|
+
] });
|
|
351
|
+
}
|
|
352
|
+
export {
|
|
353
|
+
ChatMessages,
|
|
354
|
+
EffortPicker,
|
|
355
|
+
ModelPicker,
|
|
356
|
+
RunDrillIn,
|
|
357
|
+
formatModelCost,
|
|
358
|
+
formatTokensPerSecond,
|
|
359
|
+
pendingApprovalOf
|
|
360
|
+
};
|
|
361
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/web-react/index.tsx"],"sourcesContent":["/**\n * `@tangle-network/agent-app/web-react` — the shared chat-shell components\n * every agent app's web UI hand-rolls: a model picker over the runtime's\n * model catalogue, a reasoning-effort selector, and a message thread with\n * User/Agent identity, per-message model + cost + tokens/sec metrics, tool\n * chips, and a collapsible thinking section.\n *\n * Works for BOTH chat shapes: router-backed copilots (LoopEvents from\n * `runtime/openai-stream`) and sandbox-backed chats — the thread renders\n * `ChatUiMessage`s; how they're produced is the app's business.\n *\n * Styling contract: Tailwind classes against the shared design tokens\n * (`bg-card`, `border-border`, `text-muted-foreground`, `bg-primary`, …) that\n * Tangle app shells define. No icon library — the few glyphs are inline SVGs.\n * Markdown and provider logos are injected (`renderMarkdown`,\n * `renderProviderBadge`) so this package stays dependency-free beyond React.\n */\n\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport type { CatalogModel } from '../runtime/model-catalog'\n\n// ── shared glyphs (no icon-library dependency) ────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\nfunction SearchGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n )\n}\n\nfunction SparkleGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M12 3v3m0 12v3M3 12h3m12 0h3M5.6 5.6l2.1 2.1m8.6 8.6 2.1 2.1m0-12.8-2.1 2.1M7.7 16.3l-2.1 2.1\" />\n </svg>\n )\n}\n\n/** Close an absolutely-positioned popover on outside mousedown. */\nfunction useClickOutside(onOutside: () => void) {\n const ref = useRef<HTMLDivElement>(null)\n useEffect(() => {\n function handler(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) onOutside()\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n })\n return ref\n}\n\n// ── metrics helpers ───────────────────────────────────────────────────────\n\nexport interface ChatMessageMetrics {\n modelUsed?: string\n promptTokens?: number\n completionTokens?: number\n durationMs?: number\n}\n\n/** \"$0.0042\" from token counts × catalogue per-token pricing; null when unknown. */\nexport function formatModelCost(msg: ChatMessageMetrics, models: CatalogModel[]): string | null {\n if (msg.promptTokens == null && msg.completionTokens == null) return null\n const pricing = models.find((m) => m.id === msg.modelUsed)?.pricing\n if (!pricing) return null\n const cost =\n (msg.promptTokens ?? 0) * Number(pricing.prompt ?? 0) +\n (msg.completionTokens ?? 0) * Number(pricing.completion ?? 0)\n if (!isFinite(cost) || cost <= 0) return null\n return cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`\n}\n\n/** \"38 tok/s\" from completion tokens over first-token→end duration; null when unknown. */\nexport function formatTokensPerSecond(msg: ChatMessageMetrics): string | null {\n if (msg.completionTokens == null || !msg.durationMs) return null\n return `${Math.round(msg.completionTokens / (msg.durationMs / 1000))} tok/s`\n}\n\n// ── ModelPicker ───────────────────────────────────────────────────────────\n\nexport interface ModelPickerProps {\n value: string\n onChange: (id: string) => void\n /** Catalogue models — from `GET`ing the app's catalogue route (see\n * `runtime/model-catalog`), plus any product-specific entries appended. */\n models: CatalogModel[]\n loading?: boolean\n /** Render a provider logo/badge; default is a generic sparkle. */\n renderProviderBadge?: (provider: string) => ReactNode\n /** Section label for `featured` models. */\n recommendedLabel?: string\n}\n\nfunction formatPrice(p?: string): string | undefined {\n if (!p) return undefined\n const n = Number(p)\n if (isNaN(n) || n === 0) return undefined\n const perM = n * 1_000_000\n return perM >= 1 ? `$${perM.toFixed(0)}/M` : `$${perM.toFixed(2)}/M`\n}\n\nfunction formatContext(len?: number): string | undefined {\n if (!len) return undefined\n if (len >= 1_000_000) return `${(len / 1_000_000).toFixed(1)}M ctx`\n if (len >= 1_000) return `${Math.round(len / 1_000)}K ctx`\n return `${len} ctx`\n}\n\nfunction SectionHeader({ children }: { children: ReactNode }) {\n return (\n <div className=\"px-3 pb-1 pt-3 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/70\">\n {children}\n </div>\n )\n}\n\nfunction ModelRow({\n model,\n selected,\n onSelect,\n renderProviderBadge,\n}: {\n model: CatalogModel\n selected: boolean\n onSelect: () => void\n renderProviderBadge?: (provider: string) => ReactNode\n}) {\n const price = formatPrice(model.pricing?.prompt)\n const ctx = formatContext(model.contextLength)\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition ${\n selected ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {renderProviderBadge ? renderProviderBadge(model.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"truncate\">{model.name}</span>\n {!model.supportsTools && (\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n no tools\n </span>\n )}\n <span className=\"ml-auto flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground\">\n {ctx && <span>{ctx}</span>}\n {price && <span>{price}</span>}\n </span>\n </button>\n )\n}\n\n/**\n * Searchable model picker pill + popover: a featured/recommended section\n * first, then per-provider groups in catalogue order (the server already\n * sorts providers by tier).\n */\nexport function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel = 'Recommended' }: ModelPickerProps) {\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const containerRef = useClickOutside(() => setOpen(false))\n const inputRef = useRef<HTMLInputElement>(null)\n\n useEffect(() => {\n if (open) inputRef.current?.focus()\n }, [open])\n\n const selected = models.find((m) => m.id === value)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (!q) return null\n return models.filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.name.toLowerCase().includes(q) ||\n (m.description?.toLowerCase() ?? '').includes(q) ||\n m.provider.toLowerCase().includes(q),\n )\n }, [models, query])\n\n const sections = useMemo(() => {\n const recommended = models.filter((m) => m.featured)\n const byProvider: Array<{ provider: string; items: CatalogModel[] }> = []\n for (const m of models) {\n if (m.featured) continue\n const last = byProvider[byProvider.length - 1]\n if (last && last.provider === m.provider) last.items.push(m)\n else byProvider.push({ provider: m.provider, items: [m] })\n }\n return { recommended, byProvider }\n }, [models])\n\n const select = (id: string) => {\n onChange(id)\n setOpen(false)\n setQuery('')\n }\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n {selected && renderProviderBadge ? renderProviderBadge(selected.provider) : <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />}\n <span className=\"max-w-[160px] truncate\">{selected?.name ?? value}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-[420px] overflow-hidden rounded-xl border border-border bg-card shadow-lg\">\n <div className=\"border-b border-border px-3 py-2\">\n <div className=\"flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2\">\n <SearchGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search models...\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n </div>\n </div>\n <div className=\"max-h-[400px] overflow-y-auto p-1 pb-2\">\n {loading && <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">Loading models...</div>}\n {!loading && filtered && (\n <>\n {filtered.length === 0 && (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">No models match your search</div>\n )}\n {filtered.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {!loading && !filtered && (\n <>\n {sections.recommended.length > 0 && (\n <>\n <SectionHeader>{recommendedLabel}</SectionHeader>\n {sections.recommended.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </>\n )}\n {sections.byProvider.map((g) => (\n <div key={g.provider}>\n <SectionHeader>{g.provider}</SectionHeader>\n {g.items.map((m) => (\n <ModelRow key={m.id} model={m} selected={m.id === value} onSelect={() => select(m.id)} renderProviderBadge={renderProviderBadge} />\n ))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n// ── EffortPicker ──────────────────────────────────────────────────────────\n\nconst EFFORT_LEVELS = [\n { id: 'off', label: 'Off' },\n { id: 'low', label: 'Low' },\n { id: 'medium', label: 'Medium' },\n { id: 'high', label: 'High' },\n] as const\n\nexport interface EffortPickerProps {\n value: string\n onChange: (id: string) => void\n}\n\n/** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show\n * it only when the selected model `supportsReasoning`. */\nexport function EffortPicker({ value, onChange }: EffortPickerProps) {\n const [open, setOpen] = useState(false)\n const containerRef = useClickOutside(() => setOpen(false))\n const selected = EFFORT_LEVELS.find((l) => l.id === value) ?? EFFORT_LEVELS[2]\n\n return (\n <div ref={containerRef} className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n title=\"Reasoning effort\"\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-accent/30\"\n >\n <SparkleGlyph className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <span>{selected.label}</span>\n <ChevronDown className=\"h-3.5 w-3.5 text-muted-foreground\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-50 mb-2 w-36 overflow-hidden rounded-xl border border-border bg-card p-1 shadow-lg\">\n {EFFORT_LEVELS.map((l) => (\n <button\n key={l.id}\n type=\"button\"\n onClick={() => {\n onChange(l.id)\n setOpen(false)\n }}\n className={`flex w-full items-center rounded-md px-3 py-2 text-left text-sm transition ${\n l.id === value ? 'bg-primary/10 font-medium' : 'hover:bg-accent/30'\n }`}\n >\n {l.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// ── Tool run drill-in (retained runs) ─────────────────────────────────────\n\n/** One step of a retained tool run (e.g. a sandbox command + its output). */\nexport interface ToolRunStep {\n at: string\n label: string\n detail?: string\n status?: 'ok' | 'error'\n}\n\n/** A retained tool run keyed by the parent message's toolCallId. The product\n * persists these server-side (fail-closed: only ids its own loop created)\n * and serves them to the drill-in panel. */\nexport interface ToolRunRecord {\n toolCallId: string\n toolName: string\n title: string\n status: 'running' | 'complete' | 'error'\n steps: ToolRunStep[]\n}\n\nexport interface RunDrillInProps {\n run: ToolRunRecord\n onClose: () => void\n}\n\n/**\n * Readonly side panel showing a retained tool run's transcript — the\n * \"drill into what the sandbox actually did\" view. Follow-ups happen in the\n * main chat, never here.\n */\nexport function RunDrillIn({ run, onClose }: RunDrillInProps) {\n return (\n <div className=\"fixed inset-y-0 right-0 z-50 flex w-[480px] max-w-full flex-col border-l border-border bg-card shadow-xl\">\n <div className=\"flex items-center gap-2 border-b border-border px-4 py-3\">\n <span\n className={`h-2 w-2 shrink-0 rounded-full ${\n run.status === 'running' ? 'bg-yellow-500' : run.status === 'error' ? 'bg-red-500' : 'bg-green-500'\n }`}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold\">{run.title}</p>\n <p className=\"truncate font-mono text-[11px] text-muted-foreground\">{run.toolName}</p>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n className=\"rounded-md p-1.5 text-muted-foreground transition hover:bg-accent/30 hover:text-foreground\"\n >\n <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"flex-1 space-y-3 overflow-y-auto p-4\">\n {run.steps.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No steps recorded yet.</p>\n )}\n {run.steps.map((step, i) => (\n <div key={i} className=\"rounded-lg border border-border/60 bg-background\">\n <div className=\"flex items-baseline gap-2 border-b border-border/40 px-3 py-1.5\">\n <span className={`font-mono text-[11px] ${step.status === 'error' ? 'text-red-600' : 'text-muted-foreground'}`}>\n {step.status === 'error' ? '✗' : '$'}\n </span>\n <code className=\"min-w-0 flex-1 truncate font-mono text-xs\">{step.label}</code>\n <span className=\"shrink-0 text-[10px] text-muted-foreground/60\">\n {new Date(step.at).toLocaleTimeString()}\n </span>\n </div>\n {step.detail && (\n <pre className=\"max-h-48 overflow-auto whitespace-pre-wrap px-3 py-2 font-mono text-[11px] leading-relaxed text-muted-foreground\">\n {step.detail}\n </pre>\n )}\n </div>\n ))}\n </div>\n <p className=\"border-t border-border px-4 py-2 text-[11px] text-muted-foreground/60\">\n Readonly drill-in. Follow up in the main chat.\n </p>\n </div>\n )\n}\n\n// ── ChatMessages ──────────────────────────────────────────────────────────\n\nexport interface ChatToolCallInfo {\n id: string\n name: string\n status: 'running' | 'done' | 'error'\n /** The tool outcome (`{ok, result}` shape). When `result.status` is\n * 'queued_for_approval' the chip renders the approval state. */\n result?: unknown\n}\n\n/** Extract `{proposalId, status}` from a tool outcome when it is a proposal\n * awaiting human approval; null otherwise. */\nexport function pendingApprovalOf(call: ChatToolCallInfo): { proposalId: string } | null {\n const outcome = call.result as { ok?: boolean; result?: { status?: string; proposalId?: string } } | undefined\n if (!outcome?.ok || outcome.result?.status !== 'queued_for_approval' || !outcome.result.proposalId) return null\n return { proposalId: outcome.result.proposalId }\n}\n\nexport interface ChatUiMessage extends ChatMessageMetrics {\n id: string\n role: 'user' | 'assistant' | 'system'\n content: string\n reasoning?: string\n toolCalls?: ChatToolCallInfo[]\n}\n\nexport interface ChatMessagesProps {\n messages: ChatUiMessage[]\n /** Catalogue models, for per-message cost from pricing. Pass [] to skip cost. */\n models?: CatalogModel[]\n /** Markdown renderer for assistant content; default renders pre-wrapped text. */\n renderMarkdown?: (content: string) => ReactNode\n /** Extra per-message content (artifacts, custom panels) appended after the body. */\n renderExtras?: (message: ChatUiMessage) => ReactNode\n userLabel?: string\n agentLabel?: string\n /** Render the trailing \"agent is thinking\" row. */\n loading?: boolean\n /** Approve/Reject handlers for proposals awaiting approval. When omitted the\n * chip still shows \"awaiting approval\" but without action buttons. */\n approval?: ProposalApprovalHandlers\n /** Make tool chips clickable (e.g. open a {@link RunDrillIn} panel). */\n onToolCallClick?: (call: ChatToolCallInfo, message: ChatUiMessage) => void\n}\n\nexport interface ProposalApprovalHandlers {\n onApprove: (proposalId: string, toolCallId: string) => void | Promise<void>\n onReject: (proposalId: string, toolCallId: string) => void | Promise<void>\n}\n\nfunction ToolChips({\n toolCalls,\n approval,\n onClick,\n}: {\n toolCalls: ChatToolCallInfo[]\n approval?: ProposalApprovalHandlers\n onClick?: (call: ChatToolCallInfo) => void\n}) {\n return (\n <div className=\"mt-2 flex flex-col gap-1\">\n {toolCalls.map((tc) => {\n const pending = tc.status === 'done' ? pendingApprovalOf(tc) : null\n if (pending) {\n return (\n <div\n key={tc.id}\n className=\"inline-flex w-fit items-center gap-2 rounded-md bg-amber-500/10 px-2.5 py-1 text-xs text-amber-700\"\n >\n <span className=\"font-mono opacity-70\">⏸</span>\n <span className=\"font-medium\">{tc.name}</span>\n <span className=\"opacity-60\">awaiting approval</span>\n {approval && (\n <span className=\"ml-1 inline-flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => approval.onApprove(pending.proposalId, tc.id)}\n className=\"rounded bg-green-600/90 px-2 py-0.5 text-[11px] font-semibold text-white transition hover:bg-green-600\"\n >\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => approval.onReject(pending.proposalId, tc.id)}\n className=\"rounded border border-border bg-card px-2 py-0.5 text-[11px] font-medium text-foreground transition hover:bg-accent/30\"\n >\n Reject\n </button>\n </span>\n )}\n </div>\n )\n }\n const Tag = onClick ? 'button' : 'div'\n return (\n <Tag\n key={tc.id}\n {...(onClick ? { type: 'button' as const, onClick: () => onClick(tc) } : {})}\n className={`inline-flex w-fit items-center gap-2 rounded-md px-2.5 py-1 text-xs ${\n tc.status === 'running'\n ? 'bg-yellow-500/10 text-yellow-700'\n : tc.status === 'error'\n ? 'bg-red-500/10 text-red-700'\n : 'bg-green-500/10 text-green-700'\n } ${onClick ? 'cursor-pointer transition hover:ring-1 hover:ring-border' : ''}`}\n >\n <span className=\"font-mono opacity-70\">{tc.status === 'running' ? '⚡' : tc.status === 'error' ? '✗' : '✓'}</span>\n <span className=\"font-medium\">{tc.name}</span>\n <span className=\"opacity-60\">{tc.status === 'running' ? 'running…' : tc.status === 'error' ? 'failed' : 'done'}</span>\n </Tag>\n )\n })}\n </div>\n )\n}\n\n/**\n * The message thread: one centered column; user messages are right-aligned\n * bubbles with a User label; agent messages carry an Agent meta line with\n * model id, tokens/sec, and cost, plus a collapsible thinking section and\n * tool-call chips.\n */\nexport function ChatMessages({\n messages,\n models = [],\n renderMarkdown,\n renderExtras,\n userLabel = 'User',\n agentLabel = 'Agent',\n loading,\n approval,\n onToolCallClick,\n}: ChatMessagesProps) {\n const renderBody = renderMarkdown ?? ((content: string) => <p className=\"whitespace-pre-wrap\">{content}</p>)\n const lastIsUser = messages[messages.length - 1]?.role === 'user'\n return (\n <>\n {messages.map((msg) =>\n msg.role === 'user' ? (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"ml-auto w-fit max-w-[85%]\">\n <p className=\"mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">\n {userLabel}\n </p>\n <div className=\"rounded-2xl rounded-tr-md bg-primary/10 px-4 py-2.5 text-base leading-relaxed\">\n <p className=\"whitespace-pre-wrap\">{msg.content}</p>\n </div>\n </div>\n </div>\n ) : (\n <div key={msg.id} className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <div className=\"mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60\">\n <span className=\"font-semibold uppercase\">{agentLabel}</span>\n {msg.modelUsed && <span className=\"font-mono normal-case\">{msg.modelUsed}</span>}\n {formatTokensPerSecond(msg) && <span>{formatTokensPerSecond(msg)}</span>}\n {formatModelCost(msg, models) && <span>{formatModelCost(msg, models)}</span>}\n </div>\n {msg.reasoning && (\n <details className=\"mb-2 rounded-md border border-border/40 bg-muted/30 px-3 py-2\">\n <summary className=\"cursor-pointer select-none text-xs font-medium text-muted-foreground\">Thinking…</summary>\n <div className=\"mt-2 whitespace-pre-wrap text-sm text-muted-foreground/80\">{msg.reasoning}</div>\n </details>\n )}\n <div className=\"text-base leading-[1.75]\">{renderBody(msg.content)}</div>\n {msg.toolCalls && msg.toolCalls.length > 0 && (\n <ToolChips\n toolCalls={msg.toolCalls}\n approval={approval}\n onClick={onToolCallClick ? (tc) => onToolCallClick(tc, msg) : undefined}\n />\n )}\n {renderExtras?.(msg)}\n </div>\n ),\n )}\n {loading && lastIsUser && (\n <div className=\"mx-auto w-full max-w-3xl px-6 py-3\">\n <p className=\"mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60\">{agentLabel}</p>\n <div className=\"flex items-center gap-2 text-base text-muted-foreground\">\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" aria-hidden>\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" strokeLinecap=\"round\" />\n </svg>\n Thinking...\n </div>\n </div>\n )}\n </>\n )\n}\n"],"mappings":"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","names":[]}
|