feat(embedding,vision): candle embeddings with offline fallback, on-demand clipboard vision capture, and concurrency audit
This commit is contained in:
1 parent
5bd8b1587a
commit
e4a0fe72df
47 files changed
+6292
-3503
No files matched your search
+106
-147
@@ -23,36 +23,24 @@ function switchTab(tabId, btn) {
|
||||
case "task-tab":
|
||||
loadTasks();
|
||||
break;
|
||||
case "preferences-tab":
|
||||
loadPreferences();
|
||||
break;
|
||||
case "sticky-tab":
|
||||
loadStickyNotes();
|
||||
break;
|
||||
case "techdebt-tab":
|
||||
loadTechDebt();
|
||||
break;
|
||||
case "adrs-tab":
|
||||
loadADRs();
|
||||
break;
|
||||
case "workspaces-tab":
|
||||
loadWorkspaces();
|
||||
break;
|
||||
case "pinned-tab":
|
||||
loadPinned();
|
||||
break;
|
||||
case "memos-tab":
|
||||
loadMemos();
|
||||
break;
|
||||
case "snippets-tab":
|
||||
loadSnippets();
|
||||
break;
|
||||
case "pr-tab":
|
||||
loadPRs();
|
||||
break;
|
||||
case "terminal-tab":
|
||||
loadTerminal();
|
||||
break;
|
||||
case "ledger-tab":
|
||||
loadLedger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
function toggleTheme() {
|
||||
@@ -89,7 +77,10 @@ function getColorForType(type) {
|
||||
for (let i = 0;i < type.length; i++)
|
||||
hash = type.charCodeAt(i) + ((hash << 5) - hash);
|
||||
const hue = Math.abs(hash) % 360;
|
||||
return { background: `hsl(${hue}, 70%, 60%)`, border: `hsl(${hue}, 70%, 40%)` };
|
||||
return {
|
||||
background: `hsl(${hue}, 70%, 60%)`,
|
||||
border: `hsl(${hue}, 70%, 40%)`
|
||||
};
|
||||
}
|
||||
function zoomGraph(step) {
|
||||
if (!network)
|
||||
@@ -100,7 +91,9 @@ function zoomGraph(step) {
|
||||
function resetGraph() {
|
||||
if (!network)
|
||||
return;
|
||||
network.fit({ animation: { duration: 500, easingFunction: "easeInOutQuad" } });
|
||||
network.fit({
|
||||
animation: { duration: 500, easingFunction: "easeInOutQuad" }
|
||||
});
|
||||
}
|
||||
function closeInspector() {
|
||||
const inspector = document.getElementById("inspector-panel");
|
||||
@@ -176,7 +169,9 @@ function updateGraphData() {
|
||||
title: `${name}
|
||||
Type: ${entity.entity_type}`,
|
||||
color: { background: color.bg || color.background, border: color.border },
|
||||
font: { color: document.documentElement.getAttribute("data-theme") === "dark" ? "#eee" : "#333" }
|
||||
font: {
|
||||
color: document.documentElement.getAttribute("data-theme") === "dark" ? "#eee" : "#333"
|
||||
}
|
||||
});
|
||||
nodeIds.add(name);
|
||||
}
|
||||
@@ -188,7 +183,10 @@ Type: ${entity.entity_type}`,
|
||||
to: r.to,
|
||||
label: r.relation_type,
|
||||
arrows: "to",
|
||||
font: { color: document.documentElement.getAttribute("data-theme") === "dark" ? "#aaa" : "#666", strokeWidth: 0 }
|
||||
font: {
|
||||
color: document.documentElement.getAttribute("data-theme") === "dark" ? "#aaa" : "#666",
|
||||
strokeWidth: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -218,12 +216,28 @@ async function loadGraph() {
|
||||
return;
|
||||
const options = {
|
||||
nodes: { shape: "dot", size: 16, font: { size: 12 } },
|
||||
edges: { color: { inherit: "from", opacity: 0.6 }, font: { size: 10, align: "middle" }, smooth: { type: "continuous" } },
|
||||
edges: {
|
||||
color: { inherit: "from", opacity: 0.6 },
|
||||
font: { size: 10, align: "middle" },
|
||||
smooth: { type: "continuous" }
|
||||
},
|
||||
physics: {
|
||||
enabled: true,
|
||||
solver: "barnesHut",
|
||||
barnesHut: { gravitationalConstant: -3000, centralGravity: 0.3, springLength: 95, springConstant: 0.04, damping: 0.09, avoidOverlap: 0.1 },
|
||||
stabilization: { enabled: true, iterations: 150, updateInterval: 25, fit: true }
|
||||
barnesHut: {
|
||||
gravitationalConstant: -3000,
|
||||
centralGravity: 0.3,
|
||||
springLength: 95,
|
||||
springConstant: 0.04,
|
||||
damping: 0.09,
|
||||
avoidOverlap: 0.1
|
||||
},
|
||||
stabilization: {
|
||||
enabled: true,
|
||||
iterations: 150,
|
||||
updateInterval: 25,
|
||||
fit: true
|
||||
}
|
||||
},
|
||||
interaction: { hover: true, tooltipDelay: 100, zoomView: false }
|
||||
};
|
||||
@@ -466,7 +480,17 @@ function parseActivityPayload(item) {
|
||||
else if (item.content)
|
||||
summary = String(item.content);
|
||||
else {
|
||||
const { timestamp: _t, time: _tm, updated_at: _u, category: _c, type: _tp, method: _m, params: _p, jsonrpc: _j, ...rest } = item;
|
||||
const {
|
||||
timestamp: _t,
|
||||
time: _tm,
|
||||
updated_at: _u,
|
||||
category: _c,
|
||||
type: _tp,
|
||||
method: _m,
|
||||
params: _p,
|
||||
jsonrpc: _j,
|
||||
...rest
|
||||
} = item;
|
||||
if (Object.keys(rest).length > 0) {
|
||||
summary = JSON.stringify(rest);
|
||||
} else {
|
||||
@@ -579,15 +603,11 @@ function requestDomainRefresh(domain) {
|
||||
const tabMap = {
|
||||
graph: "graph-tab",
|
||||
task: "task-tab",
|
||||
sticky: "sticky-tab",
|
||||
techdebt: "techdebt-tab",
|
||||
snippets: "snippets-tab",
|
||||
terminal: "terminal-tab",
|
||||
memos: "memos-tab",
|
||||
workspaces: "workspaces-tab",
|
||||
pinned: "pinned-tab",
|
||||
pr: "pr-tab",
|
||||
preferences: "preferences-tab"
|
||||
ledger: "ledger-tab",
|
||||
memos: "memos-tab"
|
||||
};
|
||||
const targetTab = tabMap[domain];
|
||||
if (!targetTab || targetTab !== currentTabId) {
|
||||
@@ -605,9 +625,6 @@ function requestDomainRefresh(domain) {
|
||||
case "task":
|
||||
loadTasks();
|
||||
break;
|
||||
case "sticky":
|
||||
loadStickyNotes();
|
||||
break;
|
||||
case "techdebt":
|
||||
loadTechDebt();
|
||||
break;
|
||||
@@ -617,21 +634,12 @@ function requestDomainRefresh(domain) {
|
||||
case "terminal":
|
||||
loadTerminal();
|
||||
break;
|
||||
case "ledger":
|
||||
loadLedger();
|
||||
break;
|
||||
case "memos":
|
||||
loadMemos();
|
||||
break;
|
||||
case "workspaces":
|
||||
loadWorkspaces();
|
||||
break;
|
||||
case "pinned":
|
||||
loadPinned();
|
||||
break;
|
||||
case "pr":
|
||||
loadPRs();
|
||||
break;
|
||||
case "preferences":
|
||||
loadPreferences();
|
||||
break;
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
@@ -650,24 +658,16 @@ function handleIncomingActivity(payload) {
|
||||
requestDomainRefresh("graph");
|
||||
} else if (method === "notifications/task/completed" || category.startsWith("TASK")) {
|
||||
requestDomainRefresh("task");
|
||||
} else if (category === "STICKY_NOTE" || category === "STICKY") {
|
||||
requestDomainRefresh("sticky");
|
||||
} else if (category === "TECH_DEBT" || category === "ERROR_FIX" || category === "CHECKPOINT") {
|
||||
requestDomainRefresh("techdebt");
|
||||
} else if (category === "SNIPPET") {
|
||||
requestDomainRefresh("snippets");
|
||||
} else if (category === "TERMINAL") {
|
||||
requestDomainRefresh("terminal");
|
||||
} else if (category === "CODE_CHANGE" || category === "LEDGER") {
|
||||
requestDomainRefresh("ledger");
|
||||
} else if (category === "MEMO" || category === "MILESTONE" || category === "HANDOFF_MEMO") {
|
||||
requestDomainRefresh("memos");
|
||||
} else if (category === "WORKSPACE" || category === "CONTEXT_WORKSPACE") {
|
||||
requestDomainRefresh("workspaces");
|
||||
} else if (category === "PINNED" || category === "PINNED_FILE") {
|
||||
requestDomainRefresh("pinned");
|
||||
} else if (category === "PR" || category === "PR_CHECKLIST") {
|
||||
requestDomainRefresh("pr");
|
||||
} else if (category === "PREFERENCE" || category === "PREFERENCES") {
|
||||
requestDomainRefresh("preferences");
|
||||
}
|
||||
const feed = document.getElementById("activity-feed");
|
||||
if (feed) {
|
||||
@@ -725,51 +725,6 @@ function setupWS() {
|
||||
setupSSE();
|
||||
};
|
||||
}
|
||||
async function loadPreferences() {
|
||||
try {
|
||||
const res = await fetch("/api/preferences");
|
||||
const data = await res.json();
|
||||
const container = document.getElementById("preferences-container");
|
||||
if (!container)
|
||||
return;
|
||||
container.innerHTML = "";
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
container.innerHTML = '<div style="color:var(--text-secondary); text-align:center; padding:20px;">No global preferences found.</div>';
|
||||
return;
|
||||
}
|
||||
for (const [key, pref] of Object.entries(data)) {
|
||||
const date = new Date(pref.updated_at * 1000).toLocaleString();
|
||||
container.innerHTML += `
|
||||
<div style="background:var(--card-bg); border:1px solid var(--border-color); border-radius:6px; padding:15px; box-shadow:0 1px 3px rgba(0,0,0,0.1);">
|
||||
<strong style="color:#3498db; display:block; margin-bottom:5px; font-size:1.1em;">${key}</strong>
|
||||
<div style="margin-bottom:10px;">${pref.value}</div>
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); text-align:right;">Last Updated: ${date}</div>
|
||||
</div>`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error loading preferences:", e);
|
||||
}
|
||||
}
|
||||
async function loadStickyNotes() {
|
||||
try {
|
||||
const res = await fetch("/api/sticky");
|
||||
const sticky = await res.json();
|
||||
const container = document.getElementById("sticky-notes-container");
|
||||
if (!container)
|
||||
return;
|
||||
container.innerHTML = "";
|
||||
sticky.forEach((note) => {
|
||||
const card = document.createElement("div");
|
||||
card.className = "sticky-note";
|
||||
const date = new Date(note.timestamp * 1000).toLocaleString();
|
||||
card.innerHTML = `<div style="font-size:0.75em; color:#7f8c8d; margin-bottom:8px;">${date}</div>
|
||||
<div>${note.content}</div>`;
|
||||
container.appendChild(card);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to load sticky notes", err);
|
||||
}
|
||||
}
|
||||
async function loadGenericList(endpoint, containerId, formatter) {
|
||||
try {
|
||||
const res = await fetch(endpoint);
|
||||
@@ -792,6 +747,50 @@ async function loadGenericList(endpoint, containerId, formatter) {
|
||||
console.error(`Failed to load ${endpoint}`, err);
|
||||
}
|
||||
}
|
||||
function loadLedger() {
|
||||
loadGenericList("/api/ledger?limit=100", "ledger-container", (item) => {
|
||||
const vcs = (item.vcs_type || (item.revision && item.revision.startsWith("r") ? "svn" : "git")).toUpperCase();
|
||||
const vcsColor = vcs === "SVN" ? "#e67e22" : "#3498db";
|
||||
const rev = item.revision || item.git_commit;
|
||||
const revLabel = vcs === "SVN" ? "Rev" : "Commit";
|
||||
const branch = item.branch || item.git_branch || "None";
|
||||
const repo = item.repo_name || item.namespace || "global";
|
||||
const changeKind = (item.change_kind || "modified").toLowerCase();
|
||||
let kindColor = "var(--accent-color)";
|
||||
let kindLabel = "Modified";
|
||||
if (changeKind === "added") {
|
||||
kindColor = "var(--success-color)";
|
||||
kindLabel = "+ Added";
|
||||
} else if (changeKind === "deleted") {
|
||||
kindColor = "var(--error-color)";
|
||||
kindLabel = "- Deleted";
|
||||
} else if (changeKind === "renamed") {
|
||||
kindColor = "#e67e22";
|
||||
kindLabel = "→ Renamed";
|
||||
}
|
||||
const symbols = Array.isArray(item.symbols) && item.symbols.length > 0 ? item.symbols.map((s) => `<span style="background:var(--card-bg); padding:1px 6px; border-radius:3px; font-size:0.75em; border:1px solid var(--border-color); font-family:monospace;">${escapeHtml(s)}</span>`).join(" ") : "";
|
||||
const authorBadge = item.author ? `<span style="background:var(--canvas-bg); padding:2px 8px; border-radius:4px; border:1px solid var(--border-color); font-size:0.8em; color:var(--text-secondary);">\uD83D\uDC64 ${escapeHtml(item.author)}</span>` : "";
|
||||
const lineBadge = item.line_range ? `<span style="background:var(--canvas-bg); padding:2px 8px; border-radius:4px; border:1px solid var(--border-color); font-size:0.8em; font-family:monospace;">${escapeHtml(item.line_range)}</span>` : "";
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:10px;">
|
||||
<span style="font-weight:bold; font-size:1.05em; color:var(--text-primary); font-family:monospace;">${escapeHtml(item.file_path || "Unknown")}</span>
|
||||
<div style="display:flex; gap:6px; align-items:center;">
|
||||
<span style="background:${vcsColor}; color:#fff; font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">${vcs}</span>
|
||||
<span style="background:var(--canvas-bg); color:${kindColor}; border:1px solid ${kindColor}; font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px;">${kindLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:6px; font-size:0.95em; color:var(--text-primary);">${escapeHtml(item.description || "")}</div>
|
||||
${symbols ? `<div style="margin-top:8px; display:flex; gap:5px; flex-wrap:wrap; align-items:center;"><span style="font-size:0.8em; color:var(--text-secondary);">Symbols:</span> ${symbols}</div>` : ""}
|
||||
<div style="margin-top:10px; font-size:0.85em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
<span style="background:var(--canvas-bg); padding:2px 8px; border-radius:4px; font-weight:bold; border:1px solid var(--border-color); color:var(--text-primary);">\uD83D\uDCE6 ${escapeHtml(repo)}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 8px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">${revLabel}: ${rev ? escapeHtml(rev.substring(0, 10)) : "None"}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 8px; border-radius:4px; border:1px solid var(--border-color);">\uD83C\uDF3F ${escapeHtml(branch)}</span>
|
||||
${lineBadge}
|
||||
${authorBadge}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
function loadTerminal() {
|
||||
loadGenericList("/api/terminal/history", "terminal-container", (item) => `
|
||||
<span style="font-weight:bold; font-size:1.05em; color:var(--text-primary);">${item.command}</span>
|
||||
@@ -828,19 +827,6 @@ function loadADRs() {
|
||||
${item.supersedes ? `<div style="font-size:0.9em; font-weight:bold; color:var(--warning-color);">Supersedes: ${item.supersedes}</div>` : ""}
|
||||
`);
|
||||
}
|
||||
function loadWorkspaces() {
|
||||
loadGenericList("/api/context_workspaces", "workspaces-container", (item) => `
|
||||
<strong>${item.name}</strong>
|
||||
<div style="margin-bottom:8px;">${item.description || ""}</div>
|
||||
<div style="font-family:monospace; font-size:0.85em; color:#3498db;">${(item.paths || []).join(", ")}</div>
|
||||
`);
|
||||
}
|
||||
function loadPinned() {
|
||||
loadGenericList("/api/pinned_files", "pinned-container", (item) => `
|
||||
<strong>${item.path || item.file_path || item.id}</strong>
|
||||
<div style="margin-top:5px; font-size:0.85em; color:var(--text-secondary);">${item.reason || item.description || "Pinned"}</div>
|
||||
`);
|
||||
}
|
||||
function loadMemos() {
|
||||
loadGenericList("/api/handoff_memos", "memos-container", (item) => `
|
||||
<strong>Memo from ${item.author || "System"}</strong>
|
||||
@@ -858,27 +844,13 @@ function loadSnippets() {
|
||||
<pre style="background:#111417; color:#d1d8e0; padding:10px; border-radius:4px; overflow-x:auto; font-size:0.85em;">${item.content || item.code || ""}</pre>
|
||||
`);
|
||||
}
|
||||
function loadPRs() {
|
||||
loadGenericList("/api/pr_checklists", "pr-container", (item) => `
|
||||
<strong>${item.name || "Checklist"}</strong>
|
||||
<ul style="padding-left:20px; font-size:0.9em;">
|
||||
${(item.items || []).map((i) => {
|
||||
const check = i.is_completed ? "☑" : "☐";
|
||||
const strike = i.is_completed ? "text-decoration:line-through; color:var(--text-secondary);" : "";
|
||||
return `<li style="${strike}">${check} ${i.description}</li>`;
|
||||
}).join("")}
|
||||
</ul>
|
||||
`);
|
||||
}
|
||||
function loadAllExtras() {
|
||||
loadTerminal();
|
||||
loadTechDebt();
|
||||
loadADRs();
|
||||
loadWorkspaces();
|
||||
loadPinned();
|
||||
loadMemos();
|
||||
loadSnippets();
|
||||
loadPRs();
|
||||
loadLedger();
|
||||
}
|
||||
async function testClipboard() {
|
||||
const modal = document.getElementById("clipboard-modal");
|
||||
@@ -962,35 +934,23 @@ function refreshActiveTab() {
|
||||
case "task-tab":
|
||||
loadTasks();
|
||||
break;
|
||||
case "sticky-tab":
|
||||
loadStickyNotes();
|
||||
break;
|
||||
case "techdebt-tab":
|
||||
loadTechDebt();
|
||||
break;
|
||||
case "adrs-tab":
|
||||
loadADRs();
|
||||
break;
|
||||
case "workspaces-tab":
|
||||
loadWorkspaces();
|
||||
break;
|
||||
case "pinned-tab":
|
||||
loadPinned();
|
||||
break;
|
||||
case "memos-tab":
|
||||
loadMemos();
|
||||
break;
|
||||
case "snippets-tab":
|
||||
loadSnippets();
|
||||
break;
|
||||
case "pr-tab":
|
||||
loadPRs();
|
||||
break;
|
||||
case "terminal-tab":
|
||||
loadTerminal();
|
||||
break;
|
||||
case "preferences-tab":
|
||||
loadPreferences();
|
||||
case "ledger-tab":
|
||||
loadLedger();
|
||||
break;
|
||||
case "activity-tab":
|
||||
loadActivityHistory();
|
||||
@@ -1020,17 +980,13 @@ Object.assign(window, {
|
||||
requestDomainRefresh,
|
||||
handleIncomingActivity,
|
||||
setupWS,
|
||||
loadPreferences,
|
||||
loadStickyNotes,
|
||||
loadGenericList,
|
||||
loadTerminal,
|
||||
loadLedger,
|
||||
loadTechDebt,
|
||||
loadADRs,
|
||||
loadWorkspaces,
|
||||
loadPinned,
|
||||
loadMemos,
|
||||
loadSnippets,
|
||||
loadPRs,
|
||||
loadAllExtras,
|
||||
testClipboard,
|
||||
loadVersion,
|
||||
@@ -1041,4 +997,7 @@ loadVersion();
|
||||
loadActivityHistory();
|
||||
setupWS();
|
||||
var observer = new MutationObserver(() => updateGraphData());
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-theme"]
|
||||
});
|
||||
Reference in new issue
Block a user