agentic-ui-kit 0.7.1 → 0.8.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/core.js +2 -2
- package/dist/index.js +835 -23
- package/dist/{src-D01l3nBc.js → src-BrPYkHdt.js} +285 -1
- package/dist/styles.css +1 -1
- package/dist/types/core/src/ask-user.d.ts +81 -0
- package/dist/types/core/src/index.d.ts +4 -0
- package/dist/types/core/src/ui-components.d.ts +103 -0
- package/dist/types/react/src/components/activity-group.d.ts +1 -1
- package/dist/types/react/src/components/ask-user.d.ts +17 -0
- package/dist/types/react/src/components/markdown.d.ts +17 -1
- package/dist/types/react/src/components/tool-prompt.d.ts +14 -0
- package/dist/types/react/src/components/tools.d.ts +16 -0
- package/dist/types/react/src/components/ui-components.d.ts +42 -0
- package/dist/types/react/src/components/ui-tree.d.ts +15 -5
- package/dist/types/react/src/hooks/use-chat.d.ts +21 -1
- package/dist/types/react/src/index.d.ts +9 -4
- package/dist/types/react/src/primitives/icons.d.ts +8 -0
- package/dist/types/react/src/primitives/labels.d.ts +36 -0
- package/dist/types/react/src/views/chat-view.d.ts +12 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as widgetLoaderMessages, C as readAskUserResult, D as resolveReplaces, F as activityTokens, H as settleToolCall, I as applyEvent, L as awaitingToolCalls, M as readEventStream, P as toBlocks, S as readAskUserInput, W as salvageJson, b as uiActionSchema, c as parseUiMarkup, g as matchesSchema, h as isStateRef, r as uiSpecSet, w as replacedId } from "./src-BrPYkHdt.js";
|
|
3
3
|
import { clsx } from "clsx";
|
|
4
4
|
import { extendTailwindMerge } from "tailwind-merge";
|
|
5
5
|
import { Children, Component, Fragment, createContext, createElement, isValidElement, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { Autocomplete, Button as Button$1, Dialog, DialogTrigger, DialogTrigger as DialogTrigger$1, Disclosure as Disclosure$1, DisclosurePanel, Focusable, Header, Heading, Input, Keyboard, Link, Menu as Menu$1, MenuItem as MenuItem$1, MenuSection as MenuSection$1, MenuTrigger, MenuTrigger as MenuTrigger$1, Modal as Modal$1, ModalOverlay, Popover as Popover$1, SearchField, Separator, SubmenuTrigger, Text, ToggleButton, ToggleButtonGroup, Tooltip as Tooltip$1, TooltipTrigger, useFilter } from "react-aria-components";
|
|
7
7
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { AlertTriangle, ArrowDown, ArrowUp, Check, ChevronDown, ChevronRight, ChevronsUpDown, Copy, FileText, Folder, FolderOpen, FolderPlus, Globe, Info, MessageSquare, Mic, MoreHorizontal, PanelLeft, Pencil, Pin, PinOff, Plus, RefreshCw, Share, ShieldQuestion, Square, ThumbsDown, ThumbsUp, Trash2, Volume2, X } from "lucide-react";
|
|
8
|
+
import { AlertTriangle, ArrowDown, ArrowRight, ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronsUpDown, Copy, FileText, Folder, FolderOpen, FolderPlus, Globe, GripVertical, Info, MessageSquare, Mic, MoreHorizontal, PanelLeft, Pencil, Pin, PinOff, Plus, RefreshCw, Share, ShieldQuestion, Square, ThumbsDown, ThumbsUp, Trash2, Volume2, X } from "lucide-react";
|
|
9
9
|
import { UNSAFE_PortalProvider } from "react-aria";
|
|
10
10
|
import { createMathPlugin } from "@streamdown/math";
|
|
11
11
|
import { Streamdown, defaultComponents, defaultRehypePlugins } from "streamdown";
|
|
@@ -204,6 +204,10 @@ const defaultIcons = {
|
|
|
204
204
|
info: /* @__PURE__ */ jsx(Info, {}),
|
|
205
205
|
warning: /* @__PURE__ */ jsx(AlertTriangle, {}),
|
|
206
206
|
approval: /* @__PURE__ */ jsx(ShieldQuestion, {}),
|
|
207
|
+
askPrevious: /* @__PURE__ */ jsx(ChevronLeft, {}),
|
|
208
|
+
askPagerNext: /* @__PURE__ */ jsx(ChevronRight, {}),
|
|
209
|
+
askNext: /* @__PURE__ */ jsx(ArrowRight, {}),
|
|
210
|
+
dragHandle: /* @__PURE__ */ jsx(GripVertical, {}),
|
|
207
211
|
web: /* @__PURE__ */ jsx(Globe, {}),
|
|
208
212
|
pin: /* @__PURE__ */ jsx(Pin, {}),
|
|
209
213
|
unpin: /* @__PURE__ */ jsx(PinOff, {}),
|
|
@@ -301,6 +305,24 @@ const defaultLabels = {
|
|
|
301
305
|
tokenCount: (count) => `${count} tokens`,
|
|
302
306
|
replyPlaceholder: "Reply…",
|
|
303
307
|
replyDirectly: "Or reply directly…",
|
|
308
|
+
askUserPrompt: "Question for you",
|
|
309
|
+
askUserPager: (current, total) => `${current} of ${total}`,
|
|
310
|
+
askUserPrevious: "Previous question",
|
|
311
|
+
askUserNext: "Next question",
|
|
312
|
+
askUserContinue: "Continue",
|
|
313
|
+
askUserSubmit: "Send answers",
|
|
314
|
+
askUserClose: "Close and decline",
|
|
315
|
+
askUserSomethingElse: "Something else",
|
|
316
|
+
askUserSkip: "Skip",
|
|
317
|
+
askUserSelected: (count) => `${count} selected`,
|
|
318
|
+
askUserRankHint: "Drag to re-order",
|
|
319
|
+
askUserReorder: (option) => `Move ${option}`,
|
|
320
|
+
askUserReorderKeys: "Press Alt with the up or down arrow to move",
|
|
321
|
+
askUserPicked: (options) => options.join(", "),
|
|
322
|
+
askUserRanked: (options) => `Ranked: ${options.map((o, i) => `${i + 1}. ${o}`).join(" ")}`,
|
|
323
|
+
askUserSkipped: "Skipped",
|
|
324
|
+
askUserDeclined: "You declined to answer",
|
|
325
|
+
askUserAnswers: "Your answers",
|
|
304
326
|
message: "Message",
|
|
305
327
|
stop: "Stop",
|
|
306
328
|
send: "Send",
|
|
@@ -1070,32 +1092,36 @@ function userMessage(text, parentId, attachments = []) {
|
|
|
1070
1092
|
};
|
|
1071
1093
|
}
|
|
1072
1094
|
const wantsApproval = (part, tool) => part.approval === void 0 && (part.needsApproval === true || (typeof tool?.approval === "function" ? tool.approval(part.input) : tool?.approval === true));
|
|
1095
|
+
const wantsPrompt = (part, promptTools, tool) => part.approval !== "denied" && (promptTools.includes(part.name) || tool?.view?.Prompt !== void 0);
|
|
1073
1096
|
const DECLINED = "Declined by the user";
|
|
1074
1097
|
/**
|
|
1075
1098
|
* A linear conversation driven by a Transport: sends, streams the reply through `applyEvent`, stops and
|
|
1076
1099
|
* retries. With an `AgentProvider` it also runs the agent loop: client tools run in the page, approvals
|
|
1077
1100
|
* wait for the user, then the reply continues; every finished tool call reaches its tool's `onResult`.
|
|
1078
1101
|
*/
|
|
1079
|
-
function useChat({ transport, conversationId, initialMessages = [], onTitle, agent, maxSteps = 12, resume = false }) {
|
|
1102
|
+
function useChat({ transport, conversationId, initialMessages = [], onTitle, agent, maxSteps = 12, resume = false, promptTools = ["ask_user"] }) {
|
|
1080
1103
|
const contextAgent = useAgentRegistry();
|
|
1081
1104
|
const registry = agent === void 0 ? contextAgent : agent;
|
|
1082
1105
|
const [messages, setMessages] = useState(initialMessages);
|
|
1083
1106
|
const [busy, setBusy] = useState(false);
|
|
1084
1107
|
const [approvals, setApprovals] = useState([]);
|
|
1108
|
+
const [prompts, setPrompts] = useState([]);
|
|
1085
1109
|
const run = useRef(null);
|
|
1086
1110
|
const latest = useRef({
|
|
1087
1111
|
messages,
|
|
1088
1112
|
transport,
|
|
1089
1113
|
conversationId,
|
|
1090
1114
|
onTitle,
|
|
1091
|
-
registry
|
|
1115
|
+
registry,
|
|
1116
|
+
promptTools
|
|
1092
1117
|
});
|
|
1093
1118
|
latest.current = {
|
|
1094
1119
|
messages,
|
|
1095
1120
|
transport,
|
|
1096
1121
|
conversationId,
|
|
1097
1122
|
onTitle,
|
|
1098
|
-
registry
|
|
1123
|
+
registry,
|
|
1124
|
+
promptTools
|
|
1099
1125
|
};
|
|
1100
1126
|
const loop = useRef({
|
|
1101
1127
|
options: {},
|
|
@@ -1210,6 +1236,15 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
1210
1236
|
setBusy(false);
|
|
1211
1237
|
return;
|
|
1212
1238
|
}
|
|
1239
|
+
const prompted = awaiting.filter((p) => wantsPrompt(p, latest.current.promptTools, registry?.tool(p.name)));
|
|
1240
|
+
if (prompted.length > 0) {
|
|
1241
|
+
setPrompts(prompted.map((part) => ({
|
|
1242
|
+
messageId: reply.id,
|
|
1243
|
+
part
|
|
1244
|
+
})));
|
|
1245
|
+
setBusy(false);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1213
1248
|
const controller = new AbortController();
|
|
1214
1249
|
const current = {
|
|
1215
1250
|
controller,
|
|
@@ -1298,6 +1333,29 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
1298
1333
|
advance,
|
|
1299
1334
|
commit
|
|
1300
1335
|
]);
|
|
1336
|
+
const settlePrompt = useCallback((partId, fields) => {
|
|
1337
|
+
const pending = prompts.find((p) => p.part.id === partId);
|
|
1338
|
+
if (!pending) return;
|
|
1339
|
+
const message = latest.current.messages.find((m) => m.id === pending.messageId);
|
|
1340
|
+
if (!message) return;
|
|
1341
|
+
const next = settleToolCall(message, partId, fields);
|
|
1342
|
+
commit(next);
|
|
1343
|
+
const rest = prompts.filter((p) => p.part.id !== partId);
|
|
1344
|
+
setPrompts(rest);
|
|
1345
|
+
if (rest.length === 0) advance(next);
|
|
1346
|
+
}, [
|
|
1347
|
+
prompts,
|
|
1348
|
+
advance,
|
|
1349
|
+
commit
|
|
1350
|
+
]);
|
|
1351
|
+
const answer = useCallback((partId, result) => settlePrompt(partId, {
|
|
1352
|
+
status: "done",
|
|
1353
|
+
result
|
|
1354
|
+
}), [settlePrompt]);
|
|
1355
|
+
const decline = useCallback((partId) => settlePrompt(partId, {
|
|
1356
|
+
status: "error",
|
|
1357
|
+
error: DECLINED
|
|
1358
|
+
}), [settlePrompt]);
|
|
1301
1359
|
/** Declines whatever is waiting, e.g. because the user moved on. */
|
|
1302
1360
|
const declineAll = useCallback(() => {
|
|
1303
1361
|
for (const { messageId, part } of approvals) {
|
|
@@ -1313,6 +1371,7 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
1313
1371
|
error: DECLINED
|
|
1314
1372
|
}));
|
|
1315
1373
|
setApprovals([]);
|
|
1374
|
+
setPrompts([]);
|
|
1316
1375
|
}, [approvals, commit]);
|
|
1317
1376
|
const send = useCallback((text, options = {}) => {
|
|
1318
1377
|
const attachments = options.attachments ?? [];
|
|
@@ -1337,6 +1396,7 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
1337
1396
|
const index = messages.findIndex((m) => m.id === assistantId);
|
|
1338
1397
|
if (index < 1 || messages[index - 1].role !== "user") return;
|
|
1339
1398
|
setApprovals([]);
|
|
1399
|
+
setPrompts([]);
|
|
1340
1400
|
loop.current = {
|
|
1341
1401
|
options,
|
|
1342
1402
|
step: 0
|
|
@@ -1372,6 +1432,9 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
1372
1432
|
busy,
|
|
1373
1433
|
approvals,
|
|
1374
1434
|
respond,
|
|
1435
|
+
prompts,
|
|
1436
|
+
answer,
|
|
1437
|
+
decline,
|
|
1375
1438
|
send,
|
|
1376
1439
|
stop,
|
|
1377
1440
|
retry
|
|
@@ -2211,17 +2274,18 @@ function Node({ node, registry }) {
|
|
|
2211
2274
|
}) });
|
|
2212
2275
|
}
|
|
2213
2276
|
/** Draws a `render_ui` tree with the registered components. Unknown components and bad props render nothing. */
|
|
2214
|
-
function UiTree({ id, root, state: initial, streaming = false }) {
|
|
2277
|
+
function UiTree({ id, stateKey, root, state: initial, streaming = false }) {
|
|
2278
|
+
const storeKey = stateKey ?? id;
|
|
2215
2279
|
const registry = useContext(RegistryContext);
|
|
2216
2280
|
const host = useWidgetHost();
|
|
2217
|
-
const [local, setLocal] = useState(() => registry?.persist?.load(
|
|
2281
|
+
const [local, setLocal] = useState(() => registry?.persist?.load(storeKey) ?? {});
|
|
2218
2282
|
const changed = useRef(false);
|
|
2219
2283
|
const latest = useRef(host);
|
|
2220
2284
|
latest.current = host;
|
|
2221
2285
|
useEffect(() => {
|
|
2222
|
-
if (changed.current) registry?.persist?.save(
|
|
2286
|
+
if (changed.current) registry?.persist?.save(storeKey, local);
|
|
2223
2287
|
}, [
|
|
2224
|
-
|
|
2288
|
+
storeKey,
|
|
2225
2289
|
local,
|
|
2226
2290
|
registry
|
|
2227
2291
|
]);
|
|
@@ -2272,15 +2336,20 @@ function useUiMarkupLanguage() {
|
|
|
2272
2336
|
return registry && registry.markup !== false ? registry.markup : void 0;
|
|
2273
2337
|
}
|
|
2274
2338
|
/** A ```ui block from a reply, drawn with the registered components. Safe on a block that is still streaming. */
|
|
2275
|
-
function UiMarkupBlock({ id, source, streaming = false }) {
|
|
2339
|
+
function UiMarkupBlock({ id, stateKey, source, streaming = false }) {
|
|
2276
2340
|
const { nodes, state } = useMemo(() => parseUiMarkup(source), [source]);
|
|
2277
2341
|
return /* @__PURE__ */ jsx(UiTree, {
|
|
2278
2342
|
id,
|
|
2343
|
+
...stateKey && { stateKey },
|
|
2279
2344
|
root: nodes,
|
|
2280
2345
|
state,
|
|
2281
2346
|
streaming
|
|
2282
2347
|
});
|
|
2283
2348
|
}
|
|
2349
|
+
function stateKeyOf(options, part) {
|
|
2350
|
+
const stateKey = options.stateKey?.(part);
|
|
2351
|
+
return stateKey ? { stateKey } : {};
|
|
2352
|
+
}
|
|
2284
2353
|
/**
|
|
2285
2354
|
* A tool view that draws a call's (streaming) JSX markup with your registered components, for whatever tool
|
|
2286
2355
|
* your harness uses; the suggested one is core's `showUiTool`. Register it under your tool's name:
|
|
@@ -2289,12 +2358,13 @@ function UiMarkupBlock({ id, source, streaming = false }) {
|
|
|
2289
2358
|
function uiMarkupToolView(read = (input) => {
|
|
2290
2359
|
const markup = input?.markup;
|
|
2291
2360
|
return typeof markup === "string" ? markup : void 0;
|
|
2292
|
-
}) {
|
|
2361
|
+
}, options = {}) {
|
|
2293
2362
|
function UiMarkupToolBlock({ part }) {
|
|
2294
2363
|
const markup = read(part.input ?? part.partialInput);
|
|
2295
2364
|
if (!markup) return null;
|
|
2296
2365
|
return /* @__PURE__ */ jsx(UiMarkupBlock, {
|
|
2297
2366
|
id: part.id,
|
|
2367
|
+
...stateKeyOf(options, part),
|
|
2298
2368
|
source: markup,
|
|
2299
2369
|
streaming: part.status === "streaming" || part.status === "input"
|
|
2300
2370
|
});
|
|
@@ -2318,13 +2388,14 @@ function readTreeInput(input) {
|
|
|
2318
2388
|
* your tool's input to the tree and its starting state; the default reads `{ root, state }`, the shape of
|
|
2319
2389
|
* the suggested `renderUiTool`.
|
|
2320
2390
|
*/
|
|
2321
|
-
function uiTreeToolView(read = readTreeInput) {
|
|
2391
|
+
function uiTreeToolView(read = readTreeInput, options = {}) {
|
|
2322
2392
|
function UiTreeToolBlock({ part }) {
|
|
2323
2393
|
const { root, state } = read(part.input ?? part.partialInput);
|
|
2324
2394
|
if (!root) return null;
|
|
2325
2395
|
const ok = typeof state === "object" && state !== null;
|
|
2326
2396
|
return /* @__PURE__ */ jsx(UiTree, {
|
|
2327
2397
|
id: part.id,
|
|
2398
|
+
...stateKeyOf(options, part),
|
|
2328
2399
|
root,
|
|
2329
2400
|
...ok && { state },
|
|
2330
2401
|
streaming: part.status === "streaming" || part.status === "input"
|
|
@@ -2417,7 +2488,19 @@ function rehypeWordFade() {
|
|
|
2417
2488
|
return (tree) => wrap(tree);
|
|
2418
2489
|
}
|
|
2419
2490
|
// src/components/markdown.tsx
|
|
2420
|
-
const
|
|
2491
|
+
const [sanitize, sanitizeSchema] = defaultRehypePlugins.sanitize;
|
|
2492
|
+
const rehypePlugins = Object.values({
|
|
2493
|
+
...defaultRehypePlugins,
|
|
2494
|
+
sanitize: [sanitize, {
|
|
2495
|
+
...sanitizeSchema,
|
|
2496
|
+
tagNames: sanitizeSchema.tagNames?.filter((t) => t !== "source"),
|
|
2497
|
+
protocols: {
|
|
2498
|
+
...sanitizeSchema.protocols,
|
|
2499
|
+
src: [...sanitizeSchema.protocols?.src ?? [], "data"]
|
|
2500
|
+
}
|
|
2501
|
+
}]
|
|
2502
|
+
});
|
|
2503
|
+
const fadePlugins = [...rehypePlugins, rehypeWordFade];
|
|
2421
2504
|
const math = createMathPlugin({ singleDollarTextMath: true });
|
|
2422
2505
|
/** Streamdown's own utility classes get this prefix, so neither they nor the host's classes apply. */
|
|
2423
2506
|
const PREFIX = "llmchat";
|
|
@@ -2450,6 +2533,15 @@ function Code({ node, className, children }) {
|
|
|
2450
2533
|
});
|
|
2451
2534
|
}
|
|
2452
2535
|
const components = {
|
|
2536
|
+
...Object.fromEntries([
|
|
2537
|
+
"source",
|
|
2538
|
+
"video",
|
|
2539
|
+
"audio",
|
|
2540
|
+
"track",
|
|
2541
|
+
"iframe",
|
|
2542
|
+
"embed",
|
|
2543
|
+
"object"
|
|
2544
|
+
].map((t) => [t, () => null])),
|
|
2453
2545
|
pre: ({ children }) => /* @__PURE__ */ jsx(Fragment$1, { children }),
|
|
2454
2546
|
code: ({ node, className, children }) => /* @__PURE__ */ jsx(Code, {
|
|
2455
2547
|
node,
|
|
@@ -2458,11 +2550,18 @@ const components = {
|
|
|
2458
2550
|
})
|
|
2459
2551
|
};
|
|
2460
2552
|
const LinksContext = createContext(void 0);
|
|
2461
|
-
function MarkdownLinksProvider({ renderLink, renderLinkList, children }) {
|
|
2553
|
+
function MarkdownLinksProvider({ renderLink, renderLinkList, renderImage, imageOrigins, children }) {
|
|
2462
2554
|
const value = useMemo(() => ({
|
|
2463
2555
|
renderLink,
|
|
2464
|
-
renderLinkList
|
|
2465
|
-
|
|
2556
|
+
renderLinkList,
|
|
2557
|
+
renderImage,
|
|
2558
|
+
imageOrigins
|
|
2559
|
+
}), [
|
|
2560
|
+
renderLink,
|
|
2561
|
+
renderLinkList,
|
|
2562
|
+
renderImage,
|
|
2563
|
+
imageOrigins
|
|
2564
|
+
]);
|
|
2466
2565
|
return /* @__PURE__ */ jsx(LinksContext.Provider, {
|
|
2467
2566
|
value,
|
|
2468
2567
|
children
|
|
@@ -2479,6 +2578,32 @@ function soleLink(children) {
|
|
|
2479
2578
|
};
|
|
2480
2579
|
return props.node?.tagName === "p" ? soleLink(props.children) : void 0;
|
|
2481
2580
|
}
|
|
2581
|
+
const originOf = (url, base) => {
|
|
2582
|
+
try {
|
|
2583
|
+
return new URL(url, base).origin;
|
|
2584
|
+
} catch {
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
/** Whether an image may load on render. Relative and protocol-relative URLs count as the page's origin or the one they name. */
|
|
2589
|
+
function imageAllowed(src, origins) {
|
|
2590
|
+
if (/^data:image\//i.test(src)) return true;
|
|
2591
|
+
if (origins === "all") return /^(https?:)?\/\//i.test(src) || !/^[a-z][a-z\d+.-]*:/i.test(src);
|
|
2592
|
+
if (!origins?.length) return false;
|
|
2593
|
+
const origin = originOf(src, typeof location === "undefined" ? void 0 : location.href);
|
|
2594
|
+
return origin !== void 0 && /^https?:/.test(origin) && origins.some((o) => (originOf(o) ?? o) === origin);
|
|
2595
|
+
}
|
|
2596
|
+
/** What an image the host did not allow becomes: a link to it (alt text or host name), opened only by the reader. */
|
|
2597
|
+
function ImageLink({ src, alt }) {
|
|
2598
|
+
const url = URL.canParse(src) ? new URL(src) : void 0;
|
|
2599
|
+
if (url?.protocol !== "http:" && url?.protocol !== "https:") return /* @__PURE__ */ jsx(Fragment$1, { children: alt });
|
|
2600
|
+
return /* @__PURE__ */ jsx("a", {
|
|
2601
|
+
href: src,
|
|
2602
|
+
target: "_blank",
|
|
2603
|
+
rel: "noopener noreferrer",
|
|
2604
|
+
children: alt || url.host
|
|
2605
|
+
});
|
|
2606
|
+
}
|
|
2482
2607
|
function listOf(ordered, props, renderLinkList) {
|
|
2483
2608
|
const Default = ordered ? defaultComponents.ol : defaultComponents.ul;
|
|
2484
2609
|
const { children } = props;
|
|
@@ -2521,9 +2646,27 @@ function BlockScope({ id, streaming, children }) {
|
|
|
2521
2646
|
});
|
|
2522
2647
|
}
|
|
2523
2648
|
const MarkdownBody = memo(function MarkdownBody({ text, animating, fade }) {
|
|
2524
|
-
const { renderLink, renderLinkList } = useContext(LinksContext) ?? {};
|
|
2649
|
+
const { renderLink, renderLinkList, renderImage, imageOrigins } = useContext(LinksContext) ?? {};
|
|
2525
2650
|
const withLinks = useMemo(() => ({
|
|
2526
2651
|
...components,
|
|
2652
|
+
img: ({ src, alt }) => {
|
|
2653
|
+
if (typeof src !== "string" || !src) return /* @__PURE__ */ jsx(Fragment$1, { children: alt });
|
|
2654
|
+
if (renderImage) return /* @__PURE__ */ jsx(Fragment$1, { children: renderImage({
|
|
2655
|
+
src,
|
|
2656
|
+
alt: alt ?? ""
|
|
2657
|
+
}) });
|
|
2658
|
+
if (imageAllowed(src, imageOrigins)) return /* @__PURE__ */ jsx("img", {
|
|
2659
|
+
src,
|
|
2660
|
+
alt: alt ?? "",
|
|
2661
|
+
loading: "lazy",
|
|
2662
|
+
referrerPolicy: "no-referrer",
|
|
2663
|
+
className: "max-w-full rounded-chat-md"
|
|
2664
|
+
});
|
|
2665
|
+
return /* @__PURE__ */ jsx(ImageLink, {
|
|
2666
|
+
src,
|
|
2667
|
+
alt: alt ?? ""
|
|
2668
|
+
});
|
|
2669
|
+
},
|
|
2527
2670
|
...renderLink && { a: ({ href, children }) => {
|
|
2528
2671
|
const custom = href ? renderLink({
|
|
2529
2672
|
href,
|
|
@@ -2540,11 +2683,16 @@ const MarkdownBody = memo(function MarkdownBody({ text, animating, fade }) {
|
|
|
2540
2683
|
ul: (props) => listOf(false, props, renderLinkList),
|
|
2541
2684
|
ol: (props) => listOf(true, props, renderLinkList)
|
|
2542
2685
|
}
|
|
2543
|
-
}), [
|
|
2686
|
+
}), [
|
|
2687
|
+
renderLink,
|
|
2688
|
+
renderLinkList,
|
|
2689
|
+
renderImage,
|
|
2690
|
+
imageOrigins
|
|
2691
|
+
]);
|
|
2544
2692
|
return /* @__PURE__ */ jsx(Streamdown, {
|
|
2545
2693
|
prefix: PREFIX,
|
|
2546
2694
|
mode: animating || fade ? "streaming" : "static",
|
|
2547
|
-
|
|
2695
|
+
rehypePlugins: fade ? fadePlugins : rehypePlugins,
|
|
2548
2696
|
plugins: { math },
|
|
2549
2697
|
components: withLinks,
|
|
2550
2698
|
controls: false,
|
|
@@ -2553,6 +2701,406 @@ const MarkdownBody = memo(function MarkdownBody({ text, animating, fade }) {
|
|
|
2553
2701
|
children: text
|
|
2554
2702
|
});
|
|
2555
2703
|
});
|
|
2704
|
+
// src/components/ask-user.tsx
|
|
2705
|
+
const rowClass$1 = "flex w-full cursor-pointer items-center gap-3 rounded-chat-md px-2.5 py-2 text-left text-(length:--chat-ui-font-size) leading-5 text-chat-fg outline-none hover:bg-chat-hover focus-visible:ring-2 focus-visible:ring-chat-focus";
|
|
2706
|
+
/** Moves focus to the previous or next answer row of a question, without wrapping. */
|
|
2707
|
+
function moveFocus(root, from, delta) {
|
|
2708
|
+
const rows = [...root.querySelectorAll("[data-ask-row]")];
|
|
2709
|
+
const i = rows.findIndex((row) => row === from || row.contains(from));
|
|
2710
|
+
rows[Math.min(rows.length - 1, Math.max(0, i + delta))]?.focus();
|
|
2711
|
+
}
|
|
2712
|
+
const isTextField = (target) => target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement;
|
|
2713
|
+
/** The 1-based number a plain digit key stands for, or undefined. */
|
|
2714
|
+
function digitOf(e) {
|
|
2715
|
+
if (e.altKey || e.ctrlKey || e.metaKey || isTextField(e.target)) return void 0;
|
|
2716
|
+
return /^[1-9]$/.test(e.key) ? Number(e.key) : void 0;
|
|
2717
|
+
}
|
|
2718
|
+
function Num({ children, active }) {
|
|
2719
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2720
|
+
"aria-hidden": true,
|
|
2721
|
+
className: clsx("flex size-6 shrink-0 items-center justify-center rounded-chat-sm border text-(length:--chat-ui-font-size-sm) tabular-nums", active ? "border-chat-accent bg-chat-accent text-chat-accent-fg" : "border-chat-border-strong text-chat-muted"),
|
|
2722
|
+
children
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
function Check$1({ checked }) {
|
|
2726
|
+
const icons = useIcons();
|
|
2727
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2728
|
+
"aria-hidden": true,
|
|
2729
|
+
className: clsx("flex size-5 shrink-0 items-center justify-center rounded-chat-sm border [&_svg]:size-3.5", checked ? "border-chat-accent bg-chat-accent text-chat-accent-fg" : "border-chat-border-strong"),
|
|
2730
|
+
children: checked && icons.check
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
/** The "Something else" row: a text input the number key after the last option focuses. */
|
|
2734
|
+
function OtherRow({ number, value, onChange, onEnter, leading }) {
|
|
2735
|
+
const labels = useLabels();
|
|
2736
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2737
|
+
className: clsx(rowClass$1, "cursor-text"),
|
|
2738
|
+
children: [leading, /* @__PURE__ */ jsx("input", {
|
|
2739
|
+
"data-ask-row": true,
|
|
2740
|
+
"data-ask-other": true,
|
|
2741
|
+
value,
|
|
2742
|
+
onChange: (e) => onChange(e.target.value),
|
|
2743
|
+
onKeyDown: (e) => {
|
|
2744
|
+
if (e.key === "Enter" && !e.nativeEvent.isComposing) {
|
|
2745
|
+
e.preventDefault();
|
|
2746
|
+
onEnter();
|
|
2747
|
+
}
|
|
2748
|
+
},
|
|
2749
|
+
placeholder: labels.askUserSomethingElse,
|
|
2750
|
+
"aria-label": labels.askUserSomethingElse,
|
|
2751
|
+
"aria-keyshortcuts": String(number),
|
|
2752
|
+
className: "min-w-0 flex-1 bg-transparent text-chat-fg outline-none placeholder:text-chat-subtle"
|
|
2753
|
+
})]
|
|
2754
|
+
});
|
|
2755
|
+
}
|
|
2756
|
+
function Footer({ start, onSkip, next }) {
|
|
2757
|
+
const labels = useLabels();
|
|
2758
|
+
const icons = useIcons();
|
|
2759
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2760
|
+
className: "mt-2 flex items-center gap-2 px-1",
|
|
2761
|
+
children: [
|
|
2762
|
+
/* @__PURE__ */ jsx("span", {
|
|
2763
|
+
className: "min-w-0 flex-1 truncate text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle",
|
|
2764
|
+
children: start
|
|
2765
|
+
}),
|
|
2766
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2767
|
+
size: "sm",
|
|
2768
|
+
variant: "ghost",
|
|
2769
|
+
className: "text-chat-muted",
|
|
2770
|
+
onPress: onSkip,
|
|
2771
|
+
children: labels.askUserSkip
|
|
2772
|
+
}),
|
|
2773
|
+
next && /* @__PURE__ */ jsx(IconButton, {
|
|
2774
|
+
label: next.label,
|
|
2775
|
+
variant: "primary",
|
|
2776
|
+
size: "sm",
|
|
2777
|
+
isDisabled: next.disabled,
|
|
2778
|
+
onPress: next.onPress,
|
|
2779
|
+
children: icons.askNext
|
|
2780
|
+
})
|
|
2781
|
+
]
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
function SingleQuestion({ question, initial, onAnswer, onSkip }) {
|
|
2785
|
+
const { options } = question;
|
|
2786
|
+
const chosen = typeof initial?.answer === "string" ? initial.answer : void 0;
|
|
2787
|
+
const [other, setOther] = useState(chosen !== void 0 && !options.includes(chosen) ? chosen : "");
|
|
2788
|
+
const root = useRef(null);
|
|
2789
|
+
const submitOther = () => other.trim() && onAnswer(other.trim());
|
|
2790
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2791
|
+
ref: root,
|
|
2792
|
+
role: "group",
|
|
2793
|
+
"aria-label": question.question,
|
|
2794
|
+
onKeyDown: (e) => {
|
|
2795
|
+
const n = digitOf(e);
|
|
2796
|
+
if (n !== void 0 && n <= options.length) onAnswer(options[n - 1]);
|
|
2797
|
+
else if (n === options.length + 1) root.current?.querySelector("[data-ask-other]")?.focus();
|
|
2798
|
+
else return;
|
|
2799
|
+
e.preventDefault();
|
|
2800
|
+
},
|
|
2801
|
+
children: [
|
|
2802
|
+
options.map((option, i) => /* @__PURE__ */ jsxs("button", {
|
|
2803
|
+
type: "button",
|
|
2804
|
+
"data-ask-row": true,
|
|
2805
|
+
onClick: () => onAnswer(option),
|
|
2806
|
+
className: rowClass$1,
|
|
2807
|
+
children: [/* @__PURE__ */ jsx(Num, {
|
|
2808
|
+
active: option === chosen,
|
|
2809
|
+
children: i + 1
|
|
2810
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2811
|
+
className: "min-w-0 flex-1",
|
|
2812
|
+
children: option
|
|
2813
|
+
})]
|
|
2814
|
+
}, i)),
|
|
2815
|
+
/* @__PURE__ */ jsx(OtherRow, {
|
|
2816
|
+
number: options.length + 1,
|
|
2817
|
+
value: other,
|
|
2818
|
+
onChange: setOther,
|
|
2819
|
+
onEnter: submitOther,
|
|
2820
|
+
leading: /* @__PURE__ */ jsx(Num, {
|
|
2821
|
+
active: other !== "" && other === chosen,
|
|
2822
|
+
children: options.length + 1
|
|
2823
|
+
})
|
|
2824
|
+
}),
|
|
2825
|
+
/* @__PURE__ */ jsx(Footer, { onSkip })
|
|
2826
|
+
]
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
function MultipleQuestion({ question, initial, last, onAnswer, onSkip }) {
|
|
2830
|
+
const labels = useLabels();
|
|
2831
|
+
const { options } = question;
|
|
2832
|
+
const before = Array.isArray(initial?.answer) ? initial.answer : [];
|
|
2833
|
+
const [picked, setPicked] = useState(() => new Set(options.flatMap((o, i) => before.includes(o) ? [i] : [])));
|
|
2834
|
+
const [other, setOther] = useState(before.find((a) => !options.includes(a)) ?? "");
|
|
2835
|
+
const root = useRef(null);
|
|
2836
|
+
const answer = [...options.filter((_, i) => picked.has(i)), ...other.trim() ? [other.trim()] : []];
|
|
2837
|
+
const toggle = (i) => setPicked((set) => (set.delete(i) || set.add(i), new Set(set)));
|
|
2838
|
+
const next = () => answer.length > 0 && onAnswer(answer);
|
|
2839
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2840
|
+
ref: root,
|
|
2841
|
+
role: "group",
|
|
2842
|
+
"aria-label": question.question,
|
|
2843
|
+
onKeyDown: (e) => {
|
|
2844
|
+
const n = digitOf(e);
|
|
2845
|
+
if (n !== void 0 && n <= options.length) toggle(n - 1);
|
|
2846
|
+
else if (n === options.length + 1) root.current?.querySelector("[data-ask-other]")?.focus();
|
|
2847
|
+
else return;
|
|
2848
|
+
e.preventDefault();
|
|
2849
|
+
},
|
|
2850
|
+
children: [
|
|
2851
|
+
options.map((option, i) => /* @__PURE__ */ jsxs("button", {
|
|
2852
|
+
type: "button",
|
|
2853
|
+
role: "checkbox",
|
|
2854
|
+
"aria-checked": picked.has(i),
|
|
2855
|
+
"data-ask-row": true,
|
|
2856
|
+
onClick: () => toggle(i),
|
|
2857
|
+
onKeyDown: (e) => {
|
|
2858
|
+
if (e.key !== "Enter" || answer.length === 0) return;
|
|
2859
|
+
e.preventDefault();
|
|
2860
|
+
e.stopPropagation();
|
|
2861
|
+
next();
|
|
2862
|
+
},
|
|
2863
|
+
className: rowClass$1,
|
|
2864
|
+
children: [/* @__PURE__ */ jsx(Check$1, { checked: picked.has(i) }), /* @__PURE__ */ jsx("span", {
|
|
2865
|
+
className: "min-w-0 flex-1",
|
|
2866
|
+
children: option
|
|
2867
|
+
})]
|
|
2868
|
+
}, i)),
|
|
2869
|
+
/* @__PURE__ */ jsx(OtherRow, {
|
|
2870
|
+
number: options.length + 1,
|
|
2871
|
+
value: other,
|
|
2872
|
+
onChange: setOther,
|
|
2873
|
+
onEnter: next,
|
|
2874
|
+
leading: /* @__PURE__ */ jsx(Check$1, { checked: other.trim() !== "" })
|
|
2875
|
+
}),
|
|
2876
|
+
/* @__PURE__ */ jsx(Footer, {
|
|
2877
|
+
start: labels.askUserSelected(answer.length),
|
|
2878
|
+
onSkip,
|
|
2879
|
+
next: {
|
|
2880
|
+
label: last ? labels.askUserSubmit : labels.askUserContinue,
|
|
2881
|
+
disabled: answer.length === 0,
|
|
2882
|
+
onPress: next
|
|
2883
|
+
}
|
|
2884
|
+
})
|
|
2885
|
+
]
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
function RankQuestion({ question, initial, last, onAnswer, onSkip }) {
|
|
2889
|
+
const labels = useLabels();
|
|
2890
|
+
const icons = useIcons();
|
|
2891
|
+
const { options } = question;
|
|
2892
|
+
const [order, setOrder] = useState(() => {
|
|
2893
|
+
const before = Array.isArray(initial?.answer) ? initial.answer : [];
|
|
2894
|
+
const pool = options.map((o, i) => [o, i]);
|
|
2895
|
+
const restored = before.flatMap((text) => {
|
|
2896
|
+
const at = pool.findIndex(([o]) => o === text);
|
|
2897
|
+
return at === -1 ? [] : pool.splice(at, 1).map(([, i]) => i);
|
|
2898
|
+
});
|
|
2899
|
+
return restored.length === options.length ? restored : options.map((_, i) => i);
|
|
2900
|
+
});
|
|
2901
|
+
const rows = useRef(/* @__PURE__ */ new Map());
|
|
2902
|
+
const focusAfter = useRef(null);
|
|
2903
|
+
const dragging = useRef(null);
|
|
2904
|
+
useLayoutEffect(() => {
|
|
2905
|
+
if (focusAfter.current !== null) rows.current.get(focusAfter.current)?.focus();
|
|
2906
|
+
focusAfter.current = null;
|
|
2907
|
+
}, [order]);
|
|
2908
|
+
const move = (from, to) => {
|
|
2909
|
+
if (to < 0 || to >= order.length || from === to) return;
|
|
2910
|
+
setOrder((list) => {
|
|
2911
|
+
const next = [...list];
|
|
2912
|
+
next.splice(to, 0, next.splice(from, 1)[0]);
|
|
2913
|
+
return next;
|
|
2914
|
+
});
|
|
2915
|
+
};
|
|
2916
|
+
const send = () => onAnswer(order.map((i) => options[i]));
|
|
2917
|
+
const onDragOver = (position) => (e) => {
|
|
2918
|
+
e.preventDefault();
|
|
2919
|
+
if (dragging.current === null || dragging.current === position) return;
|
|
2920
|
+
move(dragging.current, position);
|
|
2921
|
+
dragging.current = position;
|
|
2922
|
+
};
|
|
2923
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2924
|
+
role: "group",
|
|
2925
|
+
"aria-label": question.question,
|
|
2926
|
+
children: [/* @__PURE__ */ jsx("ol", { children: order.map((option, position) => /* @__PURE__ */ jsxs("li", {
|
|
2927
|
+
ref: (el) => void (el ? rows.current.set(option, el) : rows.current.delete(option)),
|
|
2928
|
+
"data-ask-row": true,
|
|
2929
|
+
tabIndex: 0,
|
|
2930
|
+
draggable: true,
|
|
2931
|
+
"aria-label": `${position + 1}. ${options[option]}`,
|
|
2932
|
+
"aria-description": labels.askUserReorderKeys,
|
|
2933
|
+
"aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown",
|
|
2934
|
+
onDragStart: () => dragging.current = position,
|
|
2935
|
+
onDragOver: onDragOver(position),
|
|
2936
|
+
onDragEnd: () => dragging.current = null,
|
|
2937
|
+
onDrop: (e) => e.preventDefault(),
|
|
2938
|
+
onKeyDown: (e) => {
|
|
2939
|
+
if (e.key === "Enter") send();
|
|
2940
|
+
else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
2941
|
+
const delta = e.key === "ArrowUp" ? -1 : 1;
|
|
2942
|
+
if (e.altKey) {
|
|
2943
|
+
focusAfter.current = option;
|
|
2944
|
+
move(position, position + delta);
|
|
2945
|
+
} else moveFocus(e.currentTarget.parentElement, e.currentTarget, delta);
|
|
2946
|
+
} else return;
|
|
2947
|
+
e.preventDefault();
|
|
2948
|
+
},
|
|
2949
|
+
className: clsx(rowClass$1, "cursor-grab"),
|
|
2950
|
+
children: [
|
|
2951
|
+
/* @__PURE__ */ jsx("span", {
|
|
2952
|
+
"aria-hidden": true,
|
|
2953
|
+
className: "text-chat-subtle",
|
|
2954
|
+
children: /* @__PURE__ */ jsx(IconSlot, {
|
|
2955
|
+
className: "[&_svg]:size-4",
|
|
2956
|
+
children: icons.dragHandle
|
|
2957
|
+
})
|
|
2958
|
+
}),
|
|
2959
|
+
/* @__PURE__ */ jsx(Num, { children: position + 1 }),
|
|
2960
|
+
/* @__PURE__ */ jsx("span", {
|
|
2961
|
+
className: "min-w-0 flex-1",
|
|
2962
|
+
children: options[option]
|
|
2963
|
+
})
|
|
2964
|
+
]
|
|
2965
|
+
}, option)) }), /* @__PURE__ */ jsx(Footer, {
|
|
2966
|
+
start: labels.askUserRankHint,
|
|
2967
|
+
onSkip,
|
|
2968
|
+
next: {
|
|
2969
|
+
label: last ? labels.askUserSubmit : labels.askUserContinue,
|
|
2970
|
+
disabled: false,
|
|
2971
|
+
onPress: send
|
|
2972
|
+
}
|
|
2973
|
+
})]
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
const questionViews = {
|
|
2977
|
+
single: SingleQuestion,
|
|
2978
|
+
multiple: MultipleQuestion,
|
|
2979
|
+
rank: RankQuestion
|
|
2980
|
+
};
|
|
2981
|
+
/**
|
|
2982
|
+
* The `ask_user` prompt: the model's questions one at a time with a pager, answered by clicking or the keyboard
|
|
2983
|
+
* (number keys, arrows, Enter; Alt+arrows reorder a rank). Submits `{ answers }` (an `AskUserResult`) after the
|
|
2984
|
+
* last question; Skip leaves a question out, the close button or Escape declines everything.
|
|
2985
|
+
*/
|
|
2986
|
+
function AskUserPrompt({ part, onSubmit, onDecline }) {
|
|
2987
|
+
const labels = useLabels();
|
|
2988
|
+
const icons = useIcons();
|
|
2989
|
+
const questions = readAskUserInput(part.input);
|
|
2990
|
+
const [index, setIndex] = useState(0);
|
|
2991
|
+
const [answers, setAnswers] = useState([]);
|
|
2992
|
+
const root = useRef(null);
|
|
2993
|
+
useEffect(() => {
|
|
2994
|
+
root.current?.querySelector("[data-ask-row]:not([data-ask-other])")?.focus();
|
|
2995
|
+
}, [index]);
|
|
2996
|
+
const question = questions[index];
|
|
2997
|
+
if (!question) return null;
|
|
2998
|
+
const Question = questionViews[question.kind];
|
|
2999
|
+
const settle = (answer) => {
|
|
3000
|
+
const next = answers.slice();
|
|
3001
|
+
next[index] = {
|
|
3002
|
+
question: question.question,
|
|
3003
|
+
kind: question.kind,
|
|
3004
|
+
...answer
|
|
3005
|
+
};
|
|
3006
|
+
setAnswers(next);
|
|
3007
|
+
for (let step = 1; step <= questions.length; step++) {
|
|
3008
|
+
const at = (index + step) % questions.length;
|
|
3009
|
+
if (next[at] === void 0) return setIndex(at);
|
|
3010
|
+
}
|
|
3011
|
+
onSubmit({ answers: questions.map((_, i) => next[i]) });
|
|
3012
|
+
};
|
|
3013
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3014
|
+
ref: root,
|
|
3015
|
+
role: "dialog",
|
|
3016
|
+
"aria-label": labels.askUserPrompt,
|
|
3017
|
+
onKeyDown: (e) => {
|
|
3018
|
+
if (e.key !== "Escape") return;
|
|
3019
|
+
e.preventDefault();
|
|
3020
|
+
onDecline();
|
|
3021
|
+
},
|
|
3022
|
+
className: "flex flex-col gap-1 rounded-chat-lg border border-chat-border-strong bg-chat-surface p-3 font-chat-sans text-chat-fg shadow-chat-popover",
|
|
3023
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3024
|
+
className: "flex items-start gap-2 px-1 pb-1",
|
|
3025
|
+
children: [
|
|
3026
|
+
/* @__PURE__ */ jsx("p", {
|
|
3027
|
+
className: "min-w-0 flex-1 text-(length:--chat-ui-font-size) leading-5 font-medium",
|
|
3028
|
+
children: question.question
|
|
3029
|
+
}),
|
|
3030
|
+
questions.length > 1 && /* @__PURE__ */ jsxs("div", {
|
|
3031
|
+
className: "flex shrink-0 items-center gap-0.5 text-(length:--chat-ui-font-size-sm) text-chat-muted",
|
|
3032
|
+
children: [
|
|
3033
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
3034
|
+
label: labels.askUserPrevious,
|
|
3035
|
+
size: "sm",
|
|
3036
|
+
isDisabled: index === 0,
|
|
3037
|
+
onPress: () => setIndex(index - 1),
|
|
3038
|
+
children: icons.askPrevious
|
|
3039
|
+
}),
|
|
3040
|
+
/* @__PURE__ */ jsx("span", {
|
|
3041
|
+
className: "tabular-nums",
|
|
3042
|
+
children: labels.askUserPager(index + 1, questions.length)
|
|
3043
|
+
}),
|
|
3044
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
3045
|
+
label: labels.askUserNext,
|
|
3046
|
+
size: "sm",
|
|
3047
|
+
isDisabled: index === questions.length - 1,
|
|
3048
|
+
onPress: () => setIndex(index + 1),
|
|
3049
|
+
children: icons.askPagerNext
|
|
3050
|
+
})
|
|
3051
|
+
]
|
|
3052
|
+
}),
|
|
3053
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
3054
|
+
label: labels.askUserClose,
|
|
3055
|
+
size: "sm",
|
|
3056
|
+
onPress: onDecline,
|
|
3057
|
+
children: icons.remove
|
|
3058
|
+
})
|
|
3059
|
+
]
|
|
3060
|
+
}), /* @__PURE__ */ jsx(Question, {
|
|
3061
|
+
question,
|
|
3062
|
+
initial: answers[index],
|
|
3063
|
+
last: answers.filter((a, i) => a !== void 0 && i !== index).length === questions.length - 1,
|
|
3064
|
+
onAnswer: (answer) => settle({ answer }),
|
|
3065
|
+
onSkip: () => settle({ skipped: true })
|
|
3066
|
+
}, index)]
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
/** The user's answers as a card in the message: each question with its answer, "Ranked: 1. …", or "Skipped". */
|
|
3070
|
+
function AskUserAnswers({ part }) {
|
|
3071
|
+
const labels = useLabels();
|
|
3072
|
+
if (part.status === "error") return /* @__PURE__ */ jsx("p", {
|
|
3073
|
+
className: "font-chat-sans text-(length:--chat-ui-font-size) leading-5 text-chat-muted",
|
|
3074
|
+
children: labels.askUserDeclined
|
|
3075
|
+
});
|
|
3076
|
+
const answers = part.status === "done" ? readAskUserResult(part.result) : void 0;
|
|
3077
|
+
if (!answers) return null;
|
|
3078
|
+
const text = (a) => {
|
|
3079
|
+
if (a.skipped || a.answer === void 0) return labels.askUserSkipped;
|
|
3080
|
+
if (typeof a.answer === "string") return a.answer;
|
|
3081
|
+
return a.kind === "rank" ? labels.askUserRanked(a.answer) : labels.askUserPicked(a.answer);
|
|
3082
|
+
};
|
|
3083
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
3084
|
+
"aria-label": labels.askUserAnswers,
|
|
3085
|
+
className: "flex flex-col gap-3 rounded-chat-lg border border-chat-border bg-chat-surface px-4 py-3 font-chat-sans text-(length:--chat-ui-font-size) leading-5",
|
|
3086
|
+
children: answers.map((a, i) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("p", {
|
|
3087
|
+
className: "text-chat-muted",
|
|
3088
|
+
children: a.question
|
|
3089
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
3090
|
+
className: clsx("text-chat-fg", (a.skipped || a.answer === void 0) && "text-chat-subtle"),
|
|
3091
|
+
children: text(a)
|
|
3092
|
+
})] }, i))
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* The view of the `ask_user` tool (`askUserTool` in `agentic-ui-kit/core`): the prompt over the composer while
|
|
3097
|
+
* the call awaits the user, then the answers card in the message. Registered in `defaultToolViews`.
|
|
3098
|
+
*/
|
|
3099
|
+
const askUserToolView = {
|
|
3100
|
+
label: (_, labels) => labels.askUserPrompt,
|
|
3101
|
+
Prompt: AskUserPrompt,
|
|
3102
|
+
Block: AskUserAnswers
|
|
3103
|
+
};
|
|
2556
3104
|
// src/components/tools.tsx
|
|
2557
3105
|
const ToolViewsContext = createContext(void 0);
|
|
2558
3106
|
function ToolViewsProvider({ tools, children }) {
|
|
@@ -2733,6 +3281,7 @@ const memoryTool = {
|
|
|
2733
3281
|
summary: (n, labels) => labels.recalledMemory(n)
|
|
2734
3282
|
};
|
|
2735
3283
|
const defaultToolViews = {
|
|
3284
|
+
ask_user: askUserToolView,
|
|
2736
3285
|
bash: commandTool,
|
|
2737
3286
|
shell: commandTool,
|
|
2738
3287
|
web_search: webSearchTool,
|
|
@@ -2819,10 +3368,12 @@ function TokenCounter({ tokens, variant = "standalone", className }) {
|
|
|
2819
3368
|
* row per step. Each row expands on its own; tool rows render through the registered `ToolView`.
|
|
2820
3369
|
* A group of one step skips the rows: the heading is the step's own label and the box holds its detail.
|
|
2821
3370
|
*/
|
|
2822
|
-
function ActivityGroup({ parts, defaultExpanded = false, className }) {
|
|
3371
|
+
function ActivityGroup({ parts: all, defaultExpanded = false, className }) {
|
|
3372
|
+
const parts = all.filter((p) => !bareThought(p) || p.title || p.durationMs);
|
|
2823
3373
|
const views = useToolViews();
|
|
2824
3374
|
const labels = useLabels();
|
|
2825
3375
|
const [expanded, setExpanded] = useState(defaultExpanded);
|
|
3376
|
+
if (parts.length === 0) return null;
|
|
2826
3377
|
const live = parts.some(isWorking);
|
|
2827
3378
|
const tokens = activityTokens(parts);
|
|
2828
3379
|
const after = live && tokens > 0 && /* @__PURE__ */ jsx(TokenCounter, {
|
|
@@ -2888,8 +3439,16 @@ function Shimmer({ children }) {
|
|
|
2888
3439
|
children
|
|
2889
3440
|
});
|
|
2890
3441
|
}
|
|
3442
|
+
/** A finished thought with nothing to open: hidden reasoning, or no text. It is a plain line, not a disclosure. */
|
|
3443
|
+
function bareThought(part) {
|
|
3444
|
+
return part.type === "thinking" && part.status === "done" && (part.visibility === "hidden" || !part.text);
|
|
3445
|
+
}
|
|
2891
3446
|
function SingleStep({ part, live, after, expanded, onExpandedChange }) {
|
|
2892
3447
|
const { summary, detail } = useStepContent(part);
|
|
3448
|
+
if (bareThought(part)) return /* @__PURE__ */ jsxs("div", {
|
|
3449
|
+
className: "flex items-center gap-1.5 text-chat-muted text-(length:--chat-activity-font-size)",
|
|
3450
|
+
children: [/* @__PURE__ */ jsx(Lead, { live }), summary]
|
|
3451
|
+
});
|
|
2893
3452
|
return /* @__PURE__ */ jsx(Disclosure, {
|
|
2894
3453
|
isExpanded: expanded,
|
|
2895
3454
|
onExpandedChange,
|
|
@@ -2912,6 +3471,10 @@ function SingleStep({ part, live, after, expanded, onExpandedChange }) {
|
|
|
2912
3471
|
function Step({ part }) {
|
|
2913
3472
|
const { summary, detail } = useStepContent(part);
|
|
2914
3473
|
const live = isWorking(part);
|
|
3474
|
+
if (bareThought(part)) return /* @__PURE__ */ jsx("div", {
|
|
3475
|
+
className: "px-3 py-2 text-chat-muted text-(length:--chat-activity-font-size)",
|
|
3476
|
+
children: summary
|
|
3477
|
+
});
|
|
2915
3478
|
return /* @__PURE__ */ jsx(Disclosure, {
|
|
2916
3479
|
className: "px-3 py-2",
|
|
2917
3480
|
summaryClassName: "text-(length:--chat-activity-font-size)",
|
|
@@ -3380,6 +3943,246 @@ function AssistantMessage({ message, onRetry, onFeedback, onReadAloud, renderPar
|
|
|
3380
3943
|
]
|
|
3381
3944
|
});
|
|
3382
3945
|
}
|
|
3946
|
+
// src/components/tool-prompt.tsx
|
|
3947
|
+
/**
|
|
3948
|
+
* Shows the `Prompt` of a pending call's tool view, above the composer (`ChatView` does this for you). Apps
|
|
3949
|
+
* that lay out the chat themselves render it from `useChat().prompts[0]`. Renders nothing if the tool's view
|
|
3950
|
+
* has no `Prompt`.
|
|
3951
|
+
*/
|
|
3952
|
+
function ToolPromptCard({ prompt, onAnswer, onDecline }) {
|
|
3953
|
+
const { part } = prompt;
|
|
3954
|
+
const Prompt = useToolView(part.name).Prompt;
|
|
3955
|
+
if (!Prompt) return null;
|
|
3956
|
+
return /* @__PURE__ */ jsx(Prompt, {
|
|
3957
|
+
part,
|
|
3958
|
+
onSubmit: (result) => onAnswer(part.id, result),
|
|
3959
|
+
onDecline: () => onDecline(part.id)
|
|
3960
|
+
}, part.id);
|
|
3961
|
+
}
|
|
3962
|
+
// src/components/ui-components.tsx
|
|
3963
|
+
const toneClass = {
|
|
3964
|
+
default: "text-chat-fg",
|
|
3965
|
+
accent: "text-chat-accent",
|
|
3966
|
+
good: "text-chat-check",
|
|
3967
|
+
warning: "text-chat-warning",
|
|
3968
|
+
bad: "text-chat-danger"
|
|
3969
|
+
};
|
|
3970
|
+
const fillClass = {
|
|
3971
|
+
default: "bg-chat-fg",
|
|
3972
|
+
accent: "bg-chat-accent",
|
|
3973
|
+
good: "bg-chat-check",
|
|
3974
|
+
warning: "bg-chat-warning",
|
|
3975
|
+
bad: "bg-chat-danger"
|
|
3976
|
+
};
|
|
3977
|
+
/** "New" / "Changed": what the model added stands out in the accent. */
|
|
3978
|
+
function Tag({ children, quiet = false }) {
|
|
3979
|
+
return /* @__PURE__ */ jsx("span", {
|
|
3980
|
+
className: `rounded-chat-sm px-1.5 text-(length:--chat-ui-font-size-sm) font-medium ${quiet ? "border border-chat-border-strong text-chat-muted" : "bg-chat-accent text-chat-accent-fg"}`,
|
|
3981
|
+
children
|
|
3982
|
+
});
|
|
3983
|
+
}
|
|
3984
|
+
const Stack = ({ direction = "column", gap = "md", children }) => /* @__PURE__ */ jsx("div", {
|
|
3985
|
+
className: `flex ${direction === "row" ? "flex-row flex-wrap" : "flex-col"} ${{
|
|
3986
|
+
sm: "gap-1.5",
|
|
3987
|
+
md: "gap-3",
|
|
3988
|
+
lg: "gap-5"
|
|
3989
|
+
}[gap]}`,
|
|
3990
|
+
children
|
|
3991
|
+
});
|
|
3992
|
+
const Grid = ({ columns = 2, children }) => /* @__PURE__ */ jsx("div", {
|
|
3993
|
+
className: "grid gap-3",
|
|
3994
|
+
style: { gridTemplateColumns: `repeat(auto-fit, minmax(min(100%, ${Math.floor(36 / columns)}rem), 1fr))` },
|
|
3995
|
+
children
|
|
3996
|
+
});
|
|
3997
|
+
const Card = ({ title, subtitle, tag, tone = "default", children }) => /* @__PURE__ */ jsxs("section", {
|
|
3998
|
+
className: `rounded-chat-lg border p-3 ${tone === "accent" ? "border-chat-accent" : "border-chat-border"}`,
|
|
3999
|
+
children: [
|
|
4000
|
+
(title || tag) && /* @__PURE__ */ jsxs("header", {
|
|
4001
|
+
className: "mb-1 flex items-center gap-2",
|
|
4002
|
+
children: [title && /* @__PURE__ */ jsx("h3", {
|
|
4003
|
+
className: "font-chat-sans text-(length:--chat-ui-font-size) font-semibold",
|
|
4004
|
+
children: title
|
|
4005
|
+
}), tag && /* @__PURE__ */ jsx(Tag, {
|
|
4006
|
+
quiet: tone !== "accent",
|
|
4007
|
+
children: tag
|
|
4008
|
+
})]
|
|
4009
|
+
}),
|
|
4010
|
+
subtitle && /* @__PURE__ */ jsx("p", {
|
|
4011
|
+
className: "mb-2 text-(length:--chat-ui-font-size-sm) text-chat-muted",
|
|
4012
|
+
children: subtitle
|
|
4013
|
+
}),
|
|
4014
|
+
children
|
|
4015
|
+
]
|
|
4016
|
+
});
|
|
4017
|
+
const Heading$1 = ({ text }) => /* @__PURE__ */ jsx("h2", {
|
|
4018
|
+
className: "font-chat-sans text-(length:--chat-ui-font-size) font-semibold",
|
|
4019
|
+
children: text
|
|
4020
|
+
});
|
|
4021
|
+
const Text$1 = ({ text, tone = "default" }) => /* @__PURE__ */ jsx("p", {
|
|
4022
|
+
className: `font-chat-sans text-(length:--chat-ui-font-size) leading-relaxed ${tone === "muted" ? "text-chat-muted" : ""}`,
|
|
4023
|
+
children: text
|
|
4024
|
+
});
|
|
4025
|
+
const Metric = ({ label, value, note, tone = "default" }) => /* @__PURE__ */ jsxs("div", {
|
|
4026
|
+
className: "min-w-24",
|
|
4027
|
+
children: [
|
|
4028
|
+
/* @__PURE__ */ jsx("div", {
|
|
4029
|
+
className: "text-(length:--chat-ui-font-size-sm) text-chat-muted",
|
|
4030
|
+
children: label
|
|
4031
|
+
}),
|
|
4032
|
+
/* @__PURE__ */ jsx("div", {
|
|
4033
|
+
className: `font-chat-sans text-xl font-semibold tabular-nums ${toneClass[tone]}`,
|
|
4034
|
+
children: value
|
|
4035
|
+
}),
|
|
4036
|
+
note && /* @__PURE__ */ jsx("div", {
|
|
4037
|
+
className: "text-(length:--chat-ui-font-size-sm) text-chat-subtle",
|
|
4038
|
+
children: note
|
|
4039
|
+
})
|
|
4040
|
+
]
|
|
4041
|
+
});
|
|
4042
|
+
const List = ({ items }) => /* @__PURE__ */ jsx("ul", {
|
|
4043
|
+
className: "divide-y divide-chat-divider",
|
|
4044
|
+
children: items.map((item, i) => /* @__PURE__ */ jsxs("li", {
|
|
4045
|
+
className: "flex items-baseline gap-2 py-1.5 font-chat-sans text-(length:--chat-ui-font-size)",
|
|
4046
|
+
children: [
|
|
4047
|
+
/* @__PURE__ */ jsx("span", {
|
|
4048
|
+
className: item.tag ? "text-chat-accent" : "",
|
|
4049
|
+
children: item.label
|
|
4050
|
+
}),
|
|
4051
|
+
item.tag && /* @__PURE__ */ jsx(Tag, { children: item.tag }),
|
|
4052
|
+
item.detail && /* @__PURE__ */ jsx("span", {
|
|
4053
|
+
className: "ml-auto text-chat-muted tabular-nums",
|
|
4054
|
+
children: item.detail
|
|
4055
|
+
})
|
|
4056
|
+
]
|
|
4057
|
+
}, i))
|
|
4058
|
+
});
|
|
4059
|
+
const Table = ({ columns, rows }) => /* @__PURE__ */ jsx("div", {
|
|
4060
|
+
className: "max-w-full overflow-x-auto",
|
|
4061
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
4062
|
+
className: "w-full border-collapse text-left font-chat-sans text-(length:--chat-ui-font-size)",
|
|
4063
|
+
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", {
|
|
4064
|
+
className: "border-b border-chat-border-strong",
|
|
4065
|
+
children: columns.map((c, i) => /* @__PURE__ */ jsx("th", {
|
|
4066
|
+
scope: "col",
|
|
4067
|
+
className: "px-2 py-1.5 font-medium text-chat-muted",
|
|
4068
|
+
children: c
|
|
4069
|
+
}, i))
|
|
4070
|
+
}) }), /* @__PURE__ */ jsx("tbody", {
|
|
4071
|
+
className: "divide-y divide-chat-divider",
|
|
4072
|
+
children: rows.map((row, r) => /* @__PURE__ */ jsx("tr", { children: columns.map((_, c) => /* @__PURE__ */ jsx("td", {
|
|
4073
|
+
className: `px-2 py-1.5 ${typeof row[c] === "number" ? "tabular-nums" : ""}`,
|
|
4074
|
+
children: row[c]
|
|
4075
|
+
}, c)) }, r))
|
|
4076
|
+
})]
|
|
4077
|
+
})
|
|
4078
|
+
});
|
|
4079
|
+
const Badge$1 = ({ label, tone = "default" }) => /* @__PURE__ */ jsx("span", {
|
|
4080
|
+
className: `inline-block self-start rounded-chat-sm border border-current px-1.5 font-chat-sans text-(length:--chat-ui-font-size-sm) font-medium ${toneClass[tone]}`,
|
|
4081
|
+
children: label
|
|
4082
|
+
});
|
|
4083
|
+
const Progress = ({ label, value, max = 100, tone = "accent" }) => {
|
|
4084
|
+
const share = Math.min(1, Math.max(0, value / max));
|
|
4085
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4086
|
+
className: "flex flex-col gap-1",
|
|
4087
|
+
children: [label && /* @__PURE__ */ jsxs("div", {
|
|
4088
|
+
className: "flex justify-between text-(length:--chat-ui-font-size-sm) text-chat-muted",
|
|
4089
|
+
children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsxs("span", {
|
|
4090
|
+
className: "tabular-nums",
|
|
4091
|
+
children: [Math.round(share * 100), "%"]
|
|
4092
|
+
})]
|
|
4093
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
4094
|
+
role: "progressbar",
|
|
4095
|
+
"aria-label": label ?? "Progress",
|
|
4096
|
+
"aria-valuemin": 0,
|
|
4097
|
+
"aria-valuemax": max,
|
|
4098
|
+
"aria-valuenow": Math.min(value, max),
|
|
4099
|
+
className: "h-1.5 overflow-hidden rounded-full bg-chat-border",
|
|
4100
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
4101
|
+
className: `h-full rounded-full ${fillClass[tone]}`,
|
|
4102
|
+
style: { width: `${share * 100}%` }
|
|
4103
|
+
})
|
|
4104
|
+
})]
|
|
4105
|
+
});
|
|
4106
|
+
};
|
|
4107
|
+
const Checklist = function Checklist({ stateKey, items }) {
|
|
4108
|
+
const [state, set] = useUiState();
|
|
4109
|
+
const done = state[stateKey] ?? [];
|
|
4110
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
4111
|
+
className: "flex flex-col gap-1",
|
|
4112
|
+
children: items.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
|
|
4113
|
+
className: "flex items-center gap-2 font-chat-sans text-(length:--chat-ui-font-size)",
|
|
4114
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
4115
|
+
type: "checkbox",
|
|
4116
|
+
className: "accent-(--chat-accent)",
|
|
4117
|
+
checked: done.includes(item),
|
|
4118
|
+
onChange: (e) => set(stateKey, e.target.checked ? [...done, item] : done.filter((d) => d !== item))
|
|
4119
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
4120
|
+
className: done.includes(item) ? "text-chat-subtle line-through" : "",
|
|
4121
|
+
children: item
|
|
4122
|
+
})]
|
|
4123
|
+
}) }, item))
|
|
4124
|
+
});
|
|
4125
|
+
};
|
|
4126
|
+
const Tabs = function Tabs({ stateKey, value, options }) {
|
|
4127
|
+
const [, set] = useUiState();
|
|
4128
|
+
const current = value ?? options[0].id;
|
|
4129
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4130
|
+
role: "tablist",
|
|
4131
|
+
className: "inline-flex max-w-full flex-wrap gap-1 self-start rounded-chat-md border border-chat-border p-0.5",
|
|
4132
|
+
children: options.map((o) => /* @__PURE__ */ jsx(Button, {
|
|
4133
|
+
size: "sm",
|
|
4134
|
+
variant: o.id === current ? "subtle" : "ghost",
|
|
4135
|
+
onPress: () => set(stateKey, o.id),
|
|
4136
|
+
"aria-selected": o.id === current,
|
|
4137
|
+
children: o.label
|
|
4138
|
+
}, o.id))
|
|
4139
|
+
});
|
|
4140
|
+
};
|
|
4141
|
+
const Show = ({ value, equals, fallback, children }) => value === equals || value === void 0 && fallback ? /* @__PURE__ */ jsx(Fragment$1, { children }) : null;
|
|
4142
|
+
/**
|
|
4143
|
+
* The drawing of each component, by name and apart from its spec. Take one to reuse in your own
|
|
4144
|
+
* component: `{ ...uiSpecs.Card, render: uiRenders.Card }`.
|
|
4145
|
+
*/
|
|
4146
|
+
const uiRenders = {
|
|
4147
|
+
Stack,
|
|
4148
|
+
Grid,
|
|
4149
|
+
Card,
|
|
4150
|
+
Heading: Heading$1,
|
|
4151
|
+
Text: Text$1,
|
|
4152
|
+
Metric,
|
|
4153
|
+
List,
|
|
4154
|
+
Table,
|
|
4155
|
+
Badge: Badge$1,
|
|
4156
|
+
Progress,
|
|
4157
|
+
Checklist,
|
|
4158
|
+
Tabs,
|
|
4159
|
+
Show,
|
|
4160
|
+
Button: function UiButton({ label, variant = "secondary", onPress }) {
|
|
4161
|
+
const run = useUiAction();
|
|
4162
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
4163
|
+
size: "sm",
|
|
4164
|
+
className: "self-start",
|
|
4165
|
+
variant: variant === "primary" ? "primary" : "subtle",
|
|
4166
|
+
onPress: () => run(onPress),
|
|
4167
|
+
children: label
|
|
4168
|
+
});
|
|
4169
|
+
}
|
|
4170
|
+
};
|
|
4171
|
+
/**
|
|
4172
|
+
* The ready components for `UiComponentsProvider`, in the order of `uiComponentSpecs`. Everything by default;
|
|
4173
|
+
* `include` and `exclude` take component names or the groups `"display"` (Stack, Grid, Card, Heading, Text,
|
|
4174
|
+
* Metric, List, Table, Badge, Progress) and `"interactive"` (Checklist, Tabs, Show, Button). `renders` swaps the
|
|
4175
|
+
* drawing of single components. Build the model's tool from the same picks so it only hears of what you
|
|
4176
|
+
* registered: `uiSpecSet({ include })` in `agentic-ui-kit/core`, or strip the renders from this list.
|
|
4177
|
+
*/
|
|
4178
|
+
function uiComponentSet({ renders = {}, ...selection } = {}) {
|
|
4179
|
+
return uiSpecSet(selection).map((spec) => ({
|
|
4180
|
+
...spec,
|
|
4181
|
+
render: renders[spec.name] ?? uiRenders[spec.name]
|
|
4182
|
+
}));
|
|
4183
|
+
}
|
|
4184
|
+
/** The whole set: `uiComponentSet()`. */
|
|
4185
|
+
const uiComponents = uiComponentSet();
|
|
3383
4186
|
// src/components/approval-card.tsx
|
|
3384
4187
|
/**
|
|
3385
4188
|
* Asks the user before a tool runs, above the composer. Shows the tool's label and what it will do: its
|
|
@@ -4738,7 +5541,7 @@ const densityVars = {
|
|
|
4738
5541
|
}
|
|
4739
5542
|
};
|
|
4740
5543
|
/** A whole chat: header, the conversation, the docked composer. An empty chat shows `emptyState`. */
|
|
4741
|
-
function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove, onRetry, onEdit, onFeedback, onReadAloud, messageActions, renderPart, widgets, header, composer, emptyState, disclaimer, renderComposer, density = "full", contentWidth, contentPadding, className }) {
|
|
5544
|
+
function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove, prompts, onAnswer, onDecline, onRetry, onEdit, onFeedback, onReadAloud, messageActions, renderPart, widgets, header, composer, emptyState, disclaimer, renderComposer, density = "full", contentWidth, contentPadding, className }) {
|
|
4742
5545
|
const style = {
|
|
4743
5546
|
...densityVars[density],
|
|
4744
5547
|
...contentWidth && { "--chat-content-width": contentWidth },
|
|
@@ -4753,6 +5556,7 @@ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove
|
|
|
4753
5556
|
const [body, short] = useShortPane();
|
|
4754
5557
|
const labels = useLabels();
|
|
4755
5558
|
const pending = onApprove && approvals?.[0];
|
|
5559
|
+
const asking = !pending && onAnswer ? prompts?.[0] : void 0;
|
|
4756
5560
|
const box = (variant) => {
|
|
4757
5561
|
if (composer === false) return null;
|
|
4758
5562
|
const element = /* @__PURE__ */ jsx(Composer, {
|
|
@@ -4761,7 +5565,7 @@ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove
|
|
|
4761
5565
|
onSubmit: onSend,
|
|
4762
5566
|
...onStop && { onStop },
|
|
4763
5567
|
...composer,
|
|
4764
|
-
...pending && { placeholder: labels.replyDirectly }
|
|
5568
|
+
...(pending || asking) && { placeholder: labels.replyDirectly }
|
|
4765
5569
|
});
|
|
4766
5570
|
return renderComposer ? renderComposer(element, variant === "centered" ? "empty" : "docked") : element;
|
|
4767
5571
|
};
|
|
@@ -4798,6 +5602,14 @@ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove
|
|
|
4798
5602
|
onRespond: onApprove,
|
|
4799
5603
|
className: "mb-2"
|
|
4800
5604
|
}, pending.part.id),
|
|
5605
|
+
asking && /* @__PURE__ */ jsx("div", {
|
|
5606
|
+
className: "mb-2",
|
|
5607
|
+
children: /* @__PURE__ */ jsx(ToolPromptCard, {
|
|
5608
|
+
prompt: asking,
|
|
5609
|
+
onAnswer,
|
|
5610
|
+
onDecline: onDecline ?? (() => {})
|
|
5611
|
+
})
|
|
5612
|
+
}),
|
|
4801
5613
|
box("docked"),
|
|
4802
5614
|
/* @__PURE__ */ jsx("div", {
|
|
4803
5615
|
className: clsx("flex items-center justify-center py-1 pb-[max(0.25rem,env(safe-area-inset-bottom))] text-center font-chat-sans text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle", short ? "min-h-2" : "min-h-6"),
|
|
@@ -5205,4 +6017,4 @@ function SidebarUser({ name, detail, avatarUrl, menu, popoverProps }) {
|
|
|
5205
6017
|
children: menu
|
|
5206
6018
|
})] });
|
|
5207
6019
|
}
|
|
5208
|
-
export { ActivityGroup, AgentProvider, AgentRegistry, AppShell, ApprovalCard, AssistantMessage, AttachButton, AttachmentCard, AttachmentRow, Badge, Button, Caret, ChatHeader, ChatList, ChatListRailButton, ChatNotice, ChatTheme, ChatTitleMenu, ChatTranscript, ChatView, CodeBlock, Composer, ContextMenu, ContextMeter, ContextPanel, CopyButton, CopyField, DRAWER_QUERY, DialogTrigger, DictationButton, DictationControls, Disclosure, Drawer, EffortButton, EffortSlider, EmptyState, IconButton, Markdown, MarkdownLinksProvider, Menu, MenuItem, MenuNote, MenuSearch, MenuSection, MenuSeparator, MenuTrigger, MessageList, Modal, ModelPicker, Panel, PastedTextDialog, Popover, PopoverTrigger, SegmentedControl, Sidebar, SidebarItem, SidebarList, SidebarToggle, SidebarUser, SubmenuTrigger, TokenCounter, ToolViewsProvider, Tooltip, UiComponentsProvider, UiMarkupBlock, UiTree, UserMessage, VoiceMeter, WIDGET_MAX_HEIGHT, WidgetFrame, WidgetHostProvider, WidgetPartView, activityHeading, activityLabel, commandTool, defaultIcons, defaultLabels, defaultToolViews, encodeWav, formatDuration, formatRelativeTime, formatTokens, htmlComponent, intersectAllow, memoryTool, messageText, openInNewTab, readTreeInput, readVisualizeInput, sseTransport, uiMarkupToolView, uiTreeToolView, useAgentContext, useAgentRegistry, useAgentSnapshot, useAgentTool, useAttachments, useChat, useChatTool, useCountUp, useDictation, useIcons, useLabels, useMediaQuery, useSidebar, useSmoothText, useToolView, useToolViews, useUiAction, useUiMarkupLanguage, useUiState, useWidgetHost, userMessage, webFetchTool, webSearchTool, widgetCsp, widgetToolView };
|
|
6020
|
+
export { ActivityGroup, AgentProvider, AgentRegistry, AppShell, ApprovalCard, AskUserAnswers, AskUserPrompt, AssistantMessage, AttachButton, AttachmentCard, AttachmentRow, Badge, Button, Caret, ChatHeader, ChatList, ChatListRailButton, ChatNotice, ChatTheme, ChatTitleMenu, ChatTranscript, ChatView, CodeBlock, Composer, ContextMenu, ContextMeter, ContextPanel, CopyButton, CopyField, DRAWER_QUERY, DialogTrigger, DictationButton, DictationControls, Disclosure, Drawer, EffortButton, EffortSlider, EmptyState, IconButton, Markdown, MarkdownLinksProvider, Menu, MenuItem, MenuNote, MenuSearch, MenuSection, MenuSeparator, MenuTrigger, MessageList, Modal, ModelPicker, Panel, PastedTextDialog, Popover, PopoverTrigger, SegmentedControl, Sidebar, SidebarItem, SidebarList, SidebarToggle, SidebarUser, SubmenuTrigger, TokenCounter, ToolPromptCard, ToolViewsProvider, Tooltip, UiComponentsProvider, UiMarkupBlock, UiTree, UserMessage, VoiceMeter, WIDGET_MAX_HEIGHT, WidgetFrame, WidgetHostProvider, WidgetPartView, activityHeading, activityLabel, askUserToolView, commandTool, defaultIcons, defaultLabels, defaultToolViews, encodeWav, formatDuration, formatRelativeTime, formatTokens, htmlComponent, intersectAllow, memoryTool, messageText, openInNewTab, readTreeInput, readVisualizeInput, sseTransport, uiComponentSet, uiComponents, uiMarkupToolView, uiRenders, uiTreeToolView, useAgentContext, useAgentRegistry, useAgentSnapshot, useAgentTool, useAttachments, useChat, useChatTool, useCountUp, useDictation, useIcons, useLabels, useMediaQuery, useSidebar, useSmoothText, useToolView, useToolViews, useUiAction, useUiMarkupLanguage, useUiState, useWidgetHost, userMessage, webFetchTool, webSearchTool, widgetCsp, widgetToolView };
|