@anchrd/intel-ui 0.3.0 → 0.5.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/components.json +7 -1
- package/package.json +3 -1
- package/src/app/action-slot/action-slot.tsx +23 -0
- package/src/app/app-sidebar/app-sidebar.tsx +71 -0
- package/src/app/app-tree/app-tree.tsx +798 -0
- package/src/app/app.tsx +99 -54
- package/src/app/header-search/header-search.tsx +291 -0
- package/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
- package/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
- package/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
- package/src/app/tree-move/tree-move.tsx +197 -0
- package/src/app/user-footer/user-footer.tsx +103 -0
- package/src/app/view-toggle/view-toggle.tsx +77 -0
- package/src/blocknote-view/blocknote-view.tsx +19 -2
- package/src/branding/favicon.default.svg +2 -2
- package/src/branding/favicon.svg +2 -2
- package/src/components/ui/breadcrumb.tsx +102 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/collapsible.tsx +20 -0
- package/src/components/ui/command.tsx +160 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/dropdown-menu.tsx +162 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +136 -0
- package/src/components/ui/sidebar.tsx +693 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/tooltip.tsx +51 -0
- package/src/data/intel-data-provider/intel-data-provider.ts +170 -85
- package/src/data/intel-data-provider/intel-data-provider.types.ts +64 -20
- package/src/document-link/document-link.tsx +132 -0
- package/src/flow-runs/flow-runs.tsx +225 -0
- package/src/flows/flows.tsx +684 -368
- package/src/flows/node-icon/node-icon.ts +28 -0
- package/src/flows/node-palette/node-palette.tsx +174 -0
- package/src/flows/node-palette/node-palette.types.ts +15 -0
- package/src/graph-pane/graph-pane.tsx +44 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/i18n/en.json +188 -52
- package/src/knowledge/knowledge.tsx +133 -709
- package/src/knowledge-editor/knowledge-editor.tsx +169 -21
- package/src/knowledge-graph/knowledge-graph.ts +26 -24
- package/src/knowledge-graph/knowledge-graph.tsx +33 -24
- package/src/knowledge-table/knowledge-table.tsx +129 -0
- package/src/main.tsx +2 -2
- package/src/resource-menu/resource-menu.tsx +580 -0
- package/src/router/router.tsx +4 -0
- package/src/router/selection-search.ts +43 -0
- package/src/save-button/save-button.tsx +103 -0
- package/src/styles.css +91 -51
- package/src/theme/theme.ts +24 -0
- package/src/tools/tools.tsx +175 -158
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
function TooltipProvider({
|
|
7
|
+
delayDuration = 0,
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
10
|
+
return (
|
|
11
|
+
<TooltipPrimitive.Provider
|
|
12
|
+
data-slot="tooltip-provider"
|
|
13
|
+
delayDuration={delayDuration}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
20
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
24
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function TooltipContent({
|
|
28
|
+
className,
|
|
29
|
+
sideOffset = 0,
|
|
30
|
+
children,
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
33
|
+
return (
|
|
34
|
+
<TooltipPrimitive.Portal>
|
|
35
|
+
<TooltipPrimitive.Content
|
|
36
|
+
data-slot="tooltip-content"
|
|
37
|
+
sideOffset={sideOffset}
|
|
38
|
+
className={cn(
|
|
39
|
+
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
|
46
|
+
</TooltipPrimitive.Content>
|
|
47
|
+
</TooltipPrimitive.Portal>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AppendKnowledgeTableRowsInput,
|
|
3
|
+
AppendKnowledgeTableRowsResult,
|
|
2
4
|
ArchiveKnowledgeNodeInput,
|
|
3
5
|
CompleteFlowRunStepInput,
|
|
4
6
|
CreateFlowInput,
|
|
5
|
-
CreateKnowledgeLinkInput,
|
|
6
7
|
CreateKnowledgeNodeInput,
|
|
7
|
-
|
|
8
|
+
DefineKnowledgeTableInput,
|
|
8
9
|
Flow,
|
|
9
10
|
FlowDocument,
|
|
10
11
|
FlowList,
|
|
12
|
+
FlowPublishPreview,
|
|
13
|
+
FlowRequirements,
|
|
14
|
+
FlowRunHistory,
|
|
15
|
+
FlowRunList,
|
|
11
16
|
FlowRunStep,
|
|
12
17
|
KnowledgeDocument,
|
|
13
18
|
KnowledgeGraph,
|
|
14
|
-
KnowledgeLink,
|
|
15
19
|
KnowledgeLinkList,
|
|
16
20
|
KnowledgeNode,
|
|
17
21
|
KnowledgeNodeList,
|
|
22
|
+
KnowledgeTable,
|
|
18
23
|
KnowledgeVersionList,
|
|
24
|
+
ListFlowRunsInput,
|
|
25
|
+
ListFlowsInput,
|
|
19
26
|
ListKnowledgeNodesInput,
|
|
27
|
+
PreviewFlowPublishInput,
|
|
20
28
|
ProblemDetails,
|
|
21
29
|
PublishFlowInput,
|
|
22
|
-
|
|
30
|
+
RelationGraph,
|
|
31
|
+
RelationGraphInput,
|
|
32
|
+
ResolveKnowledgeLinksInput,
|
|
33
|
+
ResolveKnowledgeLinksResult,
|
|
23
34
|
ResourceGrantList,
|
|
24
|
-
RevokeFlowGrantInput,
|
|
25
35
|
RevokeGrantResult,
|
|
26
36
|
RevokeKnowledgeGrantInput,
|
|
27
37
|
SaveFlowVersionInput,
|
|
@@ -29,31 +39,50 @@ import {
|
|
|
29
39
|
SaveKnowledgeVersionInput,
|
|
30
40
|
SearchKnowledgeInput,
|
|
31
41
|
SearchKnowledgeResult,
|
|
32
|
-
|
|
42
|
+
SessionUser,
|
|
33
43
|
ShareKnowledgeInput,
|
|
44
|
+
ShareKnowledgeResult,
|
|
34
45
|
StartFlowRunInput,
|
|
35
|
-
TestToolInput,
|
|
36
46
|
ToolCatalog,
|
|
37
|
-
|
|
47
|
+
UpdateFlowInput,
|
|
38
48
|
UpdateKnowledgeNodeInput,
|
|
39
49
|
} from "@anchrd/intel-contract";
|
|
40
|
-
import { z } from "zod";
|
|
41
|
-
import type { IntelDataProvider,
|
|
50
|
+
import type { z } from "zod";
|
|
51
|
+
import type { IntelDataProvider, TreeEntry } from "./intel-data-provider.types.ts";
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
// ⚠️ The refusal's `code`, not only its prose. A view that has to tell four different refusals
|
|
54
|
+
// apart — "you may not write there", "that is not a folder", "that would be a cycle", "someone
|
|
55
|
+
// else changed it" — cannot do so from `detail`, which is a sentence the server is free to
|
|
56
|
+
// reword. The code is the contract (`ProblemDetails.code`); the message stays what it was.
|
|
57
|
+
export class IntelRequestError extends Error {
|
|
58
|
+
constructor(
|
|
59
|
+
public readonly status: number,
|
|
60
|
+
public readonly code: string | null,
|
|
61
|
+
message: string,
|
|
62
|
+
) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "IntelRequestError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
44
67
|
|
|
45
68
|
// Attachment uploads and downloads share this budget, so it is generous rather than snappy.
|
|
46
69
|
const RequestTimeoutMs = 60_000;
|
|
47
70
|
|
|
48
|
-
// Deeper nesting than this is not a tree anyone navigates; the limit exists to bound the request
|
|
49
|
-
// fan-out on first paint, not to express a product rule.
|
|
50
|
-
const MaxTreeDepth = 12;
|
|
51
|
-
|
|
52
71
|
// The Worker serves the login route, so the path belongs to the data layer rather than to any view.
|
|
53
72
|
export function loginPath(returnTo?: string): string {
|
|
54
73
|
return returnTo ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}` : "/auth/login";
|
|
55
74
|
}
|
|
56
75
|
|
|
76
|
+
/**
|
|
77
|
+
* One flow as one row of the tree. Two levels show flows — the contents of a folder, and what an
|
|
78
|
+
* expanded flow calls — and they are two lists of the same rows, so they are built here once. Two
|
|
79
|
+
* copies of this would keep returning something plausible and slowly stop agreeing on what a flow
|
|
80
|
+
* row is (#30).
|
|
81
|
+
*/
|
|
82
|
+
export function flowEntry(flow: Flow): TreeEntry {
|
|
83
|
+
return { type: "flow", id: flow.id, title: flow.title, kind: "flow", flow };
|
|
84
|
+
}
|
|
85
|
+
|
|
57
86
|
export function createIntelDataProvider(
|
|
58
87
|
deps: { fetch?: typeof fetch; baseUrl?: string; onUnauthorized?(): void } = {},
|
|
59
88
|
): IntelDataProvider {
|
|
@@ -89,7 +118,9 @@ export function createIntelDataProvider(
|
|
|
89
118
|
.json()
|
|
90
119
|
.catch(() => null),
|
|
91
120
|
);
|
|
92
|
-
throw new
|
|
121
|
+
throw new IntelRequestError(
|
|
122
|
+
result.status,
|
|
123
|
+
parsed.success ? (parsed.data.code ?? null) : null,
|
|
93
124
|
parsed.success
|
|
94
125
|
? (parsed.data.detail ?? parsed.data.title)
|
|
95
126
|
: `Intel responded with ${result.status}`,
|
|
@@ -106,6 +137,17 @@ export function createIntelDataProvider(
|
|
|
106
137
|
return schema.parse(await (await response(path, init)).json());
|
|
107
138
|
}
|
|
108
139
|
|
|
140
|
+
async function listFlows(input: ListFlowsInput = {}): Promise<z.infer<typeof FlowList>> {
|
|
141
|
+
const query = ListFlowsInput.parse(input);
|
|
142
|
+
// The parameter is only sent when the caller asked for one folder. Sending an empty one for
|
|
143
|
+
// "everything" would ask the server for the root instead.
|
|
144
|
+
const params = new URLSearchParams(
|
|
145
|
+
query.parentId === undefined ? {} : { parentId: query.parentId ?? "" },
|
|
146
|
+
);
|
|
147
|
+
const search = params.toString();
|
|
148
|
+
return await request(`/flows${search ? `?${search}` : ""}`, FlowList);
|
|
149
|
+
}
|
|
150
|
+
|
|
109
151
|
async function listKnowledge(
|
|
110
152
|
input: Partial<ListKnowledgeNodesInput> = {},
|
|
111
153
|
): Promise<z.infer<typeof KnowledgeNodeList>> {
|
|
@@ -115,31 +157,37 @@ export function createIntelDataProvider(
|
|
|
115
157
|
return await request(`/knowledge?${params}`, KnowledgeNodeList);
|
|
116
158
|
}
|
|
117
159
|
|
|
118
|
-
// One request per folder, so a deep tree is an N+1 on the first paint. The depth limit bounds that
|
|
119
|
-
// and, more importantly, makes a cyclic parentId impossible to hang on: without it a cycle would
|
|
120
|
-
// recurse until the tab dies. `seen` stops a cycle at the first repeat rather than at the limit.
|
|
121
|
-
async function loadChildren(
|
|
122
|
-
parentId: string | null,
|
|
123
|
-
depth = 0,
|
|
124
|
-
seen: ReadonlySet<string> = new Set(),
|
|
125
|
-
): Promise<KnowledgeTreeNode[]> {
|
|
126
|
-
if (depth >= MaxTreeDepth) return [];
|
|
127
|
-
const { items } = await listKnowledge({ parentId });
|
|
128
|
-
return await Promise.all(
|
|
129
|
-
items.map(async (node) => ({
|
|
130
|
-
...node,
|
|
131
|
-
children:
|
|
132
|
-
node.kind === "folder" && !seen.has(node.id)
|
|
133
|
-
? await loadChildren(node.id, depth + 1, new Set([...seen, node.id]))
|
|
134
|
-
: [],
|
|
135
|
-
})),
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
160
|
return {
|
|
161
|
+
async getSession() {
|
|
162
|
+
return await request("/session", SessionUser);
|
|
163
|
+
},
|
|
140
164
|
listKnowledge,
|
|
141
|
-
async
|
|
142
|
-
|
|
165
|
+
async listTreeChildren(parentId) {
|
|
166
|
+
// Both sides of one folder, asked for in parallel and merged here rather than on the server:
|
|
167
|
+
// the folder is shared, the two records are not, and no endpoint may return a row that is a
|
|
168
|
+
// bit of both (ADR-0004).
|
|
169
|
+
const [knowledge, flows] = await Promise.all([
|
|
170
|
+
listKnowledge({ parentId }),
|
|
171
|
+
listFlows({ parentId }),
|
|
172
|
+
]);
|
|
173
|
+
const entries: TreeEntry[] = [
|
|
174
|
+
...knowledge.items.map((node) => ({
|
|
175
|
+
type: "knowledge" as const,
|
|
176
|
+
id: node.id,
|
|
177
|
+
title: node.title,
|
|
178
|
+
kind: node.kind,
|
|
179
|
+
node,
|
|
180
|
+
})),
|
|
181
|
+
...flows.items.map(flowEntry),
|
|
182
|
+
];
|
|
183
|
+
// Folders first, then everything else by title: a document and a flow sit side by side, and
|
|
184
|
+
// the icon is what tells them apart.
|
|
185
|
+
return entries.sort(
|
|
186
|
+
(left, right) =>
|
|
187
|
+
Number(right.kind === "folder") - Number(left.kind === "folder") ||
|
|
188
|
+
left.title.localeCompare(right.title) ||
|
|
189
|
+
left.id.localeCompare(right.id),
|
|
190
|
+
);
|
|
143
191
|
},
|
|
144
192
|
async getKnowledge(nodeId) {
|
|
145
193
|
return await request(`/knowledge/${encodeURIComponent(nodeId)}`, KnowledgeDocument);
|
|
@@ -156,21 +204,13 @@ export function createIntelDataProvider(
|
|
|
156
204
|
async listKnowledgeLinks(nodeId) {
|
|
157
205
|
return await request(`/knowledge/${encodeURIComponent(nodeId)}/links`, KnowledgeLinkList);
|
|
158
206
|
},
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
);
|
|
166
|
-
},
|
|
167
|
-
async deleteKnowledgeLink(input) {
|
|
168
|
-
const parsed = DeleteKnowledgeLinkInput.parse(input);
|
|
169
|
-
return await request(
|
|
170
|
-
`/knowledge/${encodeURIComponent(parsed.sourceNodeId)}/links/${encodeURIComponent(parsed.linkId)}/revoke`,
|
|
171
|
-
DeleteResult,
|
|
172
|
-
{ method: "POST", body: JSON.stringify(parsed) },
|
|
173
|
-
);
|
|
207
|
+
// ⚠️ POST, not GET. The identifiers of the documents a text links to belong in a body: a
|
|
208
|
+
// query string of them would end up in logs and referrers.
|
|
209
|
+
async resolveKnowledgeLinks(input) {
|
|
210
|
+
return await request("/knowledge/links/resolve", ResolveKnowledgeLinksResult, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
body: JSON.stringify(ResolveKnowledgeLinksInput.parse(input)),
|
|
213
|
+
});
|
|
174
214
|
},
|
|
175
215
|
async saveKnowledge(input) {
|
|
176
216
|
const parsed = SaveKnowledgeVersionInput.parse(input);
|
|
@@ -191,6 +231,28 @@ export function createIntelDataProvider(
|
|
|
191
231
|
async getKnowledgeAttachment(nodeId) {
|
|
192
232
|
return await (await response(`/knowledge/${encodeURIComponent(nodeId)}/attachment`)).blob();
|
|
193
233
|
},
|
|
234
|
+
async getKnowledgeTable(nodeId) {
|
|
235
|
+
return await request(`/knowledge/${encodeURIComponent(nodeId)}/table`, KnowledgeTable);
|
|
236
|
+
},
|
|
237
|
+
async defineKnowledgeTable(input) {
|
|
238
|
+
const parsed = DefineKnowledgeTableInput.parse(input);
|
|
239
|
+
return await request(
|
|
240
|
+
`/knowledge/${encodeURIComponent(parsed.nodeId)}/table`,
|
|
241
|
+
KnowledgeTable,
|
|
242
|
+
{
|
|
243
|
+
method: "POST",
|
|
244
|
+
body: JSON.stringify(parsed),
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
},
|
|
248
|
+
async appendKnowledgeTableRows(input) {
|
|
249
|
+
const parsed = AppendKnowledgeTableRowsInput.parse(input);
|
|
250
|
+
return await request(
|
|
251
|
+
`/knowledge/${encodeURIComponent(parsed.nodeId)}/table/rows`,
|
|
252
|
+
AppendKnowledgeTableRowsResult,
|
|
253
|
+
{ method: "POST", body: JSON.stringify(parsed) },
|
|
254
|
+
);
|
|
255
|
+
},
|
|
194
256
|
async listKnowledgeVersions(nodeId) {
|
|
195
257
|
return await request(
|
|
196
258
|
`/knowledge/${encodeURIComponent(nodeId)}/versions`,
|
|
@@ -228,7 +290,7 @@ export function createIntelDataProvider(
|
|
|
228
290
|
const parsed = ShareKnowledgeInput.parse(input);
|
|
229
291
|
return await request(
|
|
230
292
|
`/knowledge/${encodeURIComponent(parsed.resourceId)}/grants`,
|
|
231
|
-
|
|
293
|
+
ShareKnowledgeResult,
|
|
232
294
|
{ method: "POST", body: JSON.stringify(parsed) },
|
|
233
295
|
);
|
|
234
296
|
},
|
|
@@ -240,39 +302,46 @@ export function createIntelDataProvider(
|
|
|
240
302
|
{ method: "POST", body: JSON.stringify(parsed) },
|
|
241
303
|
);
|
|
242
304
|
},
|
|
243
|
-
|
|
244
|
-
return await request("/flows", FlowList);
|
|
245
|
-
},
|
|
305
|
+
listFlows,
|
|
246
306
|
async getFlow(flowId) {
|
|
247
307
|
return await request(`/flows/${encodeURIComponent(flowId)}`, FlowDocument);
|
|
248
308
|
},
|
|
309
|
+
async listFlowCalls(flowId) {
|
|
310
|
+
return await request(`/flows/${encodeURIComponent(flowId)}/calls`, FlowList);
|
|
311
|
+
},
|
|
312
|
+
async getRelationGraph(scope, limit) {
|
|
313
|
+
const parsed = RelationGraphInput.parse({
|
|
314
|
+
scope,
|
|
315
|
+
...(limit === undefined ? {} : { limit }),
|
|
316
|
+
});
|
|
317
|
+
const params = new URLSearchParams({
|
|
318
|
+
of: parsed.scope.of,
|
|
319
|
+
limit: String(parsed.limit),
|
|
320
|
+
// A folder scope without an ID is the root of the shared tree, and an absent parameter is
|
|
321
|
+
// how that is said — the same distinction `/flows?parentId=` makes.
|
|
322
|
+
...(parsed.scope.of === "flow"
|
|
323
|
+
? { flowId: parsed.scope.flowId }
|
|
324
|
+
: parsed.scope.folderId
|
|
325
|
+
? { folderId: parsed.scope.folderId }
|
|
326
|
+
: {}),
|
|
327
|
+
});
|
|
328
|
+
return await request(`/flows/graph?${params}`, RelationGraph);
|
|
329
|
+
},
|
|
330
|
+
async getFlowRequirements(flowId) {
|
|
331
|
+
return await request(`/flows/${encodeURIComponent(flowId)}/requirements`, FlowRequirements);
|
|
332
|
+
},
|
|
249
333
|
async createFlow(input) {
|
|
250
334
|
return await request("/flows", Flow, {
|
|
251
335
|
method: "POST",
|
|
252
336
|
body: JSON.stringify(CreateFlowInput.parse(input)),
|
|
253
337
|
});
|
|
254
338
|
},
|
|
255
|
-
async
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
`/flows/${encodeURIComponent(parsed.resourceId)}/grants`,
|
|
262
|
-
ResourceGrant,
|
|
263
|
-
{
|
|
264
|
-
method: "POST",
|
|
265
|
-
body: JSON.stringify(parsed),
|
|
266
|
-
},
|
|
267
|
-
);
|
|
268
|
-
},
|
|
269
|
-
async revokeFlowGrant(input) {
|
|
270
|
-
const parsed = RevokeFlowGrantInput.parse(input);
|
|
271
|
-
return await request(
|
|
272
|
-
`/flows/${encodeURIComponent(parsed.resourceId)}/grants/${encodeURIComponent(parsed.grantId)}/revoke`,
|
|
273
|
-
RevokeGrantResult,
|
|
274
|
-
{ method: "POST", body: JSON.stringify(parsed) },
|
|
275
|
-
);
|
|
339
|
+
async updateFlow(input) {
|
|
340
|
+
const parsed = UpdateFlowInput.parse(input);
|
|
341
|
+
return await request(`/flows/${encodeURIComponent(parsed.flowId)}`, Flow, {
|
|
342
|
+
method: "PATCH",
|
|
343
|
+
body: JSON.stringify(parsed),
|
|
344
|
+
});
|
|
276
345
|
},
|
|
277
346
|
async saveFlow(input) {
|
|
278
347
|
const parsed = SaveFlowVersionInput.parse(input);
|
|
@@ -281,6 +350,13 @@ export function createIntelDataProvider(
|
|
|
281
350
|
body: JSON.stringify(parsed),
|
|
282
351
|
});
|
|
283
352
|
},
|
|
353
|
+
async previewFlowPublish(input) {
|
|
354
|
+
const parsed = PreviewFlowPublishInput.parse(input);
|
|
355
|
+
return await request(
|
|
356
|
+
`/flows/${encodeURIComponent(parsed.flowId)}/versions/${encodeURIComponent(parsed.versionId)}/publish-preview`,
|
|
357
|
+
FlowPublishPreview,
|
|
358
|
+
);
|
|
359
|
+
},
|
|
284
360
|
async publishFlow(input) {
|
|
285
361
|
const parsed = PublishFlowInput.parse(input);
|
|
286
362
|
return await request(`/flows/${encodeURIComponent(parsed.flowId)}/publish`, Flow, {
|
|
@@ -295,9 +371,24 @@ export function createIntelDataProvider(
|
|
|
295
371
|
body: JSON.stringify(parsed),
|
|
296
372
|
});
|
|
297
373
|
},
|
|
374
|
+
async listFlowRuns(input) {
|
|
375
|
+
const parsed = ListFlowRunsInput.parse(input);
|
|
376
|
+
// Only what was actually asked for travels. `failedOnly` is the parameter's presence rather
|
|
377
|
+
// than a value, which is the same shape the server reads it back with.
|
|
378
|
+
const params = new URLSearchParams({ limit: String(parsed.limit) });
|
|
379
|
+
if (parsed.failedOnly) params.set("failedOnly", "");
|
|
380
|
+
if (parsed.cursor) params.set("cursor", parsed.cursor);
|
|
381
|
+
return await request(
|
|
382
|
+
`/flows/${encodeURIComponent(parsed.flowId)}/runs?${params}`,
|
|
383
|
+
FlowRunList,
|
|
384
|
+
);
|
|
385
|
+
},
|
|
298
386
|
async getFlowRun(runId) {
|
|
299
387
|
return await request(`/flow-runs/${encodeURIComponent(runId)}`, FlowRunStep);
|
|
300
388
|
},
|
|
389
|
+
async getFlowRunSteps(runId) {
|
|
390
|
+
return await request(`/flow-runs/${encodeURIComponent(runId)}/steps`, FlowRunHistory);
|
|
391
|
+
},
|
|
301
392
|
async completeFlowStep(input) {
|
|
302
393
|
const parsed = CompleteFlowRunStepInput.parse(input);
|
|
303
394
|
return await request(`/flow-runs/${encodeURIComponent(parsed.runId)}/complete`, FlowRunStep, {
|
|
@@ -308,12 +399,6 @@ export function createIntelDataProvider(
|
|
|
308
399
|
async listTools() {
|
|
309
400
|
return await request("/tools", ToolCatalog);
|
|
310
401
|
},
|
|
311
|
-
async testTool(input) {
|
|
312
|
-
return await request("/tools/test", ToolTestResult, {
|
|
313
|
-
method: "POST",
|
|
314
|
-
body: JSON.stringify(TestToolInput.parse(input)),
|
|
315
|
-
});
|
|
316
|
-
},
|
|
317
402
|
|
|
318
403
|
portalConnectUrl(returnTo = "/tools") {
|
|
319
404
|
return `${baseUrl}/auth/connect?returnTo=${encodeURIComponent(returnTo)}`;
|
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AppendKnowledgeTableRowsInput,
|
|
3
|
+
AppendKnowledgeTableRowsResult,
|
|
2
4
|
ArchiveKnowledgeNodeInput,
|
|
3
5
|
CompleteFlowRunStepInput,
|
|
4
6
|
CreateFlowInput,
|
|
5
|
-
CreateKnowledgeLinkInput,
|
|
6
7
|
CreateKnowledgeNodeInput,
|
|
7
|
-
|
|
8
|
+
DefineKnowledgeTableInput,
|
|
8
9
|
Flow,
|
|
9
10
|
FlowDocument,
|
|
10
11
|
FlowList,
|
|
12
|
+
FlowPublishPreview,
|
|
13
|
+
FlowRequirements,
|
|
14
|
+
FlowRunHistory,
|
|
15
|
+
FlowRunList,
|
|
11
16
|
FlowRunStep,
|
|
12
17
|
KnowledgeDocument,
|
|
13
18
|
KnowledgeGraph,
|
|
14
|
-
KnowledgeLink,
|
|
15
19
|
KnowledgeLinkList,
|
|
16
20
|
KnowledgeNode,
|
|
21
|
+
KnowledgeNodeKind,
|
|
17
22
|
KnowledgeNodeList,
|
|
23
|
+
KnowledgeTable,
|
|
18
24
|
KnowledgeVersionList,
|
|
25
|
+
ListFlowRunsInput,
|
|
26
|
+
ListFlowsInput,
|
|
19
27
|
ListKnowledgeNodesInput,
|
|
28
|
+
PreviewFlowPublishInput,
|
|
20
29
|
PublishFlowInput,
|
|
21
|
-
|
|
30
|
+
RelationGraph,
|
|
31
|
+
RelationGraphScope,
|
|
32
|
+
ResolveKnowledgeLinksInput,
|
|
33
|
+
ResolveKnowledgeLinksResult,
|
|
22
34
|
ResourceGrantList,
|
|
23
|
-
RevokeFlowGrantInput,
|
|
24
35
|
RevokeGrantResult,
|
|
25
36
|
RevokeKnowledgeGrantInput,
|
|
26
37
|
SaveFlowVersionInput,
|
|
@@ -28,51 +39,84 @@ import type {
|
|
|
28
39
|
SaveKnowledgeVersionInput,
|
|
29
40
|
SearchKnowledgeInput,
|
|
30
41
|
SearchKnowledgeResult,
|
|
31
|
-
|
|
42
|
+
SessionUser,
|
|
32
43
|
ShareKnowledgeInput,
|
|
44
|
+
ShareKnowledgeResult,
|
|
33
45
|
StartFlowRunInput,
|
|
34
|
-
TestToolInput,
|
|
35
46
|
ToolCatalog,
|
|
36
|
-
|
|
47
|
+
UpdateFlowInput,
|
|
37
48
|
UpdateKnowledgeNodeInput,
|
|
38
49
|
} from "@anchrd/intel-contract";
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
// One row of the shared tree. Knowledge and Flows share the folder, not their nature (ADR-0004), so
|
|
52
|
+
// this is a union that keeps each side's record whole — never a merged "node" that is a bit of both.
|
|
53
|
+
export type TreeEntry =
|
|
54
|
+
| { type: "knowledge"; id: string; title: string; kind: KnowledgeNodeKind; node: KnowledgeNode }
|
|
55
|
+
| { type: "flow"; id: string; title: string; kind: "flow"; flow: Flow };
|
|
43
56
|
|
|
44
57
|
export interface IntelDataProvider {
|
|
58
|
+
getSession(): Promise<SessionUser>;
|
|
45
59
|
listKnowledge(input?: Partial<ListKnowledgeNodesInput>): Promise<KnowledgeNodeList>;
|
|
46
|
-
|
|
60
|
+
// One level of the shared tree: the documents and the flows filed in the same folder, in one
|
|
61
|
+
// sorted list. Per level rather than recursive, so opening a folder is what costs a request.
|
|
62
|
+
listTreeChildren(parentId: string | null): Promise<TreeEntry[]>;
|
|
47
63
|
getKnowledge(nodeId: string): Promise<KnowledgeDocument>;
|
|
48
64
|
createKnowledge(input: CreateKnowledgeNodeInput): Promise<KnowledgeNode>;
|
|
49
65
|
getKnowledgeGraph(limit?: number): Promise<KnowledgeGraph>;
|
|
50
66
|
listKnowledgeLinks(nodeId: string): Promise<KnowledgeLinkList>;
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
// The titles of the documents a text links to, for the reader. A target they may not see, or one
|
|
68
|
+
// that is gone, is absent from the answer — the two look the same on purpose (#41).
|
|
69
|
+
resolveKnowledgeLinks(input: ResolveKnowledgeLinksInput): Promise<ResolveKnowledgeLinksResult>;
|
|
53
70
|
saveKnowledge(input: SaveKnowledgeVersionInput): Promise<KnowledgeDocument>;
|
|
54
71
|
saveKnowledgeAttachment(input: SaveKnowledgeAttachmentInput): Promise<KnowledgeDocument>;
|
|
55
72
|
getKnowledgeAttachment(nodeId: string): Promise<Blob>;
|
|
73
|
+
// A table as columns and rows. The CSV stays canonical — `getKnowledge` still answers with it,
|
|
74
|
+
// which is what the download uses — and this is the same bytes read by the server's one reader.
|
|
75
|
+
getKnowledgeTable(nodeId: string): Promise<KnowledgeTable>;
|
|
76
|
+
defineKnowledgeTable(input: DefineKnowledgeTableInput): Promise<KnowledgeTable>;
|
|
77
|
+
appendKnowledgeTableRows(
|
|
78
|
+
input: AppendKnowledgeTableRowsInput,
|
|
79
|
+
): Promise<AppendKnowledgeTableRowsResult>;
|
|
56
80
|
portalConnectUrl(returnTo?: string): string;
|
|
57
81
|
listKnowledgeVersions(nodeId: string): Promise<KnowledgeVersionList>;
|
|
58
82
|
updateKnowledge(input: UpdateKnowledgeNodeInput): Promise<KnowledgeNode>;
|
|
59
83
|
archiveKnowledge(input: ArchiveKnowledgeNodeInput): Promise<KnowledgeNode>;
|
|
60
84
|
searchKnowledge(input: SearchKnowledgeInput): Promise<SearchKnowledgeResult>;
|
|
61
85
|
listKnowledgeGrants(resourceId: string): Promise<ResourceGrantList>;
|
|
62
|
-
|
|
86
|
+
// The grant, and what the grant does not cover: the documents the flows in this folder read that
|
|
87
|
+
// the new principal still cannot. A warning, never a refusal (ADR-0004 §4).
|
|
88
|
+
shareKnowledge(input: ShareKnowledgeInput): Promise<ShareKnowledgeResult>;
|
|
63
89
|
revokeKnowledgeGrant(input: RevokeKnowledgeGrantInput): Promise<RevokeGrantResult>;
|
|
64
|
-
listFlows(): Promise<FlowList>;
|
|
90
|
+
listFlows(input?: ListFlowsInput): Promise<FlowList>;
|
|
65
91
|
getFlow(flowId: string): Promise<FlowDocument>;
|
|
92
|
+
// What a flow calls, read out of its graph. It answers a different question from `listTreeChildren`
|
|
93
|
+
// and deliberately gives a different answer: a shared sub-flow is listed under every caller.
|
|
94
|
+
listFlowCalls(flowId: string): Promise<FlowList>;
|
|
95
|
+
// What accesses what, for one level of the shared tree: a folder for its contents, a flow for
|
|
96
|
+
// itself. ⚠️ Only nodes the signed-in user may see come back, and one they may not is absent
|
|
97
|
+
// altogether — never a placeholder, because the edge into one would already say that it exists.
|
|
98
|
+
getRelationGraph(scope: RelationGraphScope, limit?: number): Promise<RelationGraph>;
|
|
99
|
+
// What a flow touches: the documents and tools its graph names. Documents the signed-in user may
|
|
100
|
+
// not see are counted rather than named, and nothing here claims anybody may reach them.
|
|
101
|
+
getFlowRequirements(flowId: string): Promise<FlowRequirements>;
|
|
66
102
|
createFlow(input: CreateFlowInput): Promise<Flow>;
|
|
67
|
-
|
|
68
|
-
shareFlow(input: ShareFlowInput): Promise<ResourceGrant>;
|
|
69
|
-
revokeFlowGrant(input: RevokeFlowGrantInput): Promise<RevokeGrantResult>;
|
|
103
|
+
updateFlow(input: UpdateFlowInput): Promise<Flow>;
|
|
70
104
|
saveFlow(input: SaveFlowVersionInput): Promise<FlowDocument>;
|
|
105
|
+
// Which version each sub-flow call will take once published, and which of them publishing
|
|
106
|
+
// freezes. Read before publishing, so the author agrees to the pins rather than discovering them.
|
|
107
|
+
previewFlowPublish(input: PreviewFlowPublishInput): Promise<FlowPublishPreview>;
|
|
71
108
|
publishFlow(input: PublishFlowInput): Promise<Flow>;
|
|
72
109
|
startFlow(input: StartFlowRunInput): Promise<FlowRunStep>;
|
|
110
|
+
// What this flow has done, newest first, one page at a time. ⚠️ It carries what a run did and
|
|
111
|
+
// never what it produced: a run reads Knowledge with the rights of whoever started it, so its
|
|
112
|
+
// result is not automatically readable for everyone who may read the flow.
|
|
113
|
+
listFlowRuns(input: ListFlowRunsInput): Promise<FlowRunList>;
|
|
73
114
|
getFlowRun(runId: string): Promise<FlowRunStep>;
|
|
115
|
+
// The drill-down behind one run: every step it took, and the call chain it belongs to.
|
|
116
|
+
getFlowRunSteps(runId: string): Promise<FlowRunHistory>;
|
|
74
117
|
completeFlowStep(input: CompleteFlowRunStepInput): Promise<FlowRunStep>;
|
|
118
|
+
// Reading the catalog is the whole of the tool surface here: calling a tool belongs to a flow
|
|
119
|
+
// or to the Intel MCP surface, not to the screen that shows what the portal offers.
|
|
75
120
|
listTools(): Promise<ToolCatalog>;
|
|
76
|
-
testTool(input: TestToolInput): Promise<ToolTestResult>;
|
|
77
121
|
logout(): Promise<void>;
|
|
78
122
|
}
|