ltcai 4.0.1 → 4.2.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 +33 -24
- package/desktop/electron/main.cjs +44 -0
- package/docs/CHANGELOG.md +84 -0
- package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
- package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
- package/docs/V4_1_VALIDATION_REPORT.md +47 -0
- package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +97 -0
- package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +91 -0
- package/docs/V4_2_VALIDATION_REPORT.md +89 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +31 -26
- package/frontend/index.html +24 -0
- package/frontend/openapi.json +14436 -0
- package/frontend/src/App.tsx +184 -0
- package/frontend/src/api/client.ts +320 -0
- package/frontend/src/api/openapi.ts +16921 -0
- package/frontend/src/components/primitives.tsx +204 -0
- package/frontend/src/components/ui/badge.tsx +27 -0
- package/frontend/src/components/ui/button.tsx +37 -0
- package/frontend/src/components/ui/card.tsx +22 -0
- package/frontend/src/components/ui/input.tsx +16 -0
- package/frontend/src/components/ui/textarea.tsx +16 -0
- package/frontend/src/lib/utils.ts +33 -0
- package/frontend/src/main.tsx +23 -0
- package/frontend/src/pages/Act.tsx +245 -0
- package/frontend/src/pages/Ask.tsx +200 -0
- package/frontend/src/pages/Brain.tsx +267 -0
- package/frontend/src/pages/Capture.tsx +158 -0
- package/frontend/src/pages/Library.tsx +187 -0
- package/frontend/src/pages/System.tsx +378 -0
- package/frontend/src/routes.ts +85 -0
- package/frontend/src/store/appStore.ts +54 -0
- package/frontend/src/styles.css +107 -0
- package/kg_schema.py +1 -1
- package/knowledge_graph.py +4 -4
- package/lattice_brain/__init__.py +70 -0
- package/lattice_brain/_kg_common.py +1 -0
- package/lattice_brain/archive.py +133 -0
- package/lattice_brain/context.py +3 -0
- package/lattice_brain/conversations.py +3 -0
- package/lattice_brain/core.py +82 -0
- package/lattice_brain/discovery.py +1 -0
- package/lattice_brain/documents.py +1 -0
- package/lattice_brain/embeddings.py +82 -0
- package/lattice_brain/identity.py +13 -0
- package/lattice_brain/ingest.py +1 -0
- package/lattice_brain/memory.py +3 -0
- package/lattice_brain/network.py +1 -0
- package/lattice_brain/projection.py +1 -0
- package/lattice_brain/provenance.py +1 -0
- package/lattice_brain/retrieval.py +1 -0
- package/lattice_brain/schema.py +1 -0
- package/lattice_brain/storage/__init__.py +22 -0
- package/lattice_brain/storage/base.py +72 -0
- package/lattice_brain/storage/docker.py +105 -0
- package/lattice_brain/storage/factory.py +31 -0
- package/lattice_brain/storage/migration.py +190 -0
- package/lattice_brain/storage/postgres.py +123 -0
- package/lattice_brain/storage/sqlite.py +128 -0
- package/lattice_brain/store.py +3 -0
- package/lattice_brain/write_master.py +1 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/portability.py +69 -0
- package/latticeai/api/setup.py +5 -4
- package/latticeai/api/static_routes.py +4 -4
- package/latticeai/app_factory.py +17 -10
- package/latticeai/brain/__init__.py +6 -6
- package/latticeai/brain/_kg_common.py +1 -1
- package/latticeai/brain/network.py +1 -1
- package/latticeai/brain/retrieval.py +15 -0
- package/latticeai/brain/store.py +22 -6
- package/latticeai/core/config.py +8 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/kg_portability.py +82 -1
- package/package.json +55 -15
- package/scripts/build_frontend_assets.mjs +38 -0
- package/scripts/bump_version.py +4 -1
- package/scripts/export_openapi.py +31 -0
- package/scripts/lint_frontend.mjs +91 -0
- package/scripts/migrate_brain_storage.py +53 -0
- package/scripts/run_python.mjs +47 -0
- package/scripts/wheel_smoke.py +3 -0
- package/src-tauri/Cargo.lock +4833 -0
- package/src-tauri/Cargo.toml +19 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +7 -0
- package/src-tauri/src/main.rs +78 -0
- package/src-tauri/tauri.conf.json +39 -0
- package/static/app/asset-manifest.json +32 -0
- package/static/app/assets/core-CwxXejkd.js +2 -0
- package/static/app/assets/core-CwxXejkd.js.map +1 -0
- package/static/app/assets/index-CDjiH_se.css +2 -0
- package/static/app/assets/index-C_HAkbAg.js +333 -0
- package/static/app/assets/index-C_HAkbAg.js.map +1 -0
- package/static/app/index.html +25 -0
- package/static/manifest.json +2 -2
- package/static/sw.js +4 -4
- package/scripts/build_v3_assets.mjs +0 -170
- package/scripts/lint_v3.mjs +0 -120
- package/static/v3/asset-manifest.json +0 -63
- package/static/v3/css/lattice.base.49deefb5.css +0 -128
- package/static/v3/css/lattice.base.css +0 -128
- package/static/v3/css/lattice.components.cde18231.css +0 -472
- package/static/v3/css/lattice.components.css +0 -472
- package/static/v3/css/lattice.shell.29d36d85.css +0 -452
- package/static/v3/css/lattice.shell.css +0 -452
- package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
- package/static/v3/css/lattice.tokens.css +0 -135
- package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
- package/static/v3/css/lattice.views.css +0 -360
- package/static/v3/index.html +0 -68
- package/static/v3/js/app.c5c80c46.js +0 -26
- package/static/v3/js/app.js +0 -26
- package/static/v3/js/core/api.ba0fbf14.js +0 -625
- package/static/v3/js/core/api.js +0 -625
- package/static/v3/js/core/components.f25b3b93.js +0 -230
- package/static/v3/js/core/components.js +0 -230
- package/static/v3/js/core/dom.a2773eb0.js +0 -148
- package/static/v3/js/core/dom.js +0 -148
- package/static/v3/js/core/i18n.880e1fec.js +0 -575
- package/static/v3/js/core/i18n.js +0 -575
- package/static/v3/js/core/router.584570f2.js +0 -37
- package/static/v3/js/core/router.js +0 -37
- package/static/v3/js/core/routes.37522821.js +0 -101
- package/static/v3/js/core/routes.js +0 -101
- package/static/v3/js/core/shell.e3f6bbfa.js +0 -420
- package/static/v3/js/core/shell.js +0 -420
- package/static/v3/js/core/store.7b2aa044.js +0 -123
- package/static/v3/js/core/store.js +0 -123
- package/static/v3/js/views/account.eff40715.js +0 -143
- package/static/v3/js/views/account.js +0 -143
- package/static/v3/js/views/activity.0d271ef9.js +0 -67
- package/static/v3/js/views/activity.js +0 -67
- package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
- package/static/v3/js/views/admin-audit.js +0 -185
- package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
- package/static/v3/js/views/admin-permissions.js +0 -177
- package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
- package/static/v3/js/views/admin-policies.js +0 -102
- package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
- package/static/v3/js/views/admin-private-vpc.js +0 -135
- package/static/v3/js/views/admin-security.07c66b72.js +0 -180
- package/static/v3/js/views/admin-security.js +0 -180
- package/static/v3/js/views/admin-users.f7ac7b43.js +0 -166
- package/static/v3/js/views/admin-users.js +0 -166
- package/static/v3/js/views/agents.17c5288d.js +0 -564
- package/static/v3/js/views/agents.js +0 -564
- package/static/v3/js/views/chat.e250e2cc.js +0 -624
- package/static/v3/js/views/chat.js +0 -624
- package/static/v3/js/views/files.adad14c1.js +0 -365
- package/static/v3/js/views/files.js +0 -365
- package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
- package/static/v3/js/views/graph-canvas.js +0 -509
- package/static/v3/js/views/home.24f8b8ae.js +0 -200
- package/static/v3/js/views/home.js +0 -200
- package/static/v3/js/views/hooks.37895880.js +0 -220
- package/static/v3/js/views/hooks.js +0 -220
- package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
- package/static/v3/js/views/hybrid-search.js +0 -194
- package/static/v3/js/views/knowledge-graph.4d09c537.js +0 -529
- package/static/v3/js/views/knowledge-graph.js +0 -529
- package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
- package/static/v3/js/views/marketplace.js +0 -141
- package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
- package/static/v3/js/views/mcp.js +0 -114
- package/static/v3/js/views/memory.4ebdf474.js +0 -147
- package/static/v3/js/views/memory.js +0 -147
- package/static/v3/js/views/models.a1ffa147.js +0 -256
- package/static/v3/js/views/models.js +0 -256
- package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
- package/static/v3/js/views/my-computer.js +0 -463
- package/static/v3/js/views/network.52a4f181.js +0 -97
- package/static/v3/js/views/network.js +0 -97
- package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
- package/static/v3/js/views/pipeline.js +0 -157
- package/static/v3/js/views/planning.4876fd77.js +0 -174
- package/static/v3/js/views/planning.js +0 -174
- package/static/v3/js/views/runs.b63b2afa.js +0 -144
- package/static/v3/js/views/runs.js +0 -144
- package/static/v3/js/views/settings.b7140634.js +0 -317
- package/static/v3/js/views/settings.js +0 -317
- package/static/v3/js/views/skills.c6c2f965.js +0 -109
- package/static/v3/js/views/skills.js +0 -109
- package/static/v3/js/views/snapshots.6f5db095.js +0 -135
- package/static/v3/js/views/snapshots.js +0 -135
- package/static/v3/js/views/tools.e4f11276.js +0 -108
- package/static/v3/js/views/tools.js +0 -108
- package/static/v3/js/views/workflows.7752225a.js +0 -213
- package/static/v3/js/views/workflows.js +0 -213
- package/static/v3/js/views/workspace-admin.c466029b.js +0 -156
- package/static/v3/js/views/workspace-admin.js +0 -156
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/* ============================================================================
|
|
2
|
-
* View: Workflows — workflow-driven agent execution.
|
|
3
|
-
* Trigger → agent chain → tools → memory → result. Reads workflow definitions
|
|
4
|
-
* and runs from the real workflow designer backend; runs a definition and shows
|
|
5
|
-
* the run ledger with replay. Unavailable state is explicit.
|
|
6
|
-
* ========================================================================== */
|
|
7
|
-
|
|
8
|
-
const STAGES = ["Trigger", "Agent chain", "Tools", "Memory", "Result"];
|
|
9
|
-
|
|
10
|
-
export async function render(ctx) {
|
|
11
|
-
const { h, c } = ctx;
|
|
12
|
-
const defsHost = h("div", c.loading({ lines: 3, block: true }));
|
|
13
|
-
const runsHost = h("div", c.loading({ lines: 3 }));
|
|
14
|
-
const triggerHost = h("div", c.loading({ lines: 2 }));
|
|
15
|
-
const defsSrc = h("span", c.sourceBadge("pending"));
|
|
16
|
-
const runsSrc = h("span", c.sourceBadge("pending"));
|
|
17
|
-
|
|
18
|
-
const root = h("div.lt3-stack-6",
|
|
19
|
-
c.viewHeader({
|
|
20
|
-
eyebrow: "Compute",
|
|
21
|
-
title: "Workflow Agents",
|
|
22
|
-
sub: "Repeatable automation: a trigger fires an agent chain that calls tools, reads and writes memory, and produces a result.",
|
|
23
|
-
}),
|
|
24
|
-
stageLegend(ctx),
|
|
25
|
-
c.panel({
|
|
26
|
-
eyebrow: "Triggers",
|
|
27
|
-
title: "Trigger status",
|
|
28
|
-
sub: "Interval and brain-event triggers armed from saved workflow definitions.",
|
|
29
|
-
children: triggerHost,
|
|
30
|
-
}),
|
|
31
|
-
h("section", c.sectionHead("Workflow definitions", defsSrc), defsHost),
|
|
32
|
-
c.panel({
|
|
33
|
-
head: h("div.lt3-row", { style: { "justify-content": "space-between", width: "100%" } },
|
|
34
|
-
h("div", h("div.lt3-eyebrow", "Activity"), h("h3.lt3-panel__title", "Recent runs")), runsSrc),
|
|
35
|
-
children: runsHost,
|
|
36
|
-
}),
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
loadDefs();
|
|
40
|
-
loadTriggers();
|
|
41
|
-
loadRuns();
|
|
42
|
-
return root;
|
|
43
|
-
|
|
44
|
-
function stageLegend(ctx2) {
|
|
45
|
-
return h("div.lt3-cluster", { style: { gap: "var(--lt3-space-2)" } }, STAGES.map((s, i) =>
|
|
46
|
-
h("div.lt3-row-2", { style: { gap: "var(--lt3-space-2)" } }, c.pill(s, i === STAGES.length - 1 ? "warn" : "info"), i < STAGES.length - 1 ? c.icon("arrow-right") : null)));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function loadDefs() {
|
|
50
|
-
const res = await ctx.api.workflowDefinitions();
|
|
51
|
-
defsSrc.replaceChildren(c.sourceBadge(res.source));
|
|
52
|
-
const defs = normalizeDefs(res.data);
|
|
53
|
-
if (!defs.length) {
|
|
54
|
-
defsHost.replaceChildren(c.emptyState({ icon: "sitemap", title: "No workflows yet", body: res.source === "live" ? "Create a workflow definition to automate an agent chain." : "Start the backend to load workflows." }));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
defsHost.replaceChildren(h("div.lt3-grid-auto", defs.map((w) => defCard(ctx, w))));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function defCard(ctx2, w) {
|
|
61
|
-
const nodes = w.nodes || (w.definition && w.definition.nodes) || [];
|
|
62
|
-
const triggers = nodes.filter((n) => n.type === "trigger").length || 1;
|
|
63
|
-
return c.card(h("div.lt3-stack-3",
|
|
64
|
-
h("div.lt3-row", { style: { "justify-content": "space-between", "align-items": "flex-start" } },
|
|
65
|
-
h("div", h("b", w.name || w.id), h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)", "font-family": "var(--lt3-font-mono)" } }, w.id || "")),
|
|
66
|
-
c.pill(`${nodes.length || 0} nodes`),
|
|
67
|
-
),
|
|
68
|
-
w.description ? h("p.lt3-muted", { style: { margin: 0, "font-size": "var(--lt3-text-sm)" } }, w.description) : null,
|
|
69
|
-
h("div.lt3-cluster", (nodes.slice(0, 6)).map((n) => h("span.lt3-chip", c.icon(nodeIcon(n.type)), n.name || n.type))),
|
|
70
|
-
triggerControls(ctx2, w, nodes),
|
|
71
|
-
h("div.lt3-row-2",
|
|
72
|
-
h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => runDef(ctx2, w) } }, c.icon("player-play"), "Run"),
|
|
73
|
-
h("span.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)" } }, `${triggers} trigger${triggers === 1 ? "" : "s"}`),
|
|
74
|
-
),
|
|
75
|
-
), { interactive: false });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function runDef(ctx2, w) {
|
|
79
|
-
const res = await ctx2.api.runWorkflow(w.id, {});
|
|
80
|
-
ctx2.toast(res && res.ok ? `Ran ${w.name || w.id}` : "Run unavailable", res && res.ok ? "ok" : "err");
|
|
81
|
-
loadRuns();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async function loadRuns() {
|
|
85
|
-
const res = await ctx.api.workflowRuns();
|
|
86
|
-
runsSrc.replaceChildren(c.sourceBadge(res.source));
|
|
87
|
-
const runs = normalizeRuns(res.data);
|
|
88
|
-
if (!runs.length) {
|
|
89
|
-
runsHost.replaceChildren(c.emptyState({ icon: "history-off", title: "No runs yet", body: "Workflow runs will appear here once a workflow executes." }));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
runsHost.replaceChildren(c.table(
|
|
93
|
-
[
|
|
94
|
-
{ key: "status", label: "Status", width: "1%", render: (r) => c.statePill(mapStatus(r.status)) },
|
|
95
|
-
{ key: "name", label: "Workflow", render: (r) => h("b", { style: { "font-size": "var(--lt3-text-sm)" } }, r.workflow_name || r.workflow_id || r.id) },
|
|
96
|
-
{ key: "when", label: "When", width: "1%", render: (r) => h("span.lt3-faint", { style: { "white-space": "nowrap", "font-size": "var(--lt3-text-2xs)" } }, fmtTime(r.created_at || r.started_at)) },
|
|
97
|
-
{ key: "act", label: "", width: "1%", render: (r) => h("div.lt3-row-2",
|
|
98
|
-
isActiveStatus(r.status) ? h("button.lt3-btn.lt3-btn--danger.lt3-btn--sm", { on: { click: () => stop(ctx, r) } }, c.icon("player-stop"), "Stop") : null,
|
|
99
|
-
String(r.status || "").toLowerCase() === "awaiting_approval"
|
|
100
|
-
? h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => decide(ctx, r, true) } }, c.icon("circle-check"), "Approve")
|
|
101
|
-
: null,
|
|
102
|
-
h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => replay(ctx, r) } }, c.icon("player-track-next"), "Replay"),
|
|
103
|
-
) },
|
|
104
|
-
],
|
|
105
|
-
runs.slice(0, 20),
|
|
106
|
-
));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function replay(ctx2, r) {
|
|
110
|
-
const id = r.id || r.run_id;
|
|
111
|
-
const res = await ctx2.api.workflowReplay(id);
|
|
112
|
-
ctx2.toast(res && res.ok ? `Replay ready for ${id}` : "Replay unavailable", res && res.ok ? "ok" : "err");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function stop(ctx2, r) {
|
|
116
|
-
const id = r.id || r.run_id;
|
|
117
|
-
const res = await ctx2.api.stopWorkflowRun(id);
|
|
118
|
-
ctx2.toast(res && res.ok ? `Stop requested for ${id}` : "Stop unavailable", res && res.ok ? "ok" : "err");
|
|
119
|
-
loadRuns();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function decide(ctx2, r, approved) {
|
|
123
|
-
const id = r.id || r.run_id;
|
|
124
|
-
const res = await ctx2.api.resumeWorkflowRun(id, approved);
|
|
125
|
-
ctx2.toast(res && res.ok ? `Decision recorded for ${id}` : "Decision unavailable", res && res.ok ? "ok" : "err");
|
|
126
|
-
loadRuns();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function loadTriggers() {
|
|
130
|
-
const res = await ctx.api.workflowTriggers();
|
|
131
|
-
const armed = Array.isArray(res.data?.armed) ? res.data.armed : [];
|
|
132
|
-
triggerHost.replaceChildren(
|
|
133
|
-
h("div.lt3-stack-3",
|
|
134
|
-
h("div.lt3-row-2", c.sourceBadge(res.source), c.statePill(res.data?.running ? "running" : "idle")),
|
|
135
|
-
armed.length ? c.table([
|
|
136
|
-
{ key: "name", label: "Workflow", render: (r) => h("b", r.name || r.workflow_id) },
|
|
137
|
-
{ key: "kind", label: "Trigger", width: "1%", render: (r) => c.pill(r.kind) },
|
|
138
|
-
{ key: "last", label: "Last fired", width: "1%", render: (r) => fmtTime(r.last_fired_at ? Number(r.last_fired_at) * 1000 : null) },
|
|
139
|
-
{ key: "events", label: "Recent", render: (r) => (r.recent_events || []).slice(-2).map((e) => e.type || e.trigger).join(", ") || "—" },
|
|
140
|
-
], armed) : c.emptyState({ icon: "bolt-off", title: "No triggers armed", body: "Set a workflow trigger below to arm it." }),
|
|
141
|
-
),
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function triggerControls(ctx2, w, nodes) {
|
|
146
|
-
const trigger = nodes.find((n) => n.type === "trigger") || {};
|
|
147
|
-
const cfg = trigger.config || {};
|
|
148
|
-
const kind = h("select.lt3-select", { "aria-label": "Trigger type" },
|
|
149
|
-
["manual", "interval", "brain_event"].map((value) => h("option", { value, selected: String(cfg.trigger || "manual") === value }, value)));
|
|
150
|
-
const seconds = h("input.lt3-input", { type: "number", min: "60", step: "60", value: String(cfg.interval_seconds || 300), "aria-label": "Interval seconds" });
|
|
151
|
-
const sourceType = h("input.lt3-input", { type: "text", value: cfg.source_type || "", placeholder: "source_type", "aria-label": "source_type" });
|
|
152
|
-
async function save() {
|
|
153
|
-
const updated = ensureTrigger(nodes, kind.value, Number(seconds.value) || 300, sourceType.value.trim());
|
|
154
|
-
const res = await ctx2.api.updateWorkflow(w.id, { nodes: updated, metadata: { trigger_updated_at: new Date().toISOString() } });
|
|
155
|
-
ctx2.toast(res && res.ok ? "Trigger saved" : "Trigger update unavailable", res && res.ok ? "ok" : "err");
|
|
156
|
-
if (res && res.ok) { loadDefs(); loadTriggers(); }
|
|
157
|
-
}
|
|
158
|
-
return h("div.lt3-stack-2",
|
|
159
|
-
h("div.lt3-eyebrow", "Trigger configuration"),
|
|
160
|
-
h("div.lt3-row-2", kind, seconds, sourceType, h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", { on: { click: save } }, c.icon("device-floppy"), "Save")),
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function ensureTrigger(nodes, triggerKind, intervalSeconds, sourceType) {
|
|
166
|
-
const clone = (nodes || []).map((node) => ({ ...node, config: { ...(node.config || {}) } }));
|
|
167
|
-
let trigger = clone.find((n) => n.type === "trigger");
|
|
168
|
-
if (!trigger) {
|
|
169
|
-
trigger = { id: "trigger", type: "trigger", name: "Trigger", next: clone[0]?.id || "output", config: {} };
|
|
170
|
-
clone.unshift(trigger);
|
|
171
|
-
if (!clone.some((n) => n.id === "output")) clone.push({ id: "output", type: "output", name: "Output", config: {}, next: null });
|
|
172
|
-
}
|
|
173
|
-
trigger.config.trigger = triggerKind;
|
|
174
|
-
delete trigger.config.interval_seconds;
|
|
175
|
-
delete trigger.config.source_type;
|
|
176
|
-
if (triggerKind === "interval") trigger.config.interval_seconds = Math.max(60, intervalSeconds || 300);
|
|
177
|
-
if (triggerKind === "brain_event" && sourceType) trigger.config.source_type = sourceType;
|
|
178
|
-
return clone;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function normalizeDefs(data) {
|
|
182
|
-
if (!data) return [];
|
|
183
|
-
if (Array.isArray(data.workflows)) return data.workflows;
|
|
184
|
-
if (Array.isArray(data.definitions)) return data.definitions;
|
|
185
|
-
if (Array.isArray(data)) return data;
|
|
186
|
-
return [];
|
|
187
|
-
}
|
|
188
|
-
function normalizeRuns(data) {
|
|
189
|
-
if (!data) return [];
|
|
190
|
-
if (Array.isArray(data.runs)) return data.runs;
|
|
191
|
-
if (Array.isArray(data)) return data;
|
|
192
|
-
return [];
|
|
193
|
-
}
|
|
194
|
-
function nodeIcon(type) {
|
|
195
|
-
return { trigger: "bolt", agent: "robot", plugin: "puzzle", tool: "tool", output: "flag", memory: "brain" }[type] || "point";
|
|
196
|
-
}
|
|
197
|
-
function mapStatus(s) {
|
|
198
|
-
const v = String(s || "").toLowerCase();
|
|
199
|
-
if (v === "ok" || v === "completed" || v === "success") return "ready";
|
|
200
|
-
if (v === "failed" || v === "error") return "failed";
|
|
201
|
-
if (v === "running" || v === "queued" || v === "cancelling") return "active";
|
|
202
|
-
if (v === "awaiting_approval") return "pending";
|
|
203
|
-
if (v === "cancelled" || v === "interrupted") return "warn";
|
|
204
|
-
return v || "idle";
|
|
205
|
-
}
|
|
206
|
-
function isActiveStatus(status) {
|
|
207
|
-
return ["running", "queued", "in_progress", "cancelling"].includes(String(status || "").toLowerCase());
|
|
208
|
-
}
|
|
209
|
-
function fmtTime(ts) {
|
|
210
|
-
if (!ts) return "—";
|
|
211
|
-
try { const d = new Date(ts); return Number.isNaN(d.getTime()) ? String(ts) : d.toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }); }
|
|
212
|
-
catch { return String(ts); }
|
|
213
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { t } from "../core/i18n.880e1fec.js";
|
|
2
|
-
|
|
3
|
-
export async function render(ctx) {
|
|
4
|
-
const { h, icon, api, store, c, toast } = ctx;
|
|
5
|
-
const host = h("div.lt3-stack-6", c.loading({ lines: 5, block: true }));
|
|
6
|
-
|
|
7
|
-
async function load() {
|
|
8
|
-
const [registry, invites] = await Promise.all([api.workspaceRegistry(), api.invitations()]);
|
|
9
|
-
const data = registry.data || {};
|
|
10
|
-
const workspaces = Array.isArray(data.workspaces) ? data.workspaces : [];
|
|
11
|
-
store.setWorkspaces(workspaces.length ? workspaces : store.get().workspaces);
|
|
12
|
-
host.replaceChildren(
|
|
13
|
-
c.viewHeader({
|
|
14
|
-
eyebrow: t("workspace.eyebrow"),
|
|
15
|
-
title: t("workspace.title"),
|
|
16
|
-
sub: t("workspace.sub"),
|
|
17
|
-
actions: [c.sourceBadge(registry.source)],
|
|
18
|
-
}),
|
|
19
|
-
createOrgPanel(),
|
|
20
|
-
workspaceGrid(workspaces, data),
|
|
21
|
-
invitationsPanel(invites),
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function createOrgPanel() {
|
|
26
|
-
const name = h("input.lt3-input", { type: "text", "aria-label": t("workspace.orgName"), placeholder: t("workspace.orgName") });
|
|
27
|
-
return c.panel({
|
|
28
|
-
title: t("workspace.createOrg"),
|
|
29
|
-
children: h("div.lt3-row-2",
|
|
30
|
-
name,
|
|
31
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: async () => {
|
|
32
|
-
if (!name.value.trim()) return;
|
|
33
|
-
const res = await api.createOrg(name.value.trim());
|
|
34
|
-
toast(resultText(res, t("workspace.createOrg")), res.ok ? "ok" : "err");
|
|
35
|
-
if (res.ok) { name.value = ""; load(); }
|
|
36
|
-
} } }, icon("plus"), t("workspace.createOrg")),
|
|
37
|
-
),
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function workspaceGrid(workspaces, registry) {
|
|
42
|
-
if (!workspaces.length) {
|
|
43
|
-
return c.emptyState({ icon: "building-community", title: t("workspace.title"), body: t("common.unavailable") });
|
|
44
|
-
}
|
|
45
|
-
return h("div.lt3-grid-auto", workspaces.map((ws) => workspaceCard(ws, registry)));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function workspaceCard(ws, registry) {
|
|
49
|
-
const roleOptions = registry.roles || ["owner", "admin", "member", "viewer"];
|
|
50
|
-
const members = Array.isArray(ws.members) ? ws.members : [];
|
|
51
|
-
const userId = h("input.lt3-input", { type: "text", placeholder: t("workspace.userId"), "aria-label": t("workspace.userId") });
|
|
52
|
-
const role = h("select.lt3-select", { "aria-label": t("common.role") }, roleOptions.map((r) => h("option", { value: r }, roleLabel(r))));
|
|
53
|
-
return c.card(h("div.lt3-stack-4",
|
|
54
|
-
h("div.lt3-row", { style: { "justify-content": "space-between", "align-items": "flex-start", gap: "var(--lt3-space-3)" } },
|
|
55
|
-
h("div",
|
|
56
|
-
h("b", ws.name || ws.workspace_id),
|
|
57
|
-
h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)", "font-family": "var(--lt3-font-mono)" } }, ws.workspace_id),
|
|
58
|
-
),
|
|
59
|
-
c.pill(roleLabel(ws.your_role || "member"), "info"),
|
|
60
|
-
),
|
|
61
|
-
h("dl.lt3-keyval",
|
|
62
|
-
h("dt", t("common.type")), h("dd", ws.type || "—"),
|
|
63
|
-
h("dt", t("common.status")), h("dd", c.statePill(ws.status || "active")),
|
|
64
|
-
h("dt", t("workspace.members")), h("dd", String(ws.member_count ?? members.length)),
|
|
65
|
-
),
|
|
66
|
-
h("div.lt3-row-2",
|
|
67
|
-
h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", { on: { click: () => activate(ws.workspace_id) } }, icon("selector"), t("workspace.activate")),
|
|
68
|
-
ws.type === "organization" ? h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => archive(ws.workspace_id) } }, icon("archive"), t("workspace.archive")) : null,
|
|
69
|
-
),
|
|
70
|
-
ws.type === "organization" ? h("div.lt3-stack-3",
|
|
71
|
-
h("div.lt3-eyebrow", t("workspace.members")),
|
|
72
|
-
members.length ? c.table([
|
|
73
|
-
{ key: "user", label: t("workspace.userId"), render: (m) => h("span.lt3-mono", m.user_id || "—") },
|
|
74
|
-
{ key: "role", label: t("common.role"), width: "1%", render: (m) => c.pill(roleLabel(m.role)) },
|
|
75
|
-
{ key: "act", label: "", width: "1%", render: (m) => h("button.lt3-iconbtn.lt3-iconbtn--sm", { "aria-label": t("common.cancel"), on: { click: () => removeMember(ws.workspace_id, m.user_id) } }, icon("trash")) },
|
|
76
|
-
], members) : c.emptyState({ icon: "users", title: t("workspace.members"), body: t("common.none") }),
|
|
77
|
-
h("div.lt3-row-2", userId, role, h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => addMember(ws.workspace_id, userId.value.trim(), role.value) } }, icon("user-plus"), t("workspace.addMember"))),
|
|
78
|
-
) : null,
|
|
79
|
-
), { interactive: false });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function invitationsPanel(invites) {
|
|
83
|
-
const rows = Array.isArray(invites.data?.invitations) ? invites.data.invitations : [];
|
|
84
|
-
const email = h("input.lt3-input", { type: "email", placeholder: t("workspace.inviteEmail"), "aria-label": t("workspace.inviteEmail") });
|
|
85
|
-
const workspace = h("input.lt3-input", { type: "text", placeholder: "workspace_id", "aria-label": "workspace_id", value: store.get().workspaceId || "personal" });
|
|
86
|
-
const role = h("select.lt3-select", { "aria-label": t("common.role") },
|
|
87
|
-
["member", "viewer", "admin"].map((r) => h("option", { value: r }, roleLabel(r))));
|
|
88
|
-
const token = h("input.lt3-input", { type: "text", placeholder: t("workspace.inviteToken"), "aria-label": t("workspace.inviteToken") });
|
|
89
|
-
|
|
90
|
-
return c.panel({
|
|
91
|
-
title: t("workspace.invitations"),
|
|
92
|
-
actions: [c.sourceBadge(invites.source)],
|
|
93
|
-
children: h("div.lt3-stack-4",
|
|
94
|
-
h("div.lt3-grid-2",
|
|
95
|
-
h("div.lt3-field", h("label.lt3-label", t("workspace.inviteEmail")), email),
|
|
96
|
-
h("div.lt3-field", h("label.lt3-label", "workspace_id"), workspace),
|
|
97
|
-
),
|
|
98
|
-
h("div.lt3-row-2", role, h("button.lt3-btn.lt3-btn--primary", { on: { click: () => createInvite(email.value.trim(), workspace.value.trim(), role.value) } }, icon("mail-plus"), t("workspace.invitations"))),
|
|
99
|
-
rows.length ? c.table([
|
|
100
|
-
{ key: "email", label: t("account.email"), render: (r) => r.email || "—" },
|
|
101
|
-
{ key: "role", label: t("common.role"), width: "1%", render: (r) => c.pill(roleLabel(r.role)) },
|
|
102
|
-
{ key: "token", label: t("workspace.inviteToken"), render: (r) => h("span.lt3-mono", r.token || r.id || "—") },
|
|
103
|
-
{ key: "status", label: t("common.status"), width: "1%", render: (r) => c.statePill(r.status || (r.accepted_at ? "ready" : "pending")) },
|
|
104
|
-
], rows.slice(0, 20)) : c.emptyState({ icon: "mail", title: t("workspace.invitations"), body: t("common.none") }),
|
|
105
|
-
h("hr.lt3-divider"),
|
|
106
|
-
h("div.lt3-row-2", token, h("button.lt3-btn.lt3-btn--ghost", { on: { click: () => acceptInvite(token.value.trim()) } }, icon("circle-check"), t("workspace.acceptInvite"))),
|
|
107
|
-
),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function activate(workspace_id) {
|
|
112
|
-
const res = await api.activateWorkspace(workspace_id);
|
|
113
|
-
toast(resultText(res, t("workspace.activated")), res.ok ? "ok" : "err");
|
|
114
|
-
if (res.ok) { store.setWorkspace(workspace_id); load(); }
|
|
115
|
-
}
|
|
116
|
-
async function archive(workspace_id) {
|
|
117
|
-
const res = await api.archiveWorkspace(workspace_id);
|
|
118
|
-
toast(resultText(res, t("workspace.archived")), res.ok ? "ok" : "err");
|
|
119
|
-
if (res.ok) load();
|
|
120
|
-
}
|
|
121
|
-
async function addMember(workspace_id, user_id, role) {
|
|
122
|
-
if (!user_id) return;
|
|
123
|
-
const res = await api.addWorkspaceMember(workspace_id, user_id, role);
|
|
124
|
-
toast(resultText(res, t("workspace.memberAdded")), res.ok ? "ok" : "err");
|
|
125
|
-
if (res.ok) load();
|
|
126
|
-
}
|
|
127
|
-
async function removeMember(workspace_id, user_id) {
|
|
128
|
-
const res = await api.removeWorkspaceMember(workspace_id, user_id);
|
|
129
|
-
toast(resultText(res, t("workspace.memberAdded")), res.ok ? "ok" : "err");
|
|
130
|
-
if (res.ok) load();
|
|
131
|
-
}
|
|
132
|
-
async function createInvite(email, workspace_id, role) {
|
|
133
|
-
const res = await api.createInvitation({ email: email || null, workspace_id: workspace_id || null, role, expires_hours: 168 });
|
|
134
|
-
toast(resultText(res, t("workspace.inviteCreated")), res.ok ? "ok" : "err");
|
|
135
|
-
if (res.ok) load();
|
|
136
|
-
}
|
|
137
|
-
async function acceptInvite(token) {
|
|
138
|
-
if (!token) return;
|
|
139
|
-
const res = await api.acceptInvitation(token);
|
|
140
|
-
toast(resultText(res, t("workspace.inviteAccepted")), res.ok ? "ok" : "err");
|
|
141
|
-
if (res.ok) load();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
await load();
|
|
145
|
-
return host;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function roleLabel(role) {
|
|
149
|
-
return t(`common.${String(role || "member")}`) || String(role || "member");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function resultText(res, okText) {
|
|
153
|
-
if (res && res.ok) return okText;
|
|
154
|
-
const data = (res && res.data) || {};
|
|
155
|
-
return String(data.detail || data.error || res?.error || t("common.unavailable"));
|
|
156
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { t } from "../core/i18n.js";
|
|
2
|
-
|
|
3
|
-
export async function render(ctx) {
|
|
4
|
-
const { h, icon, api, store, c, toast } = ctx;
|
|
5
|
-
const host = h("div.lt3-stack-6", c.loading({ lines: 5, block: true }));
|
|
6
|
-
|
|
7
|
-
async function load() {
|
|
8
|
-
const [registry, invites] = await Promise.all([api.workspaceRegistry(), api.invitations()]);
|
|
9
|
-
const data = registry.data || {};
|
|
10
|
-
const workspaces = Array.isArray(data.workspaces) ? data.workspaces : [];
|
|
11
|
-
store.setWorkspaces(workspaces.length ? workspaces : store.get().workspaces);
|
|
12
|
-
host.replaceChildren(
|
|
13
|
-
c.viewHeader({
|
|
14
|
-
eyebrow: t("workspace.eyebrow"),
|
|
15
|
-
title: t("workspace.title"),
|
|
16
|
-
sub: t("workspace.sub"),
|
|
17
|
-
actions: [c.sourceBadge(registry.source)],
|
|
18
|
-
}),
|
|
19
|
-
createOrgPanel(),
|
|
20
|
-
workspaceGrid(workspaces, data),
|
|
21
|
-
invitationsPanel(invites),
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function createOrgPanel() {
|
|
26
|
-
const name = h("input.lt3-input", { type: "text", "aria-label": t("workspace.orgName"), placeholder: t("workspace.orgName") });
|
|
27
|
-
return c.panel({
|
|
28
|
-
title: t("workspace.createOrg"),
|
|
29
|
-
children: h("div.lt3-row-2",
|
|
30
|
-
name,
|
|
31
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: async () => {
|
|
32
|
-
if (!name.value.trim()) return;
|
|
33
|
-
const res = await api.createOrg(name.value.trim());
|
|
34
|
-
toast(resultText(res, t("workspace.createOrg")), res.ok ? "ok" : "err");
|
|
35
|
-
if (res.ok) { name.value = ""; load(); }
|
|
36
|
-
} } }, icon("plus"), t("workspace.createOrg")),
|
|
37
|
-
),
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function workspaceGrid(workspaces, registry) {
|
|
42
|
-
if (!workspaces.length) {
|
|
43
|
-
return c.emptyState({ icon: "building-community", title: t("workspace.title"), body: t("common.unavailable") });
|
|
44
|
-
}
|
|
45
|
-
return h("div.lt3-grid-auto", workspaces.map((ws) => workspaceCard(ws, registry)));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function workspaceCard(ws, registry) {
|
|
49
|
-
const roleOptions = registry.roles || ["owner", "admin", "member", "viewer"];
|
|
50
|
-
const members = Array.isArray(ws.members) ? ws.members : [];
|
|
51
|
-
const userId = h("input.lt3-input", { type: "text", placeholder: t("workspace.userId"), "aria-label": t("workspace.userId") });
|
|
52
|
-
const role = h("select.lt3-select", { "aria-label": t("common.role") }, roleOptions.map((r) => h("option", { value: r }, roleLabel(r))));
|
|
53
|
-
return c.card(h("div.lt3-stack-4",
|
|
54
|
-
h("div.lt3-row", { style: { "justify-content": "space-between", "align-items": "flex-start", gap: "var(--lt3-space-3)" } },
|
|
55
|
-
h("div",
|
|
56
|
-
h("b", ws.name || ws.workspace_id),
|
|
57
|
-
h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)", "font-family": "var(--lt3-font-mono)" } }, ws.workspace_id),
|
|
58
|
-
),
|
|
59
|
-
c.pill(roleLabel(ws.your_role || "member"), "info"),
|
|
60
|
-
),
|
|
61
|
-
h("dl.lt3-keyval",
|
|
62
|
-
h("dt", t("common.type")), h("dd", ws.type || "—"),
|
|
63
|
-
h("dt", t("common.status")), h("dd", c.statePill(ws.status || "active")),
|
|
64
|
-
h("dt", t("workspace.members")), h("dd", String(ws.member_count ?? members.length)),
|
|
65
|
-
),
|
|
66
|
-
h("div.lt3-row-2",
|
|
67
|
-
h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", { on: { click: () => activate(ws.workspace_id) } }, icon("selector"), t("workspace.activate")),
|
|
68
|
-
ws.type === "organization" ? h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => archive(ws.workspace_id) } }, icon("archive"), t("workspace.archive")) : null,
|
|
69
|
-
),
|
|
70
|
-
ws.type === "organization" ? h("div.lt3-stack-3",
|
|
71
|
-
h("div.lt3-eyebrow", t("workspace.members")),
|
|
72
|
-
members.length ? c.table([
|
|
73
|
-
{ key: "user", label: t("workspace.userId"), render: (m) => h("span.lt3-mono", m.user_id || "—") },
|
|
74
|
-
{ key: "role", label: t("common.role"), width: "1%", render: (m) => c.pill(roleLabel(m.role)) },
|
|
75
|
-
{ key: "act", label: "", width: "1%", render: (m) => h("button.lt3-iconbtn.lt3-iconbtn--sm", { "aria-label": t("common.cancel"), on: { click: () => removeMember(ws.workspace_id, m.user_id) } }, icon("trash")) },
|
|
76
|
-
], members) : c.emptyState({ icon: "users", title: t("workspace.members"), body: t("common.none") }),
|
|
77
|
-
h("div.lt3-row-2", userId, role, h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => addMember(ws.workspace_id, userId.value.trim(), role.value) } }, icon("user-plus"), t("workspace.addMember"))),
|
|
78
|
-
) : null,
|
|
79
|
-
), { interactive: false });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function invitationsPanel(invites) {
|
|
83
|
-
const rows = Array.isArray(invites.data?.invitations) ? invites.data.invitations : [];
|
|
84
|
-
const email = h("input.lt3-input", { type: "email", placeholder: t("workspace.inviteEmail"), "aria-label": t("workspace.inviteEmail") });
|
|
85
|
-
const workspace = h("input.lt3-input", { type: "text", placeholder: "workspace_id", "aria-label": "workspace_id", value: store.get().workspaceId || "personal" });
|
|
86
|
-
const role = h("select.lt3-select", { "aria-label": t("common.role") },
|
|
87
|
-
["member", "viewer", "admin"].map((r) => h("option", { value: r }, roleLabel(r))));
|
|
88
|
-
const token = h("input.lt3-input", { type: "text", placeholder: t("workspace.inviteToken"), "aria-label": t("workspace.inviteToken") });
|
|
89
|
-
|
|
90
|
-
return c.panel({
|
|
91
|
-
title: t("workspace.invitations"),
|
|
92
|
-
actions: [c.sourceBadge(invites.source)],
|
|
93
|
-
children: h("div.lt3-stack-4",
|
|
94
|
-
h("div.lt3-grid-2",
|
|
95
|
-
h("div.lt3-field", h("label.lt3-label", t("workspace.inviteEmail")), email),
|
|
96
|
-
h("div.lt3-field", h("label.lt3-label", "workspace_id"), workspace),
|
|
97
|
-
),
|
|
98
|
-
h("div.lt3-row-2", role, h("button.lt3-btn.lt3-btn--primary", { on: { click: () => createInvite(email.value.trim(), workspace.value.trim(), role.value) } }, icon("mail-plus"), t("workspace.invitations"))),
|
|
99
|
-
rows.length ? c.table([
|
|
100
|
-
{ key: "email", label: t("account.email"), render: (r) => r.email || "—" },
|
|
101
|
-
{ key: "role", label: t("common.role"), width: "1%", render: (r) => c.pill(roleLabel(r.role)) },
|
|
102
|
-
{ key: "token", label: t("workspace.inviteToken"), render: (r) => h("span.lt3-mono", r.token || r.id || "—") },
|
|
103
|
-
{ key: "status", label: t("common.status"), width: "1%", render: (r) => c.statePill(r.status || (r.accepted_at ? "ready" : "pending")) },
|
|
104
|
-
], rows.slice(0, 20)) : c.emptyState({ icon: "mail", title: t("workspace.invitations"), body: t("common.none") }),
|
|
105
|
-
h("hr.lt3-divider"),
|
|
106
|
-
h("div.lt3-row-2", token, h("button.lt3-btn.lt3-btn--ghost", { on: { click: () => acceptInvite(token.value.trim()) } }, icon("circle-check"), t("workspace.acceptInvite"))),
|
|
107
|
-
),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function activate(workspace_id) {
|
|
112
|
-
const res = await api.activateWorkspace(workspace_id);
|
|
113
|
-
toast(resultText(res, t("workspace.activated")), res.ok ? "ok" : "err");
|
|
114
|
-
if (res.ok) { store.setWorkspace(workspace_id); load(); }
|
|
115
|
-
}
|
|
116
|
-
async function archive(workspace_id) {
|
|
117
|
-
const res = await api.archiveWorkspace(workspace_id);
|
|
118
|
-
toast(resultText(res, t("workspace.archived")), res.ok ? "ok" : "err");
|
|
119
|
-
if (res.ok) load();
|
|
120
|
-
}
|
|
121
|
-
async function addMember(workspace_id, user_id, role) {
|
|
122
|
-
if (!user_id) return;
|
|
123
|
-
const res = await api.addWorkspaceMember(workspace_id, user_id, role);
|
|
124
|
-
toast(resultText(res, t("workspace.memberAdded")), res.ok ? "ok" : "err");
|
|
125
|
-
if (res.ok) load();
|
|
126
|
-
}
|
|
127
|
-
async function removeMember(workspace_id, user_id) {
|
|
128
|
-
const res = await api.removeWorkspaceMember(workspace_id, user_id);
|
|
129
|
-
toast(resultText(res, t("workspace.memberAdded")), res.ok ? "ok" : "err");
|
|
130
|
-
if (res.ok) load();
|
|
131
|
-
}
|
|
132
|
-
async function createInvite(email, workspace_id, role) {
|
|
133
|
-
const res = await api.createInvitation({ email: email || null, workspace_id: workspace_id || null, role, expires_hours: 168 });
|
|
134
|
-
toast(resultText(res, t("workspace.inviteCreated")), res.ok ? "ok" : "err");
|
|
135
|
-
if (res.ok) load();
|
|
136
|
-
}
|
|
137
|
-
async function acceptInvite(token) {
|
|
138
|
-
if (!token) return;
|
|
139
|
-
const res = await api.acceptInvitation(token);
|
|
140
|
-
toast(resultText(res, t("workspace.inviteAccepted")), res.ok ? "ok" : "err");
|
|
141
|
-
if (res.ok) load();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
await load();
|
|
145
|
-
return host;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function roleLabel(role) {
|
|
149
|
-
return t(`common.${String(role || "member")}`) || String(role || "member");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function resultText(res, okText) {
|
|
153
|
-
if (res && res.ok) return okText;
|
|
154
|
-
const data = (res && res.data) || {};
|
|
155
|
-
return String(data.detail || data.error || res?.error || t("common.unavailable"));
|
|
156
|
-
}
|