ltcai 1.7.0 → 2.0.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.
@@ -0,0 +1,64 @@
1
+ // Lattice AI v2.0 — shared helpers for the Agentic Workspace Platform pages.
2
+ export const NAV = [
3
+ { href: "/workspace", label: "Dashboard" },
4
+ { href: "/plugins/sdk", label: "Plugins" },
5
+ { href: "/workflows", label: "Workflows" },
6
+ { href: "/agents", label: "Agents" },
7
+ { href: "/activity", label: "Activity" },
8
+ { href: "/chat", label: "Chat" },
9
+ ];
10
+
11
+ export function mountHeader(active) {
12
+ const links = NAV.map(
13
+ (n) => `<a href="${n.href}" class="${n.href === active ? "active" : ""}">${n.label}</a>`
14
+ ).join("");
15
+ document.body.insertAdjacentHTML(
16
+ "afterbegin",
17
+ `<header class="app"><div class="brand">Lattice AI<small>v2.0 Platform</small></div><nav>${links}</nav></header>`
18
+ );
19
+ }
20
+
21
+ export async function api(path, opts = {}) {
22
+ const res = await fetch(path, {
23
+ headers: { "Content-Type": "application/json" },
24
+ credentials: "same-origin",
25
+ ...opts,
26
+ });
27
+ if (res.status === 401) {
28
+ location.href = "/account";
29
+ throw new Error("unauthorized");
30
+ }
31
+ const text = await res.text();
32
+ let body;
33
+ try { body = text ? JSON.parse(text) : {}; } catch { body = { raw: text }; }
34
+ if (!res.ok) {
35
+ const detail = body && body.detail ? (typeof body.detail === "string" ? body.detail : JSON.stringify(body.detail)) : res.statusText;
36
+ throw new Error(detail);
37
+ }
38
+ return body;
39
+ }
40
+
41
+ export function el(html) {
42
+ const t = document.createElement("template");
43
+ t.innerHTML = html.trim();
44
+ return t.content.firstElementChild;
45
+ }
46
+
47
+ export function escapeHtml(s) {
48
+ return String(s == null ? "" : s).replace(/[&<>"']/g, (c) =>
49
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c])
50
+ );
51
+ }
52
+
53
+ export function toast(msg) {
54
+ const node = el(`<div class="toast">${escapeHtml(msg)}</div>`);
55
+ document.body.appendChild(node);
56
+ setTimeout(() => node.remove(), 4000);
57
+ }
58
+
59
+ export function badge(status) {
60
+ const cls = { ok: "ok", ready: "ok", valid: "ok", retried_ok: "ok",
61
+ partial: "warn", retry: "warn", skipped: "warn", available: "warn",
62
+ failed: "err", error: "err", blocked: "err" }[status] || "";
63
+ return `<span class="badge ${cls}">${escapeHtml(status || "?")}</span>`;
64
+ }
@@ -0,0 +1,121 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Workflow Designer — Lattice AI</title>
7
+ <link rel="stylesheet" href="/static/platform.css" />
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <h1>Workflow Designer</h1>
12
+ <p class="sub">Compose triggers, tools, skills, plugins, agents, conditions, and outputs into runnable workflows.</p>
13
+
14
+ <div class="row">
15
+ <button id="newBtn">+ New from template</button>
16
+ <button class="ghost" id="validateBtn">Validate</button>
17
+ <button class="ghost" id="saveBtn">Save</button>
18
+ <div class="spacer"></div>
19
+ </div>
20
+
21
+ <div class="section">
22
+ <label>Workflow name</label>
23
+ <input id="wfName" value="My workflow" />
24
+ <label>Nodes (JSON) — trigger → … → output</label>
25
+ <textarea id="wfNodes" spellcheck="false" style="min-height:240px"></textarea>
26
+ <pre id="validateOut" style="display:none"></pre>
27
+ </div>
28
+
29
+ <div class="section">
30
+ <h3>Saved workflows</h3>
31
+ <div id="list" class="grid"><div class="empty">Loading…</div></div>
32
+ </div>
33
+
34
+ <div class="section">
35
+ <h3>Run history</h3>
36
+ <div id="runs"><div class="empty">No runs yet.</div></div>
37
+ </div>
38
+ </main>
39
+
40
+ <script type="module">
41
+ import { mountHeader, api, escapeHtml, badge, toast } from "/static/scripts/platform.js";
42
+ mountHeader("/workflows");
43
+
44
+ const TEMPLATE = [
45
+ { id: "trigger", type: "trigger", name: "Manual start", config: { trigger: "manual" }, next: "review" },
46
+ { id: "review", type: "agent", name: "Multi-agent review", config: { goal: "Review the latest workspace changes", roles: ["planner", "executor", "reviewer"] }, next: "decide" },
47
+ { id: "decide", type: "condition", name: "Passed?", config: { left: "last_output", op: "truthy" }, branches: { true: "done", false: "done" } },
48
+ { id: "done", type: "output", name: "Output", config: {}, next: null },
49
+ ];
50
+
51
+ function loadTemplate() {
52
+ document.getElementById("wfName").value = "My workflow";
53
+ document.getElementById("wfNodes").value = JSON.stringify(TEMPLATE, null, 2);
54
+ }
55
+
56
+ function readNodes() { return JSON.parse(document.getElementById("wfNodes").value); }
57
+
58
+ document.getElementById("newBtn").addEventListener("click", loadTemplate);
59
+
60
+ document.getElementById("validateBtn").addEventListener("click", async () => {
61
+ const out = document.getElementById("validateOut");
62
+ out.style.display = "block";
63
+ try {
64
+ const res = await api("/workflows/api/validate", { method: "POST", body: JSON.stringify({ name: document.getElementById("wfName").value, nodes: readNodes() }) });
65
+ out.textContent = res.ok ? "✓ Valid workflow" : "Errors:\n" + res.errors.join("\n");
66
+ } catch (err) { out.textContent = "Error: " + err.message; }
67
+ });
68
+
69
+ document.getElementById("saveBtn").addEventListener("click", async () => {
70
+ try {
71
+ await api("/workflows/api/definitions", { method: "POST", body: JSON.stringify({ name: document.getElementById("wfName").value, nodes: readNodes() }) });
72
+ toast("Workflow saved"); await loadList();
73
+ } catch (err) { toast(err.message); }
74
+ });
75
+
76
+ async function loadList() {
77
+ const data = await api("/workflows/api/definitions");
78
+ const list = document.getElementById("list");
79
+ const items = data.workflows || [];
80
+ if (!items.length) { list.innerHTML = `<div class="empty">No workflows yet.</div>`; return; }
81
+ list.innerHTML = items.map((w) => `
82
+ <div class="card">
83
+ <h3>${escapeHtml(w.name)}</h3>
84
+ <div class="meta">${(w.nodes||w.steps||[]).length} node(s) · ${escapeHtml(w.updated_at||w.created_at||"")}</div>
85
+ <div class="row" style="margin-top:12px">
86
+ <button data-run="${w.id}">Run</button>
87
+ <a class="btn ghost" href="/workflows/api/export/${w.id}" target="_blank">Export</a>
88
+ </div>
89
+ </div>`).join("");
90
+ }
91
+
92
+ document.getElementById("list").addEventListener("click", async (e) => {
93
+ const btn = e.target.closest("button[data-run]");
94
+ if (!btn) return;
95
+ btn.disabled = true;
96
+ try {
97
+ const res = await api(`/workflows/api/definitions/${btn.dataset.run}/run`, { method: "POST", body: JSON.stringify({ inputs: {} }) });
98
+ toast(`Run ${res.result.status} · ${res.result.step_count} steps`);
99
+ await loadRuns();
100
+ } catch (err) { toast(err.message); } finally { btn.disabled = false; }
101
+ });
102
+
103
+ async function loadRuns() {
104
+ const data = await api("/workflows/api/runs");
105
+ const runs = data.runs || [];
106
+ const box = document.getElementById("runs");
107
+ if (!runs.length) { box.innerHTML = `<div class="empty">No runs yet.</div>`; return; }
108
+ box.innerHTML = runs.map((r) => `
109
+ <div class="card" style="margin-bottom:10px">
110
+ <div class="row"><h3>${escapeHtml(r.name)}</h3><div class="spacer"></div>${badge(r.status)}</div>
111
+ <div class="meta">${escapeHtml(r.created_at)} · ${ (r.timeline||[]).length } steps</div>
112
+ <pre>${escapeHtml(JSON.stringify(r.timeline, null, 2))}</pre>
113
+ </div>`).join("");
114
+ }
115
+
116
+ loadTemplate();
117
+ loadList().catch((e) => toast(e.message));
118
+ loadRuns().catch(() => {});
119
+ </script>
120
+ </body>
121
+ </html>
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32.png">
9
9
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap">
10
10
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
11
- <link rel="stylesheet" href="/static/workspace.css?v=1.7.0">
11
+ <link rel="stylesheet" href="/static/workspace.css?v=2.0.0">
12
12
  </head>
13
13
  <body>
14
14
  <div class="workspace-shell">
@@ -31,6 +31,10 @@
31
31
  <a href="#enterprise"><i class="ti ti-building-skyscraper"></i><span>Editions</span></a>
32
32
  </nav>
33
33
  <div class="rail-links">
34
+ <a href="/plugins/sdk"><i class="ti ti-plug"></i><span>Plugins</span></a>
35
+ <a href="/workflows"><i class="ti ti-sitemap"></i><span>Designer</span></a>
36
+ <a href="/agents"><i class="ti ti-robot"></i><span>Agents</span></a>
37
+ <a href="/activity"><i class="ti ti-broadcast"></i><span>Activity</span></a>
34
38
  <a href="/chat"><i class="ti ti-message-circle"></i><span>Chat</span></a>
35
39
  <a href="/graph"><i class="ti ti-network"></i><span>Graph Canvas</span></a>
36
40
  <a href="/admin"><i class="ti ti-shield-lock"></i><span>Admin</span></a>