@superxd-studio/adptr 0.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/GENERATIVE.md +101 -0
- package/INTERACTIONS.md +51 -0
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/REUSE.md +170 -0
- package/REVIEW.md +68 -0
- package/THIRD-PARTY-NOTICES.md +28 -0
- package/ai-sdk/index.js +96 -0
- package/assets/ibm-plex-mono-cyrillic-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-ext-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-ext-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-vietnamese-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-vietnamese-500-normal.woff2 +0 -0
- package/assets/instrument-sans-latin-ext-wght-normal.woff2 +0 -0
- package/assets/instrument-sans-latin-wght-normal.woff2 +0 -0
- package/assistant-ui/index.js +139 -0
- package/chunks/callout-DKMHSnjc.js +342 -0
- package/chunks/canvas-BJ15-XxE.js +598 -0
- package/chunks/controller-Cfpg0C7v.js +180 -0
- package/chunks/core-BCqVRLUy.js +312 -0
- package/chunks/text-field-BLtoaZFn.js +41 -0
- package/chunks/types-BGF7r1my.js +115 -0
- package/chunks/ui-9DyNsYXY.js +967 -0
- package/core/index.js +4 -0
- package/custom/index.js +64 -0
- package/examples/ai-sdk-backend/package.json +11 -0
- package/examples/ai-sdk-backend/server.mjs +316 -0
- package/examples/custom-backend/copy.mjs +7 -0
- package/examples/custom-backend/server.mjs +118 -0
- package/examples/generative/specs.json +2094 -0
- package/examples/review-backend/copy.mjs +27 -0
- package/examples/review-backend/package.json +11 -0
- package/examples/review-backend/server.mjs +276 -0
- package/examples/web-ai-sdk/index.html +12 -0
- package/examples/web-ai-sdk/package.json +21 -0
- package/examples/web-ai-sdk/src/copy.ts +1 -0
- package/examples/web-ai-sdk/src/main.tsx +64 -0
- package/examples/web-ai-sdk/tsconfig.json +13 -0
- package/examples/web-generative/index.html +12 -0
- package/examples/web-generative/package.json +23 -0
- package/examples/web-generative/src/copy.ts +42 -0
- package/examples/web-generative/src/main.tsx +74 -0
- package/examples/web-generative/tsconfig.json +14 -0
- package/examples/web-independent/index.html +12 -0
- package/examples/web-independent/package.json +20 -0
- package/examples/web-independent/src/main.tsx +32 -0
- package/examples/web-independent/tsconfig.json +14 -0
- package/examples/web-interactions/index.html +12 -0
- package/examples/web-interactions/package.json +21 -0
- package/examples/web-interactions/src/copy.ts +215 -0
- package/examples/web-interactions/src/examples.tsx +628 -0
- package/examples/web-interactions/src/main.tsx +116 -0
- package/examples/web-interactions/tsconfig.json +14 -0
- package/examples/web-review/index.html +12 -0
- package/examples/web-review/package.json +20 -0
- package/examples/web-review/src/main.tsx +59 -0
- package/examples/web-review/tsconfig.json +14 -0
- package/fonts.css +117 -0
- package/generative/index.js +1560 -0
- package/interactions/index.js +1591 -0
- package/langgraph-review/index.js +36 -0
- package/licenses/ai-sdk.txt +13 -0
- package/licenses/assistant-ui-generative.txt +21 -0
- package/licenses/assistant-ui.txt +23 -0
- package/licenses/ibm-plex-mono.txt +93 -0
- package/licenses/instrument-sans.txt +93 -0
- package/licenses/internationalized-date.txt +201 -0
- package/licenses/langgraph.txt +21 -0
- package/licenses/lucide.txt +43 -0
- package/licenses/react-markdown.txt +21 -0
- package/licenses/remark-gfm.txt +22 -0
- package/licenses/zod.txt +21 -0
- package/package.json +138 -0
- package/review/index.js +31 -0
- package/source/components/avatar.tsx +50 -0
- package/source/components/button.tsx +155 -0
- package/source/components/callout.tsx +65 -0
- package/source/components/canvas/connector.tsx +14 -0
- package/source/components/canvas/copy.ts +34 -0
- package/source/components/canvas/index.tsx +257 -0
- package/source/components/canvas/recorder.ts +36 -0
- package/source/components/date-time-field.tsx +77 -0
- package/source/components/flow-motion.tsx +109 -0
- package/source/components/icons.tsx +275 -0
- package/source/components/responsive-overlay.tsx +91 -0
- package/source/components/scenario-picker/copy.ts +5 -0
- package/source/components/scenario-picker/index.tsx +142 -0
- package/source/components/scroll-area.tsx +59 -0
- package/source/components/search-picker.tsx +127 -0
- package/source/components/segmented-control.tsx +123 -0
- package/source/components/skeleton.tsx +11 -0
- package/source/components/spinner.tsx +19 -0
- package/source/components/superxd-arrow.tsx +22 -0
- package/source/components/switch.tsx +45 -0
- package/source/components/text-field.tsx +81 -0
- package/source/components/thinking.tsx +16 -0
- package/source/components/ui/copy.ts +12 -0
- package/source/components/ui/display.tsx +276 -0
- package/source/components/ui/navigation.tsx +346 -0
- package/source/components/ui/portal-theme.tsx +28 -0
- package/source/components/ui/selection.tsx +336 -0
- package/source/lib/cn.ts +23 -0
- package/source/reusable/ai-sdk/index.ts +114 -0
- package/source/reusable/assistant-ui/index.tsx +266 -0
- package/source/reusable/core/attachments.ts +163 -0
- package/source/reusable/core/index.ts +417 -0
- package/source/reusable/custom/index.ts +90 -0
- package/source/reusable/generative/actions.ts +188 -0
- package/source/reusable/generative/copy.ts +47 -0
- package/source/reusable/generative/data.tsx +126 -0
- package/source/reusable/generative/index.tsx +517 -0
- package/source/reusable/generative/schema.ts +327 -0
- package/source/reusable/generative/timeline.tsx +214 -0
- package/source/reusable/generative/vocabulary.tsx +847 -0
- package/source/reusable/interactions/agent.ts +259 -0
- package/source/reusable/interactions/artifact.ts +236 -0
- package/source/reusable/interactions/context.tsx +38 -0
- package/source/reusable/interactions/copy.ts +117 -0
- package/source/reusable/interactions/feedback.ts +73 -0
- package/source/reusable/interactions/index.ts +11 -0
- package/source/reusable/interactions/presentation.tsx +483 -0
- package/source/reusable/interactions/store.ts +74 -0
- package/source/reusable/interactions/threads.ts +126 -0
- package/source/reusable/interactions/web.tsx +574 -0
- package/source/reusable/langgraph-review/index.ts +73 -0
- package/source/reusable/review/controller.ts +263 -0
- package/source/reusable/review/index.ts +47 -0
- package/source/reusable/review/types.ts +189 -0
- package/source/reusable/ui/index.ts +25 -0
- package/source/reusable/uploads/index.ts +48 -0
- package/source/reusable/web/attachments/copy.ts +23 -0
- package/source/reusable/web/attachments/index.tsx +233 -0
- package/source/reusable/web/copy.ts +22 -0
- package/source/reusable/web/index.tsx +470 -0
- package/source/reusable/web/message/copy.ts +26 -0
- package/source/reusable/web/message/index.tsx +388 -0
- package/source/reusable/web/message/streaming.ts +64 -0
- package/source/reusable/web/message/styles.css +21 -0
- package/source/reusable/web/review/copy.ts +78 -0
- package/source/reusable/web/review/flow.ts +24 -0
- package/source/reusable/web/review/index.tsx +506 -0
- package/source/reusable/web/styles.ts +2 -0
- package/source/styles/library.css +612 -0
- package/source/styles/theme.css +228 -0
- package/styles.css +3 -0
- package/styles.d.ts +1 -0
- package/types/components/avatar.d.ts +14 -0
- package/types/components/button.d.ts +36 -0
- package/types/components/callout.d.ts +19 -0
- package/types/components/canvas/connector.d.ts +2 -0
- package/types/components/canvas/copy.d.ts +34 -0
- package/types/components/canvas/index.d.ts +33 -0
- package/types/components/canvas/recorder.d.ts +10 -0
- package/types/components/date-time-field.d.ts +11 -0
- package/types/components/flow-motion.d.ts +38 -0
- package/types/components/icons.d.ts +138 -0
- package/types/components/responsive-overlay.d.ts +15 -0
- package/types/components/scenario-picker/copy.d.ts +5 -0
- package/types/components/scenario-picker/index.d.ts +14 -0
- package/types/components/scroll-area.d.ts +14 -0
- package/types/components/search-picker.d.ts +27 -0
- package/types/components/segmented-control.d.ts +23 -0
- package/types/components/skeleton.d.ts +4 -0
- package/types/components/spinner.d.ts +4 -0
- package/types/components/superxd-arrow.d.ts +5 -0
- package/types/components/switch.d.ts +8 -0
- package/types/components/text-field.d.ts +19 -0
- package/types/components/thinking.d.ts +8 -0
- package/types/components/ui/copy.d.ts +12 -0
- package/types/components/ui/display.d.ts +50 -0
- package/types/components/ui/navigation.d.ts +63 -0
- package/types/components/ui/portal-theme.d.ts +8 -0
- package/types/components/ui/selection.d.ts +37 -0
- package/types/lib/cn.d.ts +3 -0
- package/types/reusable/ai-sdk/index.d.ts +15 -0
- package/types/reusable/assistant-ui/index.d.ts +12 -0
- package/types/reusable/core/attachments.d.ts +54 -0
- package/types/reusable/core/index.d.ts +107 -0
- package/types/reusable/custom/index.d.ts +14 -0
- package/types/reusable/generative/actions.d.ts +45 -0
- package/types/reusable/generative/copy.d.ts +48 -0
- package/types/reusable/generative/data.d.ts +4 -0
- package/types/reusable/generative/index.d.ts +30 -0
- package/types/reusable/generative/schema.d.ts +303 -0
- package/types/reusable/generative/timeline.d.ts +21 -0
- package/types/reusable/generative/vocabulary.d.ts +34 -0
- package/types/reusable/interactions/agent.d.ts +98 -0
- package/types/reusable/interactions/artifact.d.ts +118 -0
- package/types/reusable/interactions/context.d.ts +130 -0
- package/types/reusable/interactions/copy.d.ts +115 -0
- package/types/reusable/interactions/feedback.d.ts +34 -0
- package/types/reusable/interactions/index.d.ts +10 -0
- package/types/reusable/interactions/presentation.d.ts +97 -0
- package/types/reusable/interactions/store.d.ts +29 -0
- package/types/reusable/interactions/threads.d.ts +38 -0
- package/types/reusable/interactions/web.d.ts +22 -0
- package/types/reusable/langgraph-review/index.d.ts +5 -0
- package/types/reusable/review/controller.d.ts +52 -0
- package/types/reusable/review/index.d.ts +12 -0
- package/types/reusable/review/types.d.ts +61 -0
- package/types/reusable/ui/index.d.ts +19 -0
- package/types/reusable/uploads/index.d.ts +11 -0
- package/types/reusable/web/attachments/copy.d.ts +25 -0
- package/types/reusable/web/attachments/index.d.ts +24 -0
- package/types/reusable/web/copy.d.ts +24 -0
- package/types/reusable/web/index.d.ts +41 -0
- package/types/reusable/web/message/copy.d.ts +27 -0
- package/types/reusable/web/message/index.d.ts +56 -0
- package/types/reusable/web/message/streaming.d.ts +27 -0
- package/types/reusable/web/review/copy.d.ts +72 -0
- package/types/reusable/web/review/flow.d.ts +3 -0
- package/types/reusable/web/review/index.d.ts +38 -0
- package/types/reusable/web/styles.d.ts +2 -0
- package/ui/index.js +4 -0
- package/uploads/index.js +34 -0
- package/web/index.js +931 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { InteractionStore, identity, newIdentity } from "./store.js";
|
|
3
|
+
const feedbackSchema = z.strictObject({
|
|
4
|
+
requestId: identity,
|
|
5
|
+
responseId: identity,
|
|
6
|
+
rating: z.enum(["helpful", "unhelpful"]),
|
|
7
|
+
comment: z.string().max(5000),
|
|
8
|
+
});
|
|
9
|
+
export type FeedbackRequest = z.infer<typeof feedbackSchema>;
|
|
10
|
+
export interface FeedbackAdapter {
|
|
11
|
+
/** Upsert feedback for responseId and deduplicate retries by requestId. Resolve only after storage acknowledges. */
|
|
12
|
+
save(
|
|
13
|
+
request: FeedbackRequest,
|
|
14
|
+
signal: AbortSignal,
|
|
15
|
+
): Promise<{ requestId: string; responseId: string }>;
|
|
16
|
+
}
|
|
17
|
+
export interface FeedbackSnapshot {
|
|
18
|
+
responseId: string;
|
|
19
|
+
rating: "helpful" | "unhelpful" | null;
|
|
20
|
+
comment: string;
|
|
21
|
+
phase: "editing" | "saving" | "saved" | "error";
|
|
22
|
+
request?: FeedbackRequest;
|
|
23
|
+
}
|
|
24
|
+
export class FeedbackController extends InteractionStore<FeedbackSnapshot> {
|
|
25
|
+
constructor(
|
|
26
|
+
private adapter: FeedbackAdapter,
|
|
27
|
+
responseId: string,
|
|
28
|
+
private id: () => string = newIdentity,
|
|
29
|
+
) {
|
|
30
|
+
super({ responseId: identity.parse(responseId), rating: null, comment: "", phase: "editing" });
|
|
31
|
+
}
|
|
32
|
+
edit(rating: FeedbackSnapshot["rating"], comment = this.state.comment) {
|
|
33
|
+
if (
|
|
34
|
+
this.disposed ||
|
|
35
|
+
["saving", "error"].includes(this.state.phase) ||
|
|
36
|
+
!(rating === null || rating === "helpful" || rating === "unhelpful") ||
|
|
37
|
+
typeof comment !== "string" ||
|
|
38
|
+
comment.length > 5000
|
|
39
|
+
)
|
|
40
|
+
return false;
|
|
41
|
+
this.publish({ ...this.state, rating, comment, phase: "editing", request: undefined });
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
save() {
|
|
45
|
+
if (this.disposed || !this.state.rating || !["editing", "error"].includes(this.state.phase))
|
|
46
|
+
return false;
|
|
47
|
+
const request =
|
|
48
|
+
this.state.request ??
|
|
49
|
+
Object.freeze(
|
|
50
|
+
feedbackSchema.parse({
|
|
51
|
+
requestId: this.requestIdentity(this.id),
|
|
52
|
+
responseId: this.state.responseId,
|
|
53
|
+
rating: this.state.rating,
|
|
54
|
+
comment: this.state.comment,
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
const { token, signal } = this.request();
|
|
58
|
+
this.publish({ ...this.state, phase: "saving", request });
|
|
59
|
+
void (async () => {
|
|
60
|
+
try {
|
|
61
|
+
const result = z
|
|
62
|
+
.strictObject({ requestId: identity, responseId: identity })
|
|
63
|
+
.parse(await this.adapter.save(request, signal));
|
|
64
|
+
if (result.requestId !== request.requestId || result.responseId !== request.responseId)
|
|
65
|
+
throw new Error("Wrong feedback receipt");
|
|
66
|
+
if (this.current(token)) this.publish({ ...this.state, phase: "saved" });
|
|
67
|
+
} catch {
|
|
68
|
+
if (this.current(token)) this.publish({ ...this.state, phase: "error" });
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./agent.js";
|
|
2
|
+
export * from "./artifact.js";
|
|
3
|
+
export * from "./feedback.js";
|
|
4
|
+
export * from "./threads.js";
|
|
5
|
+
export * from "./presentation.js";
|
|
6
|
+
export * from "./web.js";
|
|
7
|
+
export { interactionCopy } from "./copy.js";
|
|
8
|
+
export { jsonSchema } from "./store.js";
|
|
9
|
+
|
|
10
|
+
export { InteractionProvider } from "./context.js";
|
|
11
|
+
export type { InteractionCopy, InteractionCopyOverrides } from "./context.js";
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { useInteractionCopy } from "./context.js";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { Link } from "react-aria-components";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import {
|
|
6
|
+
Accordion,
|
|
7
|
+
Badge,
|
|
8
|
+
Card,
|
|
9
|
+
Select,
|
|
10
|
+
CheckboxGroup,
|
|
11
|
+
Menu,
|
|
12
|
+
Tabs,
|
|
13
|
+
} from "../../reusable/ui/index.js";
|
|
14
|
+
import type { Choice, MenuCommand } from "../ui/index.js";
|
|
15
|
+
import { Button } from "../../components/button.js";
|
|
16
|
+
import { Callout } from "../../components/callout.js";
|
|
17
|
+
import { Spinner } from "../../components/spinner.js";
|
|
18
|
+
import { CodeBlock } from "../web/message/index.js";
|
|
19
|
+
import type { interactionCopy as copy } from "./copy.js";
|
|
20
|
+
import { jsonSchema, identity, shortText, bodyText, type JsonValue } from "./store.js";
|
|
21
|
+
|
|
22
|
+
/** Evidence links may navigate only to explicit HTTP(S) destinations, without embedded credentials. */
|
|
23
|
+
export function evidenceUrl(value: unknown): string | undefined {
|
|
24
|
+
if (typeof value !== "string" || value.length > 2000) return;
|
|
25
|
+
try {
|
|
26
|
+
const url = new URL(value);
|
|
27
|
+
if (["https:", "http:"].includes(url.protocol) && !url.username && !url.password)
|
|
28
|
+
return url.href;
|
|
29
|
+
} catch {
|
|
30
|
+
/* unavailable */
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const sourceSchema = z.strictObject({
|
|
34
|
+
id: identity,
|
|
35
|
+
title: shortText,
|
|
36
|
+
url: z.string().max(2000).optional(),
|
|
37
|
+
excerpt: bodyText.optional(),
|
|
38
|
+
claim: shortText.optional(),
|
|
39
|
+
});
|
|
40
|
+
export type SourceDocument = z.infer<typeof sourceSchema>;
|
|
41
|
+
export function Sources({
|
|
42
|
+
sources,
|
|
43
|
+
label,
|
|
44
|
+
}: {
|
|
45
|
+
sources: readonly SourceDocument[];
|
|
46
|
+
label?: string;
|
|
47
|
+
}) {
|
|
48
|
+
const copy = useInteractionCopy();
|
|
49
|
+
label ??= copy.sources;
|
|
50
|
+
|
|
51
|
+
const parsed = z.array(sourceSchema).max(100).safeParse(sources);
|
|
52
|
+
if (!parsed.success) return <Callout tone="error">{copy.invalid}</Callout>;
|
|
53
|
+
return (
|
|
54
|
+
<section aria-label={label} className="min-w-0">
|
|
55
|
+
<h3 className="mb-3 type-heading text-fg-emphasis">{label}</h3>
|
|
56
|
+
{!parsed.data.length && <p className="type-body text-fg-subtle">{copy.noSources}</p>}
|
|
57
|
+
<ol className="flex flex-col gap-4">
|
|
58
|
+
{parsed.data.map((source, index) => {
|
|
59
|
+
const href = evidenceUrl(source.url);
|
|
60
|
+
return (
|
|
61
|
+
<li
|
|
62
|
+
key={`${source.id}-${index}`}
|
|
63
|
+
className="min-w-0 rounded-control border border-line-subtle bg-surface p-4"
|
|
64
|
+
>
|
|
65
|
+
<span className="mr-2 font-mono type-caption text-fg-subtle">{index + 1}</span>
|
|
66
|
+
{href ? (
|
|
67
|
+
<Link
|
|
68
|
+
href={href}
|
|
69
|
+
target="_blank"
|
|
70
|
+
rel="noopener noreferrer"
|
|
71
|
+
aria-label={copy.source(index + 1, source.title)}
|
|
72
|
+
className="rounded-cell type-label break-words text-fg-emphasis underline outline-none data-focus-visible:focus-ring"
|
|
73
|
+
>
|
|
74
|
+
{source.title}
|
|
75
|
+
</Link>
|
|
76
|
+
) : (
|
|
77
|
+
<span className="type-label text-fg-emphasis">
|
|
78
|
+
{source.title}
|
|
79
|
+
<span className="ml-2 type-caption text-fg-subtle">{copy.unavailable}</span>
|
|
80
|
+
</span>
|
|
81
|
+
)}
|
|
82
|
+
{source.claim && <p className="mt-2 type-body text-fg">{source.claim}</p>}
|
|
83
|
+
{source.excerpt && (
|
|
84
|
+
<blockquote className="mt-2 border-l-2 border-line-emphasis pl-3 type-body whitespace-pre-wrap text-fg-subtle">
|
|
85
|
+
{source.excerpt}
|
|
86
|
+
</blockquote>
|
|
87
|
+
)}
|
|
88
|
+
</li>
|
|
89
|
+
);
|
|
90
|
+
})}
|
|
91
|
+
</ol>
|
|
92
|
+
</section>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
export function Citation({ source, index }: { source: SourceDocument; index: number }) {
|
|
96
|
+
const copy = useInteractionCopy();
|
|
97
|
+
|
|
98
|
+
const href = evidenceUrl(source.url);
|
|
99
|
+
return href ? (
|
|
100
|
+
<Link
|
|
101
|
+
href={href}
|
|
102
|
+
target="_blank"
|
|
103
|
+
rel="noopener noreferrer"
|
|
104
|
+
aria-label={copy.source(index, source.title)}
|
|
105
|
+
className="rounded-cell font-mono type-caption text-info underline outline-none data-focus-visible:focus-ring"
|
|
106
|
+
>
|
|
107
|
+
[{index}]
|
|
108
|
+
</Link>
|
|
109
|
+
) : (
|
|
110
|
+
<span aria-label={copy.unavailable} className="font-mono type-caption text-fg-subtle">
|
|
111
|
+
[{index}]
|
|
112
|
+
</span>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
export function RetrievalInspection({
|
|
116
|
+
documents,
|
|
117
|
+
}: {
|
|
118
|
+
documents: readonly (SourceDocument & { included: boolean })[];
|
|
119
|
+
}) {
|
|
120
|
+
const copy = useInteractionCopy();
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Accordion
|
|
124
|
+
label={copy.retrieval}
|
|
125
|
+
items={[
|
|
126
|
+
{
|
|
127
|
+
id: "context",
|
|
128
|
+
title: copy.retrieval,
|
|
129
|
+
content: (
|
|
130
|
+
<div className="flex flex-col gap-4">
|
|
131
|
+
{documents.map((document, index) => (
|
|
132
|
+
<div key={`${document.id}-${index}`}>
|
|
133
|
+
<Badge>{document.included ? copy.included : copy.excluded}</Badge>
|
|
134
|
+
<Sources
|
|
135
|
+
label={document.title}
|
|
136
|
+
sources={[
|
|
137
|
+
{
|
|
138
|
+
id: document.id,
|
|
139
|
+
title: document.title,
|
|
140
|
+
url: document.url,
|
|
141
|
+
excerpt: document.excerpt,
|
|
142
|
+
claim: document.claim,
|
|
143
|
+
},
|
|
144
|
+
]}
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
]}
|
|
152
|
+
/>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
export function Reasoning({
|
|
156
|
+
text,
|
|
157
|
+
status = "complete",
|
|
158
|
+
label,
|
|
159
|
+
}: {
|
|
160
|
+
text: string;
|
|
161
|
+
status?: "working" | "complete";
|
|
162
|
+
label?: string;
|
|
163
|
+
}) {
|
|
164
|
+
const copy = useInteractionCopy();
|
|
165
|
+
label ??= copy.explanation;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<div className="min-w-0">
|
|
169
|
+
<div className="flex items-center gap-2 type-caption text-fg-subtle">
|
|
170
|
+
{status === "working" && <Spinner />}
|
|
171
|
+
{copy.supplied}
|
|
172
|
+
</div>
|
|
173
|
+
<Accordion
|
|
174
|
+
label={label}
|
|
175
|
+
items={[
|
|
176
|
+
{
|
|
177
|
+
id: "explanation",
|
|
178
|
+
title: label,
|
|
179
|
+
content: <p className="type-body break-words whitespace-pre-wrap">{text}</p>,
|
|
180
|
+
},
|
|
181
|
+
]}
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
export type ToolStatus = keyof typeof copy.toolStates;
|
|
187
|
+
export function ToolCall({
|
|
188
|
+
title,
|
|
189
|
+
status,
|
|
190
|
+
input,
|
|
191
|
+
output,
|
|
192
|
+
}: {
|
|
193
|
+
title: string;
|
|
194
|
+
status: ToolStatus;
|
|
195
|
+
input: JsonValue;
|
|
196
|
+
output?: string;
|
|
197
|
+
}) {
|
|
198
|
+
const copy = useInteractionCopy();
|
|
199
|
+
|
|
200
|
+
const parsed = jsonSchema.safeParse(input);
|
|
201
|
+
if (!parsed.success) return <Callout tone="error">{copy.invalid}</Callout>;
|
|
202
|
+
return (
|
|
203
|
+
<Card aria-label={title}>
|
|
204
|
+
<div className="mb-3 flex flex-wrap items-center justify-between gap-3">
|
|
205
|
+
<h3 className="type-heading text-fg-emphasis">{title}</h3>
|
|
206
|
+
<Badge
|
|
207
|
+
tone={status === "completed" ? "success" : status === "unknown" ? "attention" : "neutral"}
|
|
208
|
+
>
|
|
209
|
+
{status === "running" && <Spinner />}
|
|
210
|
+
{copy.toolStates[status]}
|
|
211
|
+
</Badge>
|
|
212
|
+
</div>
|
|
213
|
+
<Accordion
|
|
214
|
+
label={copy.tool}
|
|
215
|
+
items={[
|
|
216
|
+
{
|
|
217
|
+
id: "input",
|
|
218
|
+
title: copy.inputs,
|
|
219
|
+
content: <CodeBlock code={JSON.stringify(parsed.data, null, 2)} language="json" />,
|
|
220
|
+
},
|
|
221
|
+
...(output === undefined
|
|
222
|
+
? []
|
|
223
|
+
: [
|
|
224
|
+
{
|
|
225
|
+
id: "output",
|
|
226
|
+
title: copy.output,
|
|
227
|
+
content: <p className="type-body break-words whitespace-pre-wrap">{output}</p>,
|
|
228
|
+
},
|
|
229
|
+
]),
|
|
230
|
+
]}
|
|
231
|
+
allowsMultipleExpanded
|
|
232
|
+
/>
|
|
233
|
+
</Card>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
export function Confidence({ claim, explanation }: { claim: string; explanation: string }) {
|
|
237
|
+
const copy = useInteractionCopy();
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<Callout tone="attention" title={copy.checkClaim}>
|
|
241
|
+
<p>{claim}</p>
|
|
242
|
+
<p className="mt-2">{explanation}</p>
|
|
243
|
+
</Callout>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
export function Guardrail({
|
|
247
|
+
reason,
|
|
248
|
+
alternatives,
|
|
249
|
+
onChoose,
|
|
250
|
+
}: {
|
|
251
|
+
reason: string;
|
|
252
|
+
alternatives: readonly { id: string; label: string }[];
|
|
253
|
+
onChoose: (id: string) => void;
|
|
254
|
+
}) {
|
|
255
|
+
const copy = useInteractionCopy();
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<Callout
|
|
259
|
+
tone="attention"
|
|
260
|
+
title={copy.blocked}
|
|
261
|
+
actions={alternatives.map((item) => (
|
|
262
|
+
<Button key={item.id} size="sm" onPress={() => onChoose(item.id)}>
|
|
263
|
+
{item.label}
|
|
264
|
+
</Button>
|
|
265
|
+
))}
|
|
266
|
+
>
|
|
267
|
+
{reason}
|
|
268
|
+
</Callout>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
export function ConnectionStatus({
|
|
272
|
+
state,
|
|
273
|
+
onReconnect,
|
|
274
|
+
}: {
|
|
275
|
+
state: "connected" | "reconnecting" | "disconnected";
|
|
276
|
+
onReconnect?: () => void;
|
|
277
|
+
}) {
|
|
278
|
+
const copy = useInteractionCopy();
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<Callout
|
|
282
|
+
tone={state === "connected" ? "neutral" : "attention"}
|
|
283
|
+
title={copy.connection[state]}
|
|
284
|
+
actions={
|
|
285
|
+
state === "disconnected" && onReconnect ? (
|
|
286
|
+
<Button size="sm" onPress={onReconnect}>
|
|
287
|
+
{copy.reconnect}
|
|
288
|
+
</Button>
|
|
289
|
+
) : undefined
|
|
290
|
+
}
|
|
291
|
+
>
|
|
292
|
+
{state !== "connected" && copy.disconnected}
|
|
293
|
+
</Callout>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
export function ComposerTools({
|
|
297
|
+
models,
|
|
298
|
+
model,
|
|
299
|
+
onModelChange,
|
|
300
|
+
contexts = [],
|
|
301
|
+
selectedContext = [],
|
|
302
|
+
onContextChange,
|
|
303
|
+
commands = [],
|
|
304
|
+
onCommand,
|
|
305
|
+
}: {
|
|
306
|
+
models: readonly Choice[];
|
|
307
|
+
model: string;
|
|
308
|
+
onModelChange: (id: string) => void;
|
|
309
|
+
contexts?: readonly Choice[];
|
|
310
|
+
selectedContext?: readonly string[];
|
|
311
|
+
onContextChange?: (ids: string[]) => void;
|
|
312
|
+
commands?: readonly MenuCommand[];
|
|
313
|
+
onCommand?: (id: string) => void;
|
|
314
|
+
}) {
|
|
315
|
+
const copy = useInteractionCopy();
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<div className="flex min-w-0 flex-col gap-4">
|
|
319
|
+
<Select
|
|
320
|
+
label={copy.model}
|
|
321
|
+
options={models}
|
|
322
|
+
selectedKey={model}
|
|
323
|
+
onSelectionChange={(key) => {
|
|
324
|
+
if (key !== null) onModelChange(String(key));
|
|
325
|
+
}}
|
|
326
|
+
/>
|
|
327
|
+
{contexts.length > 0 && onContextChange && (
|
|
328
|
+
<CheckboxGroup
|
|
329
|
+
label={copy.context}
|
|
330
|
+
options={contexts}
|
|
331
|
+
value={[...selectedContext]}
|
|
332
|
+
onChange={onContextChange}
|
|
333
|
+
/>
|
|
334
|
+
)}{" "}
|
|
335
|
+
{commands.length > 0 && onCommand && (
|
|
336
|
+
<Menu label={copy.commands} items={commands} onAction={onCommand} />
|
|
337
|
+
)}
|
|
338
|
+
</div>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
export interface Quote {
|
|
342
|
+
text: string;
|
|
343
|
+
sourceId: string;
|
|
344
|
+
sourceTitle: string;
|
|
345
|
+
}
|
|
346
|
+
export function QuotedExcerpt({
|
|
347
|
+
quote,
|
|
348
|
+
onReply,
|
|
349
|
+
}: {
|
|
350
|
+
quote: Quote;
|
|
351
|
+
onReply?: (quote: Quote) => void;
|
|
352
|
+
}) {
|
|
353
|
+
const copy = useInteractionCopy();
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<Card aria-label={copy.quote}>
|
|
357
|
+
<blockquote className="border-l-2 border-line-emphasis pl-3 type-body break-words whitespace-pre-wrap">
|
|
358
|
+
{quote.text}
|
|
359
|
+
</blockquote>
|
|
360
|
+
<p className="mt-3 type-caption text-fg-subtle">{quote.sourceTitle}</p>
|
|
361
|
+
{onReply && (
|
|
362
|
+
<Button size="sm" className="mt-4" onPress={() => onReply({ ...quote })}>
|
|
363
|
+
{copy.reply}
|
|
364
|
+
</Button>
|
|
365
|
+
)}
|
|
366
|
+
</Card>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
export function ExecutionInspector({
|
|
370
|
+
code,
|
|
371
|
+
files,
|
|
372
|
+
output,
|
|
373
|
+
tests,
|
|
374
|
+
}: {
|
|
375
|
+
code?: string;
|
|
376
|
+
files?: readonly string[];
|
|
377
|
+
output?: string;
|
|
378
|
+
tests?: readonly { name: string; passed: boolean }[];
|
|
379
|
+
}) {
|
|
380
|
+
const copy = useInteractionCopy();
|
|
381
|
+
|
|
382
|
+
const items = [
|
|
383
|
+
...(code === undefined
|
|
384
|
+
? []
|
|
385
|
+
: [{ id: "code", label: copy.code, content: <CodeBlock code={code} /> }]),
|
|
386
|
+
...(files === undefined
|
|
387
|
+
? []
|
|
388
|
+
: [
|
|
389
|
+
{
|
|
390
|
+
id: "files",
|
|
391
|
+
label: copy.files,
|
|
392
|
+
content: (
|
|
393
|
+
<ul className="flex flex-col gap-2 font-mono type-caption">
|
|
394
|
+
{files.map((file, index) => (
|
|
395
|
+
<li key={index} className="break-words">
|
|
396
|
+
{file}
|
|
397
|
+
</li>
|
|
398
|
+
))}
|
|
399
|
+
</ul>
|
|
400
|
+
),
|
|
401
|
+
},
|
|
402
|
+
]),
|
|
403
|
+
...(output === undefined
|
|
404
|
+
? []
|
|
405
|
+
: [
|
|
406
|
+
{
|
|
407
|
+
id: "output",
|
|
408
|
+
label: copy.output,
|
|
409
|
+
content: <pre className="type-caption break-words whitespace-pre-wrap">{output}</pre>,
|
|
410
|
+
},
|
|
411
|
+
]),
|
|
412
|
+
...(tests === undefined
|
|
413
|
+
? []
|
|
414
|
+
: [
|
|
415
|
+
{
|
|
416
|
+
id: "tests",
|
|
417
|
+
label: copy.checks,
|
|
418
|
+
content: (
|
|
419
|
+
<ul className="flex flex-col gap-3">
|
|
420
|
+
{tests.map((test, index) => (
|
|
421
|
+
<li key={index} className="flex flex-wrap gap-2">
|
|
422
|
+
<Badge tone={test.passed ? "success" : "error"}>
|
|
423
|
+
{test.passed ? copy.passed : copy.failed}
|
|
424
|
+
</Badge>
|
|
425
|
+
<span className="type-body">{test.name}</span>
|
|
426
|
+
</li>
|
|
427
|
+
))}
|
|
428
|
+
</ul>
|
|
429
|
+
),
|
|
430
|
+
},
|
|
431
|
+
]),
|
|
432
|
+
];
|
|
433
|
+
return <Tabs label={copy.tool} items={items} />;
|
|
434
|
+
}
|
|
435
|
+
export function Observability({
|
|
436
|
+
measurements,
|
|
437
|
+
}: {
|
|
438
|
+
measurements: readonly { label: string; value?: string | number; unit?: string }[];
|
|
439
|
+
}) {
|
|
440
|
+
const copy = useInteractionCopy();
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<section aria-label={copy.trace}>
|
|
444
|
+
<h3 className="mb-3 type-heading text-fg-emphasis">{copy.trace}</h3>
|
|
445
|
+
<dl className="grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-2 type-body">
|
|
446
|
+
{measurements.map((measurement, index) => (
|
|
447
|
+
<div key={index} className="contents">
|
|
448
|
+
<dt className="text-fg-subtle">{measurement.label}</dt>
|
|
449
|
+
<dd className="text-right break-words numeric">
|
|
450
|
+
{measurement.value === undefined ? (
|
|
451
|
+
copy.notReported
|
|
452
|
+
) : (
|
|
453
|
+
<>
|
|
454
|
+
{measurement.value}
|
|
455
|
+
{measurement.unit && ` ${measurement.unit}`}
|
|
456
|
+
</>
|
|
457
|
+
)}
|
|
458
|
+
</dd>
|
|
459
|
+
</div>
|
|
460
|
+
))}
|
|
461
|
+
</dl>
|
|
462
|
+
</section>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
export function AgentStatus({
|
|
466
|
+
label,
|
|
467
|
+
status,
|
|
468
|
+
children,
|
|
469
|
+
}: {
|
|
470
|
+
label: string;
|
|
471
|
+
status: string;
|
|
472
|
+
children?: ReactNode;
|
|
473
|
+
}) {
|
|
474
|
+
return (
|
|
475
|
+
<div className="flex min-w-0 flex-col gap-2">
|
|
476
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
477
|
+
<h3 className="type-heading text-fg-emphasis">{label}</h3>
|
|
478
|
+
<Badge>{status}</Badge>
|
|
479
|
+
</div>
|
|
480
|
+
{children}
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export type JsonValue =
|
|
3
|
+
string | number | boolean | null | { [key: string]: JsonValue } | JsonValue[];
|
|
4
|
+
function isJson(value: unknown, depth = 0, budget = { nodes: 0 }): value is JsonValue {
|
|
5
|
+
if (++budget.nodes > 10_000 || depth > 20) return false;
|
|
6
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
7
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
8
|
+
if (Array.isArray(value)) return value.every((item) => isJson(item, depth + 1, budget));
|
|
9
|
+
if (typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype)
|
|
10
|
+
return Object.entries(value).every(
|
|
11
|
+
([key, item]) => key.length <= 200 && isJson(item, depth + 1, budget),
|
|
12
|
+
);
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
export const jsonSchema = z.custom<JsonValue>(
|
|
16
|
+
(value) => isJson(value) && JSON.stringify(value).length <= 100_000,
|
|
17
|
+
);
|
|
18
|
+
export const identity = z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.max(200)
|
|
22
|
+
.refine((value) => value === value.trim(), "Identity must be canonical");
|
|
23
|
+
export const shortText = z.string().trim().min(1).max(2000);
|
|
24
|
+
export const bodyText = z.string().max(100_000);
|
|
25
|
+
export const newIdentity = () => crypto.randomUUID();
|
|
26
|
+
function freeze<T>(value: T): T {
|
|
27
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
28
|
+
Object.values(value).forEach(freeze);
|
|
29
|
+
Object.freeze(value);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
/** Small external store shared by the concrete interaction owners. No execution is hidden here. */
|
|
34
|
+
export class InteractionStore<S> {
|
|
35
|
+
protected state: S;
|
|
36
|
+
protected disposed = false;
|
|
37
|
+
protected generation = 0;
|
|
38
|
+
protected abort?: AbortController;
|
|
39
|
+
private usedIds = new Set<string>();
|
|
40
|
+
protected requestIdentity(create: () => string = newIdentity) {
|
|
41
|
+
const id = identity.parse(create());
|
|
42
|
+
if (this.usedIds.has(id)) throw new Error("Request identity must be unique");
|
|
43
|
+
this.usedIds.add(id);
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
private listeners = new Set<() => void>();
|
|
47
|
+
constructor(initial: S) {
|
|
48
|
+
this.state = freeze(structuredClone(initial));
|
|
49
|
+
}
|
|
50
|
+
getSnapshot = (): S => this.state;
|
|
51
|
+
subscribe = (listener: () => void) => {
|
|
52
|
+
this.listeners.add(listener);
|
|
53
|
+
return () => this.listeners.delete(listener);
|
|
54
|
+
};
|
|
55
|
+
protected publish(state: S) {
|
|
56
|
+
if (this.disposed) return;
|
|
57
|
+
this.state = freeze(structuredClone(state));
|
|
58
|
+
this.listeners.forEach((listener) => listener());
|
|
59
|
+
}
|
|
60
|
+
protected request() {
|
|
61
|
+
this.abort?.abort();
|
|
62
|
+
this.abort = new AbortController();
|
|
63
|
+
return { token: ++this.generation, signal: this.abort.signal };
|
|
64
|
+
}
|
|
65
|
+
protected current(token: number) {
|
|
66
|
+
return !this.disposed && token === this.generation;
|
|
67
|
+
}
|
|
68
|
+
dispose() {
|
|
69
|
+
this.disposed = true;
|
|
70
|
+
++this.generation;
|
|
71
|
+
this.abort?.abort();
|
|
72
|
+
this.listeners.clear();
|
|
73
|
+
}
|
|
74
|
+
}
|