@tangle-network/agent-app 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-7PXRACS2.js → chunk-EO4IGDQD.js} +174 -2
- package/dist/chunk-EO4IGDQD.js.map +1 -0
- package/dist/{chunk-DXAMBUDL.js → chunk-HZZD3ZYD.js} +2 -2
- package/dist/{chunk-LT2YIMEB.js → chunk-JANT2G2E.js} +10 -3
- package/dist/chunk-JANT2G2E.js.map +1 -0
- package/dist/eval/index.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +11 -3
- 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/tools/index.d.ts +2 -2
- package/dist/tools/index.js +2 -2
- package/dist/{types-CeWor4bQ.d.ts → types-CTOaTNtU.d.ts} +8 -1
- package/dist/web-react/index.d.ts +73 -0
- package/dist/web-react/index.js +264 -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-LT2YIMEB.js.map +0 -1
- /package/dist/{chunk-DXAMBUDL.js.map → chunk-HZZD3ZYD.js.map} +0 -0
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { C as CatalogModel, M as ModelCatalog, R as RouterModel, _ as __resetCatalogCache, b as buildCatalog, f as fetchModelCatalog, n as normalizeModelId } from '../model-catalog-BEAEVDaa.js';
|
|
1
2
|
export { C as CreateTangleRouterModelConfigOptions, D as DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR, a as DEFAULT_TANGLE_ROUTER_BASE_URL, R as ResolveModelOptions, b as ResolveUserTangleExecutionKeyForUserOptions, c as ResolveUserTangleExecutionKeyOptions, d as ResolvedTangleExecutionKey, T as TangleBillingEnforcementOptions, e as TangleExecutionEnvironment, f as TangleExecutionKeyError, g as TangleExecutionKeyErrorCode, h as TangleExecutionKeyHttpError, i as TangleExecutionKeySource, j as TangleModelConfig, k as createTangleRouterModelConfig, l as isTangleBillingEnforcementDisabled, m as isTangleExecutionKeyError, r as resolveTangleExecutionEnvironment, n as resolveTangleModelConfig, o as resolveUserTangleExecutionKey, p as resolveUserTangleExecutionKeyForUser, t as tangleExecutionKeyHttpError } from '../model-CKzniMMr.js';
|
|
2
|
-
import { b as AppToolContext, e as AppToolProducedEvent, f as AppToolTaxonomy, c as AppToolHandlers, d as AppToolOutcome } from '../types-
|
|
3
|
+
import { b as AppToolContext, e as AppToolProducedEvent, f as AppToolTaxonomy, c as AppToolHandlers, d as AppToolOutcome } from '../types-CTOaTNtU.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* OpenAI-compatible stream → `LoopEvent` adapter, for NON-sandbox copilots.
|
|
@@ -23,6 +24,9 @@ interface OpenAIStreamChunk {
|
|
|
23
24
|
choices?: Array<{
|
|
24
25
|
delta?: {
|
|
25
26
|
content?: string | null;
|
|
27
|
+
/** Reasoning deltas — DeepSeek/router use `reasoning_content`; some proxies use `thinking`. */
|
|
28
|
+
reasoning_content?: string | null;
|
|
29
|
+
thinking?: string | null;
|
|
26
30
|
tool_calls?: Array<{
|
|
27
31
|
index: number;
|
|
28
32
|
id?: string;
|
|
@@ -34,6 +38,11 @@ interface OpenAIStreamChunk {
|
|
|
34
38
|
};
|
|
35
39
|
finish_reason?: string | null;
|
|
36
40
|
}>;
|
|
41
|
+
/** Final-chunk token accounting (requires `stream_options.include_usage`). */
|
|
42
|
+
usage?: {
|
|
43
|
+
prompt_tokens?: number;
|
|
44
|
+
completion_tokens?: number;
|
|
45
|
+
} | null;
|
|
37
46
|
}
|
|
38
47
|
/**
|
|
39
48
|
* Map an OpenAI-compat streaming chunk iterator to `LoopEvent`s: each content
|
|
@@ -196,14 +205,25 @@ interface LoopMessage {
|
|
|
196
205
|
tool_call_id?: string;
|
|
197
206
|
}
|
|
198
207
|
/** Events a turn stream yields. `text` accumulates into the final answer;
|
|
199
|
-
* `tool_call` is collected for dispatch
|
|
200
|
-
*
|
|
208
|
+
* `tool_call` is collected for dispatch; `reasoning` and `usage` pass through
|
|
209
|
+
* for UIs that render thinking sections and per-message token/cost metrics.
|
|
210
|
+
* Extra event types pass through untouched (the caller re-emits them to its
|
|
211
|
+
* own UI stream). */
|
|
201
212
|
type LoopEvent = {
|
|
202
213
|
type: 'text';
|
|
203
214
|
text: string;
|
|
215
|
+
} | {
|
|
216
|
+
type: 'reasoning';
|
|
217
|
+
text: string;
|
|
204
218
|
} | {
|
|
205
219
|
type: 'tool_call';
|
|
206
220
|
call: LoopToolCall;
|
|
221
|
+
} | {
|
|
222
|
+
type: 'usage';
|
|
223
|
+
usage: {
|
|
224
|
+
promptTokens: number;
|
|
225
|
+
completionTokens: number;
|
|
226
|
+
};
|
|
207
227
|
} | {
|
|
208
228
|
type: 'other';
|
|
209
229
|
event: unknown;
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
__resetCatalogCache,
|
|
3
|
+
buildCatalog,
|
|
2
4
|
createAgentRuntime,
|
|
3
5
|
createOpenAICompatStreamTurn,
|
|
6
|
+
fetchModelCatalog,
|
|
7
|
+
normalizeModelId,
|
|
4
8
|
runAppToolLoop,
|
|
5
9
|
streamAppToolLoop,
|
|
6
10
|
toLoopEvents
|
|
7
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-EO4IGDQD.js";
|
|
8
12
|
import {
|
|
9
13
|
DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
|
|
10
14
|
DEFAULT_TANGLE_ROUTER_BASE_URL,
|
|
@@ -18,16 +22,20 @@ import {
|
|
|
18
22
|
resolveUserTangleExecutionKeyForUser,
|
|
19
23
|
tangleExecutionKeyHttpError
|
|
20
24
|
} from "../chunk-EHPK7GKR.js";
|
|
21
|
-
import "../chunk-
|
|
25
|
+
import "../chunk-JANT2G2E.js";
|
|
22
26
|
export {
|
|
23
27
|
DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
|
|
24
28
|
DEFAULT_TANGLE_ROUTER_BASE_URL,
|
|
25
29
|
TangleExecutionKeyError,
|
|
30
|
+
__resetCatalogCache,
|
|
31
|
+
buildCatalog,
|
|
26
32
|
createAgentRuntime,
|
|
27
33
|
createOpenAICompatStreamTurn,
|
|
28
34
|
createTangleRouterModelConfig,
|
|
35
|
+
fetchModelCatalog,
|
|
29
36
|
isTangleBillingEnforcementDisabled,
|
|
30
37
|
isTangleExecutionKeyError,
|
|
38
|
+
normalizeModelId,
|
|
31
39
|
resolveTangleExecutionEnvironment,
|
|
32
40
|
resolveTangleModelConfig,
|
|
33
41
|
resolveUserTangleExecutionKey,
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as AppToolContext, f as AppToolTaxonomy, c as AppToolHandlers, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-
|
|
2
|
-
export { A as AddCitationArgs, a as AddCitationResult, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from '../types-
|
|
1
|
+
import { b as AppToolContext, f as AppToolTaxonomy, c as AppToolHandlers, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-CTOaTNtU.js';
|
|
2
|
+
export { A as AddCitationArgs, a as AddCitationResult, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from '../types-CTOaTNtU.js';
|
|
3
3
|
|
|
4
4
|
/** A correctable bad-input error a tool handler throws; the HTTP layer maps it
|
|
5
5
|
* to a 4xx with the code, the runtime layer to a failed tool_result. So the
|
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-HZZD3ZYD.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-JANT2G2E.js";
|
|
21
21
|
export {
|
|
22
22
|
APP_TOOL_NAMES,
|
|
23
23
|
DEFAULT_APP_TOOL_PATHS,
|
|
@@ -46,6 +46,13 @@ interface SubmitProposalResult {
|
|
|
46
46
|
proposalId: string;
|
|
47
47
|
/** True when an identical (workspace, title) proposal already existed. */
|
|
48
48
|
deduped: boolean;
|
|
49
|
+
/** Handlers that execute a proposal type immediately (an unregulated
|
|
50
|
+
* generate-style action) return 'executed'; omitted/'queued_for_approval'
|
|
51
|
+
* means a human gate. Dispatch reports this verbatim to the model. */
|
|
52
|
+
status?: 'queued_for_approval' | 'executed';
|
|
53
|
+
/** Product-specific result fields (e.g. datasetId) — passed through to the
|
|
54
|
+
* tool outcome so the model and UI see what the handler actually did. */
|
|
55
|
+
[extra: string]: unknown;
|
|
49
56
|
}
|
|
50
57
|
interface ScheduleFollowupArgs {
|
|
51
58
|
title: string;
|
|
@@ -98,7 +105,7 @@ type AppToolProducedEvent = {
|
|
|
98
105
|
type: 'proposal_created';
|
|
99
106
|
proposalId: string;
|
|
100
107
|
title: string;
|
|
101
|
-
status: 'pending';
|
|
108
|
+
status: 'pending' | 'executed';
|
|
102
109
|
} | {
|
|
103
110
|
type: 'artifact';
|
|
104
111
|
path: string;
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
interface ChatToolCallInfo {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
status: 'running' | 'done' | 'error';
|
|
44
|
+
}
|
|
45
|
+
interface ChatUiMessage extends ChatMessageMetrics {
|
|
46
|
+
id: string;
|
|
47
|
+
role: 'user' | 'assistant' | 'system';
|
|
48
|
+
content: string;
|
|
49
|
+
reasoning?: string;
|
|
50
|
+
toolCalls?: ChatToolCallInfo[];
|
|
51
|
+
}
|
|
52
|
+
interface ChatMessagesProps {
|
|
53
|
+
messages: ChatUiMessage[];
|
|
54
|
+
/** Catalogue models, for per-message cost from pricing. Pass [] to skip cost. */
|
|
55
|
+
models?: CatalogModel[];
|
|
56
|
+
/** Markdown renderer for assistant content; default renders pre-wrapped text. */
|
|
57
|
+
renderMarkdown?: (content: string) => ReactNode;
|
|
58
|
+
/** Extra per-message content (artifacts, custom panels) appended after the body. */
|
|
59
|
+
renderExtras?: (message: ChatUiMessage) => ReactNode;
|
|
60
|
+
userLabel?: string;
|
|
61
|
+
agentLabel?: string;
|
|
62
|
+
/** Render the trailing "agent is thinking" row. */
|
|
63
|
+
loading?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The message thread: one centered column; user messages are right-aligned
|
|
67
|
+
* bubbles with a User label; agent messages carry an Agent meta line with
|
|
68
|
+
* model id, tokens/sec, and cost, plus a collapsible thinking section and
|
|
69
|
+
* tool-call chips.
|
|
70
|
+
*/
|
|
71
|
+
declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, }: ChatMessagesProps): react.JSX.Element;
|
|
72
|
+
|
|
73
|
+
export { type ChatMessageMetrics, ChatMessages, type ChatMessagesProps, type ChatToolCallInfo, type ChatUiMessage, EffortPicker, type EffortPickerProps, ModelPicker, type ModelPickerProps, formatModelCost, formatTokensPerSecond };
|
|
@@ -0,0 +1,264 @@
|
|
|
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 ToolChips({ toolCalls }) {
|
|
203
|
+
return /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-col gap-1", children: toolCalls.map((tc) => /* @__PURE__ */ jsxs(
|
|
204
|
+
"div",
|
|
205
|
+
{
|
|
206
|
+
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"}`,
|
|
207
|
+
children: [
|
|
208
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono opacity-70", children: tc.status === "running" ? "\u26A1" : tc.status === "error" ? "\u2717" : "\u2713" }),
|
|
209
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: tc.name }),
|
|
210
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-60", children: tc.status === "running" ? "running\u2026" : tc.status === "error" ? "failed" : "done" })
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
tc.id
|
|
214
|
+
)) });
|
|
215
|
+
}
|
|
216
|
+
function ChatMessages({
|
|
217
|
+
messages,
|
|
218
|
+
models = [],
|
|
219
|
+
renderMarkdown,
|
|
220
|
+
renderExtras,
|
|
221
|
+
userLabel = "User",
|
|
222
|
+
agentLabel = "Agent",
|
|
223
|
+
loading
|
|
224
|
+
}) {
|
|
225
|
+
const renderBody = renderMarkdown ?? ((content) => /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: content }));
|
|
226
|
+
const lastIsUser = messages[messages.length - 1]?.role === "user";
|
|
227
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
228
|
+
messages.map(
|
|
229
|
+
(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: [
|
|
230
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-right text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60", children: userLabel }),
|
|
231
|
+
/* @__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 }) })
|
|
232
|
+
] }) }, msg.id) : /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
|
|
233
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-baseline gap-2 text-[11px] tracking-wide text-muted-foreground/60", children: [
|
|
234
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold uppercase", children: agentLabel }),
|
|
235
|
+
msg.modelUsed && /* @__PURE__ */ jsx("span", { className: "font-mono normal-case", children: msg.modelUsed }),
|
|
236
|
+
formatTokensPerSecond(msg) && /* @__PURE__ */ jsx("span", { children: formatTokensPerSecond(msg) }),
|
|
237
|
+
formatModelCost(msg, models) && /* @__PURE__ */ jsx("span", { children: formatModelCost(msg, models) })
|
|
238
|
+
] }),
|
|
239
|
+
msg.reasoning && /* @__PURE__ */ jsxs("details", { className: "mb-2 rounded-md border border-border/40 bg-muted/30 px-3 py-2", children: [
|
|
240
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer select-none text-xs font-medium text-muted-foreground", children: "Thinking\u2026" }),
|
|
241
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 whitespace-pre-wrap text-sm text-muted-foreground/80", children: msg.reasoning })
|
|
242
|
+
] }),
|
|
243
|
+
/* @__PURE__ */ jsx("div", { className: "text-base leading-[1.75]", children: renderBody(msg.content) }),
|
|
244
|
+
msg.toolCalls && msg.toolCalls.length > 0 && /* @__PURE__ */ jsx(ToolChips, { toolCalls: msg.toolCalls }),
|
|
245
|
+
renderExtras?.(msg)
|
|
246
|
+
] }, msg.id)
|
|
247
|
+
),
|
|
248
|
+
loading && lastIsUser && /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-6 py-3", children: [
|
|
249
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground/60", children: agentLabel }),
|
|
250
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-base text-muted-foreground", children: [
|
|
251
|
+
/* @__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" }) }),
|
|
252
|
+
"Thinking..."
|
|
253
|
+
] })
|
|
254
|
+
] })
|
|
255
|
+
] });
|
|
256
|
+
}
|
|
257
|
+
export {
|
|
258
|
+
ChatMessages,
|
|
259
|
+
EffortPicker,
|
|
260
|
+
ModelPicker,
|
|
261
|
+
formatModelCost,
|
|
262
|
+
formatTokensPerSecond
|
|
263
|
+
};
|
|
264
|
+
//# 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// ── ChatMessages ──────────────────────────────────────────────────────────\n\nexport interface ChatToolCallInfo {\n id: string\n name: string\n status: 'running' | 'done' | 'error'\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}\n\nfunction ToolChips({ toolCalls }: { toolCalls: ChatToolCallInfo[] }) {\n return (\n <div className=\"mt-2 flex flex-col gap-1\">\n {toolCalls.map((tc) => (\n <div\n key={tc.id}\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 }`}\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 </div>\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}: 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 && <ToolChips toolCalls={msg.toolCalls} />}\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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"packageManager": "pnpm@10.33.4",
|
|
5
5
|
"description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent\u2192app tool side channel, integration-hub client, per-workspace billing, and crypto \u2014 composed over the Tangle agent substrate through typed seams.",
|
|
6
6
|
"keywords": [
|
|
@@ -122,6 +122,11 @@
|
|
|
122
122
|
"import": "./dist/web/index.js",
|
|
123
123
|
"default": "./dist/web/index.js"
|
|
124
124
|
},
|
|
125
|
+
"./web-react": {
|
|
126
|
+
"types": "./dist/web-react/index.d.ts",
|
|
127
|
+
"import": "./dist/web-react/index.js",
|
|
128
|
+
"default": "./dist/web-react/index.js"
|
|
129
|
+
},
|
|
125
130
|
"./redact": {
|
|
126
131
|
"types": "./dist/redact/index.d.ts",
|
|
127
132
|
"import": "./dist/redact/index.js",
|
|
@@ -148,13 +153,16 @@
|
|
|
148
153
|
"@types/node": "^25.6.0",
|
|
149
154
|
"tsup": "^8.0.0",
|
|
150
155
|
"typescript": "^5.7.0",
|
|
151
|
-
"vitest": "^3.0.0"
|
|
156
|
+
"vitest": "^3.0.0",
|
|
157
|
+
"react": "^19.0.0",
|
|
158
|
+
"@types/react": "^19.0.0"
|
|
152
159
|
},
|
|
153
160
|
"peerDependencies": {
|
|
154
161
|
"@tangle-network/agent-eval": ">=0.82.0",
|
|
155
162
|
"@tangle-network/agent-integrations": ">=0.32.0",
|
|
156
163
|
"@tangle-network/agent-knowledge": ">=1.5.0",
|
|
157
|
-
"@tangle-network/agent-runtime": ">=0.21.0"
|
|
164
|
+
"@tangle-network/agent-runtime": ">=0.21.0",
|
|
165
|
+
"react": ">=18"
|
|
158
166
|
},
|
|
159
167
|
"peerDependenciesMeta": {
|
|
160
168
|
"@tangle-network/agent-knowledge": {
|
|
@@ -162,6 +170,9 @@
|
|
|
162
170
|
},
|
|
163
171
|
"@tangle-network/agent-runtime": {
|
|
164
172
|
"optional": true
|
|
173
|
+
},
|
|
174
|
+
"react": {
|
|
175
|
+
"optional": true
|
|
165
176
|
}
|
|
166
177
|
}
|
|
167
178
|
}
|