ltcai 1.5.0 → 1.6.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 +52 -33
- package/docs/CHANGELOG.md +47 -0
- package/docs/images/enterprise.png +0 -0
- package/docs/images/graph.png +0 -0
- package/docs/images/hero.gif +0 -0
- package/docs/images/model-recommendation.png +0 -0
- package/docs/images/onboarding.png +0 -0
- package/docs/images/organization.png +0 -0
- package/docs/images/skills.png +0 -0
- package/docs/images/tmp_frames/hero_00.png +0 -0
- package/docs/images/tmp_frames/hero_01.png +0 -0
- package/docs/images/tmp_frames/hero_02.png +0 -0
- package/docs/images/tmp_frames/hero_03.png +0 -0
- package/docs/images/workspace.png +0 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/package.json +1 -1
- package/static/scripts/chat.js +28 -7
- package/static/scripts/workspace.js +260 -18
- package/static/workspace.css +67 -0
- package/static/workspace.html +83 -2
package/README.md
CHANGED
|
@@ -155,42 +155,56 @@ See [docs/architecture.md](docs/architecture.md) for request and data-flow detai
|
|
|
155
155
|
</tr>
|
|
156
156
|
</table>
|
|
157
157
|
|
|
158
|
-
>
|
|
159
|
-
>
|
|
158
|
+
> Every image in this section is a **real screenshot** of the running app
|
|
159
|
+
> (Lattice AI v1.6.0), captured with a headless browser.
|
|
160
160
|
|
|
161
161
|
---
|
|
162
162
|
|
|
163
163
|
## Product Experience
|
|
164
164
|
|
|
165
|
-
###
|
|
165
|
+
### Onboard in minutes
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
**Recommended**, **Compatible**, or **Not
|
|
169
|
-
by family (Gemma, Qwen, Llama, Phi,
|
|
167
|
+
A first run detects your OS, CPU, GPU, RAM, and disk, then recommends a local
|
|
168
|
+
model and rates every option **Recommended**, **Compatible**, or **Not
|
|
169
|
+
Recommended** for your machine — grouped by family (Gemma, Qwen, Llama, Phi,
|
|
170
|
+
DeepSeek, and more), with estimated RAM and a clear next step.
|
|
170
171
|
|
|
171
172
|
<div align="center">
|
|
172
|
-
<img src="docs/images/
|
|
173
|
+
<img src="docs/images/onboarding.png" alt="Onboarding hardware scan: OS, CPU, GPU, RAM, disk, runtime" width="49%"/>
|
|
174
|
+
<img src="docs/images/model-recommendation.png" alt="Local model recommendation with best-pick callout and per-family status" width="49%"/>
|
|
173
175
|
</div>
|
|
174
176
|
|
|
175
177
|
### Workspaces & organization
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
viewer` roles map to a
|
|
179
|
+
A **Current Workspace** card shows exactly where you are; switch instantly
|
|
180
|
+
between a **Personal** workspace and shared **Organization** workspaces. Org data
|
|
181
|
+
is scoped by `workspace_id`, and `owner / admin / member / viewer` roles map to a
|
|
182
|
+
transparent permission matrix with member management.
|
|
180
183
|
|
|
181
184
|
<div align="center">
|
|
182
|
-
<img src="docs/images/workspace.png" alt="
|
|
183
|
-
<img src="docs/images/organization.png" alt="Organization
|
|
185
|
+
<img src="docs/images/workspace.png" alt="Current Workspace summary card with scoped counts" width="100%"/>
|
|
186
|
+
<img src="docs/images/organization.png" alt="Organization workspace with members and roles" width="100%"/>
|
|
184
187
|
</div>
|
|
185
188
|
|
|
186
|
-
### Knowledge graph
|
|
189
|
+
### Knowledge graph explorer
|
|
187
190
|
|
|
188
|
-
Your work becomes a typed knowledge graph
|
|
189
|
-
the
|
|
191
|
+
Your work becomes a typed knowledge graph automatically. The Entity Explorer
|
|
192
|
+
surfaces the most important entities and, on selection, their inbound/outbound
|
|
193
|
+
relationships, related entities, and a path back to you.
|
|
190
194
|
|
|
191
195
|
<div align="center">
|
|
192
|
-
<img src="docs/images/graph.png" alt="Knowledge graph
|
|
193
|
-
|
|
196
|
+
<img src="docs/images/graph.png" alt="Knowledge graph entity explorer with relationship detail" width="100%"/>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
### Skills & editions
|
|
200
|
+
|
|
201
|
+
Browse and install skills from an in-product marketplace; an honest editions
|
|
202
|
+
panel shows that every Enterprise capability is an opt-in extension point,
|
|
203
|
+
disabled in the open-source Community build.
|
|
204
|
+
|
|
205
|
+
<div align="center">
|
|
206
|
+
<img src="docs/images/skills.png" alt="Skill marketplace tabs: recommended, popular, installed, updates" width="49%"/>
|
|
207
|
+
<img src="docs/images/enterprise.png" alt="Enterprise capability status panel — all disabled in Community" width="49%"/>
|
|
194
208
|
</div>
|
|
195
209
|
|
|
196
210
|
---
|
|
@@ -333,22 +347,27 @@ Supported routes include OpenAI-compatible APIs, OpenRouter, Groq, Together, xAI
|
|
|
333
347
|
|
|
334
348
|
## Current release
|
|
335
349
|
|
|
336
|
-
**1.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
- **
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
- **Enterprise
|
|
347
|
-
|
|
348
|
-
- **
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
**1.6.0 — Product Experience Deepening.** A UX release: the screens in this README
|
|
351
|
+
are now real captured UI.
|
|
352
|
+
|
|
353
|
+
- **Knowledge Graph explorer** — entity cards, a relationship/related-entities/
|
|
354
|
+
shortest-path detail panel, recent activity, and a memory feed (additive UI on
|
|
355
|
+
existing endpoints)
|
|
356
|
+
- **Workspace UX** — a "Current Workspace" summary card with quick-switch chips
|
|
357
|
+
- **Model Recommendation 2.0** — machine summary, a best-pick callout with
|
|
358
|
+
estimated RAM and next step, per-family status, and a cloud caution
|
|
359
|
+
- **Skill Marketplace** — Recommended / Popular / Installed / Updates tabs
|
|
360
|
+
- **Enterprise capability panel** — an honest 12-capability matrix (Community: all
|
|
361
|
+
disabled, nothing gated)
|
|
362
|
+
- **Real screenshots** — `docs/images/*` refreshed from the running app; API,
|
|
363
|
+
schemas, `server:app`, CLI, MCP, and the Knowledge Graph contract unchanged
|
|
364
|
+
|
|
365
|
+
| Version | Theme |
|
|
366
|
+
|---|---|
|
|
367
|
+
| **1.6.0** | Product Experience Deepening (UX + real screenshots) |
|
|
368
|
+
| 1.5.0 | Unified Product Release (CI/VSIX recovery, model recommendation, Enterprise PoC) |
|
|
369
|
+
| 1.4.0 | Server App final decomposition |
|
|
370
|
+
| 1.1.0–1.3.0 | Organization workspaces, modularization, route safety net |
|
|
352
371
|
|
|
353
372
|
See the full [changelog](docs/CHANGELOG.md) and [RELEASE.md](RELEASE.md).
|
|
354
373
|
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.0] - 2026-06-01
|
|
4
|
+
|
|
5
|
+
> Product Experience Deepening — user-facing UX (Knowledge Graph explorer,
|
|
6
|
+
> workspace summary, model recommendation 2.0, skill marketplace tabs, Enterprise
|
|
7
|
+
> capability panel) and a refresh of `docs/images/*` to **real captured UI**
|
|
8
|
+
> screenshots. Not a refactor: API paths, request/response schemas, `server:app`,
|
|
9
|
+
> CLI, MCP, and the Knowledge Graph contract are unchanged. The only code changes
|
|
10
|
+
> are additive frontend (`static/`) and version metadata.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Knowledge Graph Explorer (Workspace OS)** — an Entity Explorer (importance-
|
|
15
|
+
ranked entity cards + search) with a detail panel showing inbound/outbound
|
|
16
|
+
relationships, related entities, and the shortest path back to you; plus a
|
|
17
|
+
Recent Activity feed and a Workspace Memory feed. Built entirely on the existing
|
|
18
|
+
`/knowledge-graph/graph` and `/workspace/relationships/*` endpoints (additive
|
|
19
|
+
UI, no new API, no schema change).
|
|
20
|
+
- **Workspace summary & quick-switch** — a "Current Workspace" card (active
|
|
21
|
+
workspace, role, members, scoped counts) and one-click switch chips, preserving
|
|
22
|
+
`workspace_id` scoping and the owner/admin/member/viewer model.
|
|
23
|
+
- **Model Recommendation 2.0** — the onboarding recommendation panel now shows a
|
|
24
|
+
machine summary (OS/RAM/GPU/engine), a "best for this PC" callout with the
|
|
25
|
+
reason, estimated RAM, and next step, per-family status, and a cloud caution.
|
|
26
|
+
Estimates are labelled and conservative.
|
|
27
|
+
- **Skill Marketplace tabs** — Recommended / Popular / Installed / Updates tabs
|
|
28
|
+
with version, category, and source, plus install / enable / disable actions on
|
|
29
|
+
the existing skill lifecycle API.
|
|
30
|
+
- **Enterprise capability panel** — a 12-capability status matrix in Workspace OS
|
|
31
|
+
(Community reports all disabled; nothing gates a Community feature).
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **Real UI visuals** — `docs/images/{hero.gif,onboarding,model-recommendation,
|
|
36
|
+
workspace,graph,organization,skills,enterprise}` are now **real screenshots**
|
|
37
|
+
captured from the running app with Playwright + headless Chrome (the v1.5.0
|
|
38
|
+
set was structural diagrams). `architecture.png` remains a structural diagram.
|
|
39
|
+
README references the new real screenshots with no broken links.
|
|
40
|
+
- Python package, npm package, VS Code extension, FastAPI app, and `/health`
|
|
41
|
+
version metadata aligned at `1.6.0`.
|
|
42
|
+
|
|
43
|
+
### Validation
|
|
44
|
+
|
|
45
|
+
- Unit tests pass; route-compatibility, startup/import, streaming, model-endpoint,
|
|
46
|
+
MCP/KG, and workspace/org permission tests preserved; `npm run check:python`
|
|
47
|
+
green; new UI verified rendering in a real browser via Playwright; VSIX build
|
|
48
|
+
verified. Test/build/packaging artifacts only — no package-store publish.
|
|
49
|
+
|
|
3
50
|
## [1.5.0] - 2026-06-01
|
|
4
51
|
|
|
5
52
|
> Unified Product Release — CI/VSIX recovery, hardware-aware local model
|
|
Binary file
|
package/docs/images/graph.png
CHANGED
|
Binary file
|
package/docs/images/hero.gif
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/images/skills.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/latticeai/__init__.py
CHANGED
|
@@ -18,7 +18,7 @@ from pathlib import Path
|
|
|
18
18
|
from typing import Any, Callable, Dict, Iterable, List, Optional
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
WORKSPACE_OS_VERSION = "1.
|
|
21
|
+
WORKSPACE_OS_VERSION = "1.6.0"
|
|
22
22
|
|
|
23
23
|
# Workspace types separate single-user Personal workspaces from shared
|
|
24
24
|
# Organization workspaces. Both keep the same local-first JSON store; the type
|
package/package.json
CHANGED
package/static/scripts/chat.js
CHANGED
|
@@ -1033,6 +1033,7 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1033
1033
|
const data = await res.json();
|
|
1034
1034
|
if (!res.ok) return;
|
|
1035
1035
|
const rec = (data && data.recommendations) || {};
|
|
1036
|
+
const profile = (data && data.profile) || {};
|
|
1036
1037
|
const families = rec.families || [];
|
|
1037
1038
|
if (!families.length) return;
|
|
1038
1039
|
const counts = rec.counts || {};
|
|
@@ -1043,25 +1044,45 @@ const chatViewport = document.getElementById('chat-viewport');
|
|
|
1043
1044
|
not_recommended: ['권장 안 함', '#9ca3af'],
|
|
1044
1045
|
};
|
|
1045
1046
|
const [label, color] = map[status] || ['', '#9ca3af'];
|
|
1046
|
-
return `<span style="display:inline-block;padding:1px
|
|
1047
|
+
return `<span style="display:inline-block;padding:1px 8px;border-radius:999px;font-size:11px;font-weight:700;color:#fff;background:${color}">${label}</span>`;
|
|
1047
1048
|
};
|
|
1049
|
+
const ram = (m) => (m.required_ram_gb != null) ? `~${m.required_ram_gb}GB RAM (est.)` : '';
|
|
1050
|
+
const nextStep = (engine) => engine === 'ollama'
|
|
1051
|
+
? 'Next: ollama pull'
|
|
1052
|
+
: engine === 'local_mlx' ? 'Next: download & load' : 'Next: connect engine';
|
|
1053
|
+
|
|
1054
|
+
// Top pick callout
|
|
1055
|
+
const top = rec.top_pick;
|
|
1056
|
+
const topHtml = top ? `
|
|
1057
|
+
<div style="border:1px solid #16a34a;background:#f0fdf4;border-radius:10px;padding:10px 12px;margin:8px 0">
|
|
1058
|
+
<div style="font-weight:700">⭐ Best for this PC — ${escapeHtml(top.name || top.id)} ${badge('recommended')}</div>
|
|
1059
|
+
<div style="font-size:12px;opacity:0.8;margin-top:3px">${escapeHtml(top.reason || '')}</div>
|
|
1060
|
+
<div style="font-size:12px;margin-top:4px">${escapeHtml(top.size || '')} · ${escapeHtml(ram(top))} · ${escapeHtml(nextStep(rec.engine))}</div>
|
|
1061
|
+
</div>` : '';
|
|
1062
|
+
|
|
1048
1063
|
const rows = families.map((fam) => {
|
|
1049
1064
|
const best = fam.best;
|
|
1050
1065
|
const items = (fam.models || []).map((m) => `
|
|
1051
|
-
<div style="display:flex;justify-content:space-between;gap:8px;padding:
|
|
1066
|
+
<div style="display:flex;justify-content:space-between;gap:8px;padding:3px 0;font-size:12px;opacity:${m.status === 'not_recommended' ? 0.55 : 1}">
|
|
1052
1067
|
<span>${escapeHtml(m.name || m.id)}</span>
|
|
1053
|
-
<span>${escapeHtml(m.size || '')} ${badge(m.status)}</span>
|
|
1068
|
+
<span style="white-space:nowrap">${escapeHtml(m.size || '')} · ${escapeHtml(ram(m))} ${badge(m.status)}</span>
|
|
1054
1069
|
</div>`).join('');
|
|
1055
1070
|
return `
|
|
1056
1071
|
<details style="margin:6px 0;border:1px solid var(--border,#e5e7eb);border-radius:8px;padding:8px 10px">
|
|
1057
|
-
<summary style="cursor:pointer;font-weight:600">${escapeHtml(fam.family)} ${best ? badge(best.status) : ''}</summary>
|
|
1072
|
+
<summary style="cursor:pointer;font-weight:600">${escapeHtml(fam.family)} ${best ? badge(best.status) : ''}${best ? ` <span style="font-weight:400;opacity:0.7">${escapeHtml(best.name || '')}</span>` : ''}</summary>
|
|
1058
1073
|
<div style="margin-top:6px">${items}</div>
|
|
1059
1074
|
</details>`;
|
|
1060
1075
|
}).join('');
|
|
1076
|
+
|
|
1077
|
+
const engineLabel = rec.engine === 'local_mlx' ? 'MLX (Apple Silicon)' : rec.engine;
|
|
1078
|
+
const machine = `${profile.os || ''} · RAM ${rec.ram_gb || '?'}GB · ${rec.apple_silicon ? 'Apple Silicon' : (profile.gpu && profile.gpu.vendor) || 'CPU'} · engine ${engineLabel}`;
|
|
1061
1079
|
container.innerHTML = `
|
|
1062
|
-
<h3 style="margin:14px 0 4px">이 PC
|
|
1063
|
-
<p style="font-size:12px;opacity:0.7;margin:0 0
|
|
1064
|
-
${
|
|
1080
|
+
<h3 style="margin:14px 0 4px">이 PC에 맞는 로컬 모델</h3>
|
|
1081
|
+
<p style="font-size:12px;opacity:0.7;margin:0 0 4px">${escapeHtml(machine)}</p>
|
|
1082
|
+
<p style="font-size:12px;opacity:0.7;margin:0 0 6px">${badge('recommended')} ${counts.recommended || 0} · ${badge('compatible')} ${counts.compatible || 0} · ${badge('not_recommended')} ${counts.not_recommended || 0} · estimates are conservative, verify before loading</p>
|
|
1083
|
+
${topHtml}
|
|
1084
|
+
${rows}
|
|
1085
|
+
<p style="font-size:12px;opacity:0.65;margin:8px 0 0">로컬 모델이 부족하면 클라우드 모델(OpenAI·OpenRouter·Groq 등, API 키 필요)을 선택할 수 있습니다.</p>`;
|
|
1065
1086
|
} catch (e) {
|
|
1066
1087
|
/* best-effort enhancement; never break onboarding */
|
|
1067
1088
|
}
|
|
@@ -6,8 +6,15 @@ const state = {
|
|
|
6
6
|
activeWorkspace: null,
|
|
7
7
|
registry: null,
|
|
8
8
|
managingWorkspace: null,
|
|
9
|
+
skillsPayload: null,
|
|
10
|
+
skillTab: "recommended",
|
|
11
|
+
entities: [],
|
|
12
|
+
activeEntity: null,
|
|
9
13
|
};
|
|
10
14
|
|
|
15
|
+
// Skills that match common workspace needs are surfaced under "Recommended".
|
|
16
|
+
const RECOMMENDED_SKILL_HINTS = ["code", "review", "doc", "test", "security", "research", "changelog", "refactor", "debug"];
|
|
17
|
+
|
|
11
18
|
function $(id) {
|
|
12
19
|
return document.getElementById(id);
|
|
13
20
|
}
|
|
@@ -194,30 +201,74 @@ function renderWorkflows(payload) {
|
|
|
194
201
|
`).join("") : `<div class="list-item"><div class="meta-line">No workflows.</div></div>`;
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
function
|
|
204
|
+
function skillName(skill) {
|
|
205
|
+
return skill.skill || skill.name || "skill";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Compute the four marketplace tabs from the registry payload (machine-global
|
|
209
|
+
// registry + locally-installed state). "Updates" = installed skills whose
|
|
210
|
+
// registry version differs from the installed version.
|
|
211
|
+
function computeSkillTabs(payload) {
|
|
198
212
|
const installed = payload.installed || [];
|
|
199
|
-
const available =
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
213
|
+
const available = payload.available || [];
|
|
214
|
+
const installedNames = new Set(installed.map(skillName));
|
|
215
|
+
const notInstalled = available.filter((s) => !installedNames.has(skillName(s)));
|
|
216
|
+
const availByName = new Map(available.map((s) => [skillName(s), s]));
|
|
217
|
+
const updates = installed.filter((s) => {
|
|
218
|
+
const remote = availByName.get(skillName(s));
|
|
219
|
+
return remote && remote.version && s.version && remote.version !== s.version;
|
|
220
|
+
});
|
|
221
|
+
const recommended = notInstalled.filter((s) => {
|
|
222
|
+
const hay = `${skillName(s)} ${s.category || ""} ${s.description || ""}`.toLowerCase();
|
|
223
|
+
return RECOMMENDED_SKILL_HINTS.some((h) => hay.includes(h));
|
|
224
|
+
});
|
|
225
|
+
return { installed, popular: notInstalled, recommended, updates };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function renderSkillRow(skill, { installed }) {
|
|
229
|
+
const name = skillName(skill);
|
|
230
|
+
const enabled = skill.enabled !== false;
|
|
231
|
+
const version = skill.version || (installed ? "local" : "registry");
|
|
232
|
+
const source = skill.plugin || skill.source || (installed ? "installed" : "marketplace");
|
|
233
|
+
const actions = installed
|
|
234
|
+
? `<button class="small-action" data-skill-action="${enabled ? "disable" : "enable"}" data-skill="${escapeHtml(name)}"><i class="ti ti-${enabled ? "toggle-left" : "toggle-right"}"></i>${enabled ? "Disable" : "Enable"}</button>`
|
|
235
|
+
: `<button class="small-action" data-skill-action="install" data-skill="${escapeHtml(name)}"><i class="ti ti-download"></i>Install</button>`;
|
|
236
|
+
return `
|
|
205
237
|
<div class="list-item">
|
|
206
238
|
<div class="list-title">
|
|
207
|
-
<span>${escapeHtml(
|
|
208
|
-
<span class="status-pill ${
|
|
239
|
+
<span>${escapeHtml(name)}</span>
|
|
240
|
+
<span class="status-pill ${installed ? (enabled ? "status-complete" : "status-failed") : ""}">${installed ? (enabled ? "enabled" : "disabled") : "available"}</span>
|
|
209
241
|
</div>
|
|
210
|
-
<div class="meta-line">${escapeHtml(skill.description || "")}</div>
|
|
242
|
+
<div class="meta-line">${escapeHtml(skill.description || "No description")}</div>
|
|
211
243
|
<div class="tag-row">
|
|
212
|
-
<span class="tag"
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<div class="item-actions">
|
|
216
|
-
<button class="small-action" data-skill-action="enable" data-skill="${escapeHtml(skill.name)}"><i class="ti ti-toggle-right"></i>Enable</button>
|
|
217
|
-
<button class="small-action" data-skill-action="disable" data-skill="${escapeHtml(skill.name)}"><i class="ti ti-toggle-left"></i>Disable</button>
|
|
244
|
+
<span class="tag">v${escapeHtml(version)}</span>
|
|
245
|
+
${skill.category ? `<span class="tag">${escapeHtml(skill.category)}</span>` : ""}
|
|
246
|
+
<span class="tag">${escapeHtml(source)}</span>
|
|
218
247
|
</div>
|
|
219
|
-
|
|
220
|
-
|
|
248
|
+
<div class="item-actions">${actions}</div>
|
|
249
|
+
</div>`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function renderSkills(payload) {
|
|
253
|
+
if (payload) state.skillsPayload = payload;
|
|
254
|
+
const data = state.skillsPayload || { installed: [], available: [] };
|
|
255
|
+
const tabs = computeSkillTabs(data);
|
|
256
|
+
const updatesCount = $("skill-updates-count");
|
|
257
|
+
if (updatesCount) updatesCount.textContent = tabs.updates.length ? String(tabs.updates.length) : "";
|
|
258
|
+
document.querySelectorAll("[data-skill-tab]").forEach((btn) => {
|
|
259
|
+
btn.classList.toggle("active", btn.dataset.skillTab === state.skillTab);
|
|
260
|
+
});
|
|
261
|
+
const tab = state.skillTab;
|
|
262
|
+
const rows = (tab === "installed" || tab === "updates")
|
|
263
|
+
? (tabs[tab] || []).map((s) => renderSkillRow(s, { installed: true }))
|
|
264
|
+
: (tabs[tab] || []).slice(0, 24).map((s) => renderSkillRow(s, { installed: false }));
|
|
265
|
+
const empty = {
|
|
266
|
+
recommended: "No recommended skills right now.",
|
|
267
|
+
popular: "Marketplace is empty.",
|
|
268
|
+
installed: "No skills installed yet.",
|
|
269
|
+
updates: "All installed skills are up to date.",
|
|
270
|
+
}[tab];
|
|
271
|
+
$("skill-list").innerHTML = rows.length ? rows.join("") : `<div class="list-item"><div class="meta-line">${escapeHtml(empty)}</div></div>`;
|
|
221
272
|
}
|
|
222
273
|
|
|
223
274
|
function renderTimeline(payload) {
|
|
@@ -327,6 +378,173 @@ async function addMember(workspaceId) {
|
|
|
327
378
|
await refreshAll();
|
|
328
379
|
}
|
|
329
380
|
|
|
381
|
+
// ── Workspace summary (Phase 3) ──────────────────────────────────────────────
|
|
382
|
+
function renderWorkspaceSummary(os) {
|
|
383
|
+
const reg = os?.workspace_registry || {};
|
|
384
|
+
const workspaces = reg.workspaces || [];
|
|
385
|
+
const activeId = state.activeWorkspace || reg.active_workspace;
|
|
386
|
+
const active = workspaces.find((w) => w.workspace_id === activeId) || workspaces[0] || { name: "Personal Workspace", type: "personal", your_role: "owner", member_count: 1 };
|
|
387
|
+
const counts = os?.counts || {};
|
|
388
|
+
const scopePill = $("summary-scope-pill");
|
|
389
|
+
if (scopePill) scopePill.textContent = active.type || "personal";
|
|
390
|
+
const summary = $("workspace-summary");
|
|
391
|
+
if (summary) {
|
|
392
|
+
const stats = [["Snapshots", counts.snapshots], ["Memories", counts.memories], ["Agent runs", counts.agent_runs], ["Workflows", counts.workflows], ["Traces", counts.traces], ["Timeline", counts.timeline]];
|
|
393
|
+
summary.innerHTML = `
|
|
394
|
+
<div class="summary-main">
|
|
395
|
+
<div class="summary-icon"><i class="ti ${active.type === "organization" ? "ti-building-community" : "ti-user"}"></i></div>
|
|
396
|
+
<div class="summary-id">
|
|
397
|
+
<div class="summary-name">${escapeHtml(active.name || "Personal Workspace")}</div>
|
|
398
|
+
<div class="meta-line">${escapeHtml(active.type || "personal")} workspace · your role <strong>${escapeHtml(active.your_role || "owner")}</strong> · ${escapeHtml(active.member_count ?? 1)} member(s)</div>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
<div class="summary-stats">
|
|
402
|
+
${stats.map(([l, v]) => `<div class="summary-stat"><strong>${escapeHtml(v || 0)}</strong><span>${escapeHtml(l)}</span></div>`).join("")}
|
|
403
|
+
</div>`;
|
|
404
|
+
}
|
|
405
|
+
const quick = $("workspace-quickswitch");
|
|
406
|
+
if (quick) {
|
|
407
|
+
quick.innerHTML = workspaces.map((w) => `
|
|
408
|
+
<button class="switch-chip ${w.workspace_id === activeId ? "active" : ""}" data-ws-action="activate" data-ws="${escapeHtml(w.workspace_id)}">
|
|
409
|
+
<i class="ti ${w.type === "organization" ? "ti-building-community" : "ti-user"}"></i>
|
|
410
|
+
<span>${escapeHtml(w.name)}</span>${w.workspace_id === activeId ? ' <i class="ti ti-check"></i>' : ""}
|
|
411
|
+
</button>`).join("");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ── Knowledge Graph explorer (Phase 2) ───────────────────────────────────────
|
|
416
|
+
const ENTITY_ICONS = { Person: "ti-user", Concept: "ti-bulb", Document: "ti-file-text", File: "ti-file", Code: "ti-code", Chat: "ti-message", Conversation: "ti-messages", Message: "ti-message-dots", Task: "ti-checklist", Decision: "ti-gavel", Error: "ti-alert-triangle", Model: "ti-cpu", Tool: "ti-tool", Project: "ti-folders", Feature: "ti-star", AIResponse: "ti-robot", Chunk: "ti-file-stack" };
|
|
417
|
+
function entityIcon(type) { return ENTITY_ICONS[type] || "ti-point"; }
|
|
418
|
+
function prettyId(id) { return String(id || "").split(":").slice(1).join(":") || String(id || ""); }
|
|
419
|
+
|
|
420
|
+
async function loadGraphExplorer() {
|
|
421
|
+
try {
|
|
422
|
+
const data = await api("/knowledge-graph/graph?limit=150");
|
|
423
|
+
const nodes = (data.nodes || []).slice();
|
|
424
|
+
nodes.sort((a, b) => (b.importance ?? b.metadata?.graph_metrics?.importance_raw ?? 0) - (a.importance ?? a.metadata?.graph_metrics?.importance_raw ?? 0));
|
|
425
|
+
state.entities = nodes;
|
|
426
|
+
renderEntities();
|
|
427
|
+
} catch (e) {
|
|
428
|
+
const el = $("entity-list");
|
|
429
|
+
if (el) el.innerHTML = `<div class="list-item"><div class="meta-line">Knowledge graph unavailable: ${escapeHtml(e.message)}</div></div>`;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function renderEntities() {
|
|
434
|
+
const el = $("entity-list");
|
|
435
|
+
if (!el) return;
|
|
436
|
+
const q = ($("entity-search")?.value || "").toLowerCase().trim();
|
|
437
|
+
const filtered = q ? state.entities.filter((n) => `${n.title || ""} ${n.type || ""} ${n.id || ""}`.toLowerCase().includes(q)) : state.entities;
|
|
438
|
+
const list = filtered.slice(0, 40);
|
|
439
|
+
el.innerHTML = list.length ? list.map((n) => {
|
|
440
|
+
const m = n.metadata?.graph_metrics || {};
|
|
441
|
+
const imp = Math.round((n.importance_norm ?? m.importance_norm ?? 0) * 100);
|
|
442
|
+
return `
|
|
443
|
+
<button class="list-item entity-card ${n.id === state.activeEntity ? "selected" : ""}" data-entity="${escapeHtml(n.id)}">
|
|
444
|
+
<div class="list-title"><span><i class="ti ${entityIcon(n.type)}"></i> ${escapeHtml(n.title || prettyId(n.id))}</span><span class="status-pill">${escapeHtml(n.type || "node")}</span></div>
|
|
445
|
+
${n.summary ? `<div class="meta-line">${escapeHtml(String(n.summary).slice(0, 110))}</div>` : ""}
|
|
446
|
+
<div class="tag-row"><span class="tag">${escapeHtml(m.degree ?? 0)} links</span><span class="tag">importance ${imp}%</span></div>
|
|
447
|
+
<div class="importance-bar"><span style="width:${imp}%"></span></div>
|
|
448
|
+
</button>`;
|
|
449
|
+
}).join("") : `<div class="list-item"><div class="meta-line">No matching entities.</div></div>`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function selectEntity(id) {
|
|
453
|
+
state.activeEntity = id;
|
|
454
|
+
renderEntities();
|
|
455
|
+
const detail = $("entity-detail");
|
|
456
|
+
const title = $("entity-detail-title");
|
|
457
|
+
if (title) title.textContent = "Loading…";
|
|
458
|
+
try {
|
|
459
|
+
const d = await api(`/workspace/relationships/${encodeURIComponent(id)}`);
|
|
460
|
+
const node = d.node || {};
|
|
461
|
+
const related = d.related_entities || [];
|
|
462
|
+
const relMap = new Map(related.map((r) => [r.id, r]));
|
|
463
|
+
const labelFor = (nodeId) => { const r = relMap.get(nodeId); return r ? (r.title || prettyId(nodeId)) : prettyId(nodeId); };
|
|
464
|
+
const edgeRow = (e, dir) => {
|
|
465
|
+
const other = dir === "out" ? e.to : e.from;
|
|
466
|
+
return `<div class="rel-row"><span class="rel-dir">${dir === "out" ? "→" : "←"}</span><span class="tag">${escapeHtml(e.type || "related")}</span><span class="rel-node">${escapeHtml(labelFor(other))}</span></div>`;
|
|
467
|
+
};
|
|
468
|
+
const inbound = (d.inbound || []).slice(0, 8);
|
|
469
|
+
const outbound = (d.outbound || []).slice(0, 8);
|
|
470
|
+
const path = Array.isArray(d.shortest_path) ? d.shortest_path : [];
|
|
471
|
+
if (title) title.textContent = node.title || prettyId(id);
|
|
472
|
+
detail.innerHTML = `
|
|
473
|
+
<div class="list-item">
|
|
474
|
+
<div class="list-title"><span><i class="ti ${entityIcon(node.type)}"></i> ${escapeHtml(node.title || prettyId(id))}</span><span class="status-pill">${escapeHtml(node.type || "node")}</span></div>
|
|
475
|
+
${node.summary ? `<div class="meta-line">${escapeHtml(node.summary)}</div>` : ""}
|
|
476
|
+
<div class="tag-row"><span class="tag">importance ${Math.round((node.importance_norm || 0) * 100)}%</span><span class="tag">${inbound.length + outbound.length} relationships</span></div>
|
|
477
|
+
</div>
|
|
478
|
+
<div class="list-item"><div class="list-title"><span>Outbound</span><span class="status-pill">${outbound.length}</span></div>${outbound.map((e) => edgeRow(e, "out")).join("") || '<div class="meta-line">None</div>'}</div>
|
|
479
|
+
<div class="list-item"><div class="list-title"><span>Inbound</span><span class="status-pill">${inbound.length}</span></div>${inbound.map((e) => edgeRow(e, "in")).join("") || '<div class="meta-line">None</div>'}</div>
|
|
480
|
+
${related.length ? `<div class="list-item"><div class="list-title"><span>Related entities</span><span class="status-pill">${related.length}</span></div><div class="tag-row">${related.slice(0, 10).map((r) => `<span class="tag"><i class="ti ${entityIcon(r.type)}"></i> ${escapeHtml(r.title || prettyId(r.id))}</span>`).join("")}</div></div>` : ""}
|
|
481
|
+
${path.length ? `<div class="list-item"><div class="list-title"><span>Path to you</span><span class="status-pill">${path.length} hops</span></div><div class="meta-line">${path.map((p) => escapeHtml(typeof p === "string" ? prettyId(p) : (p.title || prettyId(p.id)))).join(" → ")}</div></div>` : ""}
|
|
482
|
+
<div class="item-actions"><a class="small-action" href="/graph?node=${encodeURIComponent(id)}"><i class="ti ti-network"></i>Open in Graph Canvas</a></div>`;
|
|
483
|
+
} catch (e) {
|
|
484
|
+
if (title) title.textContent = "Relationships";
|
|
485
|
+
detail.innerHTML = `<div class="list-item"><div class="meta-line">No relationships available: ${escapeHtml(e.message)}</div></div>`;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ── Recent activity feed (Phase 2), built from already-fetched data ───────────
|
|
490
|
+
function renderActivity({ traces, snapshots, memories, workflows, timeline }) {
|
|
491
|
+
const items = [];
|
|
492
|
+
(traces.traces || []).forEach((t) => items.push({ ts: t.created_at, icon: "ti-search", label: `Answer trace: ${t.question || "query"}`, tag: "graph rag" }));
|
|
493
|
+
(snapshots.snapshots || []).forEach((s) => items.push({ ts: s.created_at, icon: "ti-stack-2", label: `Snapshot: ${s.name}`, tag: "snapshot" }));
|
|
494
|
+
(memories.memories || []).forEach((m) => items.push({ ts: m.updated_at, icon: "ti-book-2", label: `Memory: ${(m.content || m.kind || "").slice(0, 60)}`, tag: m.kind || "memory" }));
|
|
495
|
+
(workflows.workflows || []).forEach((w) => items.push({ ts: w.created_at, icon: "ti-git-branch", label: `Workflow: ${w.name}`, tag: "workflow" }));
|
|
496
|
+
(timeline.events || []).forEach((e) => items.push({ ts: e.timestamp, icon: "ti-timeline-event", label: e.event_type || "event", tag: e.area || "workspace" }));
|
|
497
|
+
items.sort((a, b) => String(b.ts || "").localeCompare(String(a.ts || "")));
|
|
498
|
+
const el = $("activity-list");
|
|
499
|
+
if (!el) return;
|
|
500
|
+
el.innerHTML = items.length ? items.slice(0, 18).map((it) => `
|
|
501
|
+
<div class="list-item activity-item">
|
|
502
|
+
<div class="list-title"><span><i class="ti ${it.icon}"></i> ${escapeHtml(it.label)}</span><span class="status-pill">${escapeHtml(it.tag)}</span></div>
|
|
503
|
+
<div class="meta-line">${escapeHtml(it.ts || "")}</div>
|
|
504
|
+
</div>`).join("") : `<div class="list-item"><div class="meta-line">No recent activity yet — index a folder or ask a question to get started.</div></div>`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function renderMemoryFeed(payload) {
|
|
508
|
+
const memories = payload.memories || [];
|
|
509
|
+
const el = $("memory-feed");
|
|
510
|
+
if (!el) return;
|
|
511
|
+
el.innerHTML = memories.length ? memories.slice(0, 8).map((m) => `
|
|
512
|
+
<div class="list-item">
|
|
513
|
+
<div class="list-title"><span><i class="ti ti-book-2"></i> ${escapeHtml(m.kind || "memory")}</span><span class="status-pill">${escapeHtml(m.updated_at || "")}</span></div>
|
|
514
|
+
<div class="meta-line">${escapeHtml(String(m.content || "").slice(0, 140))}</div>
|
|
515
|
+
</div>`).join("") : `<div class="list-item"><div class="meta-line">No workspace memory yet.</div></div>`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// ── Enterprise capability panel (Phase 6) ─────────────────────────────────────
|
|
519
|
+
const CAPABILITY_LABELS = {
|
|
520
|
+
sso_advanced: "Advanced SSO", idp_provisioning: "IdP Provisioning", scim: "SCIM",
|
|
521
|
+
rbac_abac_advanced: "Advanced RBAC/ABAC", tenant_isolation: "Tenant Isolation",
|
|
522
|
+
compliance_retention: "Compliance Retention", siem_export: "SIEM Export",
|
|
523
|
+
private_vpc: "Private VPC", air_gapped_deployment: "Air-gapped Deploy",
|
|
524
|
+
dlp_policy: "DLP Policy", ediscovery: "eDiscovery", admin_policy_packs: "Admin Policy Packs",
|
|
525
|
+
};
|
|
526
|
+
function renderEnterprise(edition) {
|
|
527
|
+
edition = edition || {};
|
|
528
|
+
const caps = edition.capabilities || {};
|
|
529
|
+
const editionName = edition.edition || "community";
|
|
530
|
+
const pill = $("enterprise-edition-pill");
|
|
531
|
+
if (pill) { pill.textContent = editionName; pill.className = `status-pill ${edition.is_enterprise ? "status-complete" : ""}`; }
|
|
532
|
+
const note = $("enterprise-note");
|
|
533
|
+
if (note) note.textContent = edition.community_notice || "Community edition: every Enterprise capability below is an extension point and is disabled. Nothing here gates a Community feature.";
|
|
534
|
+
const grid = $("capability-grid");
|
|
535
|
+
if (!grid) return;
|
|
536
|
+
const keys = Object.keys(caps).length ? Object.keys(caps) : Object.keys(CAPABILITY_LABELS);
|
|
537
|
+
grid.innerHTML = keys.map((k) => {
|
|
538
|
+
const on = Boolean(caps[k]);
|
|
539
|
+
return `
|
|
540
|
+
<div class="capability-card ${on ? "on" : "off"}">
|
|
541
|
+
<i class="ti ${on ? "ti-circle-check" : "ti-lock"}"></i>
|
|
542
|
+
<span class="cap-name">${escapeHtml(CAPABILITY_LABELS[k] || k)}</span>
|
|
543
|
+
<span class="status-pill ${on ? "status-complete" : "status-failed"}">${on ? "enabled" : "disabled"}</span>
|
|
544
|
+
</div>`;
|
|
545
|
+
}).join("");
|
|
546
|
+
}
|
|
547
|
+
|
|
330
548
|
async function refreshAll() {
|
|
331
549
|
const [os, onboarding, traces, indexing, snapshots, memories, computerMemory, agents, workflows, skills, timeline] = await Promise.all([
|
|
332
550
|
api("/workspace/os"),
|
|
@@ -354,6 +572,11 @@ async function refreshAll() {
|
|
|
354
572
|
renderWorkflows(workflows);
|
|
355
573
|
renderSkills(skills);
|
|
356
574
|
renderTimeline(timeline);
|
|
575
|
+
renderWorkspaceSummary(os);
|
|
576
|
+
renderEnterprise(os.edition);
|
|
577
|
+
renderActivity({ traces, snapshots, memories, workflows, timeline });
|
|
578
|
+
renderMemoryFeed(memories);
|
|
579
|
+
loadGraphExplorer();
|
|
357
580
|
}
|
|
358
581
|
|
|
359
582
|
async function createSnapshot() {
|
|
@@ -423,6 +646,19 @@ async function configureComputerMemory(enabled) {
|
|
|
423
646
|
}
|
|
424
647
|
|
|
425
648
|
document.addEventListener("click", async (event) => {
|
|
649
|
+
const entityBtn = event.target.closest("[data-entity]");
|
|
650
|
+
if (entityBtn) {
|
|
651
|
+
selectEntity(entityBtn.dataset.entity).catch((err) => toast(err.message));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const skillTab = event.target.closest("[data-skill-tab]");
|
|
656
|
+
if (skillTab) {
|
|
657
|
+
state.skillTab = skillTab.dataset.skillTab;
|
|
658
|
+
renderSkills();
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
|
|
426
662
|
const step = event.target.closest("[data-step]");
|
|
427
663
|
if (step) {
|
|
428
664
|
await api("/workspace/onboarding/step", {
|
|
@@ -521,6 +757,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
521
757
|
}));
|
|
522
758
|
$("create-demo-workflow").addEventListener("click", () => createDemoWorkflow().catch((err) => toast(err.message)));
|
|
523
759
|
$("reload-skills").addEventListener("click", () => refreshAll().catch((err) => toast(err.message)));
|
|
760
|
+
const entitySearch = $("entity-search");
|
|
761
|
+
if (entitySearch) entitySearch.addEventListener("input", () => renderEntities());
|
|
762
|
+
const reloadEntities = $("reload-entities");
|
|
763
|
+
if (reloadEntities) reloadEntities.addEventListener("click", () => loadGraphExplorer().catch((err) => toast(err.message)));
|
|
764
|
+
const reloadActivity = $("reload-activity");
|
|
765
|
+
if (reloadActivity) reloadActivity.addEventListener("click", () => refreshAll().catch((err) => toast(err.message)));
|
|
524
766
|
$("workspace-select").addEventListener("change", (event) => activateWorkspace(event.target.value).catch((err) => toast(err.message)));
|
|
525
767
|
$("create-org").addEventListener("click", () => createOrg().catch((err) => toast(err.message)));
|
|
526
768
|
$("new-org-btn").addEventListener("click", () => $("org-name").focus());
|
package/static/workspace.css
CHANGED
|
@@ -544,3 +544,70 @@ textarea {
|
|
|
544
544
|
#org-create-form {
|
|
545
545
|
margin-bottom: 12px;
|
|
546
546
|
}
|
|
547
|
+
|
|
548
|
+
/* ── Product Experience Deepening (v1.6.0) ──────────────────────────────── */
|
|
549
|
+
|
|
550
|
+
/* Workspace summary */
|
|
551
|
+
.summary-card {
|
|
552
|
+
display: flex;
|
|
553
|
+
flex-wrap: wrap;
|
|
554
|
+
justify-content: space-between;
|
|
555
|
+
gap: 16px;
|
|
556
|
+
align-items: center;
|
|
557
|
+
}
|
|
558
|
+
.summary-main { display: flex; align-items: center; gap: 14px; }
|
|
559
|
+
.summary-icon {
|
|
560
|
+
width: 48px; height: 48px; border-radius: 12px;
|
|
561
|
+
display: grid; place-items: center;
|
|
562
|
+
background: #eef2ff; color: var(--blue); font-size: 24px;
|
|
563
|
+
}
|
|
564
|
+
.summary-name { font-weight: 800; font-size: 18px; color: var(--ink); }
|
|
565
|
+
.summary-stats { display: flex; flex-wrap: wrap; gap: 18px; }
|
|
566
|
+
.summary-stat { display: grid; text-align: center; }
|
|
567
|
+
.summary-stat strong { font-size: 20px; color: var(--ink); }
|
|
568
|
+
.summary-stat span { font-size: 11px; color: var(--muted); font-weight: 700; }
|
|
569
|
+
.quickswitch-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
|
|
570
|
+
.switch-chip {
|
|
571
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
572
|
+
border: 1px solid var(--line); background: #fbfcfe; color: var(--ink);
|
|
573
|
+
border-radius: 999px; padding: 6px 12px; font-weight: 700; font-size: 13px; cursor: pointer;
|
|
574
|
+
}
|
|
575
|
+
.switch-chip.active { border-color: var(--blue); background: #eef2ff; color: var(--blue); }
|
|
576
|
+
|
|
577
|
+
/* Entity explorer */
|
|
578
|
+
.entity-card { text-align: left; cursor: pointer; width: 100%; font: inherit; }
|
|
579
|
+
.entity-card.selected { border-color: var(--blue); box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.15); }
|
|
580
|
+
.importance-bar { height: 4px; border-radius: 999px; background: #edf2f7; overflow: hidden; }
|
|
581
|
+
.importance-bar span { display: block; height: 100%; background: linear-gradient(90deg, #2563eb, #7c3aed); }
|
|
582
|
+
.rel-row { display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 2px 0; }
|
|
583
|
+
.rel-dir { color: var(--muted); font-weight: 800; width: 16px; }
|
|
584
|
+
.rel-node { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
585
|
+
.activity-item .list-title span { font-weight: 700; }
|
|
586
|
+
|
|
587
|
+
/* Skill marketplace tabs */
|
|
588
|
+
.tab-bar { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
|
|
589
|
+
.tab {
|
|
590
|
+
border: 1px solid var(--line); background: #fbfcfe; color: var(--muted);
|
|
591
|
+
border-radius: 999px; padding: 6px 14px; font-weight: 700; font-size: 13px; cursor: pointer;
|
|
592
|
+
}
|
|
593
|
+
.tab.active { border-color: var(--blue); background: #eef2ff; color: var(--blue); }
|
|
594
|
+
.tab-count {
|
|
595
|
+
display: inline-block; min-width: 16px; padding: 0 5px; margin-left: 4px;
|
|
596
|
+
border-radius: 999px; background: var(--red); color: #fff; font-size: 11px;
|
|
597
|
+
}
|
|
598
|
+
.tab-count:empty { display: none; }
|
|
599
|
+
|
|
600
|
+
/* Enterprise capability grid */
|
|
601
|
+
.capability-grid {
|
|
602
|
+
display: grid; gap: 10px;
|
|
603
|
+
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
604
|
+
margin-top: 12px;
|
|
605
|
+
}
|
|
606
|
+
.capability-card {
|
|
607
|
+
display: flex; align-items: center; gap: 10px;
|
|
608
|
+
border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; background: #fbfcfe;
|
|
609
|
+
}
|
|
610
|
+
.capability-card i { font-size: 18px; }
|
|
611
|
+
.capability-card.off i { color: var(--muted); }
|
|
612
|
+
.capability-card.on i { color: var(--green); }
|
|
613
|
+
.capability-card .cap-name { flex: 1; font-weight: 700; font-size: 13px; color: var(--ink); }
|
package/static/workspace.html
CHANGED
|
@@ -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.
|
|
11
|
+
<link rel="stylesheet" href="/static/workspace.css?v=1.6.0">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div class="workspace-shell">
|
|
@@ -20,12 +20,14 @@
|
|
|
20
20
|
<nav>
|
|
21
21
|
<a class="active" href="#overview"><i class="ti ti-layout-dashboard"></i><span>Overview</span></a>
|
|
22
22
|
<a href="#graph"><i class="ti ti-chart-dots-3"></i><span>Graph</span></a>
|
|
23
|
+
<a href="#graph-explorer"><i class="ti ti-affiliate"></i><span>Explorer</span></a>
|
|
23
24
|
<a href="#snapshots"><i class="ti ti-stack-2"></i><span>Snapshots</span></a>
|
|
24
25
|
<a href="#memory"><i class="ti ti-book-2"></i><span>Memory</span></a>
|
|
25
26
|
<a href="#agents"><i class="ti ti-route-alt-left"></i><span>Agents</span></a>
|
|
26
27
|
<a href="#workflows"><i class="ti ti-git-branch"></i><span>Workflow</span></a>
|
|
27
28
|
<a href="#skills"><i class="ti ti-puzzle"></i><span>Skills</span></a>
|
|
28
29
|
<a href="#timeline"><i class="ti ti-timeline-event"></i><span>Timeline</span></a>
|
|
30
|
+
<a href="#enterprise"><i class="ti ti-building-skyscraper"></i><span>Editions</span></a>
|
|
29
31
|
</nav>
|
|
30
32
|
<div class="rail-links">
|
|
31
33
|
<a href="/chat"><i class="ti ti-message-circle"></i><span>Chat</span></a>
|
|
@@ -54,6 +56,18 @@
|
|
|
54
56
|
|
|
55
57
|
<section class="metric-grid" id="metric-grid"></section>
|
|
56
58
|
|
|
59
|
+
<section class="workspace-band" id="workspace-summary-band">
|
|
60
|
+
<div class="section-head">
|
|
61
|
+
<div>
|
|
62
|
+
<div class="eyebrow">You are here</div>
|
|
63
|
+
<h2>Current Workspace</h2>
|
|
64
|
+
</div>
|
|
65
|
+
<span class="status-pill" id="summary-scope-pill">personal</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div id="workspace-summary" class="summary-card"></div>
|
|
68
|
+
<div id="workspace-quickswitch" class="quickswitch-row"></div>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
57
71
|
<section class="workspace-band" id="organization">
|
|
58
72
|
<div class="section-head">
|
|
59
73
|
<div>
|
|
@@ -120,6 +134,55 @@
|
|
|
120
134
|
</div>
|
|
121
135
|
</section>
|
|
122
136
|
|
|
137
|
+
<section class="workspace-grid two-col" id="graph-explorer">
|
|
138
|
+
<div class="workspace-panel">
|
|
139
|
+
<div class="section-head">
|
|
140
|
+
<div>
|
|
141
|
+
<div class="eyebrow">Knowledge Graph</div>
|
|
142
|
+
<h2>Entity Explorer</h2>
|
|
143
|
+
</div>
|
|
144
|
+
<button class="icon-action" id="reload-entities" title="Reload entities"><i class="ti ti-refresh"></i></button>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="inline-form">
|
|
147
|
+
<input id="entity-search" placeholder="Search entities (concept, person, file…)">
|
|
148
|
+
</div>
|
|
149
|
+
<div id="entity-list" class="list-stack"></div>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="workspace-panel">
|
|
152
|
+
<div class="section-head">
|
|
153
|
+
<div>
|
|
154
|
+
<div class="eyebrow">Relationships</div>
|
|
155
|
+
<h2 id="entity-detail-title">Select an entity</h2>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
<div id="entity-detail" class="list-stack">
|
|
159
|
+
<div class="list-item"><div class="meta-line">Pick an entity on the left to see its relationships, related entities, and a path back to you.</div></div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</section>
|
|
163
|
+
|
|
164
|
+
<section class="workspace-grid two-col" id="activity">
|
|
165
|
+
<div class="workspace-panel">
|
|
166
|
+
<div class="section-head">
|
|
167
|
+
<div>
|
|
168
|
+
<div class="eyebrow">Workspace</div>
|
|
169
|
+
<h2>Recent Activity</h2>
|
|
170
|
+
</div>
|
|
171
|
+
<button class="icon-action" id="reload-activity" title="Reload activity"><i class="ti ti-refresh"></i></button>
|
|
172
|
+
</div>
|
|
173
|
+
<div id="activity-list" class="list-stack"></div>
|
|
174
|
+
</div>
|
|
175
|
+
<div class="workspace-panel">
|
|
176
|
+
<div class="section-head">
|
|
177
|
+
<div>
|
|
178
|
+
<div class="eyebrow">Memory</div>
|
|
179
|
+
<h2>Workspace Memory Feed</h2>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
<div id="memory-feed" class="list-stack"></div>
|
|
183
|
+
</div>
|
|
184
|
+
</section>
|
|
185
|
+
|
|
123
186
|
<section class="workspace-grid two-col" id="snapshots">
|
|
124
187
|
<div class="workspace-panel">
|
|
125
188
|
<div class="section-head">
|
|
@@ -217,6 +280,12 @@
|
|
|
217
280
|
</div>
|
|
218
281
|
<button class="icon-action" id="reload-skills" title="Reload skills"><i class="ti ti-refresh"></i></button>
|
|
219
282
|
</div>
|
|
283
|
+
<div class="tab-bar" id="skill-tabs">
|
|
284
|
+
<button class="tab active" data-skill-tab="recommended">Recommended</button>
|
|
285
|
+
<button class="tab" data-skill-tab="popular">Popular</button>
|
|
286
|
+
<button class="tab" data-skill-tab="installed">Installed</button>
|
|
287
|
+
<button class="tab" data-skill-tab="updates">Updates <span class="tab-count" id="skill-updates-count"></span></button>
|
|
288
|
+
</div>
|
|
220
289
|
<div id="skill-list" class="list-stack"></div>
|
|
221
290
|
</div>
|
|
222
291
|
<div class="workspace-panel" id="timeline">
|
|
@@ -229,10 +298,22 @@
|
|
|
229
298
|
<div id="timeline-list" class="timeline-list"></div>
|
|
230
299
|
</div>
|
|
231
300
|
</section>
|
|
301
|
+
|
|
302
|
+
<section class="workspace-band" id="enterprise">
|
|
303
|
+
<div class="section-head">
|
|
304
|
+
<div>
|
|
305
|
+
<div class="eyebrow">Editions</div>
|
|
306
|
+
<h2>Enterprise Capabilities</h2>
|
|
307
|
+
</div>
|
|
308
|
+
<span class="status-pill" id="enterprise-edition-pill">community</span>
|
|
309
|
+
</div>
|
|
310
|
+
<p class="meta-line" id="enterprise-note"></p>
|
|
311
|
+
<div id="capability-grid" class="capability-grid"></div>
|
|
312
|
+
</section>
|
|
232
313
|
</main>
|
|
233
314
|
</div>
|
|
234
315
|
|
|
235
316
|
<div class="toast" id="toast"></div>
|
|
236
|
-
<script src="/static/scripts/workspace.js?v=1.
|
|
317
|
+
<script src="/static/scripts/workspace.js?v=1.6.0"></script>
|
|
237
318
|
</body>
|
|
238
319
|
</html>
|