@datalayer/agent-runtimes 1.3.34 → 1.3.36
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/lib/chat/ChatFloating.d.ts +8 -2
- package/lib/chat/ChatFloating.js +2 -2
- package/lib/chat/base/ChatBase.js +10 -6
- package/lib/chat/display/EmptyState.js +8 -1
- package/lib/chat/presence/Presence.d.ts +15 -1
- package/lib/chat/presence/Presence.js +0 -13
- package/lib/components/a2ui/datalayer/Chart.d.ts +132 -0
- package/lib/components/a2ui/datalayer/Chart.js +173 -0
- package/lib/components/a2ui/datalayer/Chat.d.ts +21 -0
- package/lib/components/a2ui/datalayer/Chat.js +116 -0
- package/lib/components/a2ui/datalayer/Evidence.d.ts +22 -0
- package/lib/components/a2ui/datalayer/Evidence.js +73 -0
- package/lib/components/a2ui/datalayer/FileUpload.d.ts +22 -0
- package/lib/components/a2ui/datalayer/FileUpload.js +107 -0
- package/lib/components/a2ui/datalayer/Form.d.ts +12 -0
- package/lib/components/a2ui/datalayer/Form.js +46 -0
- package/lib/components/a2ui/datalayer/Table.d.ts +20 -0
- package/lib/components/a2ui/datalayer/Table.js +103 -0
- package/lib/components/a2ui/datalayer/drawn.d.ts +10 -0
- package/lib/components/a2ui/datalayer/drawn.js +20 -0
- package/lib/components/a2ui/datalayer/implementation.d.ts +17 -0
- package/lib/components/a2ui/datalayer/implementation.js +10 -0
- package/lib/components/a2ui/datalayer/index.d.ts +23 -0
- package/lib/components/a2ui/datalayer/index.js +44 -0
- package/lib/components/a2ui/datalayer/parts.d.ts +32 -0
- package/lib/components/a2ui/datalayer/parts.js +44 -0
- package/lib/components/a2ui/datalayer/schema.d.ts +34 -0
- package/lib/components/a2ui/datalayer/schema.js +108 -0
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/a2ui/visibleWhen.d.ts +4 -2
- package/lib/components/a2ui/visibleWhen.js +6 -3
- package/lib/loop/apps/AppRenderer.d.ts +8 -0
- package/lib/loop/apps/AppRenderer.js +21 -1
- package/lib/loop/apps/appspec.d.ts +9 -2
- package/lib/loop/apps/appspec.js +14 -8
- package/lib/loop/apps/checks.js +4 -3
- package/lib/loop/embed/AppEmbed.d.ts +16 -1
- package/lib/loop/embed/AppEmbed.js +38 -9
- package/lib/loop/embed/LoopEmbed.js +2 -0
- package/lib/loop/embed/element.js +10 -1
- package/lib/loop/embed/embedTheme.d.ts +12 -0
- package/lib/loop/embed/embedTheme.js +15 -0
- package/lib/loop/plugins/assistant/index.d.ts +38 -0
- package/lib/loop/plugins/assistant/index.js +140 -0
- package/lib/loop/plugins/assistant-characters/index.d.ts +35 -1
- package/lib/loop/plugins/assistant-characters/index.js +45 -2
- package/lib/loop/plugins/chat/ChatView.js +21 -9
- package/lib/loop/plugins/chat/index.d.ts +6 -0
- package/lib/loop/presets.d.ts +3 -0
- package/lib/loop/presets.js +2 -1
- package/lib/specs/appspecSchema.js +5 -8
- package/lib/types/agentspecs.d.ts +10 -4
- package/lib/types/chat.d.ts +11 -0
- package/package.json +2 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Evidence (LOOP C-18): what an answer rests on — the sources it opened,
|
|
8
|
+
* each with its link and the passage cited from it. A source is
|
|
9
|
+
* `{title, url, passage}` (a title or a link at least); the first
|
|
10
|
+
* `max_items` show, the rest behind *more*. Opening a source opens its link
|
|
11
|
+
* in a new tab and dispatches the block's action (its `open` event), when it
|
|
12
|
+
* has one.
|
|
13
|
+
*
|
|
14
|
+
* @module components/a2ui/datalayer/Evidence
|
|
15
|
+
*/
|
|
16
|
+
import { useMemo, useState } from 'react';
|
|
17
|
+
import { Button, Link, Text } from '@primer/react';
|
|
18
|
+
import { LinkExternalIcon } from '@primer/octicons-react';
|
|
19
|
+
import { Box } from '@datalayer/primer-addons';
|
|
20
|
+
import { BlockFrame, Problem, Quiet, asWords, isRecord } from './parts';
|
|
21
|
+
import { ownImplementation } from './implementation';
|
|
22
|
+
/** The defaults the catalog gives. */
|
|
23
|
+
const TITLE = 'Sources';
|
|
24
|
+
const MAX_ITEMS = 5;
|
|
25
|
+
/** The sources a block can show, or why it cannot. */
|
|
26
|
+
export function evidenceSources(sources) {
|
|
27
|
+
if (sources === undefined || sources === null) {
|
|
28
|
+
return { sources: [] };
|
|
29
|
+
}
|
|
30
|
+
if (!Array.isArray(sources)) {
|
|
31
|
+
return { problem: 'What it shows is not a list of sources.' };
|
|
32
|
+
}
|
|
33
|
+
const read = [];
|
|
34
|
+
for (const [index, source] of sources.entries()) {
|
|
35
|
+
if (!isRecord(source) || (!source.title && !source.url)) {
|
|
36
|
+
return {
|
|
37
|
+
problem: `Source ${index + 1} has neither a title nor a link.`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
read.push({
|
|
41
|
+
title: asWords(source.title) || asWords(source.url),
|
|
42
|
+
url: asWords(source.url),
|
|
43
|
+
passage: asWords(source.passage),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return { sources: read };
|
|
47
|
+
}
|
|
48
|
+
export function EvidenceView({ props }) {
|
|
49
|
+
const { show_passages: showPassages = true, action } = props;
|
|
50
|
+
const title = props.title ?? TITLE;
|
|
51
|
+
const maxItems = props.max_items ?? MAX_ITEMS;
|
|
52
|
+
const read = useMemo(() => evidenceSources(props.sources), [props.sources]);
|
|
53
|
+
const [all, setAll] = useState(false);
|
|
54
|
+
const sources = 'sources' in read ? read.sources : [];
|
|
55
|
+
const shown = all ? sources : sources.slice(0, maxItems);
|
|
56
|
+
const hidden = sources.length - shown.length;
|
|
57
|
+
return (_jsxs(BlockFrame, { title: title, label: "Evidence", weight: props.weight, testId: "a2ui-evidence", children: ['problem' in read ? (_jsx(Problem, { children: read.problem })) : sources.length === 0 ? (_jsx(Quiet, { children: "Nothing cited yet." })) : (_jsx(Box, { as: "ol", sx: {
|
|
58
|
+
m: 0,
|
|
59
|
+
pl: 3,
|
|
60
|
+
display: 'flex',
|
|
61
|
+
flexDirection: 'column',
|
|
62
|
+
gap: 2,
|
|
63
|
+
}, children: shown.map((source, index) => (_jsxs("li", { children: [source.url ? (_jsxs(Link, { href: source.url, target: "_blank", rel: "noreferrer noopener", onClick: () => action?.(), sx: { fontWeight: 'semibold' }, children: [source.title, ' ', _jsx(LinkExternalIcon, { size: 12, "aria-label": "opens in a new tab" })] })) : (_jsx(Text, { sx: { fontWeight: 'semibold' }, children: source.title })), showPassages && source.passage ? (_jsx(Box, { as: "blockquote", sx: {
|
|
64
|
+
m: 0,
|
|
65
|
+
mt: 1,
|
|
66
|
+
pl: 2,
|
|
67
|
+
borderLeft: '3px solid',
|
|
68
|
+
borderColor: 'border.default',
|
|
69
|
+
color: 'fg.muted',
|
|
70
|
+
fontSize: 1,
|
|
71
|
+
}, children: source.passage })) : null] }, `${source.url}-${index}`))) })), hidden > 0 ? (_jsx(Box, { children: _jsxs(Button, { size: "small", variant: "invisible", onClick: () => setAll(true), children: [hidden, " more"] }) })) : null] }));
|
|
72
|
+
}
|
|
73
|
+
export const Evidence = ownImplementation('Evidence', EvidenceView);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type OwnCommon } from './implementation';
|
|
2
|
+
export type FileUploadProps = OwnCommon & {
|
|
3
|
+
label: string;
|
|
4
|
+
accept?: string[];
|
|
5
|
+
multiple?: boolean;
|
|
6
|
+
max_mb?: number;
|
|
7
|
+
files?: unknown;
|
|
8
|
+
setFiles: (files: GivenFile[]) => void;
|
|
9
|
+
};
|
|
10
|
+
/** A file as the application receives it. */
|
|
11
|
+
export type GivenFile = {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
size: number;
|
|
15
|
+
data_url: string;
|
|
16
|
+
};
|
|
17
|
+
/** Why a file is refused, or `null` when it is taken. */
|
|
18
|
+
export declare function refusal(file: Pick<File, 'name' | 'size'>, accept: string[] | undefined, maxMb: number): string | null;
|
|
19
|
+
export declare function FileUploadView({ props }: {
|
|
20
|
+
props: FileUploadProps;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
export declare const FileUpload: import("@a2ui/react/v0_9").ReactComponentImplementation;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* File upload (LOOP C-18): a file a person gives the application — chosen
|
|
8
|
+
* with *Choose a file*, or dropped on the block. One of a kind it does not
|
|
9
|
+
* accept, or larger than it takes, is refused in words and never sent. What
|
|
10
|
+
* it takes is read and written where its `files` binding points, as a list
|
|
11
|
+
* of `{name, type, size, data_url}` — the content as a data URL, which any
|
|
12
|
+
* transport carries — and then its action is dispatched, so the action's
|
|
13
|
+
* context reads the files just given, as a basic input's action reads its
|
|
14
|
+
* value.
|
|
15
|
+
*
|
|
16
|
+
* @module components/a2ui/datalayer/FileUpload
|
|
17
|
+
*/
|
|
18
|
+
import { useId, useRef, useState } from 'react';
|
|
19
|
+
import { Button, Text } from '@primer/react';
|
|
20
|
+
import { UploadIcon } from '@primer/octicons-react';
|
|
21
|
+
import { Box } from '@datalayer/primer-addons';
|
|
22
|
+
import { BlockFrame, CARD_RADIUS, Problem, Quiet, isRecord } from './parts';
|
|
23
|
+
import { ownImplementation } from './implementation';
|
|
24
|
+
/** The default the catalog gives `max_mb`. */
|
|
25
|
+
const MAX_MB = 25;
|
|
26
|
+
/** Why a file is refused, or `null` when it is taken. */
|
|
27
|
+
export function refusal(file, accept, maxMb) {
|
|
28
|
+
if (accept?.length) {
|
|
29
|
+
const lower = file.name.toLowerCase();
|
|
30
|
+
if (!accept.some(extension => lower.endsWith(extension.toLowerCase()))) {
|
|
31
|
+
return `${file.name} is not one it takes (${accept.join(', ')}).`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (file.size > maxMb * 1024 * 1024) {
|
|
35
|
+
return `${file.name} is larger than ${maxMb} MB.`;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const readFile = (file) => new Promise((resolve, reject) => {
|
|
40
|
+
const reader = new FileReader();
|
|
41
|
+
reader.onload = () => resolve({
|
|
42
|
+
name: file.name,
|
|
43
|
+
type: file.type,
|
|
44
|
+
size: file.size,
|
|
45
|
+
data_url: String(reader.result),
|
|
46
|
+
});
|
|
47
|
+
reader.onerror = () => reject(reader.error);
|
|
48
|
+
reader.readAsDataURL(file);
|
|
49
|
+
});
|
|
50
|
+
export function FileUploadView({ props }) {
|
|
51
|
+
const { label, accept, multiple = false, setFiles, action } = props;
|
|
52
|
+
const maxMb = props.max_mb ?? MAX_MB;
|
|
53
|
+
const input = useRef(null);
|
|
54
|
+
const hintId = useId();
|
|
55
|
+
const [over, setOver] = useState(false);
|
|
56
|
+
const [problems, setProblems] = useState([]);
|
|
57
|
+
const given = Array.isArray(props.files)
|
|
58
|
+
? props.files.filter(isRecord).map(file => String(file.name ?? ''))
|
|
59
|
+
: [];
|
|
60
|
+
const give = async (list) => {
|
|
61
|
+
const files = Array.from(list);
|
|
62
|
+
const chosen = multiple ? files : files.slice(0, 1);
|
|
63
|
+
const refused = chosen
|
|
64
|
+
.map(file => refusal(file, accept, maxMb))
|
|
65
|
+
.filter((reason) => reason !== null);
|
|
66
|
+
if (!multiple && files.length > 1) {
|
|
67
|
+
refused.push('It takes one file at a time.');
|
|
68
|
+
}
|
|
69
|
+
setProblems(refused);
|
|
70
|
+
if (refused.length) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
setFiles(await Promise.all(chosen.map(readFile)));
|
|
74
|
+
action?.();
|
|
75
|
+
};
|
|
76
|
+
const onDrop = (event) => {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
setOver(false);
|
|
79
|
+
void give(event.dataTransfer.files);
|
|
80
|
+
};
|
|
81
|
+
const hint = [
|
|
82
|
+
accept?.length ? `Takes ${accept.join(', ')}` : 'Takes any file',
|
|
83
|
+
`up to ${maxMb} MB`,
|
|
84
|
+
multiple ? 'several at once' : 'one at a time',
|
|
85
|
+
].join(', ');
|
|
86
|
+
return (_jsxs(BlockFrame, { label: label, weight: props.weight, testId: "a2ui-fileupload", children: [_jsxs(Box, { onDragOver: (event) => {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
setOver(true);
|
|
89
|
+
}, onDragLeave: () => setOver(false), onDrop: onDrop, "data-testid": "a2ui-fileupload-drop", sx: {
|
|
90
|
+
display: 'flex',
|
|
91
|
+
flexDirection: 'column',
|
|
92
|
+
alignItems: 'center',
|
|
93
|
+
gap: 2,
|
|
94
|
+
p: 3,
|
|
95
|
+
border: '1px dashed',
|
|
96
|
+
borderColor: over ? 'accent.fg' : 'border.default',
|
|
97
|
+
borderRadius: CARD_RADIUS,
|
|
98
|
+
bg: over ? 'accent.subtle' : 'canvas.subtle',
|
|
99
|
+
textAlign: 'center',
|
|
100
|
+
}, children: [_jsx(Text, { sx: { fontWeight: 'semibold' }, children: label }), _jsx(Button, { leadingVisual: UploadIcon, "aria-describedby": hintId, onClick: () => input.current?.click(), children: multiple ? 'Choose files' : 'Choose a file' }), _jsxs(Text, { id: hintId, sx: { fontSize: 0, color: 'fg.muted' }, children: ["or drop ", multiple ? 'them' : 'it', " here. ", hint, "."] }), _jsx("input", { ref: input, type: "file", hidden: true, "aria-label": label, accept: accept?.join(','), multiple: multiple, onChange: event => {
|
|
101
|
+
if (event.target.files) {
|
|
102
|
+
void give(event.target.files);
|
|
103
|
+
}
|
|
104
|
+
event.target.value = '';
|
|
105
|
+
} })] }), problems.map(problem => (_jsx(Problem, { children: problem }, problem))), given.length ? _jsxs(Quiet, { children: ["Given: ", given.join(', ')] }) : null] }));
|
|
106
|
+
}
|
|
107
|
+
export const FileUpload = ownImplementation('FileUpload', FileUploadView);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type OwnCommon } from './implementation';
|
|
2
|
+
export type FormProps = OwnCommon & {
|
|
3
|
+
title?: string;
|
|
4
|
+
schema: Record<string, unknown>;
|
|
5
|
+
submit_label?: string;
|
|
6
|
+
values?: unknown;
|
|
7
|
+
setValues: (values: Record<string, unknown>) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function FormView({ props }: {
|
|
10
|
+
props: FormProps;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
export declare const Form: import("@a2ui/react/v0_9").ReactComponentImplementation;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Form (LOOP C-18, C-16): several fields asked at once, generated from the
|
|
8
|
+
* JSON Schema the builder gave it by `@datalayer/primer-rjsf` — the form the
|
|
9
|
+
* properties panels are drawn with — checked as they are filled. It starts
|
|
10
|
+
* from the values its `values` binding shows; a form the schema accepts is
|
|
11
|
+
* sent with its button: the values are written where `values` points, then
|
|
12
|
+
* its action is dispatched, so the action's context reads them. A form the
|
|
13
|
+
* schema refuses says why beside each field and sends nothing.
|
|
14
|
+
*
|
|
15
|
+
* @module components/a2ui/datalayer/Form
|
|
16
|
+
*/
|
|
17
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
18
|
+
import { Form as SchemaForm } from '@datalayer/primer-rjsf';
|
|
19
|
+
import validator from '@rjsf/validator-ajv8';
|
|
20
|
+
import { BlockFrame, Problem, isRecord } from './parts';
|
|
21
|
+
import { ownImplementation } from './implementation';
|
|
22
|
+
/** The default the catalog gives `submit_label`. */
|
|
23
|
+
const SUBMIT_LABEL = 'Send';
|
|
24
|
+
export function FormView({ props }) {
|
|
25
|
+
const { title, schema, setValues, action } = props;
|
|
26
|
+
const submitLabel = props.submit_label ?? SUBMIT_LABEL;
|
|
27
|
+
const shown = isRecord(props.values) ? props.values : {};
|
|
28
|
+
const [values, setLocal] = useState(shown);
|
|
29
|
+
// Checked as it is filled once a field is left or a send is tried: an
|
|
30
|
+
// untouched form does not open with every required field in red.
|
|
31
|
+
const [touched, setTouched] = useState(false);
|
|
32
|
+
// What the application publishes anew is what the form shows.
|
|
33
|
+
const shownKey = JSON.stringify(shown);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
setLocal(JSON.parse(shownKey));
|
|
36
|
+
}, [shownKey]);
|
|
37
|
+
const uiSchema = useMemo(() => ({ 'ui:submitButtonOptions': { submitText: submitLabel } }), [submitLabel]);
|
|
38
|
+
const problem = schema.type !== undefined && schema.type !== 'object'
|
|
39
|
+
? 'Its fields are not an object schema: a form asks for several named values.'
|
|
40
|
+
: null;
|
|
41
|
+
return (_jsx(BlockFrame, { title: title, label: "Form", weight: props.weight, testId: "a2ui-form", children: problem ? (_jsx(Problem, { children: problem })) : (_jsx(SchemaForm, { schema: schema, uiSchema: uiSchema, formData: values, validator: validator, liveValidate: touched, onBlur: () => setTouched(true), onError: () => setTouched(true), showErrorList: false, onChange: event => setLocal((event.formData ?? {})), onSubmit: event => {
|
|
42
|
+
setValues((event.formData ?? {}));
|
|
43
|
+
action?.();
|
|
44
|
+
} })) }));
|
|
45
|
+
}
|
|
46
|
+
export const Form = ownImplementation('Form', FormView);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type OwnCommon } from './implementation';
|
|
2
|
+
export type TableProps = OwnCommon & {
|
|
3
|
+
title?: string;
|
|
4
|
+
columns: string[];
|
|
5
|
+
page_size?: number;
|
|
6
|
+
selectable?: boolean;
|
|
7
|
+
rows?: unknown;
|
|
8
|
+
selected?: unknown;
|
|
9
|
+
setSelected: (row: unknown) => void;
|
|
10
|
+
};
|
|
11
|
+
/** The rows a table can draw, or why it cannot. */
|
|
12
|
+
export declare function tableRows(rows: unknown): {
|
|
13
|
+
rows: Record<string, unknown>[];
|
|
14
|
+
} | {
|
|
15
|
+
problem: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function TableView({ props }: {
|
|
18
|
+
props: TableProps;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
export declare const Table: import("@a2ui/react/v0_9").ReactComponentImplementation;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Table (LOOP C-18): the rows an application publishes, under the columns
|
|
8
|
+
* the builder chose, a page at a time. A selectable table lets a person
|
|
9
|
+
* choose a row — by pointer, or by Tab and Enter or Space — which is written
|
|
10
|
+
* where its `selected` binding points, and then its action is dispatched,
|
|
11
|
+
* so the action's context reads the row just chosen.
|
|
12
|
+
*
|
|
13
|
+
* @module components/a2ui/datalayer/Table
|
|
14
|
+
*/
|
|
15
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
16
|
+
import { Button, Text } from '@primer/react';
|
|
17
|
+
import { Box } from '@datalayer/primer-addons';
|
|
18
|
+
import { BlockFrame, Problem, Quiet, asWords, isRecord } from './parts';
|
|
19
|
+
import { ownImplementation } from './implementation';
|
|
20
|
+
/** The default the catalog gives `page_size`. */
|
|
21
|
+
const PAGE_SIZE = 20;
|
|
22
|
+
const sameRow = (a, b) => a !== undefined && JSON.stringify(a) === JSON.stringify(b);
|
|
23
|
+
/** The rows a table can draw, or why it cannot. */
|
|
24
|
+
export function tableRows(rows) {
|
|
25
|
+
if (rows === undefined || rows === null) {
|
|
26
|
+
return { rows: [] };
|
|
27
|
+
}
|
|
28
|
+
if (!Array.isArray(rows)) {
|
|
29
|
+
return { problem: 'What it shows is not a list of rows.' };
|
|
30
|
+
}
|
|
31
|
+
const bad = rows.findIndex(row => !isRecord(row));
|
|
32
|
+
if (bad >= 0) {
|
|
33
|
+
return {
|
|
34
|
+
problem: `Row ${bad + 1} is not a record of the columns' values.`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return { rows: rows };
|
|
38
|
+
}
|
|
39
|
+
export function TableView({ props }) {
|
|
40
|
+
const { title, columns, selectable = false, selected, setSelected, action, } = props;
|
|
41
|
+
const pageSize = props.page_size ?? PAGE_SIZE;
|
|
42
|
+
const read = useMemo(() => tableRows(props.rows), [props.rows]);
|
|
43
|
+
const all = 'rows' in read ? read.rows : [];
|
|
44
|
+
const pages = Math.max(1, Math.ceil(all.length / pageSize));
|
|
45
|
+
const [page, setPage] = useState(0);
|
|
46
|
+
// Fewer rows than before: the page shown is one that still exists.
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (page >= pages) {
|
|
49
|
+
setPage(pages - 1);
|
|
50
|
+
}
|
|
51
|
+
}, [page, pages]);
|
|
52
|
+
const shown = all.slice(page * pageSize, (page + 1) * pageSize);
|
|
53
|
+
// The row chosen here, shown as chosen whether or not `selected` is bound;
|
|
54
|
+
// what the application publishes at `selected` wins when it is.
|
|
55
|
+
const [chosen, setChosen] = useState(undefined);
|
|
56
|
+
const current = selected !== undefined && selected !== null ? selected : chosen;
|
|
57
|
+
const choose = (row) => {
|
|
58
|
+
setChosen(row);
|
|
59
|
+
setSelected(row);
|
|
60
|
+
action?.();
|
|
61
|
+
};
|
|
62
|
+
const onKey = (event, row) => {
|
|
63
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
choose(row);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return (_jsxs(BlockFrame, { title: title, label: "Table", weight: props.weight, testId: "a2ui-table", children: ['problem' in read ? (_jsx(Problem, { children: read.problem })) : (_jsxs(Box, { sx: { overflowX: 'auto' }, children: [_jsxs(Box, { as: "table", role: selectable ? 'grid' : undefined, "aria-label": title || 'Table', sx: {
|
|
69
|
+
width: '100%',
|
|
70
|
+
borderCollapse: 'collapse',
|
|
71
|
+
fontSize: 1,
|
|
72
|
+
'& th, & td': {
|
|
73
|
+
textAlign: 'left',
|
|
74
|
+
px: 2,
|
|
75
|
+
py: 1,
|
|
76
|
+
borderBottom: '1px solid',
|
|
77
|
+
borderColor: 'border.muted',
|
|
78
|
+
},
|
|
79
|
+
'& th': { color: 'fg.muted', fontWeight: 'semibold' },
|
|
80
|
+
}, children: [_jsx("thead", { children: _jsx("tr", { children: columns.map(column => (_jsx("th", { scope: "col", children: column }, column))) }) }), _jsx("tbody", { children: shown.map((row, index) => {
|
|
81
|
+
const isSelected = selectable && sameRow(current, row);
|
|
82
|
+
return (_jsx(Box, { as: "tr", ...(selectable
|
|
83
|
+
? {
|
|
84
|
+
tabIndex: 0,
|
|
85
|
+
'aria-selected': isSelected,
|
|
86
|
+
onClick: () => choose(row),
|
|
87
|
+
onKeyDown: (event) => onKey(event, row),
|
|
88
|
+
}
|
|
89
|
+
: null), sx: {
|
|
90
|
+
cursor: selectable ? 'pointer' : undefined,
|
|
91
|
+
bg: isSelected ? 'accent.subtle' : undefined,
|
|
92
|
+
'&:hover': selectable
|
|
93
|
+
? { bg: isSelected ? 'accent.subtle' : 'canvas.subtle' }
|
|
94
|
+
: undefined,
|
|
95
|
+
'&:focus-visible': {
|
|
96
|
+
outline: '2px solid',
|
|
97
|
+
outlineColor: 'accent.fg',
|
|
98
|
+
outlineOffset: '-2px',
|
|
99
|
+
},
|
|
100
|
+
}, children: columns.map(column => (_jsx("td", { children: asWords(row[column]) }, column))) }, page * pageSize + index));
|
|
101
|
+
}) })] }), all.length === 0 ? _jsx(Quiet, { children: "No rows yet." }) : null] })), pages > 1 ? (_jsxs(Box, { as: "nav", "aria-label": "Pages", sx: { display: 'flex', alignItems: 'center', gap: 2 }, children: [_jsx(Button, { size: "small", disabled: page === 0, onClick: () => setPage(page - 1), children: "Previous" }), _jsxs(Text, { sx: { fontSize: 1, color: 'fg.muted' }, "aria-live": "polite", children: ["Page ", page + 1, " of ", pages] }), _jsx(Button, { size: "small", disabled: page >= pages - 1, onClick: () => setPage(page + 1), children: "Next" })] })) : null] }));
|
|
102
|
+
}
|
|
103
|
+
export const Table = ownImplementation('Table', TableView);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The components of Datalayer's own a renderer draws (LOOP C-18), without
|
|
3
|
+
* the renderers: what a host that does not draw — the Studio's tests, a
|
|
4
|
+
* checker — reads to know that a component of the catalog is drawn. The
|
|
5
|
+
* renderers' module refuses to load when it and the catalog disagree.
|
|
6
|
+
*
|
|
7
|
+
* @module components/a2ui/datalayer/drawn
|
|
8
|
+
*/
|
|
9
|
+
export declare const DRAWN_OWN_COMPONENTS: readonly ["Table", "Chart", "FileUpload", "Chat", "Evidence", "Form"];
|
|
10
|
+
export type DrawnOwnComponent = (typeof DRAWN_OWN_COMPONENTS)[number];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The components of Datalayer's own a renderer draws (LOOP C-18), without
|
|
7
|
+
* the renderers: what a host that does not draw — the Studio's tests, a
|
|
8
|
+
* checker — reads to know that a component of the catalog is drawn. The
|
|
9
|
+
* renderers' module refuses to load when it and the catalog disagree.
|
|
10
|
+
*
|
|
11
|
+
* @module components/a2ui/datalayer/drawn
|
|
12
|
+
*/
|
|
13
|
+
export const DRAWN_OWN_COMPONENTS = [
|
|
14
|
+
'Table',
|
|
15
|
+
'Chart',
|
|
16
|
+
'FileUpload',
|
|
17
|
+
'Chat',
|
|
18
|
+
'Evidence',
|
|
19
|
+
'Form',
|
|
20
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A component of Datalayer's own as A2UI's React renderer registers one: its
|
|
3
|
+
* schema from the catalog, its view made by A2UI's own factory — the one
|
|
4
|
+
* every basic component is made by — so its bindings resolve, its setters
|
|
5
|
+
* write back and its action dispatches exactly as a basic component's do.
|
|
6
|
+
*
|
|
7
|
+
* @module components/a2ui/datalayer/implementation
|
|
8
|
+
*/
|
|
9
|
+
import type { FC } from 'react';
|
|
10
|
+
import { type ReactA2uiComponentProps, type ReactComponentImplementation } from '@a2ui/react/v0_9';
|
|
11
|
+
/** What every component of Datalayer's own receives besides its own properties. */
|
|
12
|
+
export type OwnCommon = {
|
|
13
|
+
/** Dispatches its action, its context resolved at that moment; absent when it has none. */
|
|
14
|
+
action?: () => void;
|
|
15
|
+
weight?: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function ownImplementation<P>(id: string, Render: FC<ReactA2uiComponentProps<P>>): ReactComponentImplementation;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { createComponentImplementation, } from '@a2ui/react/v0_9';
|
|
6
|
+
import { ownComponentSchema } from './schema';
|
|
7
|
+
export function ownImplementation(id, Render) {
|
|
8
|
+
const api = { name: id, schema: ownComponentSchema(id) };
|
|
9
|
+
return createComponentImplementation(api, Render);
|
|
10
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datalayer's own components, drawn (LOOP C-18): a renderer for each the
|
|
3
|
+
* catalog has beside A2UI's basic ones — Table, Chart, File upload, Chat,
|
|
4
|
+
* Evidence and Form — made as A2UI makes its own, so that the decision
|
|
5
|
+
* surface, an application's page, the Surface view and the components page
|
|
6
|
+
* draw them as they draw a Button.
|
|
7
|
+
*
|
|
8
|
+
* @module components/a2ui/datalayer
|
|
9
|
+
*/
|
|
10
|
+
import type { ReactComponentImplementation } from '@a2ui/react/v0_9';
|
|
11
|
+
/**
|
|
12
|
+
* A renderer for every component of Datalayer's own the catalog has, in the
|
|
13
|
+
* catalog's order. A component the catalog adds without a renderer here, or
|
|
14
|
+
* a renderer for one it no longer has, fails at once.
|
|
15
|
+
*/
|
|
16
|
+
export declare const OWN_COMPONENTS: ReactComponentImplementation[];
|
|
17
|
+
export { OWN_COMPONENT_IDS, ownComponentSchema } from './schema';
|
|
18
|
+
export { DRAWN_OWN_COMPONENTS, type DrawnOwnComponent } from './drawn';
|
|
19
|
+
export { chartOption, chartPoints, chartSeries } from './Chart';
|
|
20
|
+
export { chatItems } from './Chat';
|
|
21
|
+
export { evidenceSources } from './Evidence';
|
|
22
|
+
export { refusal as fileRefusal, type GivenFile } from './FileUpload';
|
|
23
|
+
export { tableRows } from './Table';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { OWN_COMPONENT_IDS } from './schema';
|
|
6
|
+
import { DRAWN_OWN_COMPONENTS } from './drawn';
|
|
7
|
+
import { Table } from './Table';
|
|
8
|
+
import { Chart } from './Chart';
|
|
9
|
+
import { FileUpload } from './FileUpload';
|
|
10
|
+
import { Chat } from './Chat';
|
|
11
|
+
import { Evidence } from './Evidence';
|
|
12
|
+
import { Form } from './Form';
|
|
13
|
+
const RENDERERS = {
|
|
14
|
+
Table,
|
|
15
|
+
Chart,
|
|
16
|
+
FileUpload,
|
|
17
|
+
Chat,
|
|
18
|
+
Evidence,
|
|
19
|
+
Form,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A renderer for every component of Datalayer's own the catalog has, in the
|
|
23
|
+
* catalog's order. A component the catalog adds without a renderer here, or
|
|
24
|
+
* a renderer for one it no longer has, fails at once.
|
|
25
|
+
*/
|
|
26
|
+
export const OWN_COMPONENTS = (() => {
|
|
27
|
+
const drawn = DRAWN_OWN_COMPONENTS;
|
|
28
|
+
const missing = OWN_COMPONENT_IDS.filter(id => !drawn.includes(id));
|
|
29
|
+
if (missing.length) {
|
|
30
|
+
throw new Error(`The catalog has ${missing.join(', ')}, which no renderer draws.`);
|
|
31
|
+
}
|
|
32
|
+
const stray = drawn.filter(id => !OWN_COMPONENT_IDS.includes(id));
|
|
33
|
+
if (stray.length) {
|
|
34
|
+
throw new Error(`A renderer draws ${stray.join(', ')}, which the catalog does not have.`);
|
|
35
|
+
}
|
|
36
|
+
return OWN_COMPONENT_IDS.map(id => RENDERERS[id]);
|
|
37
|
+
})();
|
|
38
|
+
export { OWN_COMPONENT_IDS, ownComponentSchema } from './schema';
|
|
39
|
+
export { DRAWN_OWN_COMPONENTS } from './drawn';
|
|
40
|
+
export { chartOption, chartPoints, chartSeries } from './Chart';
|
|
41
|
+
export { chatItems } from './Chat';
|
|
42
|
+
export { evidenceSources } from './Evidence';
|
|
43
|
+
export { refusal as fileRefusal } from './FileUpload';
|
|
44
|
+
export { tableRows } from './Table';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the components of Datalayer's own share: the frame a block is drawn
|
|
3
|
+
* in — the `loop` theme's card radius and Primer's colours, so a block reads
|
|
4
|
+
* as part of the page it is on in either colour mode — its title, and the
|
|
5
|
+
* sentence it says when what it was given cannot be drawn.
|
|
6
|
+
*
|
|
7
|
+
* @module components/a2ui/datalayer/parts
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactNode } from 'react';
|
|
10
|
+
/** The theme's card radius, Primer's 6px where no theme sets it. */
|
|
11
|
+
export declare const CARD_RADIUS = "var(--theme-radius-card, 6px)";
|
|
12
|
+
/** A block's frame: its title above, what it draws inside. */
|
|
13
|
+
export declare function BlockFrame({ title, label, weight, children, testId, }: {
|
|
14
|
+
title?: string;
|
|
15
|
+
/** What assistive technologies call it when it has no title. */
|
|
16
|
+
label: string;
|
|
17
|
+
weight?: number;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
testId: string;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
21
|
+
/** What a block says instead of what it was given, when that cannot be drawn. */
|
|
22
|
+
export declare function Problem({ children }: {
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
25
|
+
/** A quiet sentence: nothing to show yet. */
|
|
26
|
+
export declare function Quiet({ children }: {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
}): import("react").JSX.Element;
|
|
29
|
+
/** Whether a value is a record — an item of a list a block shows. */
|
|
30
|
+
export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
|
|
31
|
+
/** A value as words in a cell, a point, a passage. */
|
|
32
|
+
export declare function asWords(value: unknown): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Heading, Text } from '@primer/react';
|
|
3
|
+
import { Box } from '@datalayer/primer-addons';
|
|
4
|
+
/** The theme's card radius, Primer's 6px where no theme sets it. */
|
|
5
|
+
export const CARD_RADIUS = 'var(--theme-radius-card, 6px)';
|
|
6
|
+
/** A block's frame: its title above, what it draws inside. */
|
|
7
|
+
export function BlockFrame({ title, label, weight, children, testId, }) {
|
|
8
|
+
return (_jsxs(Box, { as: "section", "aria-label": title || label, "data-testid": testId, sx: {
|
|
9
|
+
display: 'flex',
|
|
10
|
+
flexDirection: 'column',
|
|
11
|
+
gap: 2,
|
|
12
|
+
minWidth: 0,
|
|
13
|
+
boxSizing: 'border-box',
|
|
14
|
+
p: 3,
|
|
15
|
+
border: '1px solid',
|
|
16
|
+
borderColor: 'border.default',
|
|
17
|
+
borderRadius: CARD_RADIUS,
|
|
18
|
+
bg: 'canvas.default',
|
|
19
|
+
color: 'fg.default',
|
|
20
|
+
...(typeof weight === 'number'
|
|
21
|
+
? { flex: `${weight}`, minHeight: 0 }
|
|
22
|
+
: null),
|
|
23
|
+
}, children: [title ? (_jsx(Heading, { as: "h3", sx: { fontSize: 2, m: 0 }, children: title })) : null, children] }));
|
|
24
|
+
}
|
|
25
|
+
/** What a block says instead of what it was given, when that cannot be drawn. */
|
|
26
|
+
export function Problem({ children }) {
|
|
27
|
+
return (_jsx(Text, { as: "p", role: "status", sx: { m: 0, color: 'danger.fg', fontSize: 1 }, children: children }));
|
|
28
|
+
}
|
|
29
|
+
/** A quiet sentence: nothing to show yet. */
|
|
30
|
+
export function Quiet({ children }) {
|
|
31
|
+
return (_jsx(Text, { as: "p", sx: { m: 0, color: 'fg.muted', fontSize: 1 }, children: children }));
|
|
32
|
+
}
|
|
33
|
+
/** Whether a value is a record — an item of a list a block shows. */
|
|
34
|
+
export const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
35
|
+
/** A value as words in a cell, a point, a passage. */
|
|
36
|
+
export function asWords(value) {
|
|
37
|
+
if (value === null || value === undefined) {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === 'object') {
|
|
41
|
+
return JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
return String(value);
|
|
44
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The A2UI schema of a component of Datalayer's own, read from the catalog
|
|
3
|
+
* (LOOP C-13, C-18): each property its JSON Schema declares, typed as it
|
|
4
|
+
* says; each thing it shows or sends a binding into the data model — a path
|
|
5
|
+
* the application publishes, or a value given in place; its `action`, what
|
|
6
|
+
* it dispatches when a person acts on it, written as A2UI writes a Button's.
|
|
7
|
+
*
|
|
8
|
+
* A2UI's renderer reads a component's schema to tell its properties apart —
|
|
9
|
+
* a binding it resolves and writes back through, an action it dispatches, a
|
|
10
|
+
* plain value it hands over — so the schema is built with the zod A2UI reads
|
|
11
|
+
* (v3), from the catalog, and cannot drift from what the Canvas, the YAML
|
|
12
|
+
* and Python check a component against.
|
|
13
|
+
*
|
|
14
|
+
* @module components/a2ui/datalayer/schema
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod/v3';
|
|
17
|
+
import type { ComponentSpec } from '../../../types/agentspecs';
|
|
18
|
+
/** The components of Datalayer's own the catalog has, by id. */
|
|
19
|
+
export declare const OWN_COMPONENT_IDS: string[];
|
|
20
|
+
/** A component of Datalayer's own, from the catalog; it throws for any other. */
|
|
21
|
+
export declare function ownComponent(id: string): ComponentSpec;
|
|
22
|
+
/** The names a component shows and sends through, each once. */
|
|
23
|
+
export declare function bindingNames(component: ComponentSpec): string[];
|
|
24
|
+
/**
|
|
25
|
+
* The A2UI schema of a component of Datalayer's own: its properties as its
|
|
26
|
+
* JSON Schema has them, its bindings, its `action`, and the `weight` every
|
|
27
|
+
* basic component takes inside a Row or a Column. Strict, as the basic
|
|
28
|
+
* catalog's are: a key it does not declare refuses the tree.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ownComponentSchema(id: string): z.ZodObject<Record<string, z.ZodTypeAny>, "strict", z.ZodTypeAny, {
|
|
31
|
+
[x: string]: any;
|
|
32
|
+
}, {
|
|
33
|
+
[x: string]: any;
|
|
34
|
+
}>;
|