@calliopelabs/ui 0.14.0 → 0.16.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 +142 -1
- package/dist/agent/AgentChat.d.ts +8 -1
- package/dist/agent/AgentChat.js +43 -2
- package/dist/agent/WorkflowRenderers.d.ts +61 -0
- package/dist/agent/WorkflowRenderers.js +102 -0
- package/dist/agent/controller.d.ts +19 -0
- package/dist/agent/controller.js +33 -0
- package/dist/agent/copilotkit/CopilotKitAgentProvider.js +2 -2
- package/dist/agent/index.d.ts +4 -2
- package/dist/agent/index.js +3 -1
- package/dist/agent/mock.d.ts +26 -4
- package/dist/agent/mock.js +68 -37
- package/dist/agent/renderers.d.ts +49 -0
- package/dist/agent/renderers.js +27 -0
- package/dist/appearance/brands.d.ts +17 -0
- package/dist/appearance/brands.js +14 -0
- package/dist/chat/MessageBubble.js +1 -1
- package/dist/chat/MessageList.d.ts +9 -2
- package/dist/chat/MessageList.js +11 -8
- package/dist/css/appearance.js +1 -1
- package/dist/css/calliope.css +5 -0
- package/dist/css/tokens.css +5 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/primitives/Badge.d.ts +14 -0
- package/dist/primitives/Badge.js +25 -0
- package/dist/primitives/ContextMenu.d.ts +18 -0
- package/dist/primitives/ContextMenu.js +34 -0
- package/dist/primitives/ScrollArea.d.ts +16 -0
- package/dist/primitives/ScrollArea.js +11 -0
- package/dist/primitives/Toast.d.ts +37 -0
- package/dist/primitives/Toast.js +43 -0
- package/dist/primitives/Tooltip.d.ts +16 -0
- package/dist/primitives/Tooltip.js +26 -0
- package/dist/provider/messages.d.ts +69 -0
- package/dist/provider/messages.js +60 -0
- package/dist/theme/css-layer.js +2 -1
- package/dist/theme/tokens.generated.d.ts +3 -0
- package/dist/theme/tokens.generated.js +3 -0
- package/dist/workbench/FileBrowser.d.ts +62 -0
- package/dist/workbench/FileBrowser.js +117 -0
- package/dist/workbench/FileGrid.d.ts +13 -0
- package/dist/workbench/FileGrid.js +114 -0
- package/dist/workbench/FileIcon.d.ts +10 -0
- package/dist/workbench/FileIcon.js +24 -0
- package/dist/workbench/FileList.d.ts +35 -0
- package/dist/workbench/FileList.js +160 -0
- package/dist/workbench/FilePreview.d.ts +66 -0
- package/dist/workbench/FilePreview.js +108 -0
- package/dist/workbench/FileTree.d.ts +37 -0
- package/dist/workbench/FileTree.js +218 -0
- package/dist/workbench/UploadDropzone.d.ts +52 -0
- package/dist/workbench/UploadDropzone.js +115 -0
- package/dist/workbench/dnd.d.ts +17 -0
- package/dist/workbench/dnd.js +77 -0
- package/dist/workbench/index.d.ts +16 -0
- package/dist/workbench/index.js +16 -0
- package/dist/workbench/model.d.ts +72 -0
- package/dist/workbench/model.js +214 -0
- package/dist/workbench/selection.d.ts +16 -0
- package/dist/workbench/selection.js +46 -0
- package/dist/workbench/typeahead.d.ts +15 -0
- package/dist/workbench/typeahead.js +22 -0
- package/package.json +5 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file browser's data: plain values the host maps its own listing onto.
|
|
3
|
+
* Pure (no "use client"), so a server can sort or build fixtures with it.
|
|
4
|
+
*/
|
|
5
|
+
/** The parent directory of a path; the root is "". */
|
|
6
|
+
export function parentPath(path) {
|
|
7
|
+
const i = path.lastIndexOf("/");
|
|
8
|
+
return i < 0 ? "" : path.slice(0, i);
|
|
9
|
+
}
|
|
10
|
+
/** A path under a directory; the root is "". */
|
|
11
|
+
export function joinPath(dir, name) {
|
|
12
|
+
return dir ? `${dir}/${name}` : name;
|
|
13
|
+
}
|
|
14
|
+
/** True when `path` is `ancestor` or lies under it. */
|
|
15
|
+
export function isWithin(path, ancestor) {
|
|
16
|
+
return ancestor === "" || path === ancestor || path.startsWith(`${ancestor}/`);
|
|
17
|
+
}
|
|
18
|
+
/** The lower-case extension without the dot, or "" (".bashrc" has none). */
|
|
19
|
+
export function fileExtension(name) {
|
|
20
|
+
const i = name.lastIndexOf(".");
|
|
21
|
+
return i > 0 ? name.slice(i + 1).toLowerCase() : "";
|
|
22
|
+
}
|
|
23
|
+
/** The breadcrumb trail of a path: the root, then each directory down to it. */
|
|
24
|
+
export function pathSegments(path) {
|
|
25
|
+
const parts = path.split("/").filter(Boolean);
|
|
26
|
+
return parts.map((name, i) => ({ name, path: parts.slice(0, i + 1).join("/") }));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether moving `sources` into `target` does anything sensible: never into
|
|
30
|
+
* itself or its own subtree, and a move into the folder it is already in is
|
|
31
|
+
* a no-op. Copies (`copy`) may land in the same folder.
|
|
32
|
+
*/
|
|
33
|
+
export function canDrop(sources, target, copy = false) {
|
|
34
|
+
return (sources.length > 0 &&
|
|
35
|
+
sources.every((src) => !(target === src || target.startsWith(`${src}/`)) && (copy || parentPath(src) !== target)));
|
|
36
|
+
}
|
|
37
|
+
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
|
|
38
|
+
function time(value) {
|
|
39
|
+
if (value == null)
|
|
40
|
+
return null;
|
|
41
|
+
const t = value instanceof Date ? value.getTime() : Date.parse(value);
|
|
42
|
+
return Number.isNaN(t) ? null : t;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A listing in display order: directories first, then by the key, ties and
|
|
46
|
+
* missing values by name. Natural order, so "file10" follows "file9".
|
|
47
|
+
*/
|
|
48
|
+
export function sortFiles(items, sort) {
|
|
49
|
+
const sign = sort.dir === "asc" ? 1 : -1;
|
|
50
|
+
const byName = (a, b) => collator.compare(a.name, b.name);
|
|
51
|
+
const compare = (a, b) => {
|
|
52
|
+
switch (sort.key) {
|
|
53
|
+
case "size": {
|
|
54
|
+
const x = a.size ?? null;
|
|
55
|
+
const y = b.size ?? null;
|
|
56
|
+
if (x === y)
|
|
57
|
+
return 0;
|
|
58
|
+
if (x === null)
|
|
59
|
+
return 1;
|
|
60
|
+
if (y === null)
|
|
61
|
+
return -1;
|
|
62
|
+
return (x - y) * sign;
|
|
63
|
+
}
|
|
64
|
+
case "modified": {
|
|
65
|
+
const x = time(a.modified);
|
|
66
|
+
const y = time(b.modified);
|
|
67
|
+
if (x === y)
|
|
68
|
+
return 0;
|
|
69
|
+
if (x === null)
|
|
70
|
+
return 1;
|
|
71
|
+
if (y === null)
|
|
72
|
+
return -1;
|
|
73
|
+
return (x - y) * sign;
|
|
74
|
+
}
|
|
75
|
+
case "kind":
|
|
76
|
+
return collator.compare(fileExtension(a.name), fileExtension(b.name)) * sign;
|
|
77
|
+
case "name":
|
|
78
|
+
return byName(a, b) * sign;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return [...items].sort((a, b) => {
|
|
82
|
+
if (a.kind !== b.kind)
|
|
83
|
+
return a.kind === "directory" ? -1 : 1;
|
|
84
|
+
return compare(a, b) || byName(a, b);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** Bytes for people: 0 B, 912 B, 1.5 KB, 23 MB (1024-based, as file managers show). */
|
|
88
|
+
export function formatBytes(bytes, locale) {
|
|
89
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
90
|
+
return "";
|
|
91
|
+
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
92
|
+
let value = bytes;
|
|
93
|
+
let unit = 0;
|
|
94
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
95
|
+
value /= 1024;
|
|
96
|
+
unit++;
|
|
97
|
+
}
|
|
98
|
+
const digits = unit === 0 || value >= 10 ? 0 : 1;
|
|
99
|
+
return `${value.toLocaleString(locale, { maximumFractionDigits: digits })} ${units[unit]}`;
|
|
100
|
+
}
|
|
101
|
+
const TEXT_EXTENSIONS = new Set([
|
|
102
|
+
"txt",
|
|
103
|
+
"log",
|
|
104
|
+
"py",
|
|
105
|
+
"js",
|
|
106
|
+
"jsx",
|
|
107
|
+
"ts",
|
|
108
|
+
"tsx",
|
|
109
|
+
"mjs",
|
|
110
|
+
"cjs",
|
|
111
|
+
"rs",
|
|
112
|
+
"go",
|
|
113
|
+
"java",
|
|
114
|
+
"kt",
|
|
115
|
+
"rb",
|
|
116
|
+
"sh",
|
|
117
|
+
"bash",
|
|
118
|
+
"zsh",
|
|
119
|
+
"sql",
|
|
120
|
+
"html",
|
|
121
|
+
"css",
|
|
122
|
+
"scss",
|
|
123
|
+
"xml",
|
|
124
|
+
"yaml",
|
|
125
|
+
"yml",
|
|
126
|
+
"toml",
|
|
127
|
+
"ini",
|
|
128
|
+
"cfg",
|
|
129
|
+
"conf",
|
|
130
|
+
"env",
|
|
131
|
+
"r",
|
|
132
|
+
"c",
|
|
133
|
+
"h",
|
|
134
|
+
"cpp",
|
|
135
|
+
"hpp",
|
|
136
|
+
"cs",
|
|
137
|
+
"php",
|
|
138
|
+
"swift",
|
|
139
|
+
"lua",
|
|
140
|
+
"dockerfile",
|
|
141
|
+
"tf",
|
|
142
|
+
]);
|
|
143
|
+
const IMAGE_EXTENSIONS = new Set([
|
|
144
|
+
"png",
|
|
145
|
+
"jpg",
|
|
146
|
+
"jpeg",
|
|
147
|
+
"gif",
|
|
148
|
+
"svg",
|
|
149
|
+
"webp",
|
|
150
|
+
"avif",
|
|
151
|
+
"bmp",
|
|
152
|
+
"ico",
|
|
153
|
+
]);
|
|
154
|
+
export function previewKindFor(name, mimeType) {
|
|
155
|
+
const ext = fileExtension(name);
|
|
156
|
+
if (ext === "csv" || ext === "tsv" || mimeType === "text/csv")
|
|
157
|
+
return "csv";
|
|
158
|
+
if (ext === "json" || ext === "jsonl" || ext === "ipynb" || mimeType === "application/json")
|
|
159
|
+
return "json";
|
|
160
|
+
if (IMAGE_EXTENSIONS.has(ext) || mimeType?.startsWith("image/"))
|
|
161
|
+
return "image";
|
|
162
|
+
if (ext === "md" || ext === "markdown" || mimeType === "text/markdown")
|
|
163
|
+
return "markdown";
|
|
164
|
+
if (TEXT_EXTENSIONS.has(ext) || mimeType?.startsWith("text/"))
|
|
165
|
+
return "text";
|
|
166
|
+
return "binary";
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Parses CSV (RFC 4180: quoted fields, doubled quotes, newlines inside
|
|
170
|
+
* quotes) or, with `delimiter: "\t"`, TSV. Returns rows of strings; the
|
|
171
|
+
* caller decides whether the first row is a header.
|
|
172
|
+
*/
|
|
173
|
+
export function parseDelimited(text, delimiter = ",") {
|
|
174
|
+
const rows = [];
|
|
175
|
+
let row = [];
|
|
176
|
+
let field = "";
|
|
177
|
+
let quoted = false;
|
|
178
|
+
for (let i = 0; i < text.length; i++) {
|
|
179
|
+
const c = text[i];
|
|
180
|
+
if (quoted) {
|
|
181
|
+
if (c === '"') {
|
|
182
|
+
if (text[i + 1] === '"') {
|
|
183
|
+
field += '"';
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
else
|
|
187
|
+
quoted = false;
|
|
188
|
+
}
|
|
189
|
+
else
|
|
190
|
+
field += c;
|
|
191
|
+
}
|
|
192
|
+
else if (c === '"' && field === "")
|
|
193
|
+
quoted = true;
|
|
194
|
+
else if (c === delimiter) {
|
|
195
|
+
row.push(field);
|
|
196
|
+
field = "";
|
|
197
|
+
}
|
|
198
|
+
else if (c === "\n" || c === "\r") {
|
|
199
|
+
if (c === "\r" && text[i + 1] === "\n")
|
|
200
|
+
i++;
|
|
201
|
+
row.push(field);
|
|
202
|
+
rows.push(row);
|
|
203
|
+
row = [];
|
|
204
|
+
field = "";
|
|
205
|
+
}
|
|
206
|
+
else
|
|
207
|
+
field += c;
|
|
208
|
+
}
|
|
209
|
+
if (field !== "" || row.length > 0) {
|
|
210
|
+
row.push(field);
|
|
211
|
+
rows.push(row);
|
|
212
|
+
}
|
|
213
|
+
return rows;
|
|
214
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type SelectMode = "replace" | "toggle" | "range";
|
|
2
|
+
/**
|
|
3
|
+
* Finder-style selection over an ordered listing: a plain click or arrow
|
|
4
|
+
* selects one, Cmd or Ctrl toggles, Shift selects the range from the
|
|
5
|
+
* anchor (the last item chosen without Shift). The set is the host's.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useSelectionModel(order: readonly string[], selection: ReadonlySet<string>, onSelectionChange: (next: Set<string>) => void): {
|
|
8
|
+
select(path: string, mode: SelectMode): void;
|
|
9
|
+
selectAll(): void;
|
|
10
|
+
clear(): void;
|
|
11
|
+
};
|
|
12
|
+
export declare function modeOf(event: {
|
|
13
|
+
shiftKey: boolean;
|
|
14
|
+
metaKey: boolean;
|
|
15
|
+
ctrlKey: boolean;
|
|
16
|
+
}): SelectMode;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Finder-style selection over an ordered listing: a plain click or arrow
|
|
5
|
+
* selects one, Cmd or Ctrl toggles, Shift selects the range from the
|
|
6
|
+
* anchor (the last item chosen without Shift). The set is the host's.
|
|
7
|
+
*/
|
|
8
|
+
export function useSelectionModel(order, selection, onSelectionChange) {
|
|
9
|
+
const anchor = React.useRef(null);
|
|
10
|
+
return React.useMemo(() => ({
|
|
11
|
+
select(path, mode) {
|
|
12
|
+
if (mode === "range") {
|
|
13
|
+
const from = order.indexOf(anchor.current ?? path);
|
|
14
|
+
const to = order.indexOf(path);
|
|
15
|
+
const [a, b] = from < 0 ? [to, to] : from < to ? [from, to] : [to, from];
|
|
16
|
+
onSelectionChange(new Set(order.slice(a, b + 1)));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
anchor.current = path;
|
|
20
|
+
if (mode === "toggle") {
|
|
21
|
+
const next = new Set(selection);
|
|
22
|
+
if (next.has(path))
|
|
23
|
+
next.delete(path);
|
|
24
|
+
else
|
|
25
|
+
next.add(path);
|
|
26
|
+
onSelectionChange(next);
|
|
27
|
+
}
|
|
28
|
+
else
|
|
29
|
+
onSelectionChange(new Set([path]));
|
|
30
|
+
},
|
|
31
|
+
selectAll() {
|
|
32
|
+
onSelectionChange(new Set(order));
|
|
33
|
+
},
|
|
34
|
+
clear() {
|
|
35
|
+
anchor.current = null;
|
|
36
|
+
onSelectionChange(new Set());
|
|
37
|
+
},
|
|
38
|
+
}), [order, selection, onSelectionChange]);
|
|
39
|
+
}
|
|
40
|
+
export function modeOf(event) {
|
|
41
|
+
if (event.shiftKey)
|
|
42
|
+
return "range";
|
|
43
|
+
if (event.metaKey || event.ctrlKey)
|
|
44
|
+
return "toggle";
|
|
45
|
+
return "replace";
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-to-find for trees, lists and grids: printable keys typed within half
|
|
3
|
+
* a second build a prefix; the next name after the current one that starts
|
|
4
|
+
* with it (case-insensitive) is the answer. Returns that index, or null when
|
|
5
|
+
* the key is not a typeahead key or nothing matches.
|
|
6
|
+
*/
|
|
7
|
+
export declare function typeahead(state: {
|
|
8
|
+
text: string;
|
|
9
|
+
at: number;
|
|
10
|
+
}, event: {
|
|
11
|
+
key: string;
|
|
12
|
+
ctrlKey: boolean;
|
|
13
|
+
metaKey: boolean;
|
|
14
|
+
altKey: boolean;
|
|
15
|
+
}, names: readonly string[], current: number): number | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-to-find for trees, lists and grids: printable keys typed within half
|
|
3
|
+
* a second build a prefix; the next name after the current one that starts
|
|
4
|
+
* with it (case-insensitive) is the answer. Returns that index, or null when
|
|
5
|
+
* the key is not a typeahead key or nothing matches.
|
|
6
|
+
*/
|
|
7
|
+
export function typeahead(state, event, names, current) {
|
|
8
|
+
if (event.key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey || event.key === " ")
|
|
9
|
+
return null;
|
|
10
|
+
const now = Date.now();
|
|
11
|
+
state.text = now - state.at > 500 ? event.key : state.text + event.key;
|
|
12
|
+
state.at = now;
|
|
13
|
+
const prefix = state.text.toLowerCase();
|
|
14
|
+
// A repeated single letter cycles; a longer prefix may stay on the current name.
|
|
15
|
+
const start = state.text.length === 1 ? current + 1 : current;
|
|
16
|
+
for (let i = 0; i < names.length; i++) {
|
|
17
|
+
const index = (start + i) % names.length;
|
|
18
|
+
if (names[index]?.toLowerCase().startsWith(prefix))
|
|
19
|
+
return index;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calliopelabs/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Calliope AI shared operator UI: app shell, navigation, page frame, data table and list patterns, status colours, design tokens and appearance for Astrolift AI and Zentinelle AI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"calliope-ai",
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
"types": "./dist/agent/copilotkit/index.d.ts",
|
|
39
39
|
"default": "./dist/agent/copilotkit/index.js"
|
|
40
40
|
},
|
|
41
|
+
"./workbench": {
|
|
42
|
+
"types": "./dist/workbench/index.d.ts",
|
|
43
|
+
"default": "./dist/workbench/index.js"
|
|
44
|
+
},
|
|
41
45
|
"./theme.css": "./dist/styles/theme.css",
|
|
42
46
|
"./preset.css": "./dist/styles/preset.css",
|
|
43
47
|
"./css/tokens.css": "./dist/css/tokens.css",
|