@candor-ui/react 1.1.0 → 1.5.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/adapters/ag-ui.d.ts +66 -1
- package/dist/adapters/ag-ui.js +308 -1
- package/dist/adapters/ai-sdk.d.ts +6 -1
- package/dist/adapters/ai-sdk.js +18 -1
- package/dist/adapters/claude-agent-sdk.d.ts +111 -2
- package/dist/adapters/claude-agent-sdk.js +163 -5
- package/dist/adapters/claude-agent-server.d.ts +195 -10
- package/dist/adapters/claude-agent-server.js +448 -49
- package/dist/adapters/use-ag-ui.d.ts +6 -0
- package/dist/adapters/use-ag-ui.js +10 -4
- package/dist/adapters/use-candor-chat.js +2 -2
- package/dist/adapters/use-claude-agent.d.ts +35 -6
- package/dist/adapters/use-claude-agent.js +96 -20
- package/dist/chat/ai-disclosure.d.ts +37 -0
- package/dist/chat/ai-disclosure.js +48 -0
- package/dist/chat/attachment-preview.js +1 -1
- package/dist/chat/card-registry.js +1 -1
- package/dist/chat/citation.js +5 -3
- package/dist/chat/composer.d.ts +5 -2
- package/dist/chat/composer.js +4 -3
- package/dist/chat/entity-chip.js +1 -1
- package/dist/chat/folded-turns.js +1 -1
- package/dist/chat/message.js +1 -1
- package/dist/chat/payload.js +5 -2
- package/dist/chat/permission-prompt.d.ts +129 -0
- package/dist/chat/permission-prompt.js +144 -0
- package/dist/chat/reasoning.js +1 -1
- package/dist/chat/thread.d.ts +7 -1
- package/dist/chat/thread.js +24 -3
- package/dist/chat/tool-call.d.ts +5 -0
- package/dist/chat/tool-call.js +7 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/lib/chat.d.ts +15 -0
- package/dist/lib/use-virtual-list.d.ts +39 -0
- package/dist/lib/use-virtual-list.js +124 -0
- package/package.json +1 -1
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { EntityRef } from "../lib/chat.js";
|
|
3
|
-
import { type FromClaudeAgentOptions } from "./claude-agent-sdk.js";
|
|
3
|
+
import { type AgentEvent, type ClaudeApprovalAnswer, type ClaudePermissionMode, type FromClaudeAgentOptions } from "./claude-agent-sdk.js";
|
|
4
4
|
export type UseClaudeAgentOptions = Omit<FromClaudeAgentOptions, "running"> & {
|
|
5
|
-
/** Your route that runs the agent with streamClaudeAgent. It receives `{ prompt, references, sessionId, ...body }`. */
|
|
5
|
+
/** Your route that runs the agent with streamClaudeAgent. It receives `{ prompt, promptId, references, sessionId, ...body }`. */
|
|
6
6
|
api: string;
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Your route that records an answer (answerClaudeApproval). It receives `{ id, approved, reason, updatedInput,
|
|
9
|
+
* answers, alwaysAllow }` and returns `{ ok }`. Not needed with `onRespond`.
|
|
10
|
+
*/
|
|
11
|
+
approveApi?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Answer through your own route instead, e.g. one that signals the workflow running the agent. Resolve true once
|
|
14
|
+
* the answer is recorded, false if it wasn't (the request had ended).
|
|
15
|
+
*/
|
|
16
|
+
onRespond?: (approvalId: string, answer: ClaudeApprovalAnswer) => Promise<boolean>;
|
|
17
|
+
/** Events you kept from earlier (onEvent, a run store): the conversation is folded from them, as it was. */
|
|
18
|
+
initialEvents?: AgentEvent[];
|
|
19
|
+
/**
|
|
20
|
+
* Where to re-attach to a run after a reload: `(runId, from) => \`/api/agent/${runId}?from=${from}\``, a route that
|
|
21
|
+
* returns attachClaudeAgentRun. With `runId`, the hook re-attaches when it mounts.
|
|
22
|
+
*/
|
|
23
|
+
attachUrl?: (runId: string, from: number) => string;
|
|
24
|
+
/** A run to re-attach to when the hook mounts, e.g. the one you saved from `runId` before the reload. */
|
|
25
|
+
runId?: string;
|
|
26
|
+
/** Your route that calls stopClaudeAgentRun with `{ runId }`. Needed to stop a run that keeps going without the browser (`detach`). */
|
|
27
|
+
stopApi?: string;
|
|
28
|
+
/** Your route that calls setClaudeAgentPermissionMode with `{ runId, mode }`. Enables `setPermissionMode`. */
|
|
29
|
+
modeApi?: string;
|
|
9
30
|
/** Extra fields for every request, e.g. which project the agent works on. */
|
|
10
31
|
body?: Record<string, unknown>;
|
|
11
32
|
headers?: Record<string, string>;
|
|
@@ -26,7 +47,7 @@ export type UseClaudeAgentOptions = Omit<FromClaudeAgentOptions, "running"> & {
|
|
|
26
47
|
export declare function useClaudeAgent(options: UseClaudeAgentOptions): {
|
|
27
48
|
messages: import("../index.js").MessageData[];
|
|
28
49
|
approvals: import("./claude-agent-sdk.js").ClaudeApproval[];
|
|
29
|
-
respond: (approvalId: string, approved: boolean, reason?: string) => Promise<
|
|
50
|
+
respond: (approvalId: string, approved: boolean, reason?: string, more?: Omit<ClaudeApprovalAnswer, "approved" | "reason">) => Promise<boolean>;
|
|
30
51
|
/** What the SDK measured for the session: cost (its estimate), tokens and turns. */
|
|
31
52
|
usage: import("./claude-agent-sdk.js").ClaudeAgentUsage | undefined;
|
|
32
53
|
/** A passing problem the agent is handling itself, such as a retried API call. */
|
|
@@ -34,6 +55,14 @@ export declare function useClaudeAgent(options: UseClaudeAgentOptions): {
|
|
|
34
55
|
/** The request failed; say so, and let the person send again. */
|
|
35
56
|
error: string | undefined;
|
|
36
57
|
sessionId: string | undefined;
|
|
58
|
+
/** Every event so far, to keep for replay (`initialEvents`). */
|
|
59
|
+
events: AgentEvent[];
|
|
60
|
+
/** The current or last run's id (from `x-candor-run-id`): save it to re-attach after a reload. */
|
|
61
|
+
runId: string | undefined;
|
|
62
|
+
attach: (id: string, from?: number) => Promise<void>;
|
|
63
|
+
/** The permission mode last reported by the SDK or set through `setPermissionMode`. */
|
|
64
|
+
permissionMode: ClaudePermissionMode | undefined;
|
|
65
|
+
setPermissionMode: (mode: ClaudePermissionMode) => Promise<boolean>;
|
|
37
66
|
/** Forget the conversation and start a new session. */
|
|
38
67
|
reset: () => void;
|
|
39
68
|
composer: {
|
|
@@ -42,7 +71,7 @@ export declare function useClaudeAgent(options: UseClaudeAgentOptions): {
|
|
|
42
71
|
state: "streaming" | "ready" | "sending";
|
|
43
72
|
references: EntityRef[];
|
|
44
73
|
onReferencesChange: React.Dispatch<React.SetStateAction<EntityRef[]>>;
|
|
45
|
-
onStop: () => void
|
|
74
|
+
onStop: () => void;
|
|
46
75
|
onSubmit: (text: string, refs: EntityRef[]) => void;
|
|
47
76
|
};
|
|
48
77
|
};
|
|
@@ -12,45 +12,54 @@ import { fromClaudeAgentEvents } from "./claude-agent-sdk.js";
|
|
|
12
12
|
* sent as structured `references`, not pasted into the text.
|
|
13
13
|
*/
|
|
14
14
|
export function useClaudeAgent(options) {
|
|
15
|
-
const { api, approveApi, body, headers, agent, toolTitle, fetch: fetcher } = options;
|
|
16
|
-
const [events, setEvents] = React.useState([]);
|
|
15
|
+
const { api, approveApi, onRespond, body, headers, agent, toolTitle, sourcesFrom, fetch: fetcher, attachUrl, stopApi, modeApi } = options;
|
|
16
|
+
const [events, setEvents] = React.useState(() => options.initialEvents ?? []);
|
|
17
|
+
const [runId, setRunId] = React.useState(options.runId);
|
|
17
18
|
const [running, setRunning] = React.useState(false);
|
|
18
19
|
const [error, setError] = React.useState();
|
|
19
20
|
const [value, setValue] = React.useState("");
|
|
20
21
|
const [references, setReferences] = React.useState([]);
|
|
21
22
|
const run = React.useRef(null);
|
|
22
|
-
const state = React.useMemo(() => fromClaudeAgentEvents(events, { agent, toolTitle, running }), [events, agent, toolTitle, running]);
|
|
23
|
+
const state = React.useMemo(() => fromClaudeAgentEvents(events, { agent, toolTitle, sourcesFrom, running }), [events, agent, toolTitle, sourcesFrom, running]);
|
|
23
24
|
const session = React.useRef(undefined);
|
|
24
25
|
React.useEffect(() => {
|
|
25
26
|
session.current = state.sessionId;
|
|
26
27
|
}, [state.sessionId]);
|
|
27
28
|
// Stop the agent if the component goes away mid-run.
|
|
28
29
|
React.useEffect(() => () => run.current?.abort(), []);
|
|
29
|
-
|
|
30
|
+
/** Reads one response's events into state, a frame at a time. */
|
|
31
|
+
const stream = React.useCallback(async (open, before, replace = false) => {
|
|
30
32
|
if (run.current)
|
|
31
33
|
return;
|
|
32
34
|
const controller = new AbortController();
|
|
33
35
|
run.current = controller;
|
|
34
36
|
setError(undefined);
|
|
35
37
|
setRunning(true);
|
|
36
|
-
|
|
38
|
+
if (before)
|
|
39
|
+
setEvents((e) => [...e, before]);
|
|
37
40
|
// Streamed text arrives a few characters at a time; apply it once per frame.
|
|
38
|
-
const pending = { events: [], frame: 0 };
|
|
41
|
+
const pending = { events: [], frame: 0, first: replace };
|
|
39
42
|
const flush = () => {
|
|
40
43
|
pending.frame = 0;
|
|
41
44
|
const batch = pending.events.splice(0);
|
|
42
|
-
if (batch.length)
|
|
43
|
-
|
|
45
|
+
if (!batch.length)
|
|
46
|
+
return;
|
|
47
|
+
const first = pending.first ? batch[0] : undefined;
|
|
48
|
+
pending.first = false;
|
|
49
|
+
setEvents((e) => {
|
|
50
|
+
// Re-attaching to a run whose events are already here (from initialEvents): the stored run replaces them,
|
|
51
|
+
// found by the person's message that starts it, rather than being added a second time.
|
|
52
|
+
const at = first?.type === "candor_user" ? e.findIndex((x) => x.type === "candor_user" && x.id === first.id) : -1;
|
|
53
|
+
return [...(at >= 0 ? e.slice(0, at) : e), ...batch];
|
|
54
|
+
});
|
|
44
55
|
};
|
|
45
56
|
try {
|
|
46
|
-
const res = await (
|
|
47
|
-
method: "POST",
|
|
48
|
-
headers: { "content-type": "application/json", ...headers },
|
|
49
|
-
body: JSON.stringify({ ...body, prompt: text, references: refs, sessionId: session.current }),
|
|
50
|
-
signal: controller.signal,
|
|
51
|
-
});
|
|
57
|
+
const res = await open(controller.signal);
|
|
52
58
|
if (!res.ok || !res.body)
|
|
53
59
|
throw new Error(`The agent request failed (${res.status}).`);
|
|
60
|
+
const id = res.headers.get("x-candor-run-id");
|
|
61
|
+
if (id)
|
|
62
|
+
setRunId(id);
|
|
54
63
|
for await (const event of readEvents(res.body)) {
|
|
55
64
|
pending.events.push(event);
|
|
56
65
|
pending.frame ||= requestAnimationFrame(flush);
|
|
@@ -69,19 +78,77 @@ export function useClaudeAgent(options) {
|
|
|
69
78
|
run.current = null;
|
|
70
79
|
setRunning(false);
|
|
71
80
|
}
|
|
72
|
-
}, [
|
|
73
|
-
const
|
|
81
|
+
}, []);
|
|
82
|
+
const send = React.useCallback((text, refs) => {
|
|
83
|
+
const promptId = crypto.randomUUID();
|
|
84
|
+
return stream((signal) => (fetcher ?? fetch)(api, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { "content-type": "application/json", ...headers },
|
|
87
|
+
body: JSON.stringify({ ...body, prompt: text, promptId, references: refs, sessionId: session.current }),
|
|
88
|
+
signal,
|
|
89
|
+
}), { type: "candor_user", id: promptId, text, references: refs.length ? refs : undefined });
|
|
90
|
+
}, [api, body, headers, fetcher, stream]);
|
|
91
|
+
/**
|
|
92
|
+
* Re-attach to a run after a reload: its events from `from` on (default 0, the whole run), then live until it
|
|
93
|
+
* ends. Needs `attachUrl`. From 0, when the run store recorded the person's message (the server's `prompt`, with
|
|
94
|
+
* this hook's `promptId`), the run's events replace any copy of them already in `initialEvents`.
|
|
95
|
+
*/
|
|
96
|
+
const attach = React.useCallback((id, from = 0) => {
|
|
97
|
+
if (!attachUrl)
|
|
98
|
+
return Promise.resolve();
|
|
99
|
+
setRunId(id);
|
|
100
|
+
return stream((signal) => (fetcher ?? fetch)(attachUrl(id, from), { headers, signal }), undefined, from === 0);
|
|
101
|
+
}, [attachUrl, fetcher, headers, stream]);
|
|
102
|
+
// Re-attach to the run named in options once, when the hook mounts.
|
|
103
|
+
const attachOnMount = React.useRef(options.runId);
|
|
104
|
+
React.useEffect(() => {
|
|
105
|
+
const id = attachOnMount.current;
|
|
106
|
+
attachOnMount.current = undefined;
|
|
107
|
+
if (id)
|
|
108
|
+
void attach(id);
|
|
109
|
+
}, [attach]);
|
|
110
|
+
const post = React.useCallback(async (url, payload) => {
|
|
111
|
+
const res = await (fetcher ?? fetch)(url, { method: "POST", headers: { "content-type": "application/json", ...headers }, body: JSON.stringify(payload) });
|
|
112
|
+
return res.ok && !!(await res.json()).ok;
|
|
113
|
+
}, [fetcher, headers]);
|
|
114
|
+
/**
|
|
115
|
+
* Answer a waiting request. `more` carries an edited input (`updatedInput`), answers to the agent's questions
|
|
116
|
+
* (`answers`) and the "always allow" rules the person picked (`alwaysAllow`, indexes into its `suggestions`).
|
|
117
|
+
*/
|
|
118
|
+
const respond = React.useCallback(async (approvalId, approved, reason, more) => {
|
|
119
|
+
const answer = { approved, ...(reason !== undefined && { reason }), ...more };
|
|
74
120
|
try {
|
|
75
|
-
const
|
|
76
|
-
const ok = res.ok && (await res.json()).ok;
|
|
121
|
+
const ok = onRespond ? await onRespond(approvalId, answer) : approveApi ? await post(approveApi, { id: approvalId, ...answer }) : false;
|
|
77
122
|
if (!ok)
|
|
78
123
|
setError("Your answer didn’t reach the agent. The request may have ended; try sending the message again.");
|
|
124
|
+
return ok;
|
|
79
125
|
}
|
|
80
126
|
catch {
|
|
81
127
|
setError("Your answer didn’t reach the agent. Check your connection and try again.");
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}, [approveApi, onRespond, post]);
|
|
131
|
+
const stop = React.useCallback(() => {
|
|
132
|
+
// A detached run carries on without this browser, so it's stopped on the server too.
|
|
133
|
+
if (stopApi && runId)
|
|
134
|
+
void post(stopApi, { runId }).catch(() => { });
|
|
135
|
+
run.current?.abort();
|
|
136
|
+
}, [stopApi, runId, post]);
|
|
137
|
+
/** Change the run's permission mode. Resolves false when there's no run, no `modeApi`, or the server refused. */
|
|
138
|
+
const setPermissionMode = React.useCallback(async (mode) => {
|
|
139
|
+
if (!modeApi || !runId || !running)
|
|
140
|
+
return false;
|
|
141
|
+
try {
|
|
142
|
+
const ok = await post(modeApi, { runId, mode });
|
|
143
|
+
if (!ok)
|
|
144
|
+
setError("The permission mode didn’t change.");
|
|
145
|
+
return ok;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
setError("The permission mode didn’t change. Check your connection and try again.");
|
|
149
|
+
return false;
|
|
82
150
|
}
|
|
83
|
-
}, [
|
|
84
|
-
const stop = React.useCallback(() => run.current?.abort(), []);
|
|
151
|
+
}, [modeApi, runId, running, post]);
|
|
85
152
|
const composerState = running ? (state.messages.at(-1)?.author.role === "agent" ? "streaming" : "sending") : "ready";
|
|
86
153
|
return {
|
|
87
154
|
messages: state.messages,
|
|
@@ -94,10 +161,19 @@ export function useClaudeAgent(options) {
|
|
|
94
161
|
/** The request failed; say so, and let the person send again. */
|
|
95
162
|
error,
|
|
96
163
|
sessionId: state.sessionId,
|
|
164
|
+
/** Every event so far, to keep for replay (`initialEvents`). */
|
|
165
|
+
events,
|
|
166
|
+
/** The current or last run's id (from `x-candor-run-id`): save it to re-attach after a reload. */
|
|
167
|
+
runId,
|
|
168
|
+
attach,
|
|
169
|
+
/** The permission mode last reported by the SDK or set through `setPermissionMode`. */
|
|
170
|
+
permissionMode: state.permissionMode,
|
|
171
|
+
setPermissionMode,
|
|
97
172
|
/** Forget the conversation and start a new session. */
|
|
98
173
|
reset: () => {
|
|
99
174
|
run.current?.abort();
|
|
100
175
|
setEvents([]);
|
|
176
|
+
setRunId(undefined);
|
|
101
177
|
setError(undefined);
|
|
102
178
|
},
|
|
103
179
|
composer: {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AgentIdentity } from "../lib/chat.js";
|
|
2
|
+
/** AiDisclosure's fixed words; override them under `aiDisclosure` in LocaleProvider's `strings`. */
|
|
3
|
+
export declare const aiDisclosureStrings: {
|
|
4
|
+
/** The line in the thread when the app names the agent. */
|
|
5
|
+
line: string;
|
|
6
|
+
/** The line in the thread when no name is given. */
|
|
7
|
+
lineUnnamed: string;
|
|
8
|
+
/** The badge's visible text. */
|
|
9
|
+
badge: string;
|
|
10
|
+
/** The badge's accessible name when the app names the agent. */
|
|
11
|
+
badgeLabel: string;
|
|
12
|
+
/** The badge's accessible name when no name is given. */
|
|
13
|
+
badgeLabelUnnamed: string;
|
|
14
|
+
/** The heading of the badge's explanation. */
|
|
15
|
+
about: string;
|
|
16
|
+
aboutUnnamed: string;
|
|
17
|
+
learnMore: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A plain notice that the person is interacting with an AI system: a first line in the thread (`variant="line"`) or
|
|
21
|
+
* a compact "AI" badge for a thread header (`variant="badge"`) that opens to the app's own explanation. It helps an
|
|
22
|
+
* app tell people they're talking to an AI, as transparency rules such as Article 50(1) of the EU AI Act ask; whether
|
|
23
|
+
* a given placement meets those rules, and what else applies, stays the deployer's call. The agent's name and any
|
|
24
|
+
* details come from the app, never from model text. It's static: read in place, never announced as a live update.
|
|
25
|
+
*/
|
|
26
|
+
declare function AiDisclosure({ agent, variant, details, onLearnMore, className, }: {
|
|
27
|
+
/** The agent, named by the app: an AgentIdentity or a plain name. Left out, the notice says "an AI system". Never model text. */
|
|
28
|
+
agent?: AgentIdentity | string;
|
|
29
|
+
/** "line" (the default) for a first line in the thread; "badge" for a compact mark in a header, e.g. ThreadHeader's. Place each where it fits. */
|
|
30
|
+
variant?: "line" | "badge";
|
|
31
|
+
/** The app's own plain text about the system: who operates it, what data it uses, how to reach a person. Shown under the line, or in the badge's explanation. */
|
|
32
|
+
details?: string;
|
|
33
|
+
/** Opens the app's own page or dialog about its AI use. A callback, never a URL from the model. Shows a "Learn more" button. */
|
|
34
|
+
onLearnMore?: () => void;
|
|
35
|
+
className?: string;
|
|
36
|
+
}): import("react").JSX.Element;
|
|
37
|
+
export { AiDisclosure };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Bot } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { useStrings } from "../blocks/locale.js";
|
|
6
|
+
import { Button } from "../ui/button.js";
|
|
7
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
|
|
8
|
+
/** AiDisclosure's fixed words; override them under `aiDisclosure` in LocaleProvider's `strings`. */
|
|
9
|
+
export const aiDisclosureStrings = {
|
|
10
|
+
/** The line in the thread when the app names the agent. */
|
|
11
|
+
line: "{name} is an AI system, not a person.",
|
|
12
|
+
/** The line in the thread when no name is given. */
|
|
13
|
+
lineUnnamed: "You're interacting with an AI system, not a person.",
|
|
14
|
+
/** The badge's visible text. */
|
|
15
|
+
badge: "AI",
|
|
16
|
+
/** The badge's accessible name when the app names the agent. */
|
|
17
|
+
badgeLabel: "{name} is an AI system",
|
|
18
|
+
/** The badge's accessible name when no name is given. */
|
|
19
|
+
badgeLabelUnnamed: "AI system",
|
|
20
|
+
/** The heading of the badge's explanation. */
|
|
21
|
+
about: "About {name}",
|
|
22
|
+
aboutUnnamed: "About this AI system",
|
|
23
|
+
learnMore: "Learn more",
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A plain notice that the person is interacting with an AI system: a first line in the thread (`variant="line"`) or
|
|
27
|
+
* a compact "AI" badge for a thread header (`variant="badge"`) that opens to the app's own explanation. It helps an
|
|
28
|
+
* app tell people they're talking to an AI, as transparency rules such as Article 50(1) of the EU AI Act ask; whether
|
|
29
|
+
* a given placement meets those rules, and what else applies, stays the deployer's call. The agent's name and any
|
|
30
|
+
* details come from the app, never from model text. It's static: read in place, never announced as a live update.
|
|
31
|
+
*/
|
|
32
|
+
function AiDisclosure({ agent, variant = "line", details, onLearnMore, className, }) {
|
|
33
|
+
const t = useStrings("aiDisclosure", aiDisclosureStrings);
|
|
34
|
+
const name = typeof agent === "string" ? agent : agent?.name;
|
|
35
|
+
const sentence = name ? t("line", { name }) : t("lineUnnamed");
|
|
36
|
+
const learnMore = onLearnMore && (_jsx(Button, { type: "button", variant: "link", size: "xs", className: "h-6 px-0 has-[>svg]:px-0", onClick: onLearnMore, children: t("learnMore") }));
|
|
37
|
+
if (variant === "badge") {
|
|
38
|
+
const label = name ? t("badgeLabel", { name }) : t("badgeLabelUnnamed");
|
|
39
|
+
const mark = (_jsxs(_Fragment, { children: [_jsx(Bot, { "aria-hidden": true, className: "size-3 shrink-0" }), _jsx("span", { "aria-hidden": true, children: t("badge") })] }));
|
|
40
|
+
const badgeClass = "inline-flex min-h-6 min-w-6 items-center justify-center gap-1 rounded-full border px-2 text-2xs font-medium text-muted-foreground";
|
|
41
|
+
// Nothing more to say: a static mark with a name, not a button that opens nothing.
|
|
42
|
+
if (!details && !onLearnMore)
|
|
43
|
+
return (_jsx("span", { "data-slot": "ai-disclosure", "data-variant": "badge", role: "img", "aria-label": label, className: cn(badgeClass, className), children: mark }));
|
|
44
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { "data-slot": "ai-disclosure", "data-variant": "badge", "aria-label": label, className: cn(badgeClass, "outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", className), children: mark }), _jsxs(PopoverContent, { align: "start", className: "flex w-72 flex-col gap-1.5 p-3 text-xs", children: [_jsx("p", { className: "text-sm font-medium", children: name ? t("about", { name }) : t("aboutUnnamed") }), _jsx("p", { children: sentence }), details && _jsx("p", { className: "text-muted-foreground", children: details }), learnMore && _jsx("div", { children: learnMore })] })] }));
|
|
45
|
+
}
|
|
46
|
+
return (_jsxs("div", { "data-slot": "ai-disclosure", "data-variant": "line", role: "note", className: cn("flex items-start justify-center gap-1.5 py-1 text-center text-xs text-muted-foreground", className), children: [_jsx(Bot, { "aria-hidden": true, className: "mt-0.5 size-3 shrink-0" }), _jsxs("p", { className: "flex flex-wrap items-center justify-center gap-x-1.5", children: [_jsx("span", { children: sentence }), details && _jsx("span", { children: details }), learnMore] })] }));
|
|
47
|
+
}
|
|
48
|
+
export { AiDisclosure };
|
|
@@ -58,7 +58,7 @@ function AttachmentChip({ attachment: a, onRemove, className, }) {
|
|
|
58
58
|
const t = useStrings("attachmentPreview", attachmentPreviewStrings);
|
|
59
59
|
const fmt = useFormat();
|
|
60
60
|
const thumb = thumbnail(a);
|
|
61
|
-
return (_jsxs("span", { "data-slot": "attachment-chip", className: cn("relative inline-flex max-w-56 items-center gap-1.5 overflow-hidden rounded-md border bg-card py-1 pe-1 ps-2 text-xs", a.error && "border-destructive/50", className), children: [thumb ? _jsx("img", { src: thumb, alt: "", className: "size-4 shrink-0 rounded-xs object-cover" }) : _jsx(Paperclip, { "aria-hidden": true, className: "size-3 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: a.name }), _jsx("span", { className: cn("shrink-0 tabular-nums", a.error ? "text-destructive-text" : "text-faint-foreground"), children: a.error ? a.error : a.progress !== undefined ? fmt.number(Math.round(a.progress * 100) / 100, { style: "percent" }) : formatFileSize(a.size, fmt.number, t) }), onRemove && (_jsx("button", { type: "button", "aria-label": t("remove", { name: a.name }), onClick: () => onRemove(a.id), className: "rounded-sm p-0.5 text-faint-foreground hover:bg-accent hover:text-foreground", children: _jsx(X, { className: "size-3" }) })), a.progress !== undefined && !a.error && _jsx("span", { "aria-hidden": true, className: "absolute bottom-0 start-0 h-0.5 bg-primary transition-[width] duration-300", style: { width: `${a.progress * 100}%` } })] }));
|
|
61
|
+
return (_jsxs("span", { "data-slot": "attachment-chip", className: cn("relative inline-flex max-w-56 items-center gap-1.5 overflow-hidden rounded-md border bg-card py-1 pe-1 ps-2 text-xs", a.error && "border-destructive/50", className), children: [thumb ? _jsx("img", { src: thumb, alt: "", className: "size-4 shrink-0 rounded-xs object-cover" }) : _jsx(Paperclip, { "aria-hidden": true, className: "size-3 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: a.name }), _jsx("span", { className: cn("shrink-0 tabular-nums", a.error ? "text-destructive-text" : "text-faint-foreground"), children: a.error ? a.error : a.progress !== undefined ? fmt.number(Math.round(a.progress * 100) / 100, { style: "percent" }) : formatFileSize(a.size, fmt.number, t) }), onRemove && (_jsx("button", { type: "button", "aria-label": t("remove", { name: a.name }), onClick: () => onRemove(a.id), className: "relative after:absolute after:-inset-1 rounded-sm p-0.5 text-faint-foreground hover:bg-accent hover:text-foreground", children: _jsx(X, { className: "size-3" }) })), a.progress !== undefined && !a.error && _jsx("span", { "aria-hidden": true, className: "absolute bottom-0 start-0 h-0.5 bg-primary transition-[width] duration-300", style: { width: `${a.progress * 100}%` } })] }));
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
64
|
* A sent file, as a tile in a message: an image preview when the app supplied one, else an icon for its type, with
|
|
@@ -32,7 +32,7 @@ class CardBoundary extends React.Component {
|
|
|
32
32
|
}
|
|
33
33
|
function Summary({ text, }) {
|
|
34
34
|
const t = useStrings("cardRegistry", cardRegistryStrings);
|
|
35
|
-
return (_jsxs(Collapsible, { className: "group/summary", children: [_jsxs(CollapsibleTrigger, { className: "inline-flex items-center gap-1 text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40 rounded-sm", children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3 transition-transform group-data-[state=open]/summary:[transform:rotate(90deg)]" }), " ", t("showAsText")] }), _jsx(CollapsibleContent, { children: _jsx("p", { className: "mt-1.5 rounded-md bg-sunken px-3 py-2 text-sm whitespace-pre-wrap", children: text }) })] }));
|
|
35
|
+
return (_jsxs(Collapsible, { className: "group/summary", children: [_jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-1 inline-flex items-center gap-1 text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40 rounded-sm", children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3 transition-transform group-data-[state=open]/summary:[transform:rotate(90deg)]" }), " ", t("showAsText")] }), _jsx(CollapsibleContent, { children: _jsx("p", { className: "mt-1.5 rounded-md bg-sunken px-3 py-2 text-sm whitespace-pre-wrap", children: text }) })] }));
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* Renders one card from the registry. Three honest fallbacks, each with the
|
package/dist/chat/citation.js
CHANGED
|
@@ -40,7 +40,9 @@ const kindOf = (kind, t) => {
|
|
|
40
40
|
function SourceDetail({ source, }) {
|
|
41
41
|
const t = useStrings("citation", citationStrings);
|
|
42
42
|
const { Icon, noun } = kindOf(source.kind, t);
|
|
43
|
-
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-sm bg-muted text-muted-foreground", children: _jsx(Icon, { className: "size-3" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium leading-snug", children: source.label }), _jsx("p", { className: "text-xs text-muted-foreground", children: source.origin ?? noun })] })] }), source.snippet && (
|
|
43
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-sm bg-muted text-muted-foreground", children: _jsx(Icon, { className: "size-3" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium leading-snug", children: source.label }), _jsx("p", { className: "text-xs text-muted-foreground", children: source.origin ?? noun })] })] }), source.snippet && (
|
|
44
|
+
// oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
45
|
+
_jsx("blockquote", { tabIndex: 0, className: "max-h-40 overflow-auto rounded-md bg-sunken outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40 focus-visible:ring-inset px-2.5 py-2 text-xs leading-relaxed whitespace-pre-wrap text-muted-foreground", children: source.snippet })), source.provisional && (_jsx("p", { className: "text-xs text-provisional", children: t("notVerified") }))] }));
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
48
|
* An inline [n]. Opens the source in a popover on click or Enter, so evidence is
|
|
@@ -54,7 +56,7 @@ function Citation({ source, marker, }) {
|
|
|
54
56
|
}
|
|
55
57
|
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("sourceLabel", { marker: source.marker, label: source.label }), className: cn("mx-0.5 inline-flex h-4 min-w-4 -translate-y-px items-center justify-center rounded-sm px-1 align-middle text-2xs font-semibold tabular-nums transition-colors duration-150 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40", source.provisional
|
|
56
58
|
? "border border-dashed border-provisional/60 text-provisional hover:bg-muted"
|
|
57
|
-
: "bg-accent-weak text-accent-text hover:bg-primary hover:text-primary-foreground"), children: source.marker }) }), _jsx(PopoverContent, { align: "start", className: "w-80 p-3", children: _jsx(SourceDetail, { source: source }) })] }));
|
|
59
|
+
: "bg-accent-weak text-accent-text hover:bg-primary hover:text-primary-foreground"), children: source.marker }) }), _jsx(PopoverContent, { align: "start", className: "w-80 max-w-[calc(100vw-2rem)] p-3", children: _jsx(SourceDetail, { source: source }) })] }));
|
|
58
60
|
}
|
|
59
61
|
/** The sources under a reply, as chips that open the same detail. */
|
|
60
62
|
function SourceList({ sources, className, }) {
|
|
@@ -65,7 +67,7 @@ function SourceList({ sources, className, }) {
|
|
|
65
67
|
.sort((a, b) => a.marker - b.marker)
|
|
66
68
|
.map((s) => {
|
|
67
69
|
const { Icon } = kindOf(s.kind, t);
|
|
68
|
-
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn("inline-flex max-w-56 items-center gap-1.5 rounded-md border bg-card py-0.5 pe-2 ps-1 text-xs text-muted-foreground transition-colors duration-150 outline-none hover:border-border-strong hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", s.provisional && "border-dashed"), children: [_jsx("span", { className: "flex h-4 min-w-4 items-center justify-center rounded-sm bg-muted px-1 text-2xs font-semibold tabular-nums", children: s.marker }), _jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0" }), _jsx("span", { className: "truncate", children: s.label })] }) }), _jsx(PopoverContent, { align: "start", className: "w-80 p-3", children: _jsx(SourceDetail, { source: s }) })] }, s.id));
|
|
70
|
+
return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn("relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex max-w-56 items-center gap-1.5 rounded-md border bg-card py-0.5 pe-2 ps-1 text-xs text-muted-foreground transition-colors duration-150 outline-none hover:border-border-strong hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", s.provisional && "border-dashed"), children: [_jsx("span", { className: "flex h-4 min-w-4 items-center justify-center rounded-sm bg-muted px-1 text-2xs font-semibold tabular-nums", children: s.marker }), _jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0" }), _jsx("span", { className: "truncate", children: s.label })] }) }), _jsx(PopoverContent, { align: "start", className: "w-80 max-w-[calc(100vw-2rem)] p-3", children: _jsx(SourceDetail, { source: s }) })] }, s.id));
|
|
69
71
|
})] }));
|
|
70
72
|
}
|
|
71
73
|
export { Citation, SourceList, SourceDetail };
|
package/dist/chat/composer.d.ts
CHANGED
|
@@ -32,9 +32,10 @@ export type ComposerState = "ready" | "sending" | "streaming" | {
|
|
|
32
32
|
*
|
|
33
33
|
* Enter sends, Shift+Enter breaks a line. While an agent replies the send button
|
|
34
34
|
* becomes Stop. A locked composer says why; an offline one says so. With
|
|
35
|
-
* `onAttach`, files can also be pasted or dropped onto it.
|
|
35
|
+
* `onAttach`, files can also be pasted or dropped onto it. `tools` adds your
|
|
36
|
+
* own controls to the toolbar, such as a dictation button.
|
|
36
37
|
*/
|
|
37
|
-
declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onScopeClick, mentions, commands, references, onReferencesChange, attachments, onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, className, }: {
|
|
38
|
+
declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onScopeClick, mentions, commands, references, onReferencesChange, attachments, onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, className, }: {
|
|
38
39
|
/** The text in the box. */
|
|
39
40
|
value: string;
|
|
40
41
|
/** Called with the new text as the person types. */
|
|
@@ -72,6 +73,8 @@ declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onS
|
|
|
72
73
|
placeholder?: string;
|
|
73
74
|
/** The accessible name of the text box; "Message" by default. */
|
|
74
75
|
label?: string;
|
|
76
|
+
/** Your own controls in the toolbar, after the attach button: a dictation button, a model picker. */
|
|
77
|
+
tools?: React.ReactNode;
|
|
75
78
|
className?: string;
|
|
76
79
|
}): React.JSX.Element;
|
|
77
80
|
export { Composer };
|
package/dist/chat/composer.js
CHANGED
|
@@ -47,9 +47,10 @@ const hasFiles = (e) => Array.from(e.dataTransfer?.types ?? []).includes("Files"
|
|
|
47
47
|
*
|
|
48
48
|
* Enter sends, Shift+Enter breaks a line. While an agent replies the send button
|
|
49
49
|
* becomes Stop. A locked composer says why; an offline one says so. With
|
|
50
|
-
* `onAttach`, files can also be pasted or dropped onto it.
|
|
50
|
+
* `onAttach`, files can also be pasted or dropped onto it. `tools` adds your
|
|
51
|
+
* own controls to the toolbar, such as a dictation button.
|
|
51
52
|
*/
|
|
52
|
-
function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, onScopeClick, mentions = [], commands = [], references = [], onReferencesChange, attachments = [], onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, className, }) {
|
|
53
|
+
function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, onScopeClick, mentions = [], commands = [], references = [], onReferencesChange, attachments = [], onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, className, }) {
|
|
53
54
|
const kindOf = useEntityKinds();
|
|
54
55
|
const t = useStrings("composer", composerStrings);
|
|
55
56
|
const placeholder = placeholderProp ?? t("placeholder");
|
|
@@ -211,6 +212,6 @@ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, o
|
|
|
211
212
|
if (files.length)
|
|
212
213
|
onAttach(files);
|
|
213
214
|
e.target.value = "";
|
|
214
|
-
} }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("attachFiles"), disabled: Boolean(locked), onClick: () => fileInput.current?.click(), className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:opacity-40", children: _jsx(Paperclip, { className: "size-4" }) }) }), _jsx(TooltipContent, { children: t("attachFiles") })] })] })), scope && (_jsxs("button", { type: "button", onClick: onScopeClick, disabled: !onScopeClick, className: cn("inline-flex min-w-0 items-center gap-1.5 rounded-lg px-2 py-1 text-xs transition-colors duration-150 enabled:hover:bg-accent", production ? "bg-warning-weak text-warning-text enabled:hover:bg-warning-weak" : "text-muted-foreground"), children: [production ? _jsx(TriangleAlert, { "aria-hidden": true, className: "size-3 shrink-0" }) : _jsx("span", { "aria-hidden": true, className: "size-1.5 shrink-0 rounded-full bg-success" }), _jsxs("span", { className: "truncate", children: [scope.environment, " \u00B7 ", production ? t("production") : (scope.detail ?? t("nonProduction"))] })] })), _jsxs("span", { className: "ms-auto hidden text-2xs text-faint-foreground @lg:inline", children: [_jsx("kbd", { className: "font-sans", children: "\u21B5" }), " ", t("hintSend"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "\u21E7\u21B5" }), " ", t("hintNewLine"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "@" }), " ", t("hintMention")] }), state === "streaming" && onStop ? (_jsx("button", { type: "button", "aria-label": t("stop"), onClick: onStop, className: "ms-auto flex size-8 items-center justify-center rounded-lg bg-foreground text-background transition-transform duration-150 active:scale-95 @lg:ms-1", children: _jsx(Square, { className: "size-3 fill-current" }) })) : (_jsx("button", { type: "button", "aria-label": t("send"), onClick: submit, disabled: !canSend, "aria-busy": state === "sending", className: "ms-auto flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground transition-[background-color,opacity,transform] duration-150 enabled:hover:bg-primary-hover active:scale-95 disabled:opacity-30 @lg:ms-1", children: state === "sending" ? _jsx(LoaderCircle, { className: "size-4 animate-spin" }) : _jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 }) }))] })] })] }));
|
|
215
|
+
} }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("attachFiles"), disabled: Boolean(locked), onClick: () => fileInput.current?.click(), className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:opacity-40", children: _jsx(Paperclip, { className: "size-4" }) }) }), _jsx(TooltipContent, { children: t("attachFiles") })] })] })), tools, scope && (_jsxs("button", { type: "button", onClick: onScopeClick, disabled: !onScopeClick, className: cn("inline-flex min-w-0 items-center gap-1.5 rounded-lg px-2 py-1 text-xs transition-colors duration-150 enabled:hover:bg-accent", production ? "bg-warning-weak text-warning-text enabled:hover:bg-warning-weak" : "text-muted-foreground"), children: [production ? _jsx(TriangleAlert, { "aria-hidden": true, className: "size-3 shrink-0" }) : _jsx("span", { "aria-hidden": true, className: "size-1.5 shrink-0 rounded-full bg-success" }), _jsxs("span", { className: "truncate", children: [scope.environment, " \u00B7 ", production ? t("production") : (scope.detail ?? t("nonProduction"))] })] })), _jsxs("span", { className: "ms-auto hidden text-2xs text-faint-foreground @lg:inline", children: [_jsx("kbd", { className: "font-sans", children: "\u21B5" }), " ", t("hintSend"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "\u21E7\u21B5" }), " ", t("hintNewLine"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "@" }), " ", t("hintMention")] }), state === "streaming" && onStop ? (_jsx("button", { type: "button", "aria-label": t("stop"), onClick: onStop, className: "ms-auto flex size-8 items-center justify-center rounded-lg bg-foreground text-background transition-transform duration-150 active:scale-95 @lg:ms-1", children: _jsx(Square, { className: "size-3 fill-current" }) })) : (_jsx("button", { type: "button", "aria-label": t("send"), onClick: submit, disabled: !canSend, "aria-busy": state === "sending", className: "ms-auto flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground transition-[background-color,opacity,transform] duration-150 enabled:hover:bg-primary-hover active:scale-95 disabled:opacity-30 @lg:ms-1", children: state === "sending" ? _jsx(LoaderCircle, { className: "size-4 animate-spin" }) : _jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 }) }))] })] })] }));
|
|
215
216
|
}
|
|
216
217
|
export { Composer };
|
package/dist/chat/entity-chip.js
CHANGED
|
@@ -81,6 +81,6 @@ function EntityChip({ entity, onOpen, onRemove, className, }) {
|
|
|
81
81
|
: t("title", { noun, name: entity.name });
|
|
82
82
|
const body = (_jsxs(_Fragment, { children: [_jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: shown }), _jsxs("span", { className: "sr-only", children: [" (", title, ")"] })] }));
|
|
83
83
|
const base = "inline-flex max-w-full items-center gap-1 rounded-sm border bg-card px-1.5 py-px align-baseline font-mono text-[0.92em] leading-[1.35] text-foreground";
|
|
84
|
-
return (_jsxs("span", { "data-slot": "entity-chip", "data-kind": entity.kind, title: title, className: cn("inline-flex max-w-full min-w-0 items-center sm:max-w-64", className), children: [onOpen ? (_jsx("button", { type: "button", onClick: () => onOpen(entity), className: cn(base, "cursor-pointer transition-colors duration-150 hover:border-border-strong hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40 outline-none"), children: body })) : (_jsx("span", { className: base, children: body })), onRemove && (_jsx("button", { type: "button", "aria-label": t("remove", { name: shown }), onClick: () => onRemove(entity), className: "-ms-px rounded-e-sm border border-s-0 bg-card px-1 py-px text-faint-foreground hover:text-foreground", children: "\u00D7" }))] }));
|
|
84
|
+
return (_jsxs("span", { "data-slot": "entity-chip", "data-kind": entity.kind, title: title, className: cn("inline-flex max-w-full min-w-0 items-center sm:max-w-64", className), children: [onOpen ? (_jsx("button", { type: "button", onClick: () => onOpen(entity), className: cn(base, "cursor-pointer transition-colors duration-150 hover:border-border-strong hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40 outline-none"), children: body })) : (_jsx("span", { className: base, children: body })), onRemove && (_jsx("button", { type: "button", "aria-label": t("remove", { name: shown }), onClick: () => onRemove(entity), className: "relative after:absolute after:-inset-1 -ms-px rounded-e-sm border border-s-0 bg-card px-1 py-px text-faint-foreground hover:text-foreground", children: "\u00D7" }))] }));
|
|
85
85
|
}
|
|
86
86
|
export { EntityChip, EntityKindsProvider, useEntityKinds };
|
|
@@ -24,6 +24,6 @@ function FoldedTurns({ count, summary, defaultOpen = false, open, onOpenChange,
|
|
|
24
24
|
setOwn(next);
|
|
25
25
|
onOpenChange?.(next);
|
|
26
26
|
};
|
|
27
|
-
return (_jsxs(Collapsible, { open: isOpen, onOpenChange: set, "data-slot": "folded-turns", className: cn("group/folded flex flex-col gap-7", className), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 rounded-lg border border-dashed px-3 py-2 text-sm", children: [summary && _jsx("div", { className: "min-w-0 flex-1 text-muted-foreground", children: summary }), _jsxs(CollapsibleTrigger, { className: "ms-auto inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium text-accent-text outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 transition-transform duration-200 group-data-[state=open]/folded:[transform:rotate(90deg)]" }), t(isOpen ? "hide" : "show", { count })] })] }), _jsx(CollapsibleContent, { className: "flex flex-col gap-7", children: children })] }));
|
|
27
|
+
return (_jsxs(Collapsible, { open: isOpen, onOpenChange: set, "data-slot": "folded-turns", className: cn("group/folded flex flex-col gap-7", className), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 rounded-lg border border-dashed px-3 py-2 text-sm", children: [summary && _jsx("div", { className: "min-w-0 flex-1 text-muted-foreground", children: summary }), _jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 ms-auto inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium text-accent-text outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 transition-transform duration-200 group-data-[state=open]/folded:[transform:rotate(90deg)]" }), t(isOpen ? "hide" : "show", { count })] })] }), _jsx(CollapsibleContent, { className: "flex flex-col gap-7", children: children })] }));
|
|
28
28
|
}
|
|
29
29
|
export { FoldedTurns };
|
package/dist/chat/message.js
CHANGED
|
@@ -80,6 +80,6 @@ function MessageFooter({ message, onRetry, onContinue, rating, onRate, reviewedB
|
|
|
80
80
|
const streaming = message.status === "streaming";
|
|
81
81
|
const settled = !streaming && message.status !== "failed";
|
|
82
82
|
const meta = [message.attribution?.model, message.attribution?.cost].filter(Boolean).join(" · ");
|
|
83
|
-
return (_jsxs("div", { "data-slot": "message-footer", className: cn("flex flex-col gap-3 empty:hidden", className), children: [message.status === "interrupted" && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [_jsx(CircleStop, { "aria-hidden": true, className: "size-3.5" }), t("interrupted"), onContinue && (_jsx("button", { type: "button", onClick: onContinue, className: "font-medium text-accent-text hover:underline", children: t("continue") }))] })), message.status === "failed" && _jsx(DegradedPanel, { reason: t("failed"), missing: t("failedMissing"), onRetry: onRetry }), !streaming && message.sources && message.sources.length > 0 && _jsx(SourceList, { sources: message.sources }), reviewedBy && (_jsxs("p", { "data-slot": "message-reviewed", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(UserCheck, { "aria-hidden": true, className: "size-3.5" }), t(reviewedBy.edited ? "editedBy" : "checkedBy", { name: reviewedBy.name }), reviewedBy.at && (_jsxs(_Fragment, { children: [" · ", _jsx(RelativeTime, { date: reviewedBy.at })] }))] })), settled && actions === undefined && (_jsx(MessageActions, { text: blocksToText(message.blocks, message.sources), onRetry: onRetry, rating: rating, onRate: onRate, meta: meta || undefined, className: "-ms-1.5 opacity-100 transition-opacity duration-150 sm:opacity-0 sm:group-hover/message:opacity-100 sm:group-focus-within/message:opacity-100" })), settled && actions !== undefined && actions !== false && actions, settled && actions !== undefined && _jsx(MessageAttribution, { attribution: message.attribution })] }));
|
|
83
|
+
return (_jsxs("div", { "data-slot": "message-footer", className: cn("flex flex-col gap-3 empty:hidden", className), children: [message.status === "interrupted" && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [_jsx(CircleStop, { "aria-hidden": true, className: "size-3.5" }), t("interrupted"), onContinue && (_jsx("button", { type: "button", onClick: onContinue, className: "relative after:absolute after:inset-x-0 after:-inset-y-1 font-medium text-accent-text hover:underline", children: t("continue") }))] })), message.status === "failed" && _jsx(DegradedPanel, { reason: t("failed"), missing: t("failedMissing"), onRetry: onRetry }), !streaming && message.sources && message.sources.length > 0 && _jsx(SourceList, { sources: message.sources }), reviewedBy && (_jsxs("p", { "data-slot": "message-reviewed", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(UserCheck, { "aria-hidden": true, className: "size-3.5" }), t(reviewedBy.edited ? "editedBy" : "checkedBy", { name: reviewedBy.name }), reviewedBy.at && (_jsxs(_Fragment, { children: [" · ", _jsx(RelativeTime, { date: reviewedBy.at })] }))] })), settled && actions === undefined && (_jsx(MessageActions, { text: blocksToText(message.blocks, message.sources), onRetry: onRetry, rating: rating, onRate: onRate, meta: meta || undefined, className: "-ms-1.5 opacity-100 transition-opacity duration-150 sm:opacity-0 sm:group-hover/message:opacity-100 sm:group-focus-within/message:opacity-100" })), settled && actions !== undefined && actions !== false && actions, settled && actions !== undefined && _jsx(MessageAttribution, { attribution: message.attribution })] }));
|
|
84
84
|
}
|
|
85
85
|
export { Message, MessageHeader, MessageBody, MessageFooter, MessageAttribution };
|
package/dist/chat/payload.js
CHANGED
|
@@ -39,9 +39,12 @@ function Payload({ language, text, title, bare = false, highlight, className, })
|
|
|
39
39
|
const syntax = highlight === undefined ? undefined : highlight ? "highlight" : "off";
|
|
40
40
|
const code = _jsx(Code, { text: text, language: language });
|
|
41
41
|
if (bare) {
|
|
42
|
-
return (
|
|
42
|
+
return (
|
|
43
|
+
// It scrolls on its own, so it takes focus: keyboard users can scroll long output.
|
|
44
|
+
// oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
45
|
+
_jsx("pre", { "data-slot": "payload", "data-bare": "", "data-language": language, "data-syntax": syntax, tabIndex: 0, className: cn("max-h-80 overflow-auto rounded-md bg-sunken outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40 focus-visible:ring-inset px-3 py-2.5 font-mono text-xs leading-relaxed text-foreground", className), children: code }));
|
|
43
46
|
}
|
|
44
|
-
return (_jsxs("figure", { "data-slot": "payload", "data-language": language, "data-syntax": syntax, className: cn("overflow-hidden rounded-lg border bg-sunken", className), children: [_jsxs("figcaption", { className: "flex items-center justify-between gap-2 border-b px-3 py-1.5", children: [_jsxs("span", { className: "truncate text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-foreground", children: languageLabel(language, t) }), title && _jsxs("span", { children: [" \u00B7 ", title] })] }), _jsx("button", { type: "button", onClick: () => copy(text), "aria-live": "polite", className: "inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs text-muted-foreground transition-colors duration-150 outline-none hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", children: state === "copied" ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "size-3 text-success-text" }), " ", t("copied")] })) : state === "failed" ? (_jsxs(_Fragment, { children: [_jsx(X, { className: "size-3 text-destructive-text" }), " ", t("copyFailed")] })) : (_jsxs(_Fragment, { children: [_jsx(Copy, { className: "size-3" }), " ", t("copy")] })) })] }), _jsx("pre", { className: "max-h-80 overflow-auto px-3 py-2.5 font-mono text-xs leading-relaxed text-foreground", children: code })] }));
|
|
47
|
+
return (_jsxs("figure", { "data-slot": "payload", "data-language": language, "data-syntax": syntax, className: cn("overflow-hidden rounded-lg border bg-sunken", className), children: [_jsxs("figcaption", { className: "flex items-center justify-between gap-2 border-b px-3 py-1.5", children: [_jsxs("span", { className: "truncate text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-foreground", children: languageLabel(language, t) }), title && _jsxs("span", { children: [" \u00B7 ", title] })] }), _jsx("button", { type: "button", onClick: () => copy(text), "aria-live": "polite", className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs text-muted-foreground transition-colors duration-150 outline-none hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", children: state === "copied" ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "size-3 text-success-text" }), " ", t("copied")] })) : state === "failed" ? (_jsxs(_Fragment, { children: [_jsx(X, { className: "size-3 text-destructive-text" }), " ", t("copyFailed")] })) : (_jsxs(_Fragment, { children: [_jsx(Copy, { className: "size-3" }), " ", t("copy")] })) })] }), _jsx("pre", { tabIndex: 0, className: "max-h-80 overflow-auto px-3 py-2.5 font-mono text-xs leading-relaxed text-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40 focus-visible:ring-inset", children: code })] }));
|
|
45
48
|
}
|
|
46
49
|
/** The code as token spans. Each token is a text child, so nothing in it is parsed as markup. */
|
|
47
50
|
function Code({ text, language }) {
|