@anchrd/intel-ui 0.17.0 → 0.19.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/package.json +2 -9
- package/src/app/app-tree/app-tree.tsx +59 -147
- package/src/app/settings-dialog/settings-dialog.tsx +10 -32
- package/src/data/intel-data-provider/intel-data-provider.ts +0 -163
- package/src/data/intel-data-provider/intel-data-provider.types.ts +0 -100
- package/src/entry-picker/entry-picker.tsx +6 -6
- package/src/i18n/de.json +6 -213
- package/src/i18n/en.json +6 -213
- package/src/i18n/es.json +6 -213
- package/src/import-bundle/import-bundle.tsx +152 -0
- package/src/kind-icon.ts +1 -15
- package/src/main.tsx +7 -10
- package/src/node-table/node-table.tsx +5 -4
- package/src/nodes/nodes.tsx +0 -34
- package/src/resource-menu/resource-menu.tsx +68 -20
- package/src/section-hint/section-hint.tsx +2 -2
- package/src/title-row/title-row.tsx +5 -22
- package/src/tools/tools.tsx +0 -2
- package/vite.config.ts +0 -4
- package/src/agent/agent-avatar/agent-avatar.tsx +0 -34
- package/src/agent/agent-calendar/agent-calendar.tsx +0 -135
- package/src/agent/agent-chat/agent-chat.tsx +0 -118
- package/src/agent/agent-costs/agent-costs.ts +0 -95
- package/src/agent/agent-cron/agent-cron.ts +0 -68
- package/src/agent/agent-definition/agent-definition.ts +0 -96
- package/src/agent/agent-delegation-notice/agent-delegation-notice.ts +0 -48
- package/src/agent/agent-entry-title/agent-entry-title.ts +0 -72
- package/src/agent/agent-log/agent-log.tsx +0 -308
- package/src/agent/agent-models/agent-models.ts +0 -166
- package/src/agent/agent-models/agent-models.types.ts +0 -24
- package/src/agent/agent-profile/agent-profile.tsx +0 -988
- package/src/agent/agent-state/agent-state.ts +0 -85
- package/src/agent/agent-status-dot/agent-status-dot.ts +0 -73
- package/src/agent/agent.tsx +0 -482
- package/src/board/board-calendar/board-calendar.tsx +0 -89
- package/src/board/board-card/board-card.tsx +0 -106
- package/src/board/board-data/board-data.ts +0 -241
- package/src/board/board-data/board-data.types.ts +0 -63
- package/src/board/board-detail/board-detail.tsx +0 -629
- package/src/board/board-gantt/board-gantt.ts +0 -545
- package/src/board/board-gantt/board-gantt.tsx +0 -286
- package/src/board/board-graph/board-graph.ts +0 -174
- package/src/board/board-graph/board-graph.tsx +0 -168
- package/src/board/board-items/board-items.ts +0 -183
- package/src/board/board-kanban/board-kanban.ts +0 -97
- package/src/board/board-kanban/board-kanban.tsx +0 -211
- package/src/board/board-status/board-status.ts +0 -59
- package/src/board/board-statuses/board-statuses.ts +0 -63
- package/src/board/board-statuses/board-statuses.tsx +0 -228
- package/src/board/board-table/board-table.ts +0 -33
- package/src/board/board-table/board-table.tsx +0 -413
- package/src/board/board-views/board-views.tsx +0 -68
- package/src/board/board-views/board-views.types.ts +0 -29
- package/src/board/board.tsx +0 -251
- package/src/components/ui/item-calendar.tsx +0 -181
- package/src/components/ui/item-gantt.tsx +0 -463
- package/src/components/ui/kanban.tsx +0 -245
- package/src/data/agent-runtime/agent-runtime.ts +0 -131
- package/src/hooks/use-capabilities.ts +0 -22
- package/src/hooks/use-model-catalog.ts +0 -26
- package/src/timezone/timezone-combobox/timezone-combobox.tsx +0 -149
- package/src/timezone/timezone-context.tsx +0 -65
- package/src/timezone/timezone.ts +0 -174
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anchrd/intel-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -33,27 +33,20 @@
|
|
|
33
33
|
"typecheck": "tsc --noEmit"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@anchrd/intel-contract": "^0.
|
|
37
|
-
"@assistant-ui/react": "^0.15.4",
|
|
38
|
-
"@assistant-ui/react-ai-sdk": "^1.4.4",
|
|
36
|
+
"@anchrd/intel-contract": "^0.13.0",
|
|
39
37
|
"@blocknote/core": "^0.52.1",
|
|
40
38
|
"@blocknote/react": "^0.52.1",
|
|
41
39
|
"@blocknote/shadcn": "^0.52.1",
|
|
42
|
-
"@dnd-kit/core": "^6.3.1",
|
|
43
|
-
"@dnd-kit/sortable": "^10.0.0",
|
|
44
|
-
"@dnd-kit/utilities": "^3.2.2",
|
|
45
40
|
"@react-sigma/core": "^5.0.6",
|
|
46
41
|
"@tailwindcss/vite": "^4.3.3",
|
|
47
42
|
"@tanstack/react-query": "^5.101.4",
|
|
48
43
|
"@tanstack/react-router": "^1.170.18",
|
|
49
|
-
"@tanstack/react-table": "^9.1.0",
|
|
50
44
|
"@vitejs/plugin-react": "^6.0.4",
|
|
51
45
|
"@xyflow/react": "^12.11.2",
|
|
52
46
|
"class-variance-authority": "^0.7.1",
|
|
53
47
|
"clsx": "^2.1.1",
|
|
54
48
|
"cmdk": "^1.1.1",
|
|
55
49
|
"colorjs.io": "^0.7.1",
|
|
56
|
-
"croner": "^10.0.1",
|
|
57
50
|
"fflate": "^0.8.3",
|
|
58
51
|
"graphology": "^0.26.0",
|
|
59
52
|
"lucide-react": "^1.25.0",
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { useMutation, useQueries, useQueryClient } from "@tanstack/react-query";
|
|
2
2
|
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
|
3
|
-
import { zipSync } from "fflate";
|
|
4
3
|
import {
|
|
5
4
|
ChevronRight,
|
|
6
5
|
CornerLeftUp,
|
|
7
6
|
FileArchive,
|
|
8
|
-
FileText,
|
|
9
|
-
Folder,
|
|
10
7
|
FolderOpen,
|
|
11
8
|
FolderUp,
|
|
12
9
|
Plus,
|
|
13
|
-
Table,
|
|
14
10
|
Upload,
|
|
15
|
-
Workflow,
|
|
16
11
|
} from "lucide-react";
|
|
17
12
|
import { useRef, useState } from "react";
|
|
18
|
-
import { defaultModel } from "@/agent/agent-models/agent-models.ts";
|
|
19
13
|
import {
|
|
20
14
|
type MoveDestination,
|
|
21
15
|
moveErrorKey,
|
|
@@ -28,6 +22,7 @@ import {
|
|
|
28
22
|
DropdownMenu,
|
|
29
23
|
DropdownMenuContent,
|
|
30
24
|
DropdownMenuItem,
|
|
25
|
+
DropdownMenuSeparator,
|
|
31
26
|
DropdownMenuTrigger,
|
|
32
27
|
} from "@/components/ui/dropdown-menu";
|
|
33
28
|
import {
|
|
@@ -39,40 +34,19 @@ import {
|
|
|
39
34
|
} from "@/components/ui/sidebar";
|
|
40
35
|
import { flowEntry } from "@/data/intel-data-provider/intel-data-provider.ts";
|
|
41
36
|
import type { TreeEntry } from "@/data/intel-data-provider/intel-data-provider.types.ts";
|
|
42
|
-
import { useCapabilities } from "@/hooks/use-capabilities.ts";
|
|
43
37
|
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
38
|
+
import { type ImportKind, useBundleImport } from "@/import-bundle/import-bundle.tsx";
|
|
44
39
|
import { kindIcons } from "@/kind-icon.ts";
|
|
45
40
|
import { Modal } from "@/modal/modal.tsx";
|
|
46
41
|
import { useIntelRouterContext } from "@/router/router-context.ts";
|
|
47
42
|
import { selectedFrom } from "@/router/selection-search.ts";
|
|
48
43
|
|
|
49
|
-
type NewKind = "folder" | "document" | "table" | "flow"
|
|
50
|
-
// The two ways a bundle arrives (#137): an existing zip, or a picked folder the browser hands over
|
|
51
|
-
// file by file and this component zips before it travels — one wire format, one server door.
|
|
52
|
-
type ImportKind = "importZip" | "importFolder";
|
|
44
|
+
type NewKind = "folder" | "document" | "table" | "flow";
|
|
53
45
|
type Creating = { parentId: string | null; kind: NewKind };
|
|
54
46
|
|
|
55
47
|
// Attachments are inlined as base64, so the browser holds the file twice while it uploads.
|
|
56
48
|
const MaxUploadBytes = 15_000_000;
|
|
57
49
|
|
|
58
|
-
// The server refuses anything larger anyway (#137); refusing here saves uploading it first.
|
|
59
|
-
const MaxImportBytes = 64 * 1024 * 1024;
|
|
60
|
-
|
|
61
|
-
// A picked directory as the zip the import endpoint reads. `webkitRelativePath` carries the folder
|
|
62
|
-
// structure, with the picked folder itself as the first segment — kept, so the folder lands as a
|
|
63
|
-
// folder rather than spilling its contents into the target.
|
|
64
|
-
async function zipOfDirectory(files: readonly File[]): Promise<Blob> {
|
|
65
|
-
const entries: Record<string, Uint8Array> = {};
|
|
66
|
-
let total = 0;
|
|
67
|
-
for (const file of files) {
|
|
68
|
-
const path = file.webkitRelativePath || file.name;
|
|
69
|
-
total += file.size;
|
|
70
|
-
if (total > MaxImportBytes) throw new Error("Import exceeds the 64 MB limit");
|
|
71
|
-
entries[path] = new Uint8Array(await file.arrayBuffer());
|
|
72
|
-
}
|
|
73
|
-
return new Blob([zipSync(entries).slice().buffer], { type: "application/zip" });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
50
|
async function fileBase64(file: File): Promise<string> {
|
|
77
51
|
if (file.size > MaxUploadBytes) throw new Error("Attachment exceeds the 15 MB upload limit");
|
|
78
52
|
return await new Promise((resolve, reject) => {
|
|
@@ -115,9 +89,6 @@ export function AppTree() {
|
|
|
115
89
|
const [creating, setCreating] = useState<Creating | null>(null);
|
|
116
90
|
const [uploadTo, setUploadTo] = useState<string | null>(null);
|
|
117
91
|
const uploadInput = useRef<HTMLInputElement>(null);
|
|
118
|
-
const [importTo, setImportTo] = useState<string | null>(null);
|
|
119
|
-
const importZipInput = useRef<HTMLInputElement>(null);
|
|
120
|
-
const importFolderInput = useRef<HTMLInputElement>(null);
|
|
121
92
|
const [dragged, setDragged] = useState<TreeEntry | null>(null);
|
|
122
93
|
const draggedRef = useRef<TreeEntry | null>(null);
|
|
123
94
|
// Which target the pointer is over: `undefined` for none, `null` for the root strip, an id for a
|
|
@@ -131,6 +102,14 @@ export function AppTree() {
|
|
|
131
102
|
if (destination.id !== null) toggle({ id: destination.id, type: "folder" }, true);
|
|
132
103
|
},
|
|
133
104
|
});
|
|
105
|
+
// ⚠️ The root's import, and only the root's. A folder imports through its own three-dot menu
|
|
106
|
+
// (#346); the tree keeps this one because the top level has no resource whose menu could hold it.
|
|
107
|
+
const bundleImport = useBundleImport({
|
|
108
|
+
where: i18n.t("tree.move.root"),
|
|
109
|
+
onImported: (parentId) => {
|
|
110
|
+
if (parentId !== null) toggle({ id: parentId, type: "folder" }, true);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
134
113
|
|
|
135
114
|
const location = useRouterState({
|
|
136
115
|
select: (state) => ({
|
|
@@ -207,26 +186,6 @@ export function AppTree() {
|
|
|
207
186
|
});
|
|
208
187
|
return { area: "/flows" as const, id: flow.id, parentId, collection: "flows" as const };
|
|
209
188
|
}
|
|
210
|
-
// ⚠️ Its own endpoint, not `createNodes`: an agent is created WITH its first definition
|
|
211
|
-
// version, because a definition names the model and there is no agent without one. The
|
|
212
|
-
// create dialog does not ask for it — `defaultModel()` answers, and the profile changes it.
|
|
213
|
-
if (kind === "agent") {
|
|
214
|
-
const created = await data.createAgent({
|
|
215
|
-
parentId,
|
|
216
|
-
title,
|
|
217
|
-
description: null,
|
|
218
|
-
// A new agent has no tools: giving it any is a choice made in the profile, out of what
|
|
219
|
-
// this person reaches in the portal (D30).
|
|
220
|
-
definition: { references: [], schedules: [], model: defaultModel(), tools: null },
|
|
221
|
-
idempotencyKey: crypto.randomUUID(),
|
|
222
|
-
});
|
|
223
|
-
return {
|
|
224
|
-
area: "/nodes" as const,
|
|
225
|
-
id: created.node.id,
|
|
226
|
-
parentId,
|
|
227
|
-
collection: "node-graph" as const,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
189
|
const node = await data.createNodes({
|
|
231
190
|
parentId,
|
|
232
191
|
kind,
|
|
@@ -301,26 +260,6 @@ export function AppTree() {
|
|
|
301
260
|
},
|
|
302
261
|
});
|
|
303
262
|
|
|
304
|
-
// One import, one request, one answer (#137): the server creates the whole subtree or nothing,
|
|
305
|
-
// so this mutation never has a partial state to clean up after — unlike the attachment upload
|
|
306
|
-
// above, whose two steps can strand a node.
|
|
307
|
-
const importBundle = useMutation({
|
|
308
|
-
mutationFn: async ({ parentId, zip }: { parentId: string | null; zip: Blob }) => {
|
|
309
|
-
if (zip.size > MaxImportBytes) throw new Error("Import exceeds the 64 MB limit");
|
|
310
|
-
const result = await data.importNodeBundle({
|
|
311
|
-
nodeId: parentId,
|
|
312
|
-
zip,
|
|
313
|
-
idempotencyKey: crypto.randomUUID(),
|
|
314
|
-
});
|
|
315
|
-
return { parentId, result };
|
|
316
|
-
},
|
|
317
|
-
onSuccess: async (imported) => {
|
|
318
|
-
// A bundle can carry flows, so the flow collections go stale alongside the node ones.
|
|
319
|
-
await reveal(imported.parentId, "node-graph");
|
|
320
|
-
await queryClient.invalidateQueries({ queryKey: ["flows"] });
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
|
|
324
263
|
// What a drop on this target would do. `undefined` means nothing is being dragged, so the target
|
|
325
264
|
// is not a target at all.
|
|
326
265
|
function verdictFor(
|
|
@@ -376,11 +315,6 @@ export function AppTree() {
|
|
|
376
315
|
uploadInput.current?.click();
|
|
377
316
|
}
|
|
378
317
|
|
|
379
|
-
function startImport(parentId: string | null, kind: ImportKind) {
|
|
380
|
-
setImportTo(parentId);
|
|
381
|
-
(kind === "importZip" ? importZipInput : importFolderInput).current?.click();
|
|
382
|
-
}
|
|
383
|
-
|
|
384
318
|
function renderLevel(parent: Level, ancestors: ReadonlySet<string>, label?: string) {
|
|
385
319
|
const level = levelFor(parent);
|
|
386
320
|
if (!level || level.isPending) {
|
|
@@ -566,7 +500,6 @@ export function AppTree() {
|
|
|
566
500
|
label={i18n.t("tree.add", { title: entry.title })}
|
|
567
501
|
onSelect={(kind) => {
|
|
568
502
|
if (kind === "upload") startUpload(target);
|
|
569
|
-
else if (kind === "importZip" || kind === "importFolder") startImport(target, kind);
|
|
570
503
|
else setCreating({ parentId: target, kind });
|
|
571
504
|
}}
|
|
572
505
|
/>
|
|
@@ -621,9 +554,9 @@ export function AppTree() {
|
|
|
621
554
|
{/* The tree is the navigation and carries no heading of its own (ADR-0004); the list is
|
|
622
555
|
named for assistive technology instead. */}
|
|
623
556
|
{renderLevel({ id: null, type: "folder" }, new Set(), i18n.t("tree.label"))}
|
|
624
|
-
{create.isError || upload.isError ||
|
|
557
|
+
{create.isError || upload.isError || bundleImport.isError ? (
|
|
625
558
|
<p role="alert" className="px-2 py-1.5 text-sm text-destructive">
|
|
626
|
-
{
|
|
559
|
+
{bundleImport.isError
|
|
627
560
|
? i18n.t("tree.importFailed")
|
|
628
561
|
: upload.isError
|
|
629
562
|
? i18n.t("tree.uploadFailed")
|
|
@@ -651,7 +584,8 @@ export function AppTree() {
|
|
|
651
584
|
variant="row"
|
|
652
585
|
onSelect={(kind) => {
|
|
653
586
|
if (kind === "upload") startUpload(null);
|
|
654
|
-
else if (kind === "importZip" || kind === "importFolder")
|
|
587
|
+
else if (kind === "importZip" || kind === "importFolder")
|
|
588
|
+
bundleImport.start(null, kind);
|
|
655
589
|
else setCreating({ parentId: null, kind });
|
|
656
590
|
}}
|
|
657
591
|
/>
|
|
@@ -668,38 +602,7 @@ export function AppTree() {
|
|
|
668
602
|
event.currentTarget.value = "";
|
|
669
603
|
}}
|
|
670
604
|
/>
|
|
671
|
-
|
|
672
|
-
ref={importZipInput}
|
|
673
|
-
type="file"
|
|
674
|
-
accept=".zip,application/zip"
|
|
675
|
-
className="sr-only"
|
|
676
|
-
aria-label={i18n.t("tree.new.importZip")}
|
|
677
|
-
onChange={(event) => {
|
|
678
|
-
const file = event.target.files?.[0];
|
|
679
|
-
if (file) importBundle.mutate({ parentId: importTo, zip: file });
|
|
680
|
-
event.currentTarget.value = "";
|
|
681
|
-
}}
|
|
682
|
-
/>
|
|
683
|
-
{/* `webkitdirectory` is the one way a browser hands over a folder; React does not know the
|
|
684
|
-
attribute, so it is spread past the type checker rather than invented as a prop. */}
|
|
685
|
-
<input
|
|
686
|
-
ref={importFolderInput}
|
|
687
|
-
type="file"
|
|
688
|
-
multiple
|
|
689
|
-
{...({ webkitdirectory: "" } as object)}
|
|
690
|
-
className="sr-only"
|
|
691
|
-
aria-label={i18n.t("tree.new.importFolder")}
|
|
692
|
-
onChange={(event) => {
|
|
693
|
-
const files = Array.from(event.target.files ?? []);
|
|
694
|
-
const parentId = importTo;
|
|
695
|
-
if (files.length > 0) {
|
|
696
|
-
void zipOfDirectory(files)
|
|
697
|
-
.then((zip) => importBundle.mutate({ parentId, zip }))
|
|
698
|
-
.catch(() => importBundle.mutate({ parentId, zip: new Blob([]) }));
|
|
699
|
-
}
|
|
700
|
-
event.currentTarget.value = "";
|
|
701
|
-
}}
|
|
702
|
-
/>
|
|
605
|
+
{bundleImport.inputs}
|
|
703
606
|
{creating ? (
|
|
704
607
|
<Modal title={i18n.t(`tree.new.${creating.kind}`)} close={() => setCreating(null)}>
|
|
705
608
|
<CreateForm
|
|
@@ -719,40 +622,27 @@ export function AppTree() {
|
|
|
719
622
|
//
|
|
720
623
|
// `row` is the root's form: a full-width row that says what it does, because the root has no title
|
|
721
624
|
// beside which a bare icon would mean anything.
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
label: string;
|
|
728
|
-
variant?: "icon" | "
|
|
729
|
-
|
|
730
|
-
|
|
625
|
+
//
|
|
626
|
+
// ⚠️ The two variants do not offer the same things, and the type says so rather than a comment: only
|
|
627
|
+
// the root can emit an import (#346), because only the root has no three-dot menu to hold one. A
|
|
628
|
+
// single wide signature would leave the folder row with a branch nothing can reach.
|
|
629
|
+
type AddMenuProps =
|
|
630
|
+
| { label: string; variant: "row"; onSelect(kind: NewKind | "upload" | ImportKind): void }
|
|
631
|
+
| { label: string; variant?: "icon"; onSelect(kind: NewKind | "upload"): void };
|
|
632
|
+
|
|
633
|
+
function AddMenu(props: AddMenuProps) {
|
|
634
|
+
const { label, onSelect } = props;
|
|
635
|
+
const variant = props.variant ?? "icon";
|
|
731
636
|
const i18n = useI18n();
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
|
|
736
|
-
const
|
|
737
|
-
kind:
|
|
738
|
-
labelKey:
|
|
739
|
-
Icon:
|
|
740
|
-
|
|
741
|
-
{ kind: "folder", labelKey: "tree.new.folder", Icon: Folder },
|
|
742
|
-
{ kind: "document", labelKey: "tree.new.document", Icon: FileText },
|
|
743
|
-
{ kind: "table", labelKey: "tree.new.table", Icon: Table },
|
|
744
|
-
{ kind: "upload", labelKey: "tree.new.upload", Icon: Upload },
|
|
745
|
-
{ kind: "flow", labelKey: "tree.new.flow", Icon: Workflow },
|
|
746
|
-
// ⚠️ The same icon the tree draws for an agent row, taken from the one kind-icon map rather
|
|
747
|
-
// than named again here — a second `Bot` beside it is how the two start disagreeing.
|
|
748
|
-
// Offered only where a runtime exists to run what would be created (#190).
|
|
749
|
-
...(agentRuntime
|
|
750
|
-
? [{ kind: "agent" as const, labelKey: "tree.new.agent", Icon: kindIcons.agent }]
|
|
751
|
-
: []),
|
|
752
|
-
// The bundle round trip's second half (#137): what the export produced, or a plain folder from
|
|
753
|
-
// the machine, lands here as a new subtree.
|
|
754
|
-
{ kind: "importZip", labelKey: "tree.new.importZip", Icon: FileArchive },
|
|
755
|
-
{ kind: "importFolder", labelKey: "tree.new.importFolder", Icon: FolderUp },
|
|
637
|
+
// ⚠️ Three groups, and the separators are the answer to what the plus promises: four kinds are
|
|
638
|
+
// made here, an upload brings one thing in from the machine, an import brings a whole subtree
|
|
639
|
+
// (#346). The icons come from the one kind-icon map rather than being named again — a second
|
|
640
|
+
// `Folder` beside it is how the tree row and this menu start disagreeing.
|
|
641
|
+
const kinds: Array<{ kind: NewKind; labelKey: string; Icon: typeof Plus }> = [
|
|
642
|
+
{ kind: "folder", labelKey: "tree.new.folder", Icon: kindIcons.folder },
|
|
643
|
+
{ kind: "document", labelKey: "tree.new.document", Icon: kindIcons.document },
|
|
644
|
+
{ kind: "table", labelKey: "tree.new.table", Icon: kindIcons.table },
|
|
645
|
+
{ kind: "flow", labelKey: "tree.new.flow", Icon: kindIcons.flow },
|
|
756
646
|
];
|
|
757
647
|
return (
|
|
758
648
|
<DropdownMenu>
|
|
@@ -768,12 +658,34 @@ function AddMenu({
|
|
|
768
658
|
{variant === "row" ? <span className="truncate">{label}</span> : null}
|
|
769
659
|
</DropdownMenuTrigger>
|
|
770
660
|
<DropdownMenuContent align="start" className="w-44">
|
|
771
|
-
{
|
|
661
|
+
{kinds.map((item) => (
|
|
772
662
|
<DropdownMenuItem key={item.kind} onSelect={() => onSelect(item.kind)}>
|
|
773
663
|
<item.Icon aria-hidden="true" />
|
|
774
664
|
{i18n.t(item.labelKey)}
|
|
775
665
|
</DropdownMenuItem>
|
|
776
666
|
))}
|
|
667
|
+
<DropdownMenuSeparator />
|
|
668
|
+
<DropdownMenuItem onSelect={() => onSelect("upload")}>
|
|
669
|
+
<Upload aria-hidden="true" />
|
|
670
|
+
{i18n.t("tree.new.upload")}
|
|
671
|
+
</DropdownMenuItem>
|
|
672
|
+
{/* ⚠️ The root only, and that is not an omission at a folder row: a folder imports through
|
|
673
|
+
its own three-dot menu, beside the export it is the other half of (#136/#137). The root
|
|
674
|
+
has no resource and therefore no such menu, so dropping it here would put the top level
|
|
675
|
+
out of reach of an import altogether (#346). */}
|
|
676
|
+
{props.variant === "row" ? (
|
|
677
|
+
<>
|
|
678
|
+
<DropdownMenuSeparator />
|
|
679
|
+
<DropdownMenuItem onSelect={() => props.onSelect("importZip")}>
|
|
680
|
+
<FileArchive aria-hidden="true" />
|
|
681
|
+
{i18n.t("tree.new.importZip")}
|
|
682
|
+
</DropdownMenuItem>
|
|
683
|
+
<DropdownMenuItem onSelect={() => props.onSelect("importFolder")}>
|
|
684
|
+
<FolderUp aria-hidden="true" />
|
|
685
|
+
{i18n.t("tree.new.importFolder")}
|
|
686
|
+
</DropdownMenuItem>
|
|
687
|
+
</>
|
|
688
|
+
) : null}
|
|
777
689
|
</DropdownMenuContent>
|
|
778
690
|
</DropdownMenu>
|
|
779
691
|
);
|
|
@@ -14,16 +14,19 @@ import {
|
|
|
14
14
|
} from "@/components/ui/select";
|
|
15
15
|
import { useI18n, useLanguage } from "@/i18n/i18n-context.tsx";
|
|
16
16
|
import { languageName } from "@/i18n/i18n-languages/i18n-languages.ts";
|
|
17
|
-
import { SectionHint } from "@/section-hint/section-hint.tsx";
|
|
18
17
|
import { type ThemeChoice, ThemeChoices } from "@/theme/theme.ts";
|
|
19
18
|
import { useTheme } from "@/theme/theme-context.tsx";
|
|
20
|
-
import { TimezoneCombobox } from "@/timezone/timezone-combobox/timezone-combobox.tsx";
|
|
21
|
-
import { useTimezoneSelection } from "@/timezone/timezone-context.tsx";
|
|
22
19
|
|
|
23
|
-
// The settings that belong to the person rather than to the installation: language
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
20
|
+
// The settings that belong to the person rather than to the installation: language and appearance.
|
|
21
|
+
// Both are client state — they live in localStorage, not in the Intel HTTP surface — so they do not
|
|
22
|
+
// belong on a screen with a save button. A choice applies at once; a dialog that previews and only
|
|
23
|
+
// commits on "Save" would carry two states too many for two dropdowns.
|
|
24
|
+
//
|
|
25
|
+
// ⚠️ There was a third, the reader's timezone, and it left with the Agents (#388). It was a
|
|
26
|
+
// suggestion for a schedule and the lens its calendar was read through (#228); with both gone
|
|
27
|
+
// nothing read the value, and a preference that changes nothing on screen is worse than none —
|
|
28
|
+
// it invites the reader to set it and then contradicts them. Reading times in a chosen zone is a
|
|
29
|
+
// feature, and it would come back as one.
|
|
27
30
|
//
|
|
28
31
|
// The dialog is opened from outside (the user footer holds the state), because its trigger is a
|
|
29
32
|
// DropdownMenuItem: Radix unmounts the menu content when an item is chosen and would take a dialog
|
|
@@ -37,7 +40,6 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
|
|
|
37
40
|
const i18n = useI18n();
|
|
38
41
|
const language = useLanguage();
|
|
39
42
|
const theme = useTheme();
|
|
40
|
-
const timezone = useTimezoneSelection();
|
|
41
43
|
|
|
42
44
|
return (
|
|
43
45
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
@@ -93,30 +95,6 @@ export function SettingsDialog({ open, onOpenChange }: SettingsDialogProps) {
|
|
|
93
95
|
</Select>
|
|
94
96
|
</div>
|
|
95
97
|
) : null}
|
|
96
|
-
{timezone ? (
|
|
97
|
-
<div className="grid gap-2">
|
|
98
|
-
{/* ⚠️ Behind the ⓘ (#249), and the SAME component the agent profile uses — three
|
|
99
|
-
lines of prose under a dropdown explained something once and then pushed the
|
|
100
|
-
dialog apart on every open. The `sr-only` copy below is not a duplicate: Radix
|
|
101
|
-
puts a tooltip's content in the accessibility tree only while it is open, so the
|
|
102
|
-
field would silently lose the description `aria-describedby` points at. */}
|
|
103
|
-
<div className="flex items-center gap-1.5">
|
|
104
|
-
<label className="text-sm font-medium" htmlFor="settings-timezone">
|
|
105
|
-
{i18n.t("settings.timezone")}
|
|
106
|
-
</label>
|
|
107
|
-
<SectionHint hint={i18n.t("settings.timezoneHint")} />
|
|
108
|
-
</div>
|
|
109
|
-
<TimezoneCombobox
|
|
110
|
-
id="settings-timezone"
|
|
111
|
-
value={timezone.current}
|
|
112
|
-
onChange={timezone.select}
|
|
113
|
-
describedBy="settings-timezone-hint"
|
|
114
|
-
/>
|
|
115
|
-
<p id="settings-timezone-hint" className="sr-only">
|
|
116
|
-
{i18n.t("settings.timezoneHint")}
|
|
117
|
-
</p>
|
|
118
|
-
</div>
|
|
119
|
-
) : null}
|
|
120
98
|
</div>
|
|
121
99
|
</DialogContent>
|
|
122
100
|
</Dialog>
|
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AddBoardTaskInput,
|
|
3
|
-
AgentCosts,
|
|
4
|
-
AgentKeyRotated,
|
|
5
2
|
AppendTableRowsInput,
|
|
6
3
|
AppendTableRowsResult,
|
|
7
4
|
ArchiveFlowInput,
|
|
8
5
|
ArchiveNodeInput,
|
|
9
|
-
BoardTaskResult,
|
|
10
6
|
BundleImportResult,
|
|
11
7
|
CompleteFlowRunStepInput,
|
|
12
|
-
ConfigureBoardInput,
|
|
13
|
-
ConfigureBoardResult,
|
|
14
|
-
CreateAgentInput,
|
|
15
|
-
CreatedAgent,
|
|
16
8
|
CreateFlowInput,
|
|
17
9
|
CreateNodeInput,
|
|
18
10
|
DefineTableInput,
|
|
19
|
-
DeleteBoardTaskInput,
|
|
20
|
-
DeleteBoardTaskResult,
|
|
21
11
|
Flow,
|
|
22
12
|
FlowDocument,
|
|
23
13
|
FlowList,
|
|
@@ -27,15 +17,10 @@ import {
|
|
|
27
17
|
FlowRunList,
|
|
28
18
|
FlowRunStep,
|
|
29
19
|
FlowValidation,
|
|
30
|
-
IntelCapabilities,
|
|
31
20
|
ListFlowRunsInput,
|
|
32
21
|
ListFlowsInput,
|
|
33
22
|
ListNodesInput,
|
|
34
|
-
ModelCatalog,
|
|
35
|
-
MoveBoardTaskInput,
|
|
36
23
|
Node,
|
|
37
|
-
NodeAgent,
|
|
38
|
-
NodeBoard,
|
|
39
24
|
NodeDocument,
|
|
40
25
|
NodeGraph,
|
|
41
26
|
NodeLinkList,
|
|
@@ -52,7 +37,6 @@ import {
|
|
|
52
37
|
ResourceGrantList,
|
|
53
38
|
RevokeGrantInput,
|
|
54
39
|
RevokeGrantResult,
|
|
55
|
-
SaveAgentDefinitionInput,
|
|
56
40
|
SaveAttachmentInput,
|
|
57
41
|
SaveFlowVersionInput,
|
|
58
42
|
SaveNodeVersionInput,
|
|
@@ -64,18 +48,10 @@ import {
|
|
|
64
48
|
StartFlowRunInput,
|
|
65
49
|
ToolCatalog,
|
|
66
50
|
ToolServerCatalog,
|
|
67
|
-
UpdateBoardTaskInput,
|
|
68
51
|
UpdateFlowInput,
|
|
69
52
|
UpdateNodeInput,
|
|
70
53
|
} from "@anchrd/intel-contract";
|
|
71
54
|
import type { z } from "zod";
|
|
72
|
-
import {
|
|
73
|
-
AgentManualRun,
|
|
74
|
-
AgentRunDetail,
|
|
75
|
-
AgentRunList,
|
|
76
|
-
AgentState,
|
|
77
|
-
agentRuntimePath,
|
|
78
|
-
} from "@/data/agent-runtime/agent-runtime.ts";
|
|
79
55
|
import { createBrowserSignIn } from "@/data/sign-in/sign-in.ts";
|
|
80
56
|
import type { IntelDataProvider, TreeEntry } from "./intel-data-provider.types.ts";
|
|
81
57
|
|
|
@@ -211,19 +187,6 @@ export function createIntelDataProvider(
|
|
|
211
187
|
async getSession() {
|
|
212
188
|
return await request("/session", SessionUser);
|
|
213
189
|
},
|
|
214
|
-
async getCapabilities() {
|
|
215
|
-
return await request("/capabilities", IntelCapabilities);
|
|
216
|
-
},
|
|
217
|
-
/**
|
|
218
|
-
* What the offered models cost and hold (#257).
|
|
219
|
-
*
|
|
220
|
-
* ⚠️ Asked of intel, never of Cloudflare. The account endpoint needs a token and a token does
|
|
221
|
-
* not belong in a SPA — which was the stated reason the price table stayed hard-coded in this
|
|
222
|
-
* package until now. The figures cross the wire; the credential does not.
|
|
223
|
-
*/
|
|
224
|
-
async listModels() {
|
|
225
|
-
return await request("/models", ModelCatalog);
|
|
226
|
-
},
|
|
227
190
|
listNodes,
|
|
228
191
|
async listTreeChildren(parentId) {
|
|
229
192
|
// Both sides of one folder, asked for in parallel and merged here rather than on the server:
|
|
@@ -336,49 +299,6 @@ export function createIntelDataProvider(
|
|
|
336
299
|
{ method: "POST", body: JSON.stringify(parsed) },
|
|
337
300
|
);
|
|
338
301
|
},
|
|
339
|
-
async getBoard(nodeId) {
|
|
340
|
-
return await request(`/nodes/${encodeURIComponent(nodeId)}/board`, NodeBoard);
|
|
341
|
-
},
|
|
342
|
-
async configureBoard(input) {
|
|
343
|
-
const parsed = ConfigureBoardInput.parse(input);
|
|
344
|
-
return await request(
|
|
345
|
-
`/nodes/${encodeURIComponent(parsed.nodeId)}/board/configure`,
|
|
346
|
-
ConfigureBoardResult,
|
|
347
|
-
{ method: "POST", body: JSON.stringify(parsed) },
|
|
348
|
-
);
|
|
349
|
-
},
|
|
350
|
-
async addBoardTask(input) {
|
|
351
|
-
const parsed = AddBoardTaskInput.parse(input);
|
|
352
|
-
return await request(
|
|
353
|
-
`/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks`,
|
|
354
|
-
BoardTaskResult,
|
|
355
|
-
{ method: "POST", body: JSON.stringify(parsed) },
|
|
356
|
-
);
|
|
357
|
-
},
|
|
358
|
-
async updateBoardTask(input) {
|
|
359
|
-
const parsed = UpdateBoardTaskInput.parse(input);
|
|
360
|
-
return await request(
|
|
361
|
-
`/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks/update`,
|
|
362
|
-
BoardTaskResult,
|
|
363
|
-
{ method: "POST", body: JSON.stringify(parsed) },
|
|
364
|
-
);
|
|
365
|
-
},
|
|
366
|
-
async moveBoardTask(input) {
|
|
367
|
-
const parsed = MoveBoardTaskInput.parse(input);
|
|
368
|
-
return await request(
|
|
369
|
-
`/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks/move`,
|
|
370
|
-
BoardTaskResult,
|
|
371
|
-
{ method: "POST", body: JSON.stringify(parsed) },
|
|
372
|
-
);
|
|
373
|
-
},
|
|
374
|
-
async deleteBoardTask(input) {
|
|
375
|
-
const parsed = DeleteBoardTaskInput.parse(input);
|
|
376
|
-
return await request(
|
|
377
|
-
`/nodes/${encodeURIComponent(parsed.nodeId)}/board/tasks/delete`,
|
|
378
|
-
DeleteBoardTaskResult,
|
|
379
|
-
{ method: "POST", body: JSON.stringify(parsed) },
|
|
380
|
-
);
|
|
381
|
-
},
|
|
382
302
|
async listNodeVersions(nodeId) {
|
|
383
303
|
return await request(`/nodes/${encodeURIComponent(nodeId)}/versions`, NodeVersionList);
|
|
384
304
|
},
|
|
@@ -533,89 +453,6 @@ export function createIntelDataProvider(
|
|
|
533
453
|
async listToolServers() {
|
|
534
454
|
return await request("/tools/servers", ToolServerCatalog);
|
|
535
455
|
},
|
|
536
|
-
async getAgent(nodeId) {
|
|
537
|
-
return await request(`/nodes/${encodeURIComponent(nodeId)}/agent`, NodeAgent);
|
|
538
|
-
},
|
|
539
|
-
// ⚠️ `CreatedAgent` is `NodeAgent` since D29, and that is the point rather than a leftover: the
|
|
540
|
-
// create answer used to carry the Gate application key once (#182), and the browser had to be
|
|
541
|
-
// given a credential it could not do anything with. It now goes from Gate into the agent runtime
|
|
542
|
-
// inside the request, so the create answers exactly what a read answers.
|
|
543
|
-
async createAgent(input) {
|
|
544
|
-
return await request("/nodes/agents", CreatedAgent, {
|
|
545
|
-
method: "POST",
|
|
546
|
-
body: JSON.stringify(CreateAgentInput.parse(input)),
|
|
547
|
-
});
|
|
548
|
-
},
|
|
549
|
-
/**
|
|
550
|
-
* Replace the key of the Gate Application an agent runs as (D29, #207).
|
|
551
|
-
*
|
|
552
|
-
* ⚠️ The repair path for `agent_key_missing`, and the answer carries no key — intel asks Gate
|
|
553
|
-
* and hands the new one to the runtime itself. Nothing here has a credential to hold.
|
|
554
|
-
*/
|
|
555
|
-
async rotateAgentKey(nodeId) {
|
|
556
|
-
return await request(
|
|
557
|
-
`/nodes/agents/${encodeURIComponent(nodeId)}/rotate-key`,
|
|
558
|
-
AgentKeyRotated,
|
|
559
|
-
{ method: "POST" },
|
|
560
|
-
);
|
|
561
|
-
},
|
|
562
|
-
async saveAgentDefinition(input) {
|
|
563
|
-
const parsed = SaveAgentDefinitionInput.parse(input);
|
|
564
|
-
return await request(`/nodes/${encodeURIComponent(parsed.nodeId)}/agent`, NodeAgent, {
|
|
565
|
-
method: "POST",
|
|
566
|
-
body: JSON.stringify(parsed),
|
|
567
|
-
});
|
|
568
|
-
},
|
|
569
|
-
/**
|
|
570
|
-
* The agent runtime, asked through intel (#178).
|
|
571
|
-
*
|
|
572
|
-
* ⚠️ Plain `request`, with no second origin and no bearer of its own — that is the whole change.
|
|
573
|
-
* The runtime used to be a Worker this page called directly, which meant CORS it does not
|
|
574
|
-
* answer and a Gate token the browser cannot have. Intel now proxies `/agents/:agentId/*` and
|
|
575
|
-
* attaches the caller's own token server-side, so a 401 from here really is intel's session
|
|
576
|
-
* expiring and the sign-in that follows is the right one.
|
|
577
|
-
*
|
|
578
|
-
* ⚠️ A 403 is NOT that. It means the reader may not drive agents, which the agent page renders
|
|
579
|
-
* as an answer rather than as a failure.
|
|
580
|
-
*/
|
|
581
|
-
async listAgentRuns(agentId) {
|
|
582
|
-
return await request(agentRuntimePath(agentId, "/runs"), AgentRunList);
|
|
583
|
-
},
|
|
584
|
-
/**
|
|
585
|
-
* ⚠️ The one path under `/agents/:agentId` that intel answers ITSELF (#251). The runtime has no
|
|
586
|
-
* idea what anything costs — the figures come out of Cloudflare's AI Gateway log, read with a
|
|
587
|
-
* token that lives in intel and never in the browser.
|
|
588
|
-
*/
|
|
589
|
-
async getAgentCosts(agentId) {
|
|
590
|
-
return await request(agentRuntimePath(agentId, "/costs"), AgentCosts);
|
|
591
|
-
},
|
|
592
|
-
async getAgentRun(agentId, runId) {
|
|
593
|
-
return await request(
|
|
594
|
-
agentRuntimePath(agentId, `/runs/${encodeURIComponent(runId)}`),
|
|
595
|
-
AgentRunDetail,
|
|
596
|
-
);
|
|
597
|
-
},
|
|
598
|
-
async getAgentState(agentId) {
|
|
599
|
-
return await request(agentRuntimePath(agentId, "/state"), AgentState);
|
|
600
|
-
},
|
|
601
|
-
async pauseAgent(agentId) {
|
|
602
|
-
return await request(agentRuntimePath(agentId, "/pause"), AgentState, { method: "POST" });
|
|
603
|
-
},
|
|
604
|
-
async resumeAgent(agentId) {
|
|
605
|
-
return await request(agentRuntimePath(agentId, "/resume"), AgentState, { method: "POST" });
|
|
606
|
-
},
|
|
607
|
-
async runAgentNow(agentId, target) {
|
|
608
|
-
return await request(agentRuntimePath(agentId, "/run"), AgentManualRun, {
|
|
609
|
-
method: "POST",
|
|
610
|
-
body: JSON.stringify({ target }),
|
|
611
|
-
});
|
|
612
|
-
},
|
|
613
|
-
agentChatApi(agentId) {
|
|
614
|
-
// assistant-ui dials this itself, so it needs the path a browser can use — the same one every
|
|
615
|
-
// other call above goes to, intel's own `/api/v1`.
|
|
616
|
-
return `${baseUrl}/api/v1${agentRuntimePath(agentId, "/chat")}`;
|
|
617
|
-
},
|
|
618
|
-
|
|
619
456
|
// The Worker serves the connect route, so this navigation belongs to the data layer for the
|
|
620
457
|
// same reason `loginPath` does. `silent=1` is what makes it a redirect nobody has to watch.
|
|
621
458
|
startPortalSignIn(returnTo = "/tools") {
|