codetrap 0.1.7 → 0.1.8
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 +151 -52
- package/docs/installation.md +113 -29
- package/package.json +4 -3
- package/plugins/codetrap-agent/.codex-plugin/plugin.json +1 -2
- package/plugins/codetrap-agent/hooks/post-flight-capture.example.md +19 -17
- package/plugins/codetrap-agent/hooks.json +2 -2
- package/{skills → plugins/codetrap-agent/skills}/codetrap-add/SKILL.md +10 -4
- package/plugins/codetrap-agent/skills/codetrap-capture/SKILL.md +14 -3
- package/plugins/codetrap-agent/skills/codetrap-capture-external/SKILL.md +52 -9
- package/plugins/codetrap-agent/skills/codetrap-check/SKILL.md +74 -6
- package/{skills → plugins/codetrap-agent/skills}/codetrap-search/SKILL.md +6 -5
- package/plugins/codetrap-agent/templates/AGENTS.codetrap.md +31 -5
- package/scripts/search-policy-sweep.ts +131 -0
- package/src/commands/workflow.ts +144 -68
- package/src/db/embedding-queries.ts +230 -48
- package/src/db/queries.ts +0 -25
- package/src/db/repository.ts +32 -21
- package/src/db/schema.ts +80 -0
- package/src/index.ts +28 -3
- package/src/lib/command-requests.ts +112 -1
- package/src/lib/config.ts +57 -7
- package/src/lib/constants.ts +1 -1
- package/src/lib/doctor.ts +42 -12
- package/src/lib/embedder.ts +118 -3
- package/src/lib/embedding-health.ts +3 -1
- package/src/lib/embedding-job.ts +3 -0
- package/src/lib/embedding-management.ts +65 -0
- package/src/lib/embedding-runtime.ts +177 -0
- package/src/lib/output-json.ts +0 -2
- package/src/lib/scope-context.ts +12 -6
- package/src/lib/scope-migration.ts +2 -1
- package/src/lib/scope.ts +0 -2
- package/src/lib/search-eval.ts +38 -18
- package/src/lib/search-policy-sweep.ts +563 -0
- package/src/lib/search-policy.ts +0 -4
- package/src/lib/search-service.ts +14 -15
- package/src/lib/session-candidate-document.ts +175 -0
- package/src/lib/session-candidate-scope.ts +6 -0
- package/src/lib/session-capture.ts +298 -32
- package/src/lib/session-codec.ts +1 -8
- package/src/lib/session-operations.ts +83 -60
- package/src/lib/session-review.ts +327 -0
- package/src/lib/session-store.ts +87 -73
- package/src/lib/store.ts +74 -10
- package/src/lib/string-list.ts +3 -0
- package/src/lib/text-lines.ts +7 -0
- package/src/lib/trap-search-document.ts +2 -1
- package/src/lib/value-types.ts +3 -0
- package/src/web/client-review.ts +171 -0
- package/src/web/client-script.ts +426 -51
- package/src/web/client-shell.ts +414 -0
- package/src/web/client-text.ts +112 -0
- package/src/web/project-registry.ts +3 -5
- package/src/web/server.ts +117 -103
- package/src/web/static.ts +364 -19
- package/skills/codetrap-capture-external/SKILL.md +0 -62
- package/skills/codetrap-check/SKILL.md +0 -69
- package/src/lib/embedding-index.ts +0 -53
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
export const WEB_SHELL_CLIENT_SCRIPT = ` const SHELL_LAYOUT_KEY = "codetrap-shell-layout";
|
|
2
|
+
const SHELL_SIDEBAR_KEY = "codetrap-sidebar-collapsed";
|
|
3
|
+
const SHELL_QUEUE_KEY = "codetrap-queue-collapsed";
|
|
4
|
+
const SHELL_DESKTOP_QUERY = "(min-width: 1061px)";
|
|
5
|
+
const SHELL_SPLITTER_WIDTH = 8;
|
|
6
|
+
const SHELL_PANE_MIN = { rail: 250, detail: 460, queue: 320 };
|
|
7
|
+
const SHELL_COLLAPSE_THRESHOLD = { rail: 180, queue: 230 };
|
|
8
|
+
const SHELL_REVEAL_EDGE_WIDTH = 18;
|
|
9
|
+
const SHELL_PEEK_WIDTH = { rail: 330, queue: 390 };
|
|
10
|
+
|
|
11
|
+
function shellElement() {
|
|
12
|
+
return document.querySelector(".shell");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isDesktopShellLayout() {
|
|
16
|
+
return window.matchMedia(SHELL_DESKTOP_QUERY).matches;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readShellLayout() {
|
|
20
|
+
try {
|
|
21
|
+
const saved = JSON.parse(localStorage.getItem(SHELL_LAYOUT_KEY) || "null");
|
|
22
|
+
if (saved && Number.isFinite(saved.rail)) {
|
|
23
|
+
const detail = Number.isFinite(saved.detail) ? saved.detail : saved.queue;
|
|
24
|
+
if (Number.isFinite(detail)) return { rail: saved.rail, detail };
|
|
25
|
+
}
|
|
26
|
+
} catch (_error) {}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeShellLayout(layout) {
|
|
31
|
+
try {
|
|
32
|
+
localStorage.setItem(SHELL_LAYOUT_KEY, JSON.stringify({
|
|
33
|
+
rail: Math.round(layout.rail),
|
|
34
|
+
detail: Math.round(layout.detail)
|
|
35
|
+
}));
|
|
36
|
+
} catch (_error) {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clamp(value, min, max) {
|
|
40
|
+
return Math.min(Math.max(value, min), max);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function shellAvailableWidth(shell) {
|
|
44
|
+
let splitterCount = 0;
|
|
45
|
+
if (!state.sidebarCollapsed) splitterCount += 1;
|
|
46
|
+
if (!state.queueCollapsed) splitterCount += 1;
|
|
47
|
+
return Math.max(0, shell.getBoundingClientRect().width - (SHELL_SPLITTER_WIDTH * splitterCount));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function currentShellLayout() {
|
|
51
|
+
const saved = readShellLayout();
|
|
52
|
+
const railWidth = document.querySelector(".rail")?.getBoundingClientRect().width || 0;
|
|
53
|
+
const detailWidth = document.querySelector(".detail")?.getBoundingClientRect().width || 0;
|
|
54
|
+
const rail = railWidth > 0 ? railWidth : saved?.rail || SHELL_PANE_MIN.rail;
|
|
55
|
+
const detail = detailWidth > 0 ? detailWidth : saved?.detail || SHELL_PANE_MIN.detail;
|
|
56
|
+
return { rail, detail };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeSidebarCollapsedShellLayout(shell, layout) {
|
|
60
|
+
const available = shellAvailableWidth(shell);
|
|
61
|
+
const minimumTotal = SHELL_PANE_MIN.detail + SHELL_PANE_MIN.queue;
|
|
62
|
+
if (available < minimumTotal) return null;
|
|
63
|
+
|
|
64
|
+
const saved = readShellLayout();
|
|
65
|
+
const rail = Number.isFinite(Number(layout.rail)) ? Number(layout.rail) : saved?.rail || SHELL_PANE_MIN.rail;
|
|
66
|
+
let detail = Number(layout.detail);
|
|
67
|
+
if (!Number.isFinite(detail)) detail = saved?.detail || SHELL_PANE_MIN.detail;
|
|
68
|
+
detail = clamp(detail, SHELL_PANE_MIN.detail, available - SHELL_PANE_MIN.queue);
|
|
69
|
+
|
|
70
|
+
return { rail: Math.round(rail), detail: Math.round(detail), available };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function queueWidthForLayout(shell, layout) {
|
|
74
|
+
const available = shellAvailableWidth(shell);
|
|
75
|
+
const rail = state.sidebarCollapsed ? 0 : Number(layout.rail);
|
|
76
|
+
const detail = Number(layout.detail);
|
|
77
|
+
if (!Number.isFinite(detail)) return SHELL_PANE_MIN.queue;
|
|
78
|
+
return available - (Number.isFinite(rail) ? rail : SHELL_PANE_MIN.rail) - detail;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function collapseTargetForLayout(side, layout) {
|
|
82
|
+
const shell = shellElement();
|
|
83
|
+
if (!shell || !isDesktopShellLayout()) return null;
|
|
84
|
+
if (side === "left" && !state.sidebarCollapsed && Number(layout.rail) <= SHELL_COLLAPSE_THRESHOLD.rail) {
|
|
85
|
+
return "sidebar";
|
|
86
|
+
}
|
|
87
|
+
if (side === "right" && !state.queueCollapsed && queueWidthForLayout(shell, layout) <= SHELL_COLLAPSE_THRESHOLD.queue) {
|
|
88
|
+
return "queue";
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function setShellCollapseTarget(target) {
|
|
94
|
+
const shell = shellElement();
|
|
95
|
+
if (!shell) return;
|
|
96
|
+
shell.classList.toggle("rail-collapse-target", target === "sidebar");
|
|
97
|
+
shell.classList.toggle("queue-collapse-target", target === "queue");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeQueueCollapsedShellLayout(shell, layout) {
|
|
101
|
+
const available = shellAvailableWidth(shell);
|
|
102
|
+
const minimumTotal = SHELL_PANE_MIN.rail + SHELL_PANE_MIN.detail;
|
|
103
|
+
if (available < minimumTotal) return null;
|
|
104
|
+
|
|
105
|
+
let rail = Number(layout.rail);
|
|
106
|
+
if (!Number.isFinite(rail)) rail = SHELL_PANE_MIN.rail;
|
|
107
|
+
rail = clamp(rail, SHELL_PANE_MIN.rail, available - SHELL_PANE_MIN.detail);
|
|
108
|
+
const detail = available - rail;
|
|
109
|
+
|
|
110
|
+
return { rail: Math.round(rail), detail: Math.round(detail), available };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function normalizeDetailOnlyShellLayout(shell, layout) {
|
|
114
|
+
const available = shellAvailableWidth(shell);
|
|
115
|
+
if (available < SHELL_PANE_MIN.detail) return null;
|
|
116
|
+
|
|
117
|
+
const saved = readShellLayout();
|
|
118
|
+
const rail = Number.isFinite(Number(layout.rail)) ? Number(layout.rail) : saved?.rail || SHELL_PANE_MIN.rail;
|
|
119
|
+
const detail = Number.isFinite(Number(layout.detail)) ? Number(layout.detail) : saved?.detail || SHELL_PANE_MIN.detail;
|
|
120
|
+
return { rail: Math.round(rail), detail: Math.round(detail), available };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeShellLayout(shell, layout) {
|
|
124
|
+
const available = shellAvailableWidth(shell);
|
|
125
|
+
const minimumTotal = SHELL_PANE_MIN.rail + SHELL_PANE_MIN.detail + SHELL_PANE_MIN.queue;
|
|
126
|
+
if (available < minimumTotal) return null;
|
|
127
|
+
|
|
128
|
+
let rail = Number(layout.rail);
|
|
129
|
+
let detail = Number(layout.detail);
|
|
130
|
+
if (!Number.isFinite(rail)) rail = SHELL_PANE_MIN.rail;
|
|
131
|
+
if (!Number.isFinite(detail)) detail = SHELL_PANE_MIN.detail;
|
|
132
|
+
|
|
133
|
+
rail = clamp(rail, SHELL_PANE_MIN.rail, available - SHELL_PANE_MIN.detail - SHELL_PANE_MIN.queue);
|
|
134
|
+
detail = clamp(detail, SHELL_PANE_MIN.detail, available - rail - SHELL_PANE_MIN.queue);
|
|
135
|
+
|
|
136
|
+
return { rail: Math.round(rail), detail: Math.round(detail), available };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function syncShellSplitterValues(layout) {
|
|
140
|
+
const left = document.querySelector("[data-splitter='left']");
|
|
141
|
+
const right = document.querySelector("[data-splitter='right']");
|
|
142
|
+
if (left) {
|
|
143
|
+
left.setAttribute("aria-hidden", String(state.sidebarCollapsed));
|
|
144
|
+
left.setAttribute("aria-valuemin", String(SHELL_PANE_MIN.rail));
|
|
145
|
+
left.setAttribute("aria-valuenow", String(Math.round(layout.rail)));
|
|
146
|
+
left.tabIndex = state.sidebarCollapsed ? -1 : 0;
|
|
147
|
+
}
|
|
148
|
+
if (right) {
|
|
149
|
+
right.setAttribute("aria-hidden", String(state.queueCollapsed));
|
|
150
|
+
right.setAttribute("aria-valuemin", String(SHELL_PANE_MIN.detail));
|
|
151
|
+
right.setAttribute("aria-valuenow", String(Math.round(layout.detail)));
|
|
152
|
+
right.tabIndex = state.queueCollapsed ? -1 : 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function applyShellLayout(layout, persist = false) {
|
|
157
|
+
const shell = shellElement();
|
|
158
|
+
if (!shell) return null;
|
|
159
|
+
if (!isDesktopShellLayout()) {
|
|
160
|
+
shell.classList.remove("rail-collapsed");
|
|
161
|
+
shell.classList.remove("queue-collapsed");
|
|
162
|
+
clearShellPeek();
|
|
163
|
+
shell.style.gridTemplateColumns = "";
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
shell.classList.toggle("rail-collapsed", state.sidebarCollapsed);
|
|
167
|
+
shell.classList.toggle("queue-collapsed", state.queueCollapsed);
|
|
168
|
+
if (state.sidebarCollapsed && state.queueCollapsed) {
|
|
169
|
+
const detailOnly = normalizeDetailOnlyShellLayout(shell, layout);
|
|
170
|
+
if (!detailOnly) {
|
|
171
|
+
shell.style.gridTemplateColumns = "";
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
shell.style.gridTemplateColumns = "minmax(" + SHELL_PANE_MIN.detail + "px, 1fr)";
|
|
175
|
+
syncShellSplitterValues(detailOnly);
|
|
176
|
+
if (persist) writeShellLayout(detailOnly);
|
|
177
|
+
return detailOnly;
|
|
178
|
+
}
|
|
179
|
+
if (state.sidebarCollapsed) {
|
|
180
|
+
const collapsed = normalizeSidebarCollapsedShellLayout(shell, layout);
|
|
181
|
+
if (!collapsed) {
|
|
182
|
+
shell.style.gridTemplateColumns = "";
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
shell.style.gridTemplateColumns = collapsed.detail + "px " + SHELL_SPLITTER_WIDTH + "px minmax(" + SHELL_PANE_MIN.queue + "px, 1fr)";
|
|
186
|
+
syncShellSplitterValues(collapsed);
|
|
187
|
+
if (persist) writeShellLayout(collapsed);
|
|
188
|
+
return collapsed;
|
|
189
|
+
}
|
|
190
|
+
if (state.queueCollapsed) {
|
|
191
|
+
const collapsed = normalizeQueueCollapsedShellLayout(shell, layout);
|
|
192
|
+
if (!collapsed) {
|
|
193
|
+
shell.style.gridTemplateColumns = "";
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
shell.style.gridTemplateColumns = collapsed.rail + "px " + SHELL_SPLITTER_WIDTH + "px minmax(" + SHELL_PANE_MIN.detail + "px, 1fr)";
|
|
197
|
+
syncShellSplitterValues(collapsed);
|
|
198
|
+
if (persist) writeShellLayout(collapsed);
|
|
199
|
+
return collapsed;
|
|
200
|
+
}
|
|
201
|
+
const normalized = normalizeShellLayout(shell, layout);
|
|
202
|
+
if (!normalized) {
|
|
203
|
+
shell.style.gridTemplateColumns = "";
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
shell.style.gridTemplateColumns = normalized.rail + "px " + SHELL_SPLITTER_WIDTH + "px " + normalized.detail + "px " + SHELL_SPLITTER_WIDTH + "px minmax(" + SHELL_PANE_MIN.queue + "px, 1fr)";
|
|
207
|
+
syncShellSplitterValues(normalized);
|
|
208
|
+
if (persist) writeShellLayout(normalized);
|
|
209
|
+
return normalized;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function applySavedShellLayout() {
|
|
213
|
+
const shell = shellElement();
|
|
214
|
+
if (!shell) return;
|
|
215
|
+
if (!isDesktopShellLayout()) {
|
|
216
|
+
shell.classList.remove("rail-collapsed");
|
|
217
|
+
shell.classList.remove("queue-collapsed");
|
|
218
|
+
clearShellPeek();
|
|
219
|
+
shell.style.gridTemplateColumns = "";
|
|
220
|
+
renderSidebarToggle();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
shell.classList.toggle("rail-collapsed", state.sidebarCollapsed);
|
|
224
|
+
shell.classList.toggle("queue-collapsed", state.queueCollapsed);
|
|
225
|
+
const saved = readShellLayout();
|
|
226
|
+
if (saved) {
|
|
227
|
+
applyShellLayout(saved);
|
|
228
|
+
} else {
|
|
229
|
+
shell.style.gridTemplateColumns = "";
|
|
230
|
+
syncShellSplitterValues(currentShellLayout());
|
|
231
|
+
}
|
|
232
|
+
renderSidebarToggle();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function resetShellLayout() {
|
|
236
|
+
const shell = shellElement();
|
|
237
|
+
try {
|
|
238
|
+
localStorage.removeItem(SHELL_LAYOUT_KEY);
|
|
239
|
+
} catch (_error) {}
|
|
240
|
+
if (shell) shell.style.gridTemplateColumns = "";
|
|
241
|
+
syncShellSplitterValues(currentShellLayout());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function setSidebarCollapsed(collapsed) {
|
|
245
|
+
state.sidebarCollapsed = Boolean(collapsed);
|
|
246
|
+
try {
|
|
247
|
+
localStorage.setItem(SHELL_SIDEBAR_KEY, state.sidebarCollapsed ? "true" : "false");
|
|
248
|
+
} catch (_error) {}
|
|
249
|
+
if (!state.sidebarCollapsed) setShellPeek("rail", false);
|
|
250
|
+
applySavedShellLayout();
|
|
251
|
+
renderSidebarToggle();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function setQueueCollapsed(collapsed) {
|
|
255
|
+
state.queueCollapsed = Boolean(collapsed);
|
|
256
|
+
try {
|
|
257
|
+
localStorage.setItem(SHELL_QUEUE_KEY, state.queueCollapsed ? "true" : "false");
|
|
258
|
+
} catch (_error) {}
|
|
259
|
+
if (!state.queueCollapsed) setShellPeek("queue", false);
|
|
260
|
+
applySavedShellLayout();
|
|
261
|
+
renderSidebarToggle();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function renderSidebarToggle() {
|
|
265
|
+
const button = el("sidebar-toggle");
|
|
266
|
+
const queueButton = el("queue-toggle");
|
|
267
|
+
const desktop = isDesktopShellLayout();
|
|
268
|
+
if (button) {
|
|
269
|
+
const effectiveCollapsed = state.sidebarCollapsed && desktop;
|
|
270
|
+
const label = t(effectiveCollapsed ? "action.showSidebar" : "action.hideSidebar");
|
|
271
|
+
button.classList.toggle("active", effectiveCollapsed);
|
|
272
|
+
button.setAttribute("aria-label", label);
|
|
273
|
+
button.setAttribute("aria-pressed", String(effectiveCollapsed));
|
|
274
|
+
button.title = label;
|
|
275
|
+
}
|
|
276
|
+
if (queueButton) {
|
|
277
|
+
const effectiveCollapsed = state.queueCollapsed && desktop;
|
|
278
|
+
const label = t(effectiveCollapsed ? "action.showQueue" : "action.hideQueue");
|
|
279
|
+
queueButton.classList.toggle("active", effectiveCollapsed);
|
|
280
|
+
queueButton.setAttribute("aria-label", label);
|
|
281
|
+
queueButton.setAttribute("aria-pressed", String(effectiveCollapsed));
|
|
282
|
+
queueButton.title = label;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function nextShellLayout(side, start, delta) {
|
|
287
|
+
if (side === "left") {
|
|
288
|
+
return { rail: start.rail + delta, detail: start.detail - delta };
|
|
289
|
+
}
|
|
290
|
+
return { rail: start.rail, detail: start.detail + delta };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function nudgeShellLayout(side, delta) {
|
|
294
|
+
applyShellLayout(nextShellLayout(side, currentShellLayout(), delta), true);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function setShellPeek(side, active) {
|
|
298
|
+
const shell = shellElement();
|
|
299
|
+
if (!shell) return;
|
|
300
|
+
const enabled = Boolean(active) && isDesktopShellLayout();
|
|
301
|
+
if (side === "rail") shell.classList.toggle("rail-peeking", enabled && state.sidebarCollapsed);
|
|
302
|
+
if (side === "queue") shell.classList.toggle("queue-peeking", enabled && state.queueCollapsed);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function clearShellPeek() {
|
|
306
|
+
const shell = shellElement();
|
|
307
|
+
if (!shell) return;
|
|
308
|
+
shell.classList.remove("rail-peeking");
|
|
309
|
+
shell.classList.remove("queue-peeking");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function updateShellPeekFromPointer(event) {
|
|
313
|
+
const shell = shellElement();
|
|
314
|
+
if (!shell || !isDesktopShellLayout()) {
|
|
315
|
+
clearShellPeek();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const rect = shell.getBoundingClientRect();
|
|
319
|
+
const insideY = event.clientY >= rect.top && event.clientY <= rect.bottom;
|
|
320
|
+
if (!insideY) {
|
|
321
|
+
clearShellPeek();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (state.sidebarCollapsed) {
|
|
325
|
+
const leftDistance = event.clientX - rect.left;
|
|
326
|
+
const leftLimit = shell.classList.contains("rail-peeking") ? SHELL_PEEK_WIDTH.rail : SHELL_REVEAL_EDGE_WIDTH;
|
|
327
|
+
setShellPeek("rail", leftDistance >= 0 && leftDistance <= leftLimit);
|
|
328
|
+
} else {
|
|
329
|
+
setShellPeek("rail", false);
|
|
330
|
+
}
|
|
331
|
+
if (state.queueCollapsed) {
|
|
332
|
+
const rightDistance = rect.right - event.clientX;
|
|
333
|
+
const rightLimit = shell.classList.contains("queue-peeking") ? SHELL_PEEK_WIDTH.queue : SHELL_REVEAL_EDGE_WIDTH;
|
|
334
|
+
setShellPeek("queue", rightDistance >= 0 && rightDistance <= rightLimit);
|
|
335
|
+
} else {
|
|
336
|
+
setShellPeek("queue", false);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function initShellResizers() {
|
|
341
|
+
const splitters = document.querySelectorAll("[data-splitter]");
|
|
342
|
+
if (!splitters.length) return;
|
|
343
|
+
applySavedShellLayout();
|
|
344
|
+
|
|
345
|
+
splitters.forEach((splitter) => {
|
|
346
|
+
splitter.addEventListener("pointerdown", (event) => {
|
|
347
|
+
if (!isDesktopShellLayout()) return;
|
|
348
|
+
if (splitter.dataset.splitter === "left" && state.sidebarCollapsed) return;
|
|
349
|
+
if (splitter.dataset.splitter === "right" && state.queueCollapsed) return;
|
|
350
|
+
event.preventDefault();
|
|
351
|
+
const side = splitter.dataset.splitter;
|
|
352
|
+
const start = currentShellLayout();
|
|
353
|
+
const startX = event.clientX;
|
|
354
|
+
let lastDelta = 0;
|
|
355
|
+
|
|
356
|
+
splitter.classList.add("dragging");
|
|
357
|
+
document.body.classList.add("resizing-panes");
|
|
358
|
+
if (splitter.setPointerCapture) splitter.setPointerCapture(event.pointerId);
|
|
359
|
+
|
|
360
|
+
const onMove = (moveEvent) => {
|
|
361
|
+
lastDelta = moveEvent.clientX - startX;
|
|
362
|
+
const requested = nextShellLayout(side, start, lastDelta);
|
|
363
|
+
setShellCollapseTarget(collapseTargetForLayout(side, requested));
|
|
364
|
+
applyShellLayout(requested);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const stop = () => {
|
|
368
|
+
window.removeEventListener("pointermove", onMove);
|
|
369
|
+
window.removeEventListener("pointerup", stop);
|
|
370
|
+
window.removeEventListener("pointercancel", stop);
|
|
371
|
+
splitter.classList.remove("dragging");
|
|
372
|
+
document.body.classList.remove("resizing-panes");
|
|
373
|
+
const requested = nextShellLayout(side, start, lastDelta);
|
|
374
|
+
const collapseTarget = collapseTargetForLayout(side, requested);
|
|
375
|
+
setShellCollapseTarget(null);
|
|
376
|
+
if (collapseTarget === "sidebar") {
|
|
377
|
+
setSidebarCollapsed(true);
|
|
378
|
+
} else if (collapseTarget === "queue") {
|
|
379
|
+
setQueueCollapsed(true);
|
|
380
|
+
} else {
|
|
381
|
+
applyShellLayout(currentShellLayout(), true);
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
window.addEventListener("pointermove", onMove);
|
|
386
|
+
window.addEventListener("pointerup", stop);
|
|
387
|
+
window.addEventListener("pointercancel", stop);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
splitter.addEventListener("keydown", (event) => {
|
|
391
|
+
if (!isDesktopShellLayout() || (event.key !== "ArrowLeft" && event.key !== "ArrowRight")) return;
|
|
392
|
+
if (splitter.dataset.splitter === "left" && state.sidebarCollapsed) return;
|
|
393
|
+
if (splitter.dataset.splitter === "right" && state.queueCollapsed) return;
|
|
394
|
+
event.preventDefault();
|
|
395
|
+
const step = event.shiftKey ? 48 : 16;
|
|
396
|
+
nudgeShellLayout(splitter.dataset.splitter, event.key === "ArrowRight" ? step : -step);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
splitter.addEventListener("dblclick", resetShellLayout);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
let resizeFrame = 0;
|
|
403
|
+
window.addEventListener("resize", () => {
|
|
404
|
+
if (resizeFrame) cancelAnimationFrame(resizeFrame);
|
|
405
|
+
resizeFrame = requestAnimationFrame(() => {
|
|
406
|
+
resizeFrame = 0;
|
|
407
|
+
applySavedShellLayout();
|
|
408
|
+
renderSidebarToggle();
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
window.addEventListener("mousemove", updateShellPeekFromPointer);
|
|
412
|
+
document.addEventListener("mouseleave", clearShellPeek);
|
|
413
|
+
}
|
|
414
|
+
`;
|
package/src/web/client-text.ts
CHANGED
|
@@ -4,18 +4,36 @@ export const WEB_TEXT = {
|
|
|
4
4
|
"nav.review": "Review",
|
|
5
5
|
"nav.library": "Library",
|
|
6
6
|
"nav.insights": "Insights",
|
|
7
|
+
"nav.embeddings": "Embeddings",
|
|
7
8
|
"action.refresh": "Refresh",
|
|
8
9
|
"action.add": "Add",
|
|
10
|
+
"action.hideSidebar": "Hide sidebar",
|
|
11
|
+
"action.showSidebar": "Show sidebar",
|
|
12
|
+
"action.hideQueue": "Hide queue pane",
|
|
13
|
+
"action.showQueue": "Show queue pane",
|
|
9
14
|
"action.deleteSession": "Delete",
|
|
10
15
|
"action.cleanDeletedCandidates": "Clear deleted candidates",
|
|
16
|
+
"action.useProvider": "Use provider",
|
|
17
|
+
"action.reindexProject": "Reindex project",
|
|
18
|
+
"action.reindexGlobal": "Reindex global",
|
|
11
19
|
"section.sessions": "sessions",
|
|
12
20
|
"placeholder.projectPath": "/path/to/project",
|
|
21
|
+
"placeholder.endpoint": "http://127.0.0.1:11434",
|
|
22
|
+
"placeholder.model": "qwen3-embedding:0.6b",
|
|
13
23
|
"title.candidateInbox": "candidate inbox",
|
|
14
24
|
"title.candidateDetail": "candidate detail",
|
|
15
25
|
"title.trapLibrary": "trap library",
|
|
16
26
|
"title.trapDetail": "trap detail",
|
|
17
27
|
"title.growthInsights": "growth insights",
|
|
18
28
|
"title.insightDetail": "insight detail",
|
|
29
|
+
"title.embeddings": "embeddings",
|
|
30
|
+
"title.embeddingDetail": "semantic search detail",
|
|
31
|
+
"title.currentProfile": "current profile",
|
|
32
|
+
"title.projectEmbeddings": "project embeddings",
|
|
33
|
+
"title.globalEmbeddings": "global embeddings",
|
|
34
|
+
"title.storedProfiles": "stored profiles",
|
|
35
|
+
"title.providerSetup": "provider setup",
|
|
36
|
+
"title.reindex": "reindex",
|
|
19
37
|
"title.recentTraps": "recent traps",
|
|
20
38
|
"title.mostViewed": "most viewed",
|
|
21
39
|
"title.recentHighSeverity": "recent high severity",
|
|
@@ -30,6 +48,8 @@ export const WEB_TEXT = {
|
|
|
30
48
|
"meta.sessionCounts": "{goal} / {pending} pending, {reviewed} reviewed",
|
|
31
49
|
"meta.libraryCounts": "{shown} shown / {loaded} loaded / {sort}",
|
|
32
50
|
"meta.insightCounts": "{count} traps / {status} status",
|
|
51
|
+
"meta.embeddingCounts": "{provider} / project {projectFresh}/{projectTotal} fresh / global {globalFresh}/{globalTotal} fresh",
|
|
52
|
+
"meta.embeddingDetail": "{profile} / {state}",
|
|
33
53
|
"meta.selectCandidate": "select a candidate",
|
|
34
54
|
"meta.selectTrap": "select a trap",
|
|
35
55
|
"meta.selectProject": "select a project",
|
|
@@ -44,6 +64,7 @@ export const WEB_TEXT = {
|
|
|
44
64
|
"empty.noEvidence": "No evidence",
|
|
45
65
|
"empty.noData": "No data",
|
|
46
66
|
"empty.noTraps": "No traps",
|
|
67
|
+
"empty.noProfiles": "No stored profiles",
|
|
47
68
|
"action.viewTrap": "View trap",
|
|
48
69
|
"action.clearFilters": "Clear filters",
|
|
49
70
|
"action.save": "Save",
|
|
@@ -61,6 +82,18 @@ export const WEB_TEXT = {
|
|
|
61
82
|
"label.sort": "Sort",
|
|
62
83
|
"label.module": "Module",
|
|
63
84
|
"label.owner": "Owner",
|
|
85
|
+
"label.provider": "Provider",
|
|
86
|
+
"label.endpoint": "Endpoint",
|
|
87
|
+
"label.model": "Model",
|
|
88
|
+
"label.dimensions": "Dimensions",
|
|
89
|
+
"label.profileId": "Profile id",
|
|
90
|
+
"label.available": "Available",
|
|
91
|
+
"label.setupAction": "Next action",
|
|
92
|
+
"label.fresh": "Fresh",
|
|
93
|
+
"label.stale": "Stale",
|
|
94
|
+
"label.missing": "Missing",
|
|
95
|
+
"label.total": "Total",
|
|
96
|
+
"label.count": "Count",
|
|
64
97
|
"label.title": "Title",
|
|
65
98
|
"label.severity": "Severity",
|
|
66
99
|
"label.tags": "Tags",
|
|
@@ -80,6 +113,10 @@ export const WEB_TEXT = {
|
|
|
80
113
|
"metric.topCategory": "Top category",
|
|
81
114
|
"metric.focusArea": "Focus area",
|
|
82
115
|
"metric.mostViewed": "Most viewed",
|
|
116
|
+
"metric.activeProvider": "Active provider",
|
|
117
|
+
"metric.activeProfile": "Active profile",
|
|
118
|
+
"metric.projectFresh": "Project fresh",
|
|
119
|
+
"metric.globalFresh": "Global fresh",
|
|
83
120
|
"metric.currentFilters": "current filters",
|
|
84
121
|
"metric.selectedScope": "selected scope",
|
|
85
122
|
"metric.errorCritical": "error + critical",
|
|
@@ -105,6 +142,7 @@ export const WEB_TEXT = {
|
|
|
105
142
|
"sortLabel.category": "category sort",
|
|
106
143
|
"sortLabel.title": "title sort",
|
|
107
144
|
"pill.hits": "{count} hits",
|
|
145
|
+
"pill.pending": "{count} pending",
|
|
108
146
|
"pill.candidates": "{count} candidates",
|
|
109
147
|
"pill.accepted": "{count} accepted",
|
|
110
148
|
"pill.warnings": "{count} warnings",
|
|
@@ -112,10 +150,19 @@ export const WEB_TEXT = {
|
|
|
112
150
|
"pill.conflict": "conflict {status}",
|
|
113
151
|
"pill.action": "action {action}",
|
|
114
152
|
"review.pending": "pending review",
|
|
153
|
+
"reviewSummary.pending": "{count} pending candidate(s)",
|
|
154
|
+
"reviewSummary.sessions": "{count} session(s)",
|
|
155
|
+
"reviewSummary.quality": "{high} high quality / {edit} need edit",
|
|
115
156
|
"review.rejected": "rejected",
|
|
116
157
|
"review.accepted": "accepted -> trap #{id}",
|
|
117
158
|
"review.acceptedDeleted": "accepted -> trap #{id} deleted",
|
|
118
159
|
"review.acceptedLinkMissing": "accepted -> trap link missing",
|
|
160
|
+
"embedding.available": "available",
|
|
161
|
+
"embedding.unavailable": "unavailable",
|
|
162
|
+
"embedding.notConfigured": "not configured",
|
|
163
|
+
"embedding.noProfile": "no active profile",
|
|
164
|
+
"embedding.activeProfile": "active profile",
|
|
165
|
+
"embedding.profileNeedsReindex": "profile needs reindex",
|
|
119
166
|
"status.refreshed": "Refreshed",
|
|
120
167
|
"status.candidateSaved": "Candidate saved",
|
|
121
168
|
"status.candidateRejected": "Candidate rejected",
|
|
@@ -126,10 +173,19 @@ export const WEB_TEXT = {
|
|
|
126
173
|
"status.trapNotInLibrary": "Trap #{id} is not in the current library",
|
|
127
174
|
"status.sessionDeleted": "Session deleted",
|
|
128
175
|
"status.deletedCandidatesCleaned": "Deleted candidate links cleared",
|
|
176
|
+
"status.embeddingProviderSaved": "Embedding provider saved",
|
|
177
|
+
"status.embeddingsReindexed": "Embeddings reindexed: {generated} generated, {skipped} skipped",
|
|
178
|
+
"status.invalidDimensions": "Dimensions must be a positive integer",
|
|
179
|
+
"hint.acceptUsesCurrentDraft": "Accept uses the current draft.",
|
|
180
|
+
"hint.unsavedDraftAccepted": "Unsaved edits will be accepted.",
|
|
181
|
+
"hint.jinaEnv": "Jina uses JINA_API_KEY from the environment; the web console does not save API keys.",
|
|
182
|
+
"hint.reindexAfterSwitch": "Reindex project or global embeddings before semantic/hybrid search can use the active profile.",
|
|
129
183
|
"prompt.rejectReason": "Reject reason",
|
|
130
184
|
"prompt.deleteSession": "Delete session {id}?",
|
|
131
185
|
"value.project": "project",
|
|
132
186
|
"value.global": "global",
|
|
187
|
+
"value.ollama": "Ollama",
|
|
188
|
+
"value.jina": "Jina",
|
|
133
189
|
"value.active": "active",
|
|
134
190
|
"value.all": "all",
|
|
135
191
|
"value.archived": "archived",
|
|
@@ -168,18 +224,36 @@ export const WEB_TEXT = {
|
|
|
168
224
|
"nav.review": "审核",
|
|
169
225
|
"nav.library": "库",
|
|
170
226
|
"nav.insights": "洞察",
|
|
227
|
+
"nav.embeddings": "嵌入",
|
|
171
228
|
"action.refresh": "刷新",
|
|
172
229
|
"action.add": "添加",
|
|
230
|
+
"action.hideSidebar": "隐藏侧边栏",
|
|
231
|
+
"action.showSidebar": "显示侧边栏",
|
|
232
|
+
"action.hideQueue": "隐藏队列栏",
|
|
233
|
+
"action.showQueue": "显示队列栏",
|
|
173
234
|
"action.deleteSession": "删除",
|
|
174
235
|
"action.cleanDeletedCandidates": "清除已删除候选",
|
|
236
|
+
"action.useProvider": "使用提供方",
|
|
237
|
+
"action.reindexProject": "重建项目索引",
|
|
238
|
+
"action.reindexGlobal": "重建全局索引",
|
|
175
239
|
"section.sessions": "会话",
|
|
176
240
|
"placeholder.projectPath": "/项目/路径",
|
|
241
|
+
"placeholder.endpoint": "http://127.0.0.1:11434",
|
|
242
|
+
"placeholder.model": "qwen3-embedding:0.6b",
|
|
177
243
|
"title.candidateInbox": "候选收件箱",
|
|
178
244
|
"title.candidateDetail": "候选详情",
|
|
179
245
|
"title.trapLibrary": "陷阱库",
|
|
180
246
|
"title.trapDetail": "陷阱详情",
|
|
181
247
|
"title.growthInsights": "成长洞察",
|
|
182
248
|
"title.insightDetail": "洞察详情",
|
|
249
|
+
"title.embeddings": "嵌入",
|
|
250
|
+
"title.embeddingDetail": "语义搜索详情",
|
|
251
|
+
"title.currentProfile": "当前 profile",
|
|
252
|
+
"title.projectEmbeddings": "项目嵌入",
|
|
253
|
+
"title.globalEmbeddings": "全局嵌入",
|
|
254
|
+
"title.storedProfiles": "已存 profile",
|
|
255
|
+
"title.providerSetup": "提供方设置",
|
|
256
|
+
"title.reindex": "重建索引",
|
|
183
257
|
"title.recentTraps": "最近陷阱",
|
|
184
258
|
"title.mostViewed": "查看最多",
|
|
185
259
|
"title.recentHighSeverity": "最近高严重度",
|
|
@@ -194,6 +268,8 @@ export const WEB_TEXT = {
|
|
|
194
268
|
"meta.sessionCounts": "{goal} / {pending} 个待审,{reviewed} 个已审",
|
|
195
269
|
"meta.libraryCounts": "显示 {shown} / 已加载 {loaded} / {sort}",
|
|
196
270
|
"meta.insightCounts": "{count} 条陷阱 / 状态 {status}",
|
|
271
|
+
"meta.embeddingCounts": "{provider} / 项目 {projectFresh}/{projectTotal} fresh / 全局 {globalFresh}/{globalTotal} fresh",
|
|
272
|
+
"meta.embeddingDetail": "{profile} / {state}",
|
|
197
273
|
"meta.selectCandidate": "选择一个候选",
|
|
198
274
|
"meta.selectTrap": "选择一个陷阱",
|
|
199
275
|
"meta.selectProject": "选择一个项目",
|
|
@@ -208,6 +284,7 @@ export const WEB_TEXT = {
|
|
|
208
284
|
"empty.noEvidence": "没有证据",
|
|
209
285
|
"empty.noData": "没有数据",
|
|
210
286
|
"empty.noTraps": "没有陷阱",
|
|
287
|
+
"empty.noProfiles": "没有已存 profile",
|
|
211
288
|
"action.viewTrap": "查看陷阱",
|
|
212
289
|
"action.clearFilters": "清除筛选",
|
|
213
290
|
"action.save": "保存",
|
|
@@ -225,6 +302,18 @@ export const WEB_TEXT = {
|
|
|
225
302
|
"label.sort": "排序",
|
|
226
303
|
"label.module": "模块",
|
|
227
304
|
"label.owner": "负责人",
|
|
305
|
+
"label.provider": "提供方",
|
|
306
|
+
"label.endpoint": "端点",
|
|
307
|
+
"label.model": "模型",
|
|
308
|
+
"label.dimensions": "维度",
|
|
309
|
+
"label.profileId": "Profile id",
|
|
310
|
+
"label.available": "可用",
|
|
311
|
+
"label.setupAction": "下一步",
|
|
312
|
+
"label.fresh": "Fresh",
|
|
313
|
+
"label.stale": "Stale",
|
|
314
|
+
"label.missing": "Missing",
|
|
315
|
+
"label.total": "总数",
|
|
316
|
+
"label.count": "数量",
|
|
228
317
|
"label.title": "标题",
|
|
229
318
|
"label.severity": "严重度",
|
|
230
319
|
"label.tags": "标签",
|
|
@@ -244,6 +333,10 @@ export const WEB_TEXT = {
|
|
|
244
333
|
"metric.topCategory": "最高分类",
|
|
245
334
|
"metric.focusArea": "关注区域",
|
|
246
335
|
"metric.mostViewed": "查看最多",
|
|
336
|
+
"metric.activeProvider": "当前提供方",
|
|
337
|
+
"metric.activeProfile": "当前 profile",
|
|
338
|
+
"metric.projectFresh": "项目 fresh",
|
|
339
|
+
"metric.globalFresh": "全局 fresh",
|
|
247
340
|
"metric.currentFilters": "当前筛选",
|
|
248
341
|
"metric.selectedScope": "选中范围",
|
|
249
342
|
"metric.errorCritical": "error + critical",
|
|
@@ -269,6 +362,7 @@ export const WEB_TEXT = {
|
|
|
269
362
|
"sortLabel.category": "按分类排序",
|
|
270
363
|
"sortLabel.title": "按标题排序",
|
|
271
364
|
"pill.hits": "{count} 次查看",
|
|
365
|
+
"pill.pending": "{count} 个待审",
|
|
272
366
|
"pill.candidates": "{count} 个候选",
|
|
273
367
|
"pill.accepted": "{count} 个已接受",
|
|
274
368
|
"pill.warnings": "{count} 个警告",
|
|
@@ -276,10 +370,19 @@ export const WEB_TEXT = {
|
|
|
276
370
|
"pill.conflict": "冲突 {status}",
|
|
277
371
|
"pill.action": "建议 {action}",
|
|
278
372
|
"review.pending": "待审核",
|
|
373
|
+
"reviewSummary.pending": "{count} 个待审候选",
|
|
374
|
+
"reviewSummary.sessions": "{count} 个会话",
|
|
375
|
+
"reviewSummary.quality": "{high} 个高质量 / {edit} 个需编辑",
|
|
279
376
|
"review.rejected": "已拒绝",
|
|
280
377
|
"review.accepted": "已接受 -> 陷阱 #{id}",
|
|
281
378
|
"review.acceptedDeleted": "已接受 -> 陷阱 #{id} 已删除",
|
|
282
379
|
"review.acceptedLinkMissing": "已接受 -> 缺少陷阱链接",
|
|
380
|
+
"embedding.available": "可用",
|
|
381
|
+
"embedding.unavailable": "不可用",
|
|
382
|
+
"embedding.notConfigured": "未配置",
|
|
383
|
+
"embedding.noProfile": "没有当前 profile",
|
|
384
|
+
"embedding.activeProfile": "当前 profile",
|
|
385
|
+
"embedding.profileNeedsReindex": "profile 需要重建索引",
|
|
283
386
|
"status.refreshed": "已刷新",
|
|
284
387
|
"status.candidateSaved": "候选已保存",
|
|
285
388
|
"status.candidateRejected": "候选已拒绝",
|
|
@@ -290,10 +393,19 @@ export const WEB_TEXT = {
|
|
|
290
393
|
"status.trapNotInLibrary": "当前陷阱库里没有陷阱 #{id}",
|
|
291
394
|
"status.sessionDeleted": "会话已删除",
|
|
292
395
|
"status.deletedCandidatesCleaned": "已清除删除候选链接",
|
|
396
|
+
"status.embeddingProviderSaved": "嵌入提供方已保存",
|
|
397
|
+
"status.embeddingsReindexed": "嵌入索引已重建:生成 {generated},跳过 {skipped}",
|
|
398
|
+
"status.invalidDimensions": "维度必须是正整数",
|
|
399
|
+
"hint.acceptUsesCurrentDraft": "接受时会使用当前草稿。",
|
|
400
|
+
"hint.unsavedDraftAccepted": "未保存编辑也会随接受生效。",
|
|
401
|
+
"hint.jinaEnv": "Jina 使用环境变量 JINA_API_KEY;网页控制台不会保存 API key。",
|
|
402
|
+
"hint.reindexAfterSwitch": "重建项目或全局嵌入后,语义/混合搜索才能使用当前 profile。",
|
|
293
403
|
"prompt.rejectReason": "拒绝原因",
|
|
294
404
|
"prompt.deleteSession": "删除会话 {id}?",
|
|
295
405
|
"value.project": "项目",
|
|
296
406
|
"value.global": "全局",
|
|
407
|
+
"value.ollama": "Ollama",
|
|
408
|
+
"value.jina": "Jina",
|
|
297
409
|
"value.active": "有效",
|
|
298
410
|
"value.all": "全部",
|
|
299
411
|
"value.archived": "已归档",
|