@adecore/agents-react 0.20.1 → 0.21.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/chat/logic/generated-image.d.ts +27 -0
- package/dist/chat/logic/generated-image.js +32 -0
- package/dist/chat/logic/timeline-target.d.ts +13 -1
- package/dist/chat/logic/timeline-target.js +26 -0
- package/dist/chat/logic/timeline.js +16 -10
- package/dist/chat/subagent-conversation.d.ts +2 -1
- package/dist/chat/subagent-conversation.js +7 -1
- package/dist/chat/timeline-flash.d.ts +3 -1
- package/dist/chat/timeline-flash.js +2 -2
- package/dist/chat/ui/ImageView.d.ts +8 -2
- package/dist/chat/ui/ImageView.js +12 -6
- package/dist/chat/ui/SubagentTimeline.js +6 -2
- package/dist/chat/ui/Timeline.js +67 -7
- package/dist/chat/ui/intelligent-ui/UiBlockFooter.d.ts +15 -0
- package/dist/chat/ui/intelligent-ui/UiBlockFooter.js +50 -0
- package/dist/chat/ui/intelligent-ui/UiBlockFrame.d.ts +10 -0
- package/dist/chat/ui/intelligent-ui/UiBlockFrame.js +61 -0
- package/dist/chat/ui/intelligent-ui/UiFallbackPart.d.ts +10 -0
- package/dist/chat/ui/intelligent-ui/UiFallbackPart.js +11 -0
- package/dist/chat/ui/intelligent-ui/UiReply.d.ts +13 -0
- package/dist/chat/ui/intelligent-ui/UiReply.js +325 -0
- package/dist/chat/ui/intelligent-ui/block-local.d.ts +2 -0
- package/dist/chat/ui/intelligent-ui/block-local.js +13 -0
- package/dist/chat/ui/intelligent-ui/chart-data.d.ts +16 -0
- package/dist/chat/ui/intelligent-ui/chart-data.js +46 -0
- package/dist/chat/ui/intelligent-ui/node-text.d.ts +9 -0
- package/dist/chat/ui/intelligent-ui/node-text.js +52 -0
- package/dist/chat/ui/intelligent-ui/registry.d.ts +2 -0
- package/dist/chat/ui/intelligent-ui/registry.js +50 -0
- package/dist/chat/ui/intelligent-ui/render-context.d.ts +50 -0
- package/dist/chat/ui/intelligent-ui/render-context.js +3 -0
- package/dist/chat/ui/intelligent-ui/renderers/chart.d.ts +3 -0
- package/dist/chat/ui/intelligent-ui/renderers/chart.js +125 -0
- package/dist/chat/ui/intelligent-ui/renderers/choices.d.ts +4 -0
- package/dist/chat/ui/intelligent-ui/renderers/choices.js +41 -0
- package/dist/chat/ui/intelligent-ui/renderers/content.d.ts +14 -0
- package/dist/chat/ui/intelligent-ui/renderers/content.js +66 -0
- package/dist/chat/ui/intelligent-ui/renderers/data.d.ts +7 -0
- package/dist/chat/ui/intelligent-ui/renderers/data.js +87 -0
- package/dist/chat/ui/intelligent-ui/renderers/inputs.d.ts +7 -0
- package/dist/chat/ui/intelligent-ui/renderers/inputs.js +60 -0
- package/dist/chat/ui/intelligent-ui/renderers/links.d.ts +10 -0
- package/dist/chat/ui/intelligent-ui/renderers/links.js +86 -0
- package/dist/chat/ui/intelligent-ui/renderers/structure.d.ts +6 -0
- package/dist/chat/ui/intelligent-ui/renderers/structure.js +32 -0
- package/dist/chat/ui/intelligent-ui/renderers/text.d.ts +8 -0
- package/dist/chat/ui/intelligent-ui/renderers/text.js +61 -0
- package/dist/chat/ui/intelligent-ui/reply-parts.d.ts +9 -0
- package/dist/chat/ui/intelligent-ui/reply-parts.js +18 -0
- package/dist/chat/ui/intelligent-ui/table-data.d.ts +31 -0
- package/dist/chat/ui/intelligent-ui/table-data.js +79 -0
- package/dist/chat/ui/intelligent-ui/tones.d.ts +7 -0
- package/dist/chat/ui/intelligent-ui/tones.js +31 -0
- package/dist/chat/ui/intelligent-ui/use-later.d.ts +1 -0
- package/dist/chat/ui/intelligent-ui/use-later.js +16 -0
- package/dist/chat/ui/intelligent-ui/use-ui-links.d.ts +9 -0
- package/dist/chat/ui/intelligent-ui/use-ui-links.js +107 -0
- package/dist/chat/ui/intelligent-ui/use-ui-queries.d.ts +20 -0
- package/dist/chat/ui/intelligent-ui/use-ui-queries.js +259 -0
- package/dist/chat/ui/reply-context.d.ts +8 -0
- package/dist/chat/ui/reply-context.js +1 -0
- package/dist/chat/ui/rows/GeneratedImageRow.d.ts +5 -0
- package/dist/chat/ui/rows/GeneratedImageRow.js +121 -0
- package/dist/chat/ui/rows/MessageRows.js +21 -4
- package/dist/chat/ui/rows/Rows.js +4 -2
- package/dist/chat/ui/rows/SubagentRow.js +5 -0
- package/dist/chat/ui/rows/WorkRows.d.ts +4 -1
- package/dist/chat/ui/rows/WorkRows.js +4 -3
- package/dist/host.d.ts +11 -1
- package/dist/locales/en/agent-chat.json +82 -0
- package/dist/locales/nl/agent-chat.json +82 -0
- package/dist/state/chats.js +9 -0
- package/dist/theme.css +19 -0
- package/package.json +133 -7
- package/src/chat/logic/generated-image.ts +48 -0
- package/src/chat/logic/timeline-target.ts +41 -1
- package/src/chat/logic/timeline.ts +19 -10
- package/src/chat/subagent-conversation.ts +13 -2
- package/src/chat/timeline-flash.ts +5 -3
- package/src/chat/ui/ImageView.tsx +51 -8
- package/src/chat/ui/SubagentTimeline.tsx +24 -1
- package/src/chat/ui/Timeline.tsx +97 -21
- package/src/chat/ui/intelligent-ui/UiBlockFooter.tsx +139 -0
- package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +89 -0
- package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +21 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +531 -0
- package/src/chat/ui/intelligent-ui/block-local.ts +16 -0
- package/src/chat/ui/intelligent-ui/chart-data.ts +67 -0
- package/src/chat/ui/intelligent-ui/node-text.ts +59 -0
- package/src/chat/ui/intelligent-ui/registry.ts +52 -0
- package/src/chat/ui/intelligent-ui/render-context.ts +78 -0
- package/src/chat/ui/intelligent-ui/renderers/chart.tsx +321 -0
- package/src/chat/ui/intelligent-ui/renderers/choices.tsx +98 -0
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +145 -0
- package/src/chat/ui/intelligent-ui/renderers/data.tsx +187 -0
- package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +121 -0
- package/src/chat/ui/intelligent-ui/renderers/links.tsx +157 -0
- package/src/chat/ui/intelligent-ui/renderers/structure.tsx +84 -0
- package/src/chat/ui/intelligent-ui/renderers/text.tsx +124 -0
- package/src/chat/ui/intelligent-ui/reply-parts.ts +22 -0
- package/src/chat/ui/intelligent-ui/table-data.ts +104 -0
- package/src/chat/ui/intelligent-ui/tones.ts +37 -0
- package/src/chat/ui/intelligent-ui/use-later.ts +17 -0
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +123 -0
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +282 -0
- package/src/chat/ui/reply-context.ts +7 -0
- package/src/chat/ui/rows/GeneratedImageRow.tsx +244 -0
- package/src/chat/ui/rows/MessageRows.tsx +60 -4
- package/src/chat/ui/rows/Rows.tsx +4 -2
- package/src/chat/ui/rows/SubagentRow.tsx +5 -0
- package/src/chat/ui/rows/WorkRows.tsx +20 -5
- package/src/host.ts +27 -1
- package/src/locales/en/agent-chat.json +82 -0
- package/src/locales/nl/agent-chat.json +82 -0
- package/src/state/chats.ts +9 -0
- package/src/theme.css +19 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { UiComponentName, UiProps, UiViewNode } from '@adecore/intelligent-ui';
|
|
3
|
+
export type { UiLinkTarget } from '@adecore/intelligent-ui/links';
|
|
4
|
+
import type { UiLinkTarget } from '@adecore/intelligent-ui/links';
|
|
5
|
+
export interface UiLink {
|
|
6
|
+
state: 'chip' | 'plain';
|
|
7
|
+
label?: string;
|
|
8
|
+
reason?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface UiAnswer {
|
|
11
|
+
choiceId: string;
|
|
12
|
+
state: 'sending' | 'sent' | 'queued';
|
|
13
|
+
}
|
|
14
|
+
export interface UiLiveStatus {
|
|
15
|
+
state: 'fresh' | 'reading' | 'failed' | 'refused';
|
|
16
|
+
sources: readonly string[];
|
|
17
|
+
readAt: number | null;
|
|
18
|
+
source?: string;
|
|
19
|
+
reason?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface UiLiveValue {
|
|
22
|
+
previous?: unknown;
|
|
23
|
+
changed: boolean;
|
|
24
|
+
stale: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface UiRenderContext {
|
|
27
|
+
scopeId: string;
|
|
28
|
+
chatId: string;
|
|
29
|
+
itemId: string;
|
|
30
|
+
blockId: string;
|
|
31
|
+
phase: 'streaming' | 'final';
|
|
32
|
+
answer: UiAnswer | null;
|
|
33
|
+
failedChoiceId?: string | null;
|
|
34
|
+
live?: UiLiveStatus | null;
|
|
35
|
+
link?(target: UiLinkTarget): UiLink;
|
|
36
|
+
openLink?(target: UiLinkTarget): void;
|
|
37
|
+
openUrl?(url: string): void;
|
|
38
|
+
onChoose?(nodeId: string): void;
|
|
39
|
+
liveValue?(nodeId: string, prop: string, path?: readonly [row: number, key: string]): UiLiveValue | undefined;
|
|
40
|
+
}
|
|
41
|
+
export interface UiRendererProps<Props> {
|
|
42
|
+
node: UiViewNode<Props>;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
context: UiRenderContext;
|
|
45
|
+
}
|
|
46
|
+
export type UiRenderer<Props> = (props: UiRendererProps<Props>) => ReactNode;
|
|
47
|
+
export type UiRenderers = {
|
|
48
|
+
readonly [Name in UiComponentName]: UiRenderer<UiProps<Name>> | null;
|
|
49
|
+
};
|
|
50
|
+
export declare const UiHeadContext: import("react").Context<string | null | undefined>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { SegmentBar, useMeasuredWidth } from '@adecore/ui';
|
|
6
|
+
import { formatRounded } from '@adecore/ui/format';
|
|
7
|
+
import { niceCeiling, UI_CHART_ROWS, uiChartData } from '../chart-data.js';
|
|
8
|
+
import { UiFallbackPart } from '../UiFallbackPart.js';
|
|
9
|
+
const HEIGHT = 160;
|
|
10
|
+
const HBAR_ROW = 24;
|
|
11
|
+
const STACKED_ROW = 28;
|
|
12
|
+
const AXIS_WIDTH = 44;
|
|
13
|
+
const PLOT_TOP = 8;
|
|
14
|
+
const LABEL_ROOM = 20;
|
|
15
|
+
/* The narrowest band a category label is written under; narrower bands skip labels. */
|
|
16
|
+
const LABEL_BAND = 56;
|
|
17
|
+
/* Points get a dot up to this many categories; more would crowd the line. */
|
|
18
|
+
const DOTS_UP_TO = 30;
|
|
19
|
+
const GROW = 'transition-[scale,opacity] duration-400 ease-out motion-reduce:transition-none';
|
|
20
|
+
function seriesColor(index) {
|
|
21
|
+
return `var(--chart-${index + 1})`;
|
|
22
|
+
}
|
|
23
|
+
function valueText(value, unit) {
|
|
24
|
+
if (value === null) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
const text = formatRounded(value, 2);
|
|
28
|
+
return unit === undefined || unit === '' ? text : unit === '%' ? `${text}%` : `${text} ${unit}`;
|
|
29
|
+
}
|
|
30
|
+
/* The height a chart will take, which its place holds while the node streams. */
|
|
31
|
+
function chartHeight(kind, rows) {
|
|
32
|
+
const count = Math.max(1, Math.min(rows, UI_CHART_ROWS));
|
|
33
|
+
return kind === 'hbar' ? count * HBAR_ROW : kind === 'stacked' ? count * STACKED_ROW : HEIGHT;
|
|
34
|
+
}
|
|
35
|
+
/* One sentence with the main point, which a screen reader hears in place of the drawing. */
|
|
36
|
+
function chartSummary(chart, unit, t) {
|
|
37
|
+
let top = null;
|
|
38
|
+
for (const series of chart.series) {
|
|
39
|
+
for (let i = 0; i < series.values.length; i++) {
|
|
40
|
+
const value = series.values[i];
|
|
41
|
+
if (value !== null && value !== undefined && (top === null || value > top.value)) {
|
|
42
|
+
top = { label: chart.labels[i] ?? '', series: series.key, value };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (top === null) {
|
|
47
|
+
return t('blocks.chart.values');
|
|
48
|
+
}
|
|
49
|
+
return chart.series.length > 1
|
|
50
|
+
? t('blocks.chart.summarySeries', { count: chart.labels.length, series: top.series, label: top.label, value: valueText(top.value, unit) })
|
|
51
|
+
: t('blocks.chart.summary', { count: chart.labels.length, label: top.label, value: valueText(top.value, unit) });
|
|
52
|
+
}
|
|
53
|
+
function Legend({ chart }) {
|
|
54
|
+
return (_jsx("div", { "aria-hidden": true, className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-muted", children: chart.series.map((series, index) => (_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "size-2 rounded-sm", style: { backgroundColor: seriesColor(index) } }), series.key] }, series.key))) }));
|
|
55
|
+
}
|
|
56
|
+
/* The numbers behind the drawing, for a screen reader. */
|
|
57
|
+
function ValuesTable({ chart, unit }) {
|
|
58
|
+
const { t } = useTranslation('agent-chat');
|
|
59
|
+
return (_jsxs("table", { className: "sr-only", children: [_jsx("caption", { children: t('blocks.chart.values') }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("td", {}), chart.series.map((series) => (_jsx("th", { scope: "col", children: series.key }, series.key)))] }) }), _jsx("tbody", { children: chart.labels.map((label, index) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: label }), chart.series.map((series) => (_jsx("td", { children: valueText(series.values[index] ?? null, unit) }, series.key)))] }, index))) })] }));
|
|
60
|
+
}
|
|
61
|
+
/* The values of one category beside the pointer. */
|
|
62
|
+
function Readout({ chart, index, left, unit }) {
|
|
63
|
+
return (_jsxs("div", { "aria-hidden": true, className: "pointer-events-none absolute top-0 z-1 flex -translate-x-1/2 flex-col gap-0.5 rounded-md bg-surface-raised px-2 py-1 text-xs whitespace-nowrap shadow-raised", style: { left }, children: [_jsx("span", { className: "font-medium text-text", children: chart.labels[index] }), chart.series.map((series, at) => (_jsxs("span", { className: "flex items-center gap-1.5 text-text-muted tabular-nums", children: [_jsx("span", { className: "size-2 rounded-sm", style: { backgroundColor: seriesColor(at) } }), chart.series.length > 1 && _jsx("span", { children: series.key }), _jsx("span", { className: "text-text", children: valueText(series.values[index] ?? null, unit) })] }, series.key)))] }));
|
|
64
|
+
}
|
|
65
|
+
/* Bars and lines over a value axis, in SVG at the measured width. */
|
|
66
|
+
function VerticalChart({ chart, kind, unit, width }) {
|
|
67
|
+
const [hover, setHover] = useState(null);
|
|
68
|
+
const low = chart.min < 0 ? -niceCeiling(-chart.min) : 0;
|
|
69
|
+
const high = chart.max > 0 ? niceCeiling(chart.max) : low === 0 ? 1 : 0;
|
|
70
|
+
const plotWidth = Math.max(0, width - AXIS_WIDTH);
|
|
71
|
+
const plotHeight = HEIGHT - PLOT_TOP - LABEL_ROOM;
|
|
72
|
+
const band = plotWidth / chart.labels.length;
|
|
73
|
+
const y = (value) => Math.round(PLOT_TOP + ((high - value) / (high - low)) * plotHeight);
|
|
74
|
+
const baseline = y(0);
|
|
75
|
+
const ticks = low === 0 ? [0, high / 2, high] : [low, 0, high];
|
|
76
|
+
const labelEvery = Math.max(1, Math.ceil(LABEL_BAND / Math.max(1, band)));
|
|
77
|
+
const center = (index) => Math.round(AXIS_WIDTH + band * index + band / 2);
|
|
78
|
+
const gap = band * 0.2;
|
|
79
|
+
const barWidth = Math.max(1, (band - gap) / chart.series.length);
|
|
80
|
+
const segments = (values) => values
|
|
81
|
+
.map((value, index) => (value === null ? null : `${center(index)} ${y(value)}`))
|
|
82
|
+
.reduce((paths, point, index, all) => {
|
|
83
|
+
if (point !== null) {
|
|
84
|
+
paths.push(`${index === 0 || all[index - 1] === null ? 'M' : 'L'}${point}`);
|
|
85
|
+
}
|
|
86
|
+
return paths;
|
|
87
|
+
}, [])
|
|
88
|
+
.join(' ');
|
|
89
|
+
return (_jsxs(_Fragment, { children: [_jsxs("svg", { width: width, height: HEIGHT, "aria-hidden": true, className: "block overflow-visible", onPointerLeave: () => setHover(null), children: [hover !== null && (_jsx("rect", { x: Math.round(AXIS_WIDTH + band * hover), y: PLOT_TOP, width: Math.round(band), height: plotHeight, className: "fill-surface-hover" })), ticks.map((tick) => (_jsxs("g", { children: [_jsx("line", { x1: AXIS_WIDTH, x2: width, y1: y(tick), y2: y(tick), className: tick === 0 ? 'stroke-border' : 'stroke-border-soft', shapeRendering: "crispEdges" }), _jsx("text", { x: AXIS_WIDTH - 6, y: y(tick), dy: "0.32em", textAnchor: "end", className: "fill-text-faint text-xs tabular-nums", children: valueText(tick, unit) })] }, tick))), chart.labels.map((label, index) => index % labelEvery === 0 ? (_jsx("text", { x: center(index), y: HEIGHT - 4, textAnchor: "middle", className: "fill-text-faint text-xs", children: label }, index)) : null), kind === 'bar' ? (_jsx("g", { className: clsx(GROW, 'starting:scale-y-0'), style: { transformOrigin: `0px ${baseline}px` }, children: chart.series.map((series, at) => series.values.map((value, index) => value === null ? null : (_jsx("rect", { x: Math.round(AXIS_WIDTH + band * index + gap / 2 + barWidth * at), y: Math.min(y(value), baseline), width: Math.max(1, Math.round(barWidth) - 1), height: Math.abs(y(value) - baseline), rx: 2, style: { fill: seriesColor(at) } }, `${series.key}:${index}`)))) })) : (_jsx("g", { className: clsx(GROW, 'starting:opacity-0'), children: chart.series.map((series, at) => (_jsxs("g", { style: { color: seriesColor(at) }, children: [_jsx("path", { d: segments(series.values), fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinejoin: "round", strokeLinecap: "round" }), chart.labels.length <= DOTS_UP_TO &&
|
|
90
|
+
series.values.map((value, index) => value === null ? null : _jsx("circle", { cx: center(index), cy: y(value), r: 2.5, fill: "currentColor" }, index))] }, series.key))) })), chart.labels.map((_, index) => (_jsx("rect", { x: Math.round(AXIS_WIDTH + band * index), y: 0, width: Math.max(1, Math.round(band)), height: HEIGHT, fill: "transparent", onPointerEnter: () => setHover(index) }, index)))] }), hover !== null && _jsx(Readout, { chart: chart, index: hover, unit: unit, left: Math.min(Math.max(center(hover), 64), Math.max(64, width - 64)) })] }));
|
|
91
|
+
}
|
|
92
|
+
/* A row of 24px per category, its label beside the bars. */
|
|
93
|
+
function HorizontalChart({ chart, unit }) {
|
|
94
|
+
const high = niceCeiling(chart.max);
|
|
95
|
+
const single = chart.series.length === 1;
|
|
96
|
+
return (_jsx("div", { "aria-hidden": true, className: "flex flex-col", children: chart.labels.map((label, index) => (_jsxs("div", { className: "flex h-6 items-center gap-2 text-xs", children: [_jsx("span", { className: "w-2/5 max-w-36 shrink-0 truncate text-text-muted", children: label }), _jsx("span", { className: "flex min-w-0 grow flex-col justify-center gap-px", children: chart.series.map((series, at) => (_jsx("span", { className: clsx(GROW, 'block origin-left rounded-sm starting:scale-x-0', single ? 'h-3' : 'h-1'), style: { width: `${(Math.max(0, series.values[index] ?? 0) / high) * 100}%`, backgroundColor: seriesColor(at) } }, series.key))) }), single && (_jsx("span", { className: "w-16 shrink-0 text-right text-text-faint tabular-nums", children: valueText(chart.series[0].values[index] ?? null, unit) }))] }, index))) }));
|
|
97
|
+
}
|
|
98
|
+
/* A bar per category split into its series, on one scale so the rows compare. */
|
|
99
|
+
function StackedChart({ chart, unit }) {
|
|
100
|
+
return (_jsx("div", { className: "flex flex-col gap-2", children: chart.labels.map((label, index) => (_jsxs("div", { className: "flex items-center gap-2 text-xs", children: [chart.labels.length > 1 && _jsx("span", { className: "w-2/5 max-w-36 shrink-0 truncate text-text-muted", children: label }), _jsx(SegmentBar, { size: "sm", className: "grow", label: label, range: [0, chart.max], parts: chart.series.map((series, at) => ({
|
|
101
|
+
value: Math.max(0, series.values[index] ?? 0),
|
|
102
|
+
color: seriesColor(at),
|
|
103
|
+
label: `${series.key} ${valueText(series.values[index] ?? 0, unit)}`
|
|
104
|
+
})) })] }, index))) }));
|
|
105
|
+
}
|
|
106
|
+
function DrawnChart({ chart, kind, unit }) {
|
|
107
|
+
const { t } = useTranslation('agent-chat');
|
|
108
|
+
const [measure, width] = useMeasuredWidth();
|
|
109
|
+
return (_jsxs("figure", { className: "flex flex-col gap-2 px-2", children: [chart.series.length > 1 && _jsx(Legend, { chart: chart }), _jsx("div", { ref: measure, role: "img", "aria-label": chartSummary(chart, unit, t), className: "relative", children: kind === 'hbar' ? (_jsx(HorizontalChart, { chart: chart, unit: unit })) : kind === 'stacked' ? (_jsx(StackedChart, { chart: chart, unit: unit })) : (_jsx("div", { style: { height: HEIGHT }, children: width > 0 && _jsx(VerticalChart, { chart: chart, kind: kind, unit: unit, width: width }) })) }), _jsx(ValuesTable, { chart: chart, unit: unit })] }));
|
|
110
|
+
}
|
|
111
|
+
/*
|
|
112
|
+
* Draws only once its node closed; until then a quiet surface of the height it will take holds its
|
|
113
|
+
* place. It grows in once, and not at all for a person who asked for less motion.
|
|
114
|
+
*/
|
|
115
|
+
export function ChartRenderer({ node }) {
|
|
116
|
+
const { kind, data, unit } = node.props;
|
|
117
|
+
const chart = useMemo(() => (node.complete ? uiChartData(data, kind) : null), [node.complete, data, kind]);
|
|
118
|
+
if (!node.complete) {
|
|
119
|
+
return _jsx("div", { "aria-hidden": true, className: "mx-2 rounded-md bg-surface-hover", style: { height: chartHeight(kind, data.length) } });
|
|
120
|
+
}
|
|
121
|
+
if (chart === null) {
|
|
122
|
+
return _jsx(UiFallbackPart, { fallback: node.fallback, problem: { kind: 'failed' } });
|
|
123
|
+
}
|
|
124
|
+
return _jsx(DrawnChart, { chart: chart, kind: kind, unit: unit });
|
|
125
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { UiProps } from '@adecore/intelligent-ui';
|
|
2
|
+
import type { UiRendererProps } from '../render-context.js';
|
|
3
|
+
export declare function ChoicesRenderer({ children, context }: UiRendererProps<UiProps<'Choices'>>): import("react").JSX.Element;
|
|
4
|
+
export declare function ChoiceRenderer({ node, children, context }: UiRendererProps<UiProps<'Choice'>>): import("react").JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useState } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { ArrowRight, Check } from 'lucide-react';
|
|
6
|
+
import { DisabledReason, Icon, Spinner, Tooltip } from '@adecore/ui';
|
|
7
|
+
import { useLater } from '../use-later.js';
|
|
8
|
+
/* A context this long may run past its two lines, so the row offers all of it in a tooltip. */
|
|
9
|
+
const LONG_CONTEXT = 120;
|
|
10
|
+
const SPINNER_AFTER_MS = 300;
|
|
11
|
+
/* Says once, politely, that the choices opened when the reply was done; never on a block that was already done. */
|
|
12
|
+
export function ChoicesRenderer({ children, context }) {
|
|
13
|
+
const { t } = useTranslation('agent-chat');
|
|
14
|
+
const [streamed] = useState(context.phase === 'streaming');
|
|
15
|
+
return (_jsxs("div", { role: "group", "aria-label": t('blocks.choices'), className: "chat-ui-nodes flex flex-col gap-1.5", children: [children, _jsx("span", { className: "sr-only", role: "status", children: streamed && context.phase === 'final' ? t('blocks.choice.ready') : '' })] }));
|
|
16
|
+
}
|
|
17
|
+
/*
|
|
18
|
+
* A row that shows what it sends: the label, and under it the context the agent receives. It stays
|
|
19
|
+
* closed while the reply streams, once the block is answered and without a host to send it, and
|
|
20
|
+
* remains focusable then so a screen reader hears why.
|
|
21
|
+
*/
|
|
22
|
+
export function ChoiceRenderer({ node, children, context }) {
|
|
23
|
+
const { t } = useTranslation('agent-chat');
|
|
24
|
+
const descriptionId = useId();
|
|
25
|
+
const { context: detail, primary, disabled } = node.props;
|
|
26
|
+
const { answer, onChoose } = context;
|
|
27
|
+
const chosen = answer !== null && answer.choiceId === node.id;
|
|
28
|
+
const waiting = context.phase === 'streaming';
|
|
29
|
+
const unavailable = disabled === true || onChoose === undefined;
|
|
30
|
+
const failed = answer === null && context.failedChoiceId === node.id;
|
|
31
|
+
const closed = waiting || unavailable || answer !== null;
|
|
32
|
+
const slow = useLater(chosen && answer.state === 'sending', SPINNER_AFTER_MS);
|
|
33
|
+
const reason = waiting ? t('blocks.choice.waiting') : answer === null && unavailable ? t('blocks.choice.unavailable') : null;
|
|
34
|
+
const described = detail !== undefined && detail !== '';
|
|
35
|
+
const row = (_jsxs("button", { type: "button", "aria-disabled": closed || undefined, "aria-describedby": described ? descriptionId : undefined, className: clsx('flex w-full items-start gap-2 rounded-md border px-2 py-1.5 text-left transition-[opacity,scale,background-color] duration-150 motion-reduce:transition-none', chosen ? 'border-accent bg-accent-soft' : failed ? 'border-status-error bg-surface-hover' : 'border-border bg-surface-hover', !closed && 'hover:bg-surface-active active:scale-99', waiting ? 'opacity-60' : closed && !chosen && 'opacity-50'), onClick: () => {
|
|
36
|
+
if (!closed) {
|
|
37
|
+
onChoose?.(node.id);
|
|
38
|
+
}
|
|
39
|
+
}, children: [_jsx(Icon, { icon: ArrowRight, size: 14, className: clsx('mt-1 shrink-0', primary === true ? 'text-accent' : 'text-text-faint') }), _jsxs("span", { className: "flex min-w-0 grow flex-col", children: [_jsx("span", { className: "text-sm font-semibold text-text", children: children }), described && (_jsx("span", { id: descriptionId, className: "line-clamp-2 text-xs text-text-muted", children: detail }))] }), chosen && (_jsxs("span", { className: "mt-0.5 flex shrink-0 items-center gap-1 text-xs text-accent", children: [slow ? _jsx(Spinner, { size: 12 }) : _jsx(Icon, { icon: Check, size: 12 }), answer.state === 'queued' ? t('blocks.choice.queued') : t('blocks.choice.sent')] }))] }));
|
|
40
|
+
return (_jsxs("div", { className: clsx('flex flex-col gap-1', primary === true && 'order-first'), children: [reason !== null ? (_jsx(DisabledReason, { reason: reason, children: row })) : described && detail.length > LONG_CONTEXT ? (_jsx(Tooltip, { label: detail, children: row })) : (row), failed && (_jsx("p", { role: "alert", className: "px-2 text-xs text-status-error", children: t('blocks.choice.failed') }))] }));
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { UiProps } from '@adecore/intelligent-ui';
|
|
2
|
+
import type { UiRendererProps } from '../render-context.js';
|
|
3
|
+
export declare function CodeBlockRenderer({ node }: UiRendererProps<UiProps<'CodeBlock'>>): import("react").JSX.Element;
|
|
4
|
+
export declare function ImageRenderer({ node, children, context }: UiRendererProps<UiProps<'Image'>>): import("react").JSX.Element;
|
|
5
|
+
export declare function SourcesRenderer({ node, children }: UiRendererProps<UiProps<'Sources'>>): import("react").JSX.Element;
|
|
6
|
+
export interface UiSourceCitationProps {
|
|
7
|
+
number: number;
|
|
8
|
+
title: string;
|
|
9
|
+
url: string;
|
|
10
|
+
onReveal?: () => void;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function UiSourceCitation({ number, title, url, onReveal, className }: UiSourceCitationProps): import("react").JSX.Element;
|
|
14
|
+
export declare function SourceRenderer({ node, context }: UiRendererProps<UiProps<'Source'>>): import("react").JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { PreviewCard } from '@adecore/ui';
|
|
6
|
+
import { formatNumber } from '@adecore/ui/format';
|
|
7
|
+
import { chatHost } from '../../../../host.js';
|
|
8
|
+
import { CodeBlock } from '../../CodeBlock.js';
|
|
9
|
+
import { CodeStreamingContext } from '../../code-streaming.js';
|
|
10
|
+
import { ImageThumb } from '../../ImageView.js';
|
|
11
|
+
import { uiChildrenOf, uiNodeLabel, uiNodeText } from '../node-text.js';
|
|
12
|
+
// The number of every source by its node id, which the list counts in its own order.
|
|
13
|
+
const SourceNumbers = createContext(new Map());
|
|
14
|
+
/* Plain text in the same measure until the node closes, so highlighting never makes it jump. */
|
|
15
|
+
export function CodeBlockRenderer({ node }) {
|
|
16
|
+
return (_jsx(CodeStreamingContext.Provider, { value: !node.complete, children: _jsx(CodeBlock, { code: uiNodeText(node).replace(/^\n|\n$/g, ''), lang: node.props.language ?? 'text' }) }));
|
|
17
|
+
}
|
|
18
|
+
/* Its own component, so the host's hook is only called for an image that names an attachment. */
|
|
19
|
+
function AttachedImage({ scopeId, chatId, attachmentId, alt }) {
|
|
20
|
+
const source = chatHost().attachments.useUrl(scopeId, chatId, attachmentId);
|
|
21
|
+
return _jsx(ImageThumb, { source: source, alt: alt, className: "max-h-70 max-w-full object-contain" });
|
|
22
|
+
}
|
|
23
|
+
/*
|
|
24
|
+
* An attachment of the chat, never a path or an address the model wrote. An image of the latest
|
|
25
|
+
* generation stays a quiet square until the daemon resolved it to an attachment.
|
|
26
|
+
*/
|
|
27
|
+
export function ImageRenderer({ node, children, context }) {
|
|
28
|
+
const { t } = useTranslation('agent-chat');
|
|
29
|
+
const { attachment, alt } = node.props;
|
|
30
|
+
const caption = uiNodeLabel(node);
|
|
31
|
+
const name = alt ?? (caption === '' ? t('blocks.image.label') : caption);
|
|
32
|
+
return (_jsxs("figure", { className: "flex flex-col items-start gap-1.5", children: [attachment === undefined ? (_jsx(ImageThumb, { source: { url: null, failure: t('blocks.image.unavailable') }, alt: name })) : (_jsx(AttachedImage, { scopeId: context.scopeId, chatId: context.chatId, attachmentId: attachment, alt: name })), caption !== '' && _jsx("figcaption", { className: "px-2 text-xs text-text-muted", children: children })] }));
|
|
33
|
+
}
|
|
34
|
+
export function SourcesRenderer({ node, children }) {
|
|
35
|
+
const { t } = useTranslation('agent-chat');
|
|
36
|
+
const numbers = new Map(uiChildrenOf(node, 'Source').map((source, index) => [source.id, index + 1]));
|
|
37
|
+
return (_jsx(SourceNumbers, { value: numbers, children: _jsxs("section", { "aria-label": t('blocks.sources'), className: "flex flex-col", children: [_jsx("span", { className: "px-2 pb-1 text-xs font-medium text-text-muted", children: t('blocks.sources') }), _jsx("ol", { className: "chat-ui-nodes flex flex-col", children: children })] }) }));
|
|
38
|
+
}
|
|
39
|
+
function domainOf(url) {
|
|
40
|
+
try {
|
|
41
|
+
return new URL(url).hostname.replace(/^www\./, '');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return url;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/*
|
|
48
|
+
* A source number in running text. Resting on it or focusing it shows the title and the domain, and
|
|
49
|
+
* a tap reveals the row in the list of sources; only that row opens the address, so nothing loads
|
|
50
|
+
* before a person asks for it.
|
|
51
|
+
*/
|
|
52
|
+
export function UiSourceCitation({ number, title, url, onReveal, className }) {
|
|
53
|
+
const { t } = useTranslation('agent-chat');
|
|
54
|
+
return (_jsxs(PreviewCard.Root, { children: [_jsx(PreviewCard.Trigger, { delay: 300, closeDelay: 150, render: onReveal === undefined ? _jsx("span", {}) : _jsx("button", { type: "button" }), "aria-label": onReveal === undefined ? undefined : t('blocks.citation', { number: formatNumber(number), title }), className: clsx('inline-flex h-4 min-w-4.5 items-center justify-center rounded-sm bg-text/7 px-1 align-[1px] text-2xs leading-4 text-text-muted tabular-nums', onReveal !== undefined && 'cursor-pointer hover:bg-text/12 hover:text-text', className), onClick: onReveal, children: formatNumber(number) }), _jsxs(PreviewCard.Popup, { side: "top", align: "center", sideOffset: 4, className: "flex max-w-72 flex-col px-2.5 py-1.5 text-xs", children: [_jsx("span", { className: "line-clamp-2 text-text", children: title }), _jsx("span", { className: "truncate text-text-faint", children: domainOf(url) })] })] }));
|
|
55
|
+
}
|
|
56
|
+
/*
|
|
57
|
+
* Opens through the host and loads nothing before, not even an icon. The row itself takes focus
|
|
58
|
+
* when a citation reveals it, apart from the button that opens it.
|
|
59
|
+
*/
|
|
60
|
+
export function SourceRenderer({ node, context }) {
|
|
61
|
+
const number = useContext(SourceNumbers).get(node.id);
|
|
62
|
+
const { title, url } = node.props;
|
|
63
|
+
const { openUrl } = context;
|
|
64
|
+
const face = (_jsxs(_Fragment, { children: [_jsx("span", { className: "w-5 shrink-0 text-right text-text-faint tabular-nums", children: number }), _jsx("span", { className: "min-w-0 truncate text-text", children: title }), _jsx("span", { className: "shrink-0 text-text-faint", children: domainOf(url) })] }));
|
|
65
|
+
return (_jsx("li", { "data-ui-source": node.id, tabIndex: -1, className: "chat-ui-source scroll-my-2 rounded-md", children: openUrl === undefined ? (_jsx("span", { className: "flex h-8 items-center gap-2 px-2 text-xs", children: face })) : (_jsx("button", { type: "button", className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-left text-xs hover:bg-surface-hover", onClick: () => openUrl(url), children: face })) }));
|
|
66
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { UiProps } from '@adecore/intelligent-ui';
|
|
2
|
+
import type { UiRendererProps } from '../render-context.js';
|
|
3
|
+
export declare function StatsRenderer({ children }: UiRendererProps<UiProps<'Stats'>>): import("react").JSX.Element;
|
|
4
|
+
export declare function StatRenderer({ node, context }: UiRendererProps<UiProps<'Stat'>>): import("react").JSX.Element;
|
|
5
|
+
export declare function EntityListRenderer({ node, children }: UiRendererProps<UiProps<'EntityList'>>): import("react").JSX.Element;
|
|
6
|
+
export declare function EntryRenderer({ node, children }: UiRendererProps<UiProps<'Entry'>>): import("react").JSX.Element;
|
|
7
|
+
export declare function TableRenderer({ node, context }: UiRendererProps<UiProps<'Table'>>): import("react").JSX.Element;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { ArrowDown, ArrowRight, ArrowUp } from 'lucide-react';
|
|
5
|
+
import { Button, Icon, KeyValueList, Pill } from '@adecore/ui';
|
|
6
|
+
import { formatBytes, formatDuration, formatMoment, formatNumber, formatPercent, formatRounded } from '@adecore/ui/format';
|
|
7
|
+
import { useBlockLocal } from '../block-local.js';
|
|
8
|
+
import { uiChildrenOf } from '../node-text.js';
|
|
9
|
+
import { isNumericColumn, UI_TABLE_ROWS, uiTableCell, uiTableColumns } from '../table-data.js';
|
|
10
|
+
import { UI_TONE_TEXT } from '../tones.js';
|
|
11
|
+
import { UiFallbackPart } from '../UiFallbackPart.js';
|
|
12
|
+
import { UiLinkChip } from './links.js';
|
|
13
|
+
/* EntityList draws a hairline between its rows from this many on. */
|
|
14
|
+
const DIVIDED_FROM = 7;
|
|
15
|
+
/* A number with its unit, where a percent sign hugs the number and every other unit stands apart from it. */
|
|
16
|
+
function withUnit(text, unit) {
|
|
17
|
+
if (unit === undefined || unit === '') {
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
return unit === '%' ? `${text}${unit}` : `${text} ${unit}`;
|
|
21
|
+
}
|
|
22
|
+
/*
|
|
23
|
+
* A number that live readings keep up to date. Keyed by its text, so a value that changed mounts
|
|
24
|
+
* again and lights up once, even when it changes again before the last light settled.
|
|
25
|
+
*/
|
|
26
|
+
function LiveFigure({ text, previous, live, className }) {
|
|
27
|
+
const lit = live !== undefined && live.changed && !live.stale && previous !== text;
|
|
28
|
+
return (_jsx("span", { className: clsx(live?.stale ? 'text-text-muted' : 'text-text', lit && 'chat-ui-changed', className), children: text }, text));
|
|
29
|
+
}
|
|
30
|
+
export function StatsRenderer({ children }) {
|
|
31
|
+
return _jsx("div", { className: "chat-ui-nodes grid grid-cols-[repeat(auto-fit,minmax(128px,1fr))] gap-2", children: children });
|
|
32
|
+
}
|
|
33
|
+
/* The arrow and the number say which way a value went; only a tone says whether that is good. */
|
|
34
|
+
export function StatRenderer({ node, context }) {
|
|
35
|
+
const { t } = useTranslation('agent-chat');
|
|
36
|
+
const { label, value, previous, unit, tone } = node.props;
|
|
37
|
+
const live = context.liveValue?.(node.id, 'value');
|
|
38
|
+
const change = previous === undefined ? null : value - previous;
|
|
39
|
+
const arrow = change === null || change === 0 ? ArrowRight : change > 0 ? ArrowUp : ArrowDown;
|
|
40
|
+
const from = previous === undefined ? '' : withUnit(formatRounded(previous, 2), unit);
|
|
41
|
+
// A change from zero has no percentage.
|
|
42
|
+
const percent = change === null || previous === undefined || previous === 0 ? null : formatPercent(Math.abs((change / previous) * 100));
|
|
43
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5 rounded-md bg-surface-hover px-2 py-1.5", children: [_jsx("span", { className: "truncate text-xs text-text-muted", children: label }), _jsx(LiveFigure, { text: withUnit(formatRounded(value, 2), unit), previous: typeof live?.previous === 'number' ? withUnit(formatRounded(live.previous, 2), unit) : undefined, live: live, className: "text-lg font-semibold tabular-nums" }), change !== null && (_jsxs("span", { className: clsx('flex items-center gap-1 text-xs tabular-nums', tone === undefined ? 'text-text-faint' : UI_TONE_TEXT[tone]), children: [_jsx(Icon, { icon: arrow, size: 12, className: "shrink-0" }), _jsx("span", { className: "min-w-0 truncate", children: percent === null ? t('blocks.stat.from', { previous: from }) : t('blocks.stat.change', { change: percent, previous: from }) })] }))] }));
|
|
44
|
+
}
|
|
45
|
+
export function EntityListRenderer({ node, children }) {
|
|
46
|
+
return (_jsx(KeyValueList.Root, { divided: uiChildrenOf(node, 'Entry').length >= DIVIDED_FROM, className: "chat-ui-nodes px-2", children: children }));
|
|
47
|
+
}
|
|
48
|
+
export function EntryRenderer({ node, children }) {
|
|
49
|
+
return (_jsxs(KeyValueList.Item, { children: [_jsx(KeyValueList.Name, { children: node.props.label }), _jsx(KeyValueList.Value, { children: children })] }));
|
|
50
|
+
}
|
|
51
|
+
function numberText(cell, unit) {
|
|
52
|
+
return cell.as === 'bytes' ? formatBytes(cell.value) : cell.as === 'duration' ? formatDuration(cell.value) : withUnit(formatRounded(cell.value, 2), unit);
|
|
53
|
+
}
|
|
54
|
+
function Cell({ value, column, live, context }) {
|
|
55
|
+
const cell = uiTableCell(value, column);
|
|
56
|
+
switch (cell.kind) {
|
|
57
|
+
case 'empty':
|
|
58
|
+
return null;
|
|
59
|
+
case 'text':
|
|
60
|
+
return _jsx(_Fragment, { children: cell.text });
|
|
61
|
+
case 'number': {
|
|
62
|
+
const before = live?.previous === undefined ? undefined : uiTableCell(live.previous, column);
|
|
63
|
+
return (_jsx(LiveFigure, { text: numberText(cell, column.unit), previous: before?.kind === 'number' ? numberText(before, column.unit) : undefined, live: live }));
|
|
64
|
+
}
|
|
65
|
+
case 'date':
|
|
66
|
+
return _jsx("time", { dateTime: new Date(cell.at).toISOString(), children: formatMoment(cell.at) });
|
|
67
|
+
case 'file':
|
|
68
|
+
return _jsx(UiLinkChip, { target: { type: 'File', path: cell.path }, context: context });
|
|
69
|
+
case 'tag':
|
|
70
|
+
return (_jsx(Pill, { shape: "tag", size: "sm", children: cell.text }));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/*
|
|
74
|
+
* Rows under a sticky head, scrolling inside 320px and sideways when wider than the column. Rows
|
|
75
|
+
* arrive one by one while the block streams, without animation; past fifty a person asks for the rest.
|
|
76
|
+
*/
|
|
77
|
+
export function TableRenderer({ node, context }) {
|
|
78
|
+
const { t } = useTranslation('agent-chat');
|
|
79
|
+
const [all, setAll] = useBlockLocal(context, node.id, () => false);
|
|
80
|
+
const columns = uiTableColumns(node);
|
|
81
|
+
if (columns.length === 0) {
|
|
82
|
+
return _jsx(UiFallbackPart, { fallback: node.fallback, problem: { kind: 'failed' } });
|
|
83
|
+
}
|
|
84
|
+
const { rows } = node.props;
|
|
85
|
+
const shown = all ? rows : rows.slice(0, UI_TABLE_ROWS);
|
|
86
|
+
return (_jsxs("div", { className: "flex flex-col items-start gap-1.5", children: [_jsx("div", { className: "max-h-80 w-full overflow-auto rounded-md border border-border-soft", children: _jsxs("table", { className: "w-full border-collapse text-xs tabular-nums", children: [_jsx("thead", { className: "sticky top-0 z-1 bg-surface-hover", children: _jsx("tr", { children: columns.map((column) => (_jsx("th", { scope: "col", className: clsx('h-8 px-2 font-medium whitespace-nowrap text-text-muted', isNumericColumn(column) ? 'text-right' : 'text-left'), children: column.title }, column.key))) }) }), _jsx("tbody", { children: shown.map((row, index) => (_jsx("tr", { className: "border-t border-border-soft", children: columns.map((column) => (_jsx("td", { className: clsx('h-8 px-2 text-text', isNumericColumn(column) ? 'text-right whitespace-nowrap' : 'text-left'), children: _jsx(Cell, { value: row[column.key], column: column, live: isNumericColumn(column) ? context.liveValue?.(node.id, 'rows', [index, column.key]) : undefined, context: context }) }, column.key))) }, index))) })] }) }), !all && rows.length > UI_TABLE_ROWS && (_jsx(Button, { size: "xs", variant: "secondary", onClick: () => setAll(true), children: t('blocks.table.showAll', { total: formatNumber(rows.length) }) }))] }));
|
|
87
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { UiProps } from '@adecore/intelligent-ui';
|
|
2
|
+
import type { UiRendererProps } from '../render-context.js';
|
|
3
|
+
export declare function ChecklistRenderer({ node, children, context }: UiRendererProps<UiProps<'Checklist'>>): import("react").JSX.Element;
|
|
4
|
+
export declare function ItemRenderer({ node, children }: UiRendererProps<UiProps<'Item'>>): import("react").JSX.Element;
|
|
5
|
+
export declare function SwitchRenderer({ node, children, context }: UiRendererProps<UiProps<'Switch'>>): import("react").JSX.Element;
|
|
6
|
+
export declare function SliderRenderer({ node, context }: UiRendererProps<UiProps<'Slider'>>): import("react").JSX.Element;
|
|
7
|
+
export declare function SegmentedRenderer({ node, context }: UiRendererProps<UiProps<'Segmented'>>): import("react").JSX.Element;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { Checkbox, Segmented, Slider, Switch } from '@adecore/ui';
|
|
6
|
+
import { uiChildrenOf, uiNodeLabel } from '../node-text.js';
|
|
7
|
+
const ChecklistContext = createContext({ values: [], enabled: false, toggle: () => undefined });
|
|
8
|
+
/*
|
|
9
|
+
* The binding of an input, or null for a read-only node. An input works once its own node closed and
|
|
10
|
+
* only through its binding, so the value never leaves the block until a Choice carries it.
|
|
11
|
+
*/
|
|
12
|
+
function bindingOf(node, context) {
|
|
13
|
+
const binding = node.bindings.value;
|
|
14
|
+
return binding === undefined || !node.complete || context.answer !== null ? null : binding;
|
|
15
|
+
}
|
|
16
|
+
/* An input whose node has not closed yet stands at 60% and takes no pointer. */
|
|
17
|
+
const PENDING = 'pointer-events-none opacity-60';
|
|
18
|
+
export function ChecklistRenderer({ node, children, context }) {
|
|
19
|
+
const binding = bindingOf(node, context);
|
|
20
|
+
const values = binding?.value ?? node.props.value;
|
|
21
|
+
const toggle = (value) => {
|
|
22
|
+
binding?.onValueChange(values.includes(value) ? values.filter((entry) => entry !== value) : [...values, value]);
|
|
23
|
+
};
|
|
24
|
+
return (_jsx(ChecklistContext, { value: { values, enabled: binding !== null, toggle }, children: _jsx("div", { className: clsx('chat-ui-nodes flex flex-col', !node.complete && PENDING), children: children }) }));
|
|
25
|
+
}
|
|
26
|
+
/* The whole row is the label; a link inside it keeps its own click and checks nothing. */
|
|
27
|
+
export function ItemRenderer({ node, children }) {
|
|
28
|
+
const list = useContext(ChecklistContext);
|
|
29
|
+
const { value } = node.props;
|
|
30
|
+
const toggleRow = (event) => {
|
|
31
|
+
if (list.enabled && !event.target.closest('button, a')) {
|
|
32
|
+
list.toggle(value);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return (_jsxs("div", { className: clsx('flex min-h-8 items-center gap-2 rounded-md px-2 text-sm text-text', list.enabled && 'hover:bg-surface-hover'), onClick: toggleRow, children: [_jsx(Checkbox, { checked: list.values.includes(value), onCheckedChange: () => list.toggle(value), label: uiNodeLabel(node), disabled: !list.enabled }), _jsx("div", { className: "min-w-0 grow", children: children })] }));
|
|
36
|
+
}
|
|
37
|
+
export function SwitchRenderer({ node, children, context }) {
|
|
38
|
+
const binding = bindingOf(node, context);
|
|
39
|
+
return (_jsxs("div", { className: clsx('flex min-h-7 items-center justify-between gap-3 px-2 text-sm', !node.complete && PENDING), children: [_jsx("span", { className: "min-w-0 text-text", children: children }), _jsx(Switch, { checked: binding?.value ?? node.props.value, onCheckedChange: (next) => binding?.onValueChange(next), label: uiNodeLabel(node), disabled: binding === null })] }));
|
|
40
|
+
}
|
|
41
|
+
/* Its children are the label, read as text: the slider writes its own label line. */
|
|
42
|
+
export function SliderRenderer({ node, context }) {
|
|
43
|
+
const binding = bindingOf(node, context);
|
|
44
|
+
const { value, min, max, step, unit } = node.props;
|
|
45
|
+
return (_jsx(Slider, { value: binding?.value ?? value, onValueChange: (next) => binding?.onValueChange(next), min: min, max: max, step: step, unit: unit, label: uiNodeLabel(node), disabled: binding === null, className: clsx('px-2', !node.complete && PENDING) }));
|
|
46
|
+
}
|
|
47
|
+
/* Left aligned, never the full width. Each Option is a segment by its place, since a value may be any scalar. */
|
|
48
|
+
export function SegmentedRenderer({ node, context }) {
|
|
49
|
+
const { t } = useTranslation('agent-chat');
|
|
50
|
+
const binding = bindingOf(node, context);
|
|
51
|
+
const current = binding?.value ?? node.props.value;
|
|
52
|
+
const options = uiChildrenOf(node, 'Option').map((option, index) => ({ id: String(index), label: uiNodeLabel(option), value: option.props.value }));
|
|
53
|
+
const picked = options.find((option) => option.value === current)?.id ?? '';
|
|
54
|
+
return (_jsx("div", { className: clsx('px-2', !node.complete && PENDING), children: _jsx(Segmented, { value: picked, options: options, label: t('blocks.options'), disabled: binding === null, onValueChange: (id) => {
|
|
55
|
+
const option = options[Number(id)];
|
|
56
|
+
if (option !== undefined) {
|
|
57
|
+
binding?.onValueChange(option.value);
|
|
58
|
+
}
|
|
59
|
+
} }) }));
|
|
60
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { UiProps } from '@adecore/intelligent-ui';
|
|
2
|
+
import type { UiLinkTarget, UiRenderContext, UiRendererProps } from '../render-context.js';
|
|
3
|
+
export declare function UiLinkChip({ target, context }: {
|
|
4
|
+
target: UiLinkTarget;
|
|
5
|
+
context: UiRenderContext;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export declare function FileRenderer({ node, children, context }: UiRendererProps<UiProps<'File'>>): import("react").JSX.Element;
|
|
8
|
+
export declare function DiffRenderer({ node, children, context }: UiRendererProps<UiProps<'Diff'>>): import("react").JSX.Element;
|
|
9
|
+
export declare function CommitRenderer({ node, children, context }: UiRendererProps<UiProps<'Commit'>>): import("react").JSX.Element;
|
|
10
|
+
export declare function NodeRenderer({ node, children, context }: UiRendererProps<UiProps<'Node'>>): import("react").JSX.Element;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { FileDiff, GitCommitHorizontal, SquareDashed } from 'lucide-react';
|
|
5
|
+
import { FileIcon, Icon, Tooltip } from '@adecore/ui';
|
|
6
|
+
import { CHIP_IN_MESSAGE, MENTION_TONE } from '../../chips.js';
|
|
7
|
+
const PLAIN = { state: 'plain' };
|
|
8
|
+
function baseName(path) {
|
|
9
|
+
const trimmed = path.replace(/\/+$/, '');
|
|
10
|
+
return trimmed.slice(trimmed.lastIndexOf('/') + 1) || path;
|
|
11
|
+
}
|
|
12
|
+
function plainText(target, link) {
|
|
13
|
+
switch (target.type) {
|
|
14
|
+
case 'File':
|
|
15
|
+
return target.line === undefined ? target.path : `${target.path}:${target.line}`;
|
|
16
|
+
case 'Diff':
|
|
17
|
+
return target.path;
|
|
18
|
+
case 'Commit':
|
|
19
|
+
return target.sha.slice(0, 7);
|
|
20
|
+
case 'Node':
|
|
21
|
+
return link.label ?? target.id;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function chipFace(target, link) {
|
|
25
|
+
switch (target.type) {
|
|
26
|
+
case 'File':
|
|
27
|
+
return {
|
|
28
|
+
face: (_jsxs(_Fragment, { children: [_jsx(FileIcon, { path: target.path, size: 14 }), _jsx("span", { className: "truncate", children: baseName(target.path) }), target.line !== undefined && _jsxs("span", { className: "opacity-70", children: [":", target.line] })] })),
|
|
29
|
+
hint: target.line === undefined ? target.path : `${target.path}:${target.line}`
|
|
30
|
+
};
|
|
31
|
+
case 'Diff':
|
|
32
|
+
return {
|
|
33
|
+
face: (_jsxs(_Fragment, { children: [_jsx(Icon, { icon: FileDiff, size: 12 }), _jsx("span", { className: "truncate", children: baseName(target.path) })] })),
|
|
34
|
+
hint: target.path
|
|
35
|
+
};
|
|
36
|
+
case 'Commit':
|
|
37
|
+
return {
|
|
38
|
+
face: (_jsxs(_Fragment, { children: [_jsx(Icon, { icon: GitCommitHorizontal, size: 12 }), _jsx("span", { className: "font-mono", children: target.sha.slice(0, 7) })] })),
|
|
39
|
+
hint: link.label ?? target.sha
|
|
40
|
+
};
|
|
41
|
+
case 'Node':
|
|
42
|
+
return {
|
|
43
|
+
face: (_jsxs(_Fragment, { children: [_jsx(Icon, { icon: SquareDashed, size: 12 }), _jsx("span", { className: "truncate", children: link.label ?? target.id })] })),
|
|
44
|
+
hint: null
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/*
|
|
49
|
+
* A resource of the host as a chip, the shape a mention has in a message. The host says whether it
|
|
50
|
+
* can open the target; one it cannot, outside the project or gone, stays mono text with the reason
|
|
51
|
+
* in a tooltip. A chip fades in, since a link reads as text until the host checked it.
|
|
52
|
+
*/
|
|
53
|
+
export function UiLinkChip({ target, context }) {
|
|
54
|
+
const { t } = useTranslation('agent-chat');
|
|
55
|
+
const link = context.link?.(target) ?? PLAIN;
|
|
56
|
+
if (link.state === 'plain') {
|
|
57
|
+
const reason = link.reason ?? (context.link === undefined ? null : t('blocks.outsideProject'));
|
|
58
|
+
// Focusable only with a reason, so a keyboard reaches the tooltip.
|
|
59
|
+
const text = (_jsx("span", { tabIndex: reason === null ? undefined : 0, className: "font-mono text-code break-all text-text-muted", children: plainText(target, link) }));
|
|
60
|
+
return reason === null ? text : _jsx(Tooltip, { label: reason, children: text });
|
|
61
|
+
}
|
|
62
|
+
const { face, hint } = chipFace(target, link);
|
|
63
|
+
const { openLink } = context;
|
|
64
|
+
const chip = openLink === undefined ? (_jsx("span", { className: clsx(CHIP_IN_MESSAGE, MENTION_TONE, 'transition-opacity duration-120 starting:opacity-0'), children: face })) : (_jsx("button", { type: "button", className: clsx(CHIP_IN_MESSAGE, MENTION_TONE, 'transition-opacity duration-120 hover:brightness-95 starting:opacity-0'), onClick: () => openLink(target), children: face }));
|
|
65
|
+
return hint === null ? chip : _jsx(Tooltip, { label: hint, children: chip });
|
|
66
|
+
}
|
|
67
|
+
/* A link with children is a row: the chip and what the agent says about it. */
|
|
68
|
+
function LinkRow({ target, context, described, children }) {
|
|
69
|
+
if (!described) {
|
|
70
|
+
return _jsx(UiLinkChip, { target: target, context: context });
|
|
71
|
+
}
|
|
72
|
+
return (_jsxs("div", { className: "flex min-w-0 items-baseline gap-2 px-2 text-sm", children: [_jsx("span", { className: "shrink-0", children: _jsx(UiLinkChip, { target: target, context: context }) }), _jsx("span", { className: "min-w-0 text-text-muted", children: children })] }));
|
|
73
|
+
}
|
|
74
|
+
export function FileRenderer({ node, children, context }) {
|
|
75
|
+
const { path, line } = node.props;
|
|
76
|
+
return (_jsx(LinkRow, { target: { type: 'File', path, line }, context: context, described: node.children.length > 0, children: children }));
|
|
77
|
+
}
|
|
78
|
+
export function DiffRenderer({ node, children, context }) {
|
|
79
|
+
return (_jsx(LinkRow, { target: { type: 'Diff', path: node.props.path }, context: context, described: node.children.length > 0, children: children }));
|
|
80
|
+
}
|
|
81
|
+
export function CommitRenderer({ node, children, context }) {
|
|
82
|
+
return (_jsx(LinkRow, { target: { type: 'Commit', sha: node.props.sha }, context: context, described: node.children.length > 0, children: children }));
|
|
83
|
+
}
|
|
84
|
+
export function NodeRenderer({ node, children, context }) {
|
|
85
|
+
return (_jsx(LinkRow, { target: { type: 'Node', id: node.props.id }, context: context, described: node.children.length > 0, children: children }));
|
|
86
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UiProps } from '@adecore/intelligent-ui';
|
|
2
|
+
import type { UiRendererProps } from '../render-context.js';
|
|
3
|
+
export declare function TabsRenderer({ node, children, context }: UiRendererProps<UiProps<'Tabs'>>): import("react").JSX.Element;
|
|
4
|
+
export declare function TabRenderer({ node, children }: UiRendererProps<UiProps<'Tab'>>): import("react").JSX.Element;
|
|
5
|
+
export declare function SectionsRenderer({ node, children, context }: UiRendererProps<UiProps<'Sections'>>): import("react").JSX.Element;
|
|
6
|
+
export declare function SectionRenderer({ node, children, context }: UiRendererProps<UiProps<'Section'>>): import("react").JSX.Element;
|