@tulipfarm/sprout 0.2.0 → 0.3.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/README.md +111 -0
- package/dist/components/agent-fanout.d.ts +46 -0
- package/dist/components/agent-fanout.js +228 -0
- package/dist/components/chat-sources.js +11 -2
- package/dist/components/presence-cursor.d.ts +19 -0
- package/dist/components/presence-cursor.js +14 -0
- package/dist/components/schema-diagram.js +5 -78
- package/dist/components/sql-viewer.d.ts +76 -0
- package/dist/components/sql-viewer.js +409 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/internal/flow.d.ts +22 -0
- package/dist/internal/flow.js +82 -0
- package/dist/styles/agent-fanout.css +529 -0
- package/dist/styles/index.css +3 -0
- package/dist/styles/message.css +1 -1
- package/dist/styles/presence-cursor.css +68 -0
- package/dist/styles/sql-viewer.css +406 -0
- package/dist/styles/themes/tulipfarm.css +1 -1
- package/package.json +19 -7
package/README.md
CHANGED
|
@@ -94,6 +94,117 @@ Each component imports from `@tulipfarm/sprout/<name>`, for example
|
|
|
94
94
|
`@tulipfarm/sprout/dialog`. See the [component reference](https://github.com/TulipFarm/sprout/blob/main/docs/components.md)
|
|
95
95
|
for notes on each one.
|
|
96
96
|
|
|
97
|
+
### Bundle size
|
|
98
|
+
|
|
99
|
+
<!-- bundle-size:start -->
|
|
100
|
+
|
|
101
|
+
Full library: **786.3 kB** gzip. Each component path is smaller. Sizes are minified, with React and other peer dependencies left out.
|
|
102
|
+
|
|
103
|
+
<details>
|
|
104
|
+
<summary>Size of each component</summary>
|
|
105
|
+
|
|
106
|
+
| Component | Raw | Gzip | Brotli |
|
|
107
|
+
| ------------------- | -------: | -------: | -------: |
|
|
108
|
+
| `accordion` | 64.1 kB | 21.9 kB | 19.3 kB |
|
|
109
|
+
| `agent-fanout` | 260.9 kB | 86.7 kB | 74.6 kB |
|
|
110
|
+
| `alert` | 35.2 kB | 11.5 kB | 10.1 kB |
|
|
111
|
+
| `alert-dialog` | 169.1 kB | 57.0 kB | 49.8 kB |
|
|
112
|
+
| `aspect-ratio` | 30.2 kB | 9.7 kB | 8.5 kB |
|
|
113
|
+
| `attachment` | 459.9 kB | 155.6 kB | 128.6 kB |
|
|
114
|
+
| `avatar` | 45.5 kB | 15.4 kB | 13.6 kB |
|
|
115
|
+
| `badge` | 34.9 kB | 11.4 kB | 10.0 kB |
|
|
116
|
+
| `brand-icon` | 195.1 kB | 70.2 kB | 61.2 kB |
|
|
117
|
+
| `breadcrumb` | 40.5 kB | 13.4 kB | 11.7 kB |
|
|
118
|
+
| `button` | 50.5 kB | 16.7 kB | 14.6 kB |
|
|
119
|
+
| `button-group` | 37.9 kB | 12.1 kB | 10.7 kB |
|
|
120
|
+
| `calendar` | 312.3 kB | 103.5 kB | 88.2 kB |
|
|
121
|
+
| `canvas` | 235.1 kB | 80.8 kB | 69.2 kB |
|
|
122
|
+
| `card` | 35.4 kB | 11.6 kB | 10.1 kB |
|
|
123
|
+
| `carousel` | 60.4 kB | 19.9 kB | 17.6 kB |
|
|
124
|
+
| `chart` | 393.4 kB | 119.0 kB | 101.3 kB |
|
|
125
|
+
| `chat-confirmation` | 76.7 kB | 25.8 kB | 22.4 kB |
|
|
126
|
+
| `chat-plan` | 42.3 kB | 13.4 kB | 11.7 kB |
|
|
127
|
+
| `chat-sources` | 206.2 kB | 70.5 kB | 60.3 kB |
|
|
128
|
+
| `chat-input` | 962.4 kB | 317.0 kB | 261.5 kB |
|
|
129
|
+
| `checkbox` | 60.0 kB | 21.0 kB | 18.5 kB |
|
|
130
|
+
| `choice-list` | 43.0 kB | 14.5 kB | 12.6 kB |
|
|
131
|
+
| `code-block` | 74.4 kB | 25.9 kB | 22.3 kB |
|
|
132
|
+
| `collapsible` | 55.5 kB | 18.7 kB | 16.5 kB |
|
|
133
|
+
| `combobox` | 268.6 kB | 90.9 kB | 77.7 kB |
|
|
134
|
+
| `command` | 181.9 kB | 61.6 kB | 53.5 kB |
|
|
135
|
+
| `context-menu` | 242.8 kB | 81.0 kB | 69.7 kB |
|
|
136
|
+
| `dialog` | 170.7 kB | 57.5 kB | 50.2 kB |
|
|
137
|
+
| `direction` | 1.4 kB | 0.7 kB | 0.7 kB |
|
|
138
|
+
| `dropdown-menu` | 239.6 kB | 80.0 kB | 69.2 kB |
|
|
139
|
+
| `field` | 97.9 kB | 34.2 kB | 29.5 kB |
|
|
140
|
+
| `file-explorer` | 527.9 kB | 180.1 kB | 147.5 kB |
|
|
141
|
+
| `file-viewer` | 459.5 kB | 155.5 kB | 128.4 kB |
|
|
142
|
+
| `form` | 109.7 kB | 37.3 kB | 32.2 kB |
|
|
143
|
+
| `glass-banner` | 33.0 kB | 10.6 kB | 9.4 kB |
|
|
144
|
+
| `gradient-banner` | 32.2 kB | 10.3 kB | 9.1 kB |
|
|
145
|
+
| `hover-card` | 163.2 kB | 56.3 kB | 49.1 kB |
|
|
146
|
+
| `input` | 108.2 kB | 37.4 kB | 32.3 kB |
|
|
147
|
+
| `input-group` | 119.1 kB | 40.5 kB | 34.8 kB |
|
|
148
|
+
| `input-otp` | 65.3 kB | 22.9 kB | 20.3 kB |
|
|
149
|
+
| `kbd` | 30.4 kB | 9.7 kB | 8.5 kB |
|
|
150
|
+
| `label` | 30.2 kB | 9.6 kB | 8.4 kB |
|
|
151
|
+
| `mention-list` | 52.6 kB | 17.2 kB | 15.2 kB |
|
|
152
|
+
| `menubar` | 244.9 kB | 82.2 kB | 70.7 kB |
|
|
153
|
+
| `message` | 36.2 kB | 11.7 kB | 10.3 kB |
|
|
154
|
+
| `message-actions` | 71.4 kB | 24.9 kB | 20.8 kB |
|
|
155
|
+
| `message-response` | 232.7 kB | 74.2 kB | 64.2 kB |
|
|
156
|
+
| `message-scroller` | 69.0 kB | 22.1 kB | 19.6 kB |
|
|
157
|
+
| `navigation-menu` | 182.3 kB | 63.0 kB | 54.7 kB |
|
|
158
|
+
| `notice-banner` | 30.4 kB | 9.7 kB | 8.5 kB |
|
|
159
|
+
| `page-content` | 30.2 kB | 9.6 kB | 8.4 kB |
|
|
160
|
+
| `page-header` | 54.6 kB | 17.6 kB | 15.5 kB |
|
|
161
|
+
| `pagination` | 55.3 kB | 17.9 kB | 15.6 kB |
|
|
162
|
+
| `plan-outline` | 79.9 kB | 26.4 kB | 22.9 kB |
|
|
163
|
+
| `popover` | 178.0 kB | 60.7 kB | 52.6 kB |
|
|
164
|
+
| `presence-cursor` | 30.8 kB | 9.9 kB | 8.6 kB |
|
|
165
|
+
| `progress` | 39.8 kB | 13.2 kB | 11.6 kB |
|
|
166
|
+
| `prompt-list` | 199.0 kB | 71.9 kB | 62.6 kB |
|
|
167
|
+
| `questionnaire` | 69.5 kB | 23.3 kB | 20.4 kB |
|
|
168
|
+
| `radio-group` | 80.8 kB | 27.9 kB | 24.7 kB |
|
|
169
|
+
| `reasoning-effort` | 35.5 kB | 12.0 kB | 10.5 kB |
|
|
170
|
+
| `resizable` | 39.4 kB | 13.4 kB | 11.8 kB |
|
|
171
|
+
| `result-card` | 240.1 kB | 85.1 kB | 72.1 kB |
|
|
172
|
+
| `review-card` | 61.6 kB | 21.0 kB | 18.4 kB |
|
|
173
|
+
| `rich-text-editor` | 442.5 kB | 140.2 kB | 119.8 kB |
|
|
174
|
+
| `schema-diagram` | 247.9 kB | 82.9 kB | 71.2 kB |
|
|
175
|
+
| `sql-viewer` | 265.6 kB | 89.1 kB | 76.3 kB |
|
|
176
|
+
| `scroll-area` | 57.3 kB | 19.7 kB | 17.4 kB |
|
|
177
|
+
| `search-dialog` | 190.2 kB | 64.6 kB | 55.5 kB |
|
|
178
|
+
| `search-field` | 122.2 kB | 41.4 kB | 35.5 kB |
|
|
179
|
+
| `select` | 230.4 kB | 79.3 kB | 67.8 kB |
|
|
180
|
+
| `selection-slot` | 31.5 kB | 10.2 kB | 8.9 kB |
|
|
181
|
+
| `separator` | 36.9 kB | 11.9 kB | 10.5 kB |
|
|
182
|
+
| `settings-list` | 31.1 kB | 9.9 kB | 8.7 kB |
|
|
183
|
+
| `sheet` | 213.4 kB | 72.9 kB | 63.2 kB |
|
|
184
|
+
| `sidebar` | 251.1 kB | 85.8 kB | 72.8 kB |
|
|
185
|
+
| `skeleton` | 35.1 kB | 11.5 kB | 10.1 kB |
|
|
186
|
+
| `slider` | 83.6 kB | 29.5 kB | 25.9 kB |
|
|
187
|
+
| `spinner` | 36.5 kB | 11.6 kB | 10.1 kB |
|
|
188
|
+
| `state-panel` | 459.7 kB | 155.6 kB | 128.5 kB |
|
|
189
|
+
| `state-sample` | 33.5 kB | 10.5 kB | 9.2 kB |
|
|
190
|
+
| `switch` | 50.3 kB | 17.3 kB | 15.3 kB |
|
|
191
|
+
| `table` | 536.6 kB | 171.9 kB | 144.4 kB |
|
|
192
|
+
| `table-nuqs` | 2.1 kB | 1.1 kB | 1.0 kB |
|
|
193
|
+
| `tabs` | 80.2 kB | 27.6 kB | 24.4 kB |
|
|
194
|
+
| `textarea` | 98.2 kB | 34.2 kB | 29.5 kB |
|
|
195
|
+
| `toast` | 189.5 kB | 65.2 kB | 56.7 kB |
|
|
196
|
+
| `toggle` | 46.1 kB | 15.7 kB | 13.8 kB |
|
|
197
|
+
| `toggle-group` | 67.1 kB | 23.0 kB | 20.3 kB |
|
|
198
|
+
| `tool-activity` | 86.7 kB | 29.2 kB | 25.2 kB |
|
|
199
|
+
| `toolbar-status` | 189.3 kB | 64.3 kB | 55.6 kB |
|
|
200
|
+
| `tooltip` | 164.7 kB | 56.7 kB | 49.4 kB |
|
|
201
|
+
| `typed-text` | 31.2 kB | 10.1 kB | 8.8 kB |
|
|
202
|
+
|
|
203
|
+
</details>
|
|
204
|
+
<!-- bundle-size:end -->
|
|
205
|
+
|
|
206
|
+
CI compares sizes on each pull request. Run `pnpm size` to see them locally.
|
|
207
|
+
|
|
97
208
|
| Group | Components |
|
|
98
209
|
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
99
210
|
| **Actions** | Button · Button Group · Toggle · Toggle Group · Toolbar Status · Kbd |
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export declare const agentFanoutStatusNames: readonly ["queued", "working", "done", "failed", "stopped"];
|
|
3
|
+
export declare const agentFanoutStateNames: readonly ["running", "done", "stopped"];
|
|
4
|
+
export type AgentFanoutStatus = (typeof agentFanoutStatusNames)[number];
|
|
5
|
+
export type AgentFanoutState = (typeof agentFanoutStateNames)[number];
|
|
6
|
+
export interface AgentFanoutAgent {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
/** A 16px icon for the subagent's role. */
|
|
10
|
+
icon: ReactNode;
|
|
11
|
+
/** What the subagent was asked to do, such as “Web search”. */
|
|
12
|
+
task: string;
|
|
13
|
+
/** What it found. Replaces `task` once the subagent is done or failed. */
|
|
14
|
+
result?: string | undefined;
|
|
15
|
+
status: AgentFanoutStatus;
|
|
16
|
+
/** How long the subagent took, in ms. Shown once it is done. */
|
|
17
|
+
duration?: number | undefined;
|
|
18
|
+
/** Any CSS color. Defaults to the next of five `--sprout-chart-*` colors. */
|
|
19
|
+
color?: string | undefined;
|
|
20
|
+
/** Subagents this agent calls in turn. They take its color. */
|
|
21
|
+
agents?: AgentFanoutAgent[] | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface AgentFanoutProps {
|
|
24
|
+
/** The parent agent's task, shown above the graph. */
|
|
25
|
+
task: ReactNode;
|
|
26
|
+
agents: AgentFanoutAgent[];
|
|
27
|
+
/** The parent agent. `done` once it has merged the results into an answer. */
|
|
28
|
+
state?: AgentFanoutState | undefined;
|
|
29
|
+
/** Time since the parent started, in ms. */
|
|
30
|
+
elapsed?: number | undefined;
|
|
31
|
+
/** Replaces the footer text that names the current phase. */
|
|
32
|
+
label?: ReactNode;
|
|
33
|
+
/** Shows the per-agent list at first. It starts closed. */
|
|
34
|
+
defaultOpen?: boolean | undefined;
|
|
35
|
+
/** Shows a Stop button while the parent is running. */
|
|
36
|
+
onStop?: (() => void) | undefined;
|
|
37
|
+
/** Shows a Run again button once the parent is done or stopped. */
|
|
38
|
+
onRestart?: (() => void) | undefined;
|
|
39
|
+
className?: string | undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A parent agent that hands one task to several subagents and merges what
|
|
43
|
+
* they find. Each status change sends a pulse along the subagent's line:
|
|
44
|
+
* down when it starts, back up when it finishes.
|
|
45
|
+
*/
|
|
46
|
+
export declare function AgentFanout({ task, agents, state, elapsed, label, defaultOpen, onStop, onRestart, className, }: AgentFanoutProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { BaseEdge, EdgeLabelRenderer, Handle, Position, ReactFlow, } from "@xyflow/react";
|
|
4
|
+
import { useLayoutEffect, useRef, useState, } from "react";
|
|
5
|
+
import { Check } from "reicon-react/icons/Check";
|
|
6
|
+
import { ChevronDown } from "reicon-react/icons/ChevronDown";
|
|
7
|
+
import { Danger } from "reicon-react/icons/Danger";
|
|
8
|
+
import { MinusCircle } from "reicon-react/icons/MinusCircle";
|
|
9
|
+
import { RotateLeft } from "reicon-react/icons/RotateLeft";
|
|
10
|
+
import { Sparkle } from "reicon-react/icons/Sparkle";
|
|
11
|
+
import { cn } from "../internal/classes.js";
|
|
12
|
+
import { Button } from "./button.js";
|
|
13
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "./collapsible.js";
|
|
14
|
+
export const agentFanoutStatusNames = [
|
|
15
|
+
"queued",
|
|
16
|
+
"working",
|
|
17
|
+
"done",
|
|
18
|
+
"failed",
|
|
19
|
+
"stopped",
|
|
20
|
+
];
|
|
21
|
+
export const agentFanoutStateNames = ["running", "done", "stopped"];
|
|
22
|
+
const rootTop = 12;
|
|
23
|
+
const rootSize = 44;
|
|
24
|
+
const levelTop = 120;
|
|
25
|
+
const levelGap = 108;
|
|
26
|
+
const nodeLabel = 22;
|
|
27
|
+
const bottomGap = 36;
|
|
28
|
+
const colorCount = 5;
|
|
29
|
+
const statusText = {
|
|
30
|
+
queued: "Queued",
|
|
31
|
+
working: "Working",
|
|
32
|
+
done: "Done",
|
|
33
|
+
failed: "Failed",
|
|
34
|
+
stopped: "Stopped",
|
|
35
|
+
};
|
|
36
|
+
const seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
|
|
37
|
+
function phaseText(agents, state, elapsed) {
|
|
38
|
+
const time = elapsed === undefined ? "" : seconds(elapsed);
|
|
39
|
+
if (state === "done") {
|
|
40
|
+
const merged = agents.filter((agent) => agent.status === "done").length;
|
|
41
|
+
return {
|
|
42
|
+
text: `Merged ${merged} ${merged === 1 ? "result" : "results"}${time && ` in ${time}`}`,
|
|
43
|
+
time: "",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (state === "stopped")
|
|
47
|
+
return { text: time ? `Stopped after ${time}` : "Stopped", time: "" };
|
|
48
|
+
const settled = agents.filter((agent) => agent.status === "done" || agent.status === "failed").length;
|
|
49
|
+
const text = agents.every((agent) => agent.status === "queued")
|
|
50
|
+
? "Delegating work"
|
|
51
|
+
: settled === agents.length
|
|
52
|
+
? "Composing answer"
|
|
53
|
+
: settled > 0
|
|
54
|
+
? "Merging results"
|
|
55
|
+
: "Specialists working";
|
|
56
|
+
return { text, time };
|
|
57
|
+
}
|
|
58
|
+
function useWidth() {
|
|
59
|
+
const ref = useRef(null);
|
|
60
|
+
const [width, setWidth] = useState(0);
|
|
61
|
+
useLayoutEffect(() => {
|
|
62
|
+
const element = ref.current;
|
|
63
|
+
if (!element)
|
|
64
|
+
return;
|
|
65
|
+
setWidth(element.clientWidth);
|
|
66
|
+
const observer = new ResizeObserver(() => setWidth(element.clientWidth));
|
|
67
|
+
observer.observe(element);
|
|
68
|
+
return () => observer.disconnect();
|
|
69
|
+
}, []);
|
|
70
|
+
return [ref, width];
|
|
71
|
+
}
|
|
72
|
+
/** Every agent in tree order, with the parent it reports to. */
|
|
73
|
+
function flatten(agents, initial, parent = rootId, depth = 1, color) {
|
|
74
|
+
return agents.flatMap((agent, index) => {
|
|
75
|
+
const own = agent.color ?? color ?? `var(--sprout-chart-${(index % colorCount) + 1})`;
|
|
76
|
+
const changed = initial.get(agent.id) !== agent.status;
|
|
77
|
+
const line = {
|
|
78
|
+
agent,
|
|
79
|
+
parent,
|
|
80
|
+
depth,
|
|
81
|
+
changed: changed || undefined,
|
|
82
|
+
trip: !changed
|
|
83
|
+
? null
|
|
84
|
+
: agent.status === "working"
|
|
85
|
+
? "out"
|
|
86
|
+
: agent.status === "done"
|
|
87
|
+
? "back"
|
|
88
|
+
: null,
|
|
89
|
+
style: {
|
|
90
|
+
"--sprout-agent-fanout-color": own,
|
|
91
|
+
"--sprout-agent-fanout-index": index,
|
|
92
|
+
"--sprout-agent-fanout-depth": depth - 1,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
return [
|
|
96
|
+
line,
|
|
97
|
+
...flatten(agent.agents ?? [], initial, agent.id, depth + 1, own),
|
|
98
|
+
];
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const rootId = "agent-fanout-root";
|
|
102
|
+
// Fresh node objects every render drop React Flow's measured handles, which
|
|
103
|
+
// unmounts the edges and restarts their pulses. Declared handles survive.
|
|
104
|
+
const handle = (type, x, y) => ({
|
|
105
|
+
type,
|
|
106
|
+
position: type === "source" ? Position.Bottom : Position.Top,
|
|
107
|
+
x,
|
|
108
|
+
y,
|
|
109
|
+
width: 0,
|
|
110
|
+
height: 0,
|
|
111
|
+
});
|
|
112
|
+
/** Leaves share the width; each parent sits over the middle of its children. */
|
|
113
|
+
function layout(lines, width, state, rootChanged) {
|
|
114
|
+
const children = new Map();
|
|
115
|
+
for (const line of lines)
|
|
116
|
+
children.set(line.parent, [...(children.get(line.parent) ?? []), line]);
|
|
117
|
+
const leaves = lines.filter((line) => !children.has(line.agent.id)).length;
|
|
118
|
+
const column = width / Math.max(leaves, 1);
|
|
119
|
+
const centers = new Map();
|
|
120
|
+
let leaf = 0;
|
|
121
|
+
const place = (id) => {
|
|
122
|
+
const kids = children.get(id);
|
|
123
|
+
const xs = kids?.map((kid) => place(kid.agent.id));
|
|
124
|
+
const x = xs ? (xs[0] + xs.at(-1)) / 2 : (leaf++ + 0.5) * column;
|
|
125
|
+
centers.set(id, x);
|
|
126
|
+
return x;
|
|
127
|
+
};
|
|
128
|
+
place(rootId);
|
|
129
|
+
const depth = Math.max(0, ...lines.map((line) => line.depth));
|
|
130
|
+
const dot = width ? Math.min(40, Math.max(28, column - 12)) : 40;
|
|
131
|
+
const nodes = [
|
|
132
|
+
{
|
|
133
|
+
id: rootId,
|
|
134
|
+
type: "root",
|
|
135
|
+
position: { x: width / 2 - rootSize / 2, y: rootTop },
|
|
136
|
+
width: rootSize,
|
|
137
|
+
height: rootSize,
|
|
138
|
+
handles: [handle("source", rootSize / 2, rootSize)],
|
|
139
|
+
data: { state, changed: rootChanged },
|
|
140
|
+
},
|
|
141
|
+
...lines.map((line) => ({
|
|
142
|
+
id: line.agent.id,
|
|
143
|
+
type: "agent",
|
|
144
|
+
position: {
|
|
145
|
+
x: centers.get(line.agent.id) - column / 2,
|
|
146
|
+
y: levelTop + (line.depth - 1) * levelGap,
|
|
147
|
+
},
|
|
148
|
+
width: column,
|
|
149
|
+
height: dot + nodeLabel,
|
|
150
|
+
handles: [
|
|
151
|
+
handle("target", column / 2, 0),
|
|
152
|
+
handle("source", column / 2, dot),
|
|
153
|
+
],
|
|
154
|
+
data: line,
|
|
155
|
+
})),
|
|
156
|
+
];
|
|
157
|
+
const edges = lines.map((line) => ({
|
|
158
|
+
id: `${line.parent}-${line.agent.id}`,
|
|
159
|
+
type: "trip",
|
|
160
|
+
source: line.parent,
|
|
161
|
+
target: line.agent.id,
|
|
162
|
+
data: { trip: line.trip, style: line.style },
|
|
163
|
+
}));
|
|
164
|
+
return {
|
|
165
|
+
nodes,
|
|
166
|
+
edges,
|
|
167
|
+
dot,
|
|
168
|
+
height: levelTop +
|
|
169
|
+
Math.max(depth - 1, 0) * levelGap +
|
|
170
|
+
dot +
|
|
171
|
+
nodeLabel +
|
|
172
|
+
bottomGap,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function RootMark({ data: { state, changed } }) {
|
|
176
|
+
return (_jsxs("span", { "data-slot": "agent-fanout-root", "data-state": state, "data-changed": changed, className: "sprout-agent-fanout-root", children: [state === "done" ? (_jsx(Check, { size: 18 })) : state === "stopped" ? (_jsx(MinusCircle, { size: 18 })) : (_jsx(Sparkle, { size: 18 })), _jsx(Handle, { type: "source", position: Position.Bottom, isConnectable: false, className: "sprout-agent-fanout-handle" })] }));
|
|
177
|
+
}
|
|
178
|
+
function AgentMark({ data: { agent, changed, style } }) {
|
|
179
|
+
return (_jsxs("div", { "data-slot": "agent-fanout-node", "data-status": agent.status, "data-changed": changed, className: "sprout-agent-fanout-node", style: style, children: [_jsxs("span", { className: "sprout-agent-fanout-dot", children: [_jsx(NodeMark, { agent: agent }), _jsx(Handle, { type: "target", position: Position.Top, isConnectable: false, className: "sprout-agent-fanout-handle" }), agent.agents && agent.agents.length > 0 && (_jsx(Handle, { type: "source", position: Position.Bottom, isConnectable: false, className: "sprout-agent-fanout-handle" }))] }), _jsx("span", { className: "sprout-agent-fanout-node-name", children: agent.name })] }));
|
|
180
|
+
}
|
|
181
|
+
function TripLine({ id, sourceX, sourceY, targetX, targetY, data, }) {
|
|
182
|
+
const drop = targetY - sourceY;
|
|
183
|
+
const d = `M ${sourceX} ${sourceY} C ${sourceX} ${sourceY + drop * 0.5}, ${targetX} ${sourceY + drop * 0.2}, ${targetX} ${targetY}`;
|
|
184
|
+
const trip = data?.trip;
|
|
185
|
+
return (_jsxs("g", { style: data?.style, children: [_jsx(BaseEdge, { id: id, path: d, className: "sprout-agent-fanout-edge" }), trip && (_jsxs(_Fragment, { children: [_jsx("path", { d: d, pathLength: 1, "data-trip": trip, className: "sprout-agent-fanout-trail" }, trip), _jsx(EdgeLabelRenderer, { children: _jsx("span", { "data-trip": trip, className: "sprout-agent-fanout-comet", style: { ...data.style, offsetPath: `path("${d}")` } }, trip) })] }))] }));
|
|
186
|
+
}
|
|
187
|
+
const nodeTypes = { root: RootMark, agent: AgentMark };
|
|
188
|
+
const edgeTypes = { trip: TripLine };
|
|
189
|
+
const viewport = { x: 0, y: 0, zoom: 1 };
|
|
190
|
+
function NodeMark({ agent }) {
|
|
191
|
+
if (agent.status === "done")
|
|
192
|
+
return _jsx(Check, { size: 16, "aria-hidden": "true" });
|
|
193
|
+
if (agent.status === "failed")
|
|
194
|
+
return _jsx(Danger, { size: 16, "aria-hidden": "true" });
|
|
195
|
+
return _jsx("span", { "aria-hidden": "true", children: agent.icon });
|
|
196
|
+
}
|
|
197
|
+
function StatusMark({ agent }) {
|
|
198
|
+
if (agent.status === "done")
|
|
199
|
+
return (_jsxs(_Fragment, { children: [_jsx(Check, { size: 12, "aria-hidden": "true" }), agent.duration === undefined ? "Done" : seconds(agent.duration)] }));
|
|
200
|
+
if (agent.status === "working")
|
|
201
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: "sprout-agent-fanout-pulse" }), "Working"] }));
|
|
202
|
+
return _jsx(_Fragment, { children: statusText[agent.status] });
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* A parent agent that hands one task to several subagents and merges what
|
|
206
|
+
* they find. Each status change sends a pulse along the subagent's line:
|
|
207
|
+
* down when it starts, back up when it finishes.
|
|
208
|
+
*/
|
|
209
|
+
export function AgentFanout({ task, agents, state = "running", elapsed, label, defaultOpen = false, onStop, onRestart, className, }) {
|
|
210
|
+
const [graphRef, width] = useWidth();
|
|
211
|
+
// Only changes after the first render animate, so a finished fanout
|
|
212
|
+
// renders still instead of replaying every pulse.
|
|
213
|
+
const [initial] = useState(() => ({
|
|
214
|
+
state,
|
|
215
|
+
statuses: new Map(flatten(agents, new Map()).map(({ agent }) => [agent.id, agent.status])),
|
|
216
|
+
}));
|
|
217
|
+
const phase = phaseText(agents, state, elapsed);
|
|
218
|
+
const lines = flatten(agents, initial.statuses);
|
|
219
|
+
const graph = layout(lines, width, state, initial.state !== state || undefined);
|
|
220
|
+
const finished = lines.filter(({ agent }) => agent.status === "done" || agent.status === "failed").length;
|
|
221
|
+
return (_jsxs("section", { "data-slot": "agent-fanout", "data-state": state, "aria-label": "Subagents", className: cn("sprout-agent-fanout", className), children: [_jsxs("div", { className: "sprout-agent-fanout-panel", children: [_jsx("div", { className: "sprout-agent-fanout-task", children: _jsx("span", { className: "sprout-agent-fanout-task-text", children: task }) }), _jsx("div", { ref: graphRef, "aria-hidden": "true", "data-slot": "agent-fanout-graph", className: "sprout-agent-fanout-graph", style: {
|
|
222
|
+
height: graph.height,
|
|
223
|
+
"--sprout-agent-fanout-dot": `${graph.dot}px`,
|
|
224
|
+
}, children: width > 0 && (_jsx(ReactFlow, { className: "sprout-agent-fanout-flow", nodes: graph.nodes, edges: graph.edges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, defaultViewport: viewport, minZoom: 1, maxZoom: 1, panOnDrag: false, panOnScroll: false, zoomOnScroll: false, zoomOnPinch: false, zoomOnDoubleClick: false, preventScrolling: false, nodesDraggable: false, nodesConnectable: false, nodesFocusable: false, edgesFocusable: false, elementsSelectable: false, proOptions: { hideAttribution: true } })) }), _jsxs(Collapsible, { defaultOpen: defaultOpen, className: "sprout-agent-fanout-details", children: [_jsxs(CollapsibleTrigger, { className: "sprout-agent-fanout-trigger", children: [_jsx("span", { className: "sprout-agent-fanout-trigger-label", children: "Details" }), _jsx("span", { className: "sprout-agent-fanout-trigger-count", children: `${finished} of ${lines.length} finished` }), _jsx(ChevronDown, { size: 14, "aria-hidden": "true", "data-slot": "collapsible-indicator" })] }), _jsx(CollapsibleContent, { keepMounted: true, children: _jsx("ul", { "aria-label": "Subagent status", className: "sprout-agent-fanout-rows", children: lines.map(({ agent, changed, style }) => {
|
|
225
|
+
const settled = agent.status === "done" || agent.status === "failed";
|
|
226
|
+
return (_jsxs("li", { "data-slot": "agent-fanout-row", "data-status": agent.status, className: "sprout-agent-fanout-row", style: style, children: [_jsx("span", { className: "sprout-agent-fanout-row-name", children: agent.name }), _jsx("span", { "data-changed": changed, className: "sprout-agent-fanout-row-detail", children: settled && agent.result ? agent.result : agent.task }, settled && agent.result ? "result" : "task"), _jsx("span", { "data-changed": changed, className: "sprout-agent-fanout-row-status", children: _jsx(StatusMark, { agent: agent }) }, agent.status)] }, agent.id));
|
|
227
|
+
}) }) })] })] }), _jsxs("div", { className: "sprout-agent-fanout-footer", children: [_jsxs("p", { className: "sprout-agent-fanout-phase", children: [_jsx("span", { role: "status", children: label ?? phase.text }), !label && phase.time && (_jsx("span", { className: "sprout-agent-fanout-time", children: phase.time }))] }), state === "running" && onStop && (_jsxs(Button, { variant: "outline", size: "sm", onClick: onStop, className: "sprout-agent-fanout-action", children: [_jsx(MinusCircle, { size: 14, "aria-hidden": "true" }), "Stop"] })), state !== "running" && onRestart && (_jsxs(Button, { variant: "outline", size: "sm", onClick: onRestart, className: "sprout-agent-fanout-action", children: [_jsx(RotateLeft, { size: 14, "aria-hidden": "true" }), "Run again"] }))] })] }));
|
|
228
|
+
}
|
|
@@ -53,9 +53,18 @@ export const citationStateKindNames = [
|
|
|
53
53
|
*/
|
|
54
54
|
export function CheckedCitation({ label, state, onOpen, onRetry, }) {
|
|
55
55
|
const popup = useRef(null);
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const openReason = useRef(undefined);
|
|
57
|
+
return (_jsxs(Popover, { onOpenChange: (open, details) => {
|
|
58
|
+
if (open) {
|
|
59
|
+
openReason.current = details.reason;
|
|
58
60
|
onOpen();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Base UI keeps a hover-close timer from before a reopen; a passage
|
|
64
|
+
// opened by click or keyboard closes only by click, Escape, or outside press.
|
|
65
|
+
if (details.reason === "trigger-hover" &&
|
|
66
|
+
openReason.current === "trigger-press")
|
|
67
|
+
details.cancel();
|
|
59
68
|
}, children: [_jsx(PopoverTrigger, { openOnHover: true, delay: hoverDelay, closeDelay: hoverCloseDelay, "data-slot": "checked-citation", className: "sprout-chat-sources-checked", render: _jsx(Button, { variant: "link", size: "task-link", "aria-label": `Source: ${label}` }), children: label }), _jsx(PopoverContent, { ref: popup, "data-citation-state": state.kind, className: "sprout-chat-sources-preview", children: state.kind === "idle" || state.kind === "loading" ? (_jsxs("p", { role: "status", className: "sprout-chat-sources-checking", children: [_jsx(Spinner, { "aria-hidden": "true" }), "Checking access to this source\u2026"] })) : state.kind === "ready" ? (_jsxs("div", { "data-slot": "chat-source", className: "sprout-chat-sources-source", children: [_jsx("p", { className: "sprout-chat-sources-title", children: state.title }), _jsx("p", { className: "sprout-chat-sources-location", children: state.location }), _jsx("p", { className: "sprout-chat-sources-excerpt", "data-preserve-lines": "", children: state.excerpt })] })) : (_jsxs("div", { className: "sprout-chat-sources-source", children: [_jsx("p", { className: "sprout-chat-sources-title", children: state.kind === "unavailable"
|
|
60
69
|
? "Source unavailable"
|
|
61
70
|
: "Couldn’t load this source" }), _jsx("p", { role: "alert", className: "sprout-chat-sources-excerpt", children: state.message }), state.kind === "error" && (_jsx(Button, { variant: "outline", size: "sm", className: "sprout-chat-sources-retry", onClick: () => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
export interface PresenceCursorProps extends Omit<ComponentProps<"div">, "children" | "color"> {
|
|
3
|
+
/** The person's name or email. Shown in the tag, or read out with an avatar. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Left offset of the arrow tip in px, from the positioned parent. */
|
|
6
|
+
x: number;
|
|
7
|
+
/** Top offset of the arrow tip in px, from the positioned parent. */
|
|
8
|
+
y: number;
|
|
9
|
+
/** Any CSS color. White text sits on it, so keep 4.5:1 contrast. */
|
|
10
|
+
color: string;
|
|
11
|
+
/** Shows this, such as an `Avatar`, next to the arrow instead of the name tag. */
|
|
12
|
+
avatar?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Another person's pointer on a shared surface, with a name tag or an avatar.
|
|
16
|
+
* Put it in a parent with `position: relative`. It ignores pointer events and
|
|
17
|
+
* glides between positions unless reduced motion is requested.
|
|
18
|
+
*/
|
|
19
|
+
export declare function PresenceCursor({ name, x, y, color, avatar, className, style, ...props }: PresenceCursorProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/classes.js";
|
|
3
|
+
/**
|
|
4
|
+
* Another person's pointer on a shared surface, with a name tag or an avatar.
|
|
5
|
+
* Put it in a parent with `position: relative`. It ignores pointer events and
|
|
6
|
+
* glides between positions unless reduced motion is requested.
|
|
7
|
+
*/
|
|
8
|
+
export function PresenceCursor({ name, x, y, color, avatar, className, style, ...props }) {
|
|
9
|
+
return (_jsxs("div", { "data-slot": "presence-cursor", role: "img", "aria-label": name, className: cn("sprout-presence-cursor", className), style: {
|
|
10
|
+
"--sprout-presence-color": color,
|
|
11
|
+
transform: `translate(${x}px, ${y}px)`,
|
|
12
|
+
...style,
|
|
13
|
+
}, ...props, children: [_jsx("svg", { className: "sprout-presence-cursor-arrow", viewBox: "0 0 16 16", "aria-hidden": "true", children: _jsx("path", { d: "M1.5 1.5 14.5 6.6 8.4 8.4 6.6 14.5Z" }) }), avatar != null ? (_jsx("span", { "data-slot": "presence-cursor-avatar", className: "sprout-presence-cursor-avatar", "aria-hidden": "true", children: avatar })) : (_jsx("span", { "data-slot": "presence-cursor-label", className: "sprout-presence-cursor-label", "aria-hidden": "true", children: name }))] }));
|
|
14
|
+
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { Background, BaseEdge, EdgeLabelRenderer, getSmoothStepPath, Handle,
|
|
4
|
-
import {
|
|
5
|
-
import { Add } from "reicon-react/icons/Add";
|
|
3
|
+
import { Background, BaseEdge, EdgeLabelRenderer, getSmoothStepPath, Handle, Position, ReactFlow, ReactFlowProvider, } from "@xyflow/react";
|
|
4
|
+
import { useId, useMemo } from "react";
|
|
6
5
|
import { Key } from "reicon-react/icons/Key";
|
|
7
|
-
import { Minus } from "reicon-react/icons/Minus";
|
|
8
|
-
import { Scan } from "reicon-react/icons/Scan";
|
|
9
6
|
import { cn } from "../internal/classes.js";
|
|
7
|
+
import { FitOnResize, flowMaxZoom as maxZoom, flowMinZoom as minZoom, useKeyboardView, useViewportMobile, ZoomControls, } from "../internal/flow.js";
|
|
10
8
|
import { Badge } from "./badge.js";
|
|
11
|
-
import { Button } from "./button.js";
|
|
12
9
|
export const schemaTableStateNames = [
|
|
13
10
|
"new",
|
|
14
11
|
"changed",
|
|
@@ -24,10 +21,7 @@ export const schemaDiagramLayoutNames = [
|
|
|
24
21
|
const tableWidth = 264;
|
|
25
22
|
const columnGap = 80;
|
|
26
23
|
const rowGap = 48;
|
|
27
|
-
const minZoom = 0.2;
|
|
28
|
-
const maxZoom = 1.5;
|
|
29
24
|
const fitOptions = { padding: 0.15, maxZoom: 1 };
|
|
30
|
-
const panStep = 48;
|
|
31
25
|
const tableHeight = (table) => 44 +
|
|
32
26
|
(table.fields.length === 0
|
|
33
27
|
? 32
|
|
@@ -117,80 +111,13 @@ function LinkLine({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targ
|
|
|
117
111
|
}
|
|
118
112
|
const nodeTypes = { table: TableCard };
|
|
119
113
|
const edgeTypes = { link: LinkLine };
|
|
120
|
-
const mobileQuery = "(max-width: 767px)";
|
|
121
|
-
const motionQuery = "(prefers-reduced-motion: reduce)";
|
|
122
|
-
function subscribeMobile(callback) {
|
|
123
|
-
const media = window.matchMedia(mobileQuery);
|
|
124
|
-
media.addEventListener("change", callback);
|
|
125
|
-
return () => media.removeEventListener("change", callback);
|
|
126
|
-
}
|
|
127
|
-
function useViewportMobile() {
|
|
128
|
-
return useSyncExternalStore(subscribeMobile, () => window.matchMedia(mobileQuery).matches, () => false);
|
|
129
|
-
}
|
|
130
|
-
const motion = (ms) => typeof window !== "undefined" && window.matchMedia(motionQuery).matches
|
|
131
|
-
? 0
|
|
132
|
-
: ms;
|
|
133
|
-
// The canvas panel animates open, so the first fit sees a narrower pane than the final one.
|
|
134
|
-
function FitOnResize({ nodes }) {
|
|
135
|
-
const { fitView } = useReactFlow();
|
|
136
|
-
const width = useStore((state) => state.width);
|
|
137
|
-
const height = useStore((state) => state.height);
|
|
138
|
-
const measured = useNodesInitialized();
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
if (!measured)
|
|
141
|
-
return;
|
|
142
|
-
const frame = requestAnimationFrame(() => void fitView(fitOptions));
|
|
143
|
-
return () => cancelAnimationFrame(frame);
|
|
144
|
-
}, [fitView, width, height, measured, nodes]);
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
function ZoomControls({ position, }) {
|
|
148
|
-
const { zoomIn, zoomOut, fitView } = useReactFlow();
|
|
149
|
-
const zoom = useStore((state) => state.transform[2]);
|
|
150
|
-
return (_jsxs(Panel, { position: position, className: "sprout-schema-controls", "data-slot": "schema-diagram-controls", role: "toolbar", "aria-label": "Diagram view", "aria-orientation": "vertical", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Zoom in", title: "Zoom in", disabled: zoom >= maxZoom, onClick: () => void zoomIn({ duration: motion(160) }), children: _jsx(Add, { size: 16, "aria-hidden": "true" }) }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Zoom out", title: "Zoom out", disabled: zoom <= minZoom, onClick: () => void zoomOut({ duration: motion(160) }), children: _jsx(Minus, { size: 16, "aria-hidden": "true" }) }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Fit to view", title: "Fit to view", onClick: () => void fitView({ ...fitOptions, duration: motion(180) }), children: _jsx(Scan, { size: 16, "aria-hidden": "true" }) })] }));
|
|
151
|
-
}
|
|
152
|
-
function useKeyboardView() {
|
|
153
|
-
const { getViewport, setViewport, zoomIn, zoomOut, fitView } = useReactFlow();
|
|
154
|
-
return (event) => {
|
|
155
|
-
const target = event.target;
|
|
156
|
-
if (event.altKey || event.ctrlKey || event.metaKey)
|
|
157
|
-
return;
|
|
158
|
-
if (target !== event.currentTarget &&
|
|
159
|
-
!target.classList.contains("react-flow__node"))
|
|
160
|
-
return;
|
|
161
|
-
const step = panStep * (event.shiftKey ? 4 : 1);
|
|
162
|
-
const pan = {
|
|
163
|
-
ArrowLeft: [step, 0],
|
|
164
|
-
ArrowRight: [-step, 0],
|
|
165
|
-
ArrowUp: [0, step],
|
|
166
|
-
ArrowDown: [0, -step],
|
|
167
|
-
};
|
|
168
|
-
const move = pan[event.key];
|
|
169
|
-
if (move) {
|
|
170
|
-
const { x, y, zoom } = getViewport();
|
|
171
|
-
void setViewport({ x: x + move[0], y: y + move[1], zoom }, { duration: motion(120) });
|
|
172
|
-
}
|
|
173
|
-
else if (event.key === "+" || event.key === "=") {
|
|
174
|
-
void zoomIn({ duration: motion(160) });
|
|
175
|
-
}
|
|
176
|
-
else if (event.key === "-" || event.key === "_") {
|
|
177
|
-
void zoomOut({ duration: motion(160) });
|
|
178
|
-
}
|
|
179
|
-
else if (event.key === "0") {
|
|
180
|
-
void fitView({ ...fitOptions, duration: motion(180) });
|
|
181
|
-
}
|
|
182
|
-
else
|
|
183
|
-
return;
|
|
184
|
-
event.preventDefault();
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
114
|
const keyboardHint = "Arrow keys pan, plus and minus zoom, and 0 fits the diagram. Tab moves between tables.";
|
|
188
115
|
function DiagramFlow({ nodes, edges, direction, label, hintId, }) {
|
|
189
|
-
const onKeyDown = useKeyboardView();
|
|
116
|
+
const onKeyDown = useKeyboardView(fitOptions);
|
|
190
117
|
return (_jsxs(ReactFlow, { className: "sprout-schema-flow", "data-slot": "schema-diagram-viewport", "aria-label": label, "aria-describedby": hintId, tabIndex: 0, onKeyDown: onKeyDown, nodes: nodes, edges: edges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, fitView: true, fitViewOptions: fitOptions, minZoom: minZoom, maxZoom: maxZoom, nodesConnectable: false, elementsSelectable: false, edgesFocusable: false, ariaLabelConfig: {
|
|
191
118
|
"node.a11yDescription.default": keyboardHint,
|
|
192
119
|
"node.a11yDescription.keyboardDisabled": keyboardHint,
|
|
193
|
-
}, proOptions: { hideAttribution: true }, children: [_jsx(Background, { gap: 16, size: 1 }), _jsx(FitOnResize, { nodes: nodes }), _jsx(ZoomControls, { position: direction === "vertical" ? "top-right" : "bottom-right" })] }));
|
|
120
|
+
}, proOptions: { hideAttribution: true }, children: [_jsx(Background, { gap: 16, size: 1 }), _jsx(FitOnResize, { nodes: nodes, fit: fitOptions }), _jsx(ZoomControls, { position: direction === "vertical" ? "top-right" : "bottom-right", fit: fitOptions, className: "sprout-schema-controls", slot: "schema-diagram-controls" })] }));
|
|
194
121
|
}
|
|
195
122
|
/**
|
|
196
123
|
* A pan-and-zoom diagram of tables, their fields, and the links between them,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type HTMLAttributes } from "react";
|
|
2
|
+
export declare const sqlViewerLayoutNames: readonly ["auto", "horizontal", "vertical"];
|
|
3
|
+
export type SqlViewerLayout = (typeof sqlViewerLayoutNames)[number];
|
|
4
|
+
export type SqlViewerCardinality = "one" | "many";
|
|
5
|
+
export interface SqlViewerOption {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
retired: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** One field of a TulipFarm table definition (format 2) or table document (format 3). */
|
|
11
|
+
export type SqlViewerField = {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
required: boolean;
|
|
15
|
+
} & ({
|
|
16
|
+
kind: "text";
|
|
17
|
+
maxLength: number;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "numeric";
|
|
20
|
+
precision: number;
|
|
21
|
+
scale: number;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "boolean";
|
|
24
|
+
} | {
|
|
25
|
+
kind: "date";
|
|
26
|
+
} | {
|
|
27
|
+
kind: "timestamp";
|
|
28
|
+
timezone?: "UTC";
|
|
29
|
+
} | {
|
|
30
|
+
kind: "single-select" | "multi-select";
|
|
31
|
+
options: readonly SqlViewerOption[];
|
|
32
|
+
} | {
|
|
33
|
+
kind: "relationship";
|
|
34
|
+
/** A table name (format 3) or table UUID (format 2), matching a table's `id`. */
|
|
35
|
+
targetTableId: string;
|
|
36
|
+
cardinality: SqlViewerCardinality;
|
|
37
|
+
inverseCardinality: SqlViewerCardinality;
|
|
38
|
+
reverse?: {
|
|
39
|
+
fieldId: string;
|
|
40
|
+
label: string;
|
|
41
|
+
} | null;
|
|
42
|
+
onDelete?: "restrict";
|
|
43
|
+
} | {
|
|
44
|
+
kind: "file";
|
|
45
|
+
cardinality: SqlViewerCardinality;
|
|
46
|
+
access?: "independent";
|
|
47
|
+
});
|
|
48
|
+
export interface SqlViewerUniqueKey {
|
|
49
|
+
id: string;
|
|
50
|
+
fieldIds: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
/** A TulipFarm table definition or table document. Extra keys are ignored. */
|
|
53
|
+
export interface SqlViewerTable {
|
|
54
|
+
id: string;
|
|
55
|
+
label: string;
|
|
56
|
+
owningTeamId?: string;
|
|
57
|
+
fields: readonly SqlViewerField[];
|
|
58
|
+
uniqueKeys?: readonly SqlViewerUniqueKey[];
|
|
59
|
+
}
|
|
60
|
+
export interface SqlViewerProps extends Omit<HTMLAttributes<HTMLDivElement>, "role" | "children"> {
|
|
61
|
+
/** TulipFarm table definitions or table documents, in display order. */
|
|
62
|
+
tables: readonly SqlViewerTable[];
|
|
63
|
+
/** Names the diagram for assistive tech, such as “Tables in Sales”. */
|
|
64
|
+
label: string;
|
|
65
|
+
/** Team names by `owningTeamId`. Tables of a named team sit in one tinted group. */
|
|
66
|
+
groups?: Readonly<Record<string, string>> | undefined;
|
|
67
|
+
/** Tables shown before the rest collapse into one “+N tables” card. Default 4. */
|
|
68
|
+
maxVisible?: number | undefined;
|
|
69
|
+
/** `auto` stacks tables under 768px and uses columns otherwise. */
|
|
70
|
+
layout?: SqlViewerLayout | undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A pan-and-zoom map of SQL tables, their columns and keys, and the joins
|
|
74
|
+
* between them. Hover or pin a table or a foreign key to trace its joins.
|
|
75
|
+
*/
|
|
76
|
+
export declare function SqlViewer({ tables, label, groups, maxVisible, layout, className, ...props }: SqlViewerProps): import("react").JSX.Element;
|