jinzd-ai-cli 0.4.89 → 0.4.90
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/dist/{batch-UMQYXVKG.js → batch-3MJ56YAA.js} +2 -2
- package/dist/chat-index-QKFH7ZP6.js +17 -0
- package/dist/chunk-5S3PIG5O.js +453 -0
- package/dist/{chunk-3O3U3L5W.js → chunk-AB2LA33A.js} +1 -1
- package/dist/{chunk-4WVXTADR.js → chunk-BJXGZFE6.js} +1 -1
- package/dist/{chunk-FVRLRIKC.js → chunk-DJGP7AR6.js} +2 -2
- package/dist/{chunk-TKYNTXKB.js → chunk-EEEAFWNK.js} +1 -1
- package/dist/{chunk-ABPT6XCI.js → chunk-G65IDWVP.js} +2 -2
- package/dist/chunk-JV5N65KN.js +50 -0
- package/dist/{chunk-GTKJUEBS.js → chunk-MO7MWNWC.js} +6 -4
- package/dist/{chunk-XMA222FQ.js → chunk-PASCDYMH.js} +17 -63
- package/dist/{chunk-E7YC4GWV.js → chunk-WPQ4D6T3.js} +1 -1
- package/dist/electron-server.js +127 -202
- package/dist/{hub-4P2BH57W.js → hub-B7NJSCWF.js} +1 -1
- package/dist/index.js +10 -10
- package/dist/{run-tests-5TO5G3YH.js → run-tests-2DYVHTIH.js} +2 -2
- package/dist/{run-tests-TGGXTOFF.js → run-tests-37FEBJTR.js} +1 -1
- package/dist/{semantic-MYAXLDCZ.js → semantic-3KJPAUW6.js} +3 -2
- package/dist/{server-NG7AEAD5.js → server-FCTPLKGO.js} +112 -6
- package/dist/{server-U2BBLP4Y.js → server-S6JYNMMF.js} +3 -3
- package/dist/{task-orchestrator-ODU45UQG.js → task-orchestrator-K6HDX4YE.js} +3 -3
- package/dist/{vector-store-UR7IARXB.js → vector-store-NDUFLNGN.js} +2 -1
- package/dist/web/client/app.js +201 -0
- package/dist/web/client/index.html +24 -0
- package/package.json +1 -1
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
pathTokens,
|
|
5
5
|
rebuildSemanticIndex,
|
|
6
6
|
semanticSearch
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-MO7MWNWC.js";
|
|
8
8
|
import "./chunk-BJAT4GNC.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-PASCDYMH.js";
|
|
10
|
+
import "./chunk-JV5N65KN.js";
|
|
10
11
|
export {
|
|
11
12
|
buildEmbeddingText,
|
|
12
13
|
hasSemanticIndex,
|
|
@@ -23,10 +23,10 @@ import {
|
|
|
23
23
|
persistToolRound,
|
|
24
24
|
rebuildExtraMessages,
|
|
25
25
|
setupProxy
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-DJGP7AR6.js";
|
|
27
27
|
import {
|
|
28
28
|
ConfigManager
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-AB2LA33A.js";
|
|
30
30
|
import {
|
|
31
31
|
ToolExecutor,
|
|
32
32
|
ToolRegistry,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
spawnAgentContext,
|
|
45
45
|
truncateOutput,
|
|
46
46
|
undoStack
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-G65IDWVP.js";
|
|
48
48
|
import "./chunk-2ZD3YTVM.js";
|
|
49
49
|
import "./chunk-4BKXL7SM.js";
|
|
50
50
|
import "./chunk-ANYYM4CF.js";
|
|
@@ -53,7 +53,7 @@ import "./chunk-KJLJPUY2.js";
|
|
|
53
53
|
import "./chunk-6VRJGH25.js";
|
|
54
54
|
import "./chunk-2DXY7UGF.js";
|
|
55
55
|
import "./chunk-KHYD3WXE.js";
|
|
56
|
-
import "./chunk-
|
|
56
|
+
import "./chunk-EEEAFWNK.js";
|
|
57
57
|
import {
|
|
58
58
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
59
59
|
AUTHOR,
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
SKILLS_DIR_NAME,
|
|
73
73
|
VERSION,
|
|
74
74
|
buildUserIdentityPrompt
|
|
75
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-WPQ4D6T3.js";
|
|
76
76
|
|
|
77
77
|
// src/web/server.ts
|
|
78
78
|
import express from "express";
|
|
@@ -622,6 +622,12 @@ var SessionHandler = class _SessionHandler {
|
|
|
622
622
|
}
|
|
623
623
|
return;
|
|
624
624
|
}
|
|
625
|
+
case "memory_search":
|
|
626
|
+
return this.handleMemorySearch(msg.query, msg.topK, msg.minScore, msg.excludeCurrentSession);
|
|
627
|
+
case "memory_status_request":
|
|
628
|
+
return this.handleMemoryStatus();
|
|
629
|
+
case "memory_rebuild":
|
|
630
|
+
return this.handleMemoryRebuild(Boolean(msg.full));
|
|
625
631
|
case "auto_pause_response": {
|
|
626
632
|
const resolve3 = this.pendingAutoPause.get(msg.requestId);
|
|
627
633
|
if (resolve3) {
|
|
@@ -2231,7 +2237,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
2231
2237
|
case "test": {
|
|
2232
2238
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
2233
2239
|
try {
|
|
2234
|
-
const { executeTests } = await import("./run-tests-
|
|
2240
|
+
const { executeTests } = await import("./run-tests-2DYVHTIH.js");
|
|
2235
2241
|
const argStr = args.join(" ").trim();
|
|
2236
2242
|
let testArgs = {};
|
|
2237
2243
|
if (argStr) {
|
|
@@ -2745,6 +2751,106 @@ Add .md files to create commands.` });
|
|
|
2745
2751
|
this.send({ type: "error", message: `Failed to clear memory: ${err.message}` });
|
|
2746
2752
|
}
|
|
2747
2753
|
}
|
|
2754
|
+
// ── B4 chat memory recall (v0.4.90+) ──────────────────────────────
|
|
2755
|
+
// Lazy-imported so the 117 MB embedder stays out of the load path for
|
|
2756
|
+
// clients that never open the Memory panel.
|
|
2757
|
+
async handleMemorySearch(query, topK, minScore, excludeCurrent) {
|
|
2758
|
+
const q = (query ?? "").trim();
|
|
2759
|
+
if (!q) {
|
|
2760
|
+
this.send({ type: "error", message: "Memory search: query is empty." });
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
try {
|
|
2764
|
+
const { searchChatMemory, loadChatIndex } = await import("./chat-index-W2UZ34ZI.js");
|
|
2765
|
+
const loaded = loadChatIndex();
|
|
2766
|
+
if (!loaded || loaded.idx.chunks.length === 0) {
|
|
2767
|
+
this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
const hits = await searchChatMemory(q, {
|
|
2771
|
+
topK: topK ?? 8,
|
|
2772
|
+
minScore: minScore ?? 0.2,
|
|
2773
|
+
excludeSessionId: excludeCurrent ? this.sessions.current?.id : void 0
|
|
2774
|
+
});
|
|
2775
|
+
this.send({
|
|
2776
|
+
type: "memory_hits",
|
|
2777
|
+
query: q,
|
|
2778
|
+
hits: hits.map((h) => ({
|
|
2779
|
+
id: h.chunk.id,
|
|
2780
|
+
sessionId: h.chunk.sessionId,
|
|
2781
|
+
sessionTitle: h.chunk.sessionTitle,
|
|
2782
|
+
provider: h.chunk.provider,
|
|
2783
|
+
model: h.chunk.model,
|
|
2784
|
+
timestamp: h.chunk.timestamp,
|
|
2785
|
+
score: h.score,
|
|
2786
|
+
text: h.chunk.text,
|
|
2787
|
+
startMessageIdx: h.chunk.startMessageIdx,
|
|
2788
|
+
endMessageIdx: h.chunk.endMessageIdx
|
|
2789
|
+
}))
|
|
2790
|
+
});
|
|
2791
|
+
} catch (err) {
|
|
2792
|
+
this.send({
|
|
2793
|
+
type: "error",
|
|
2794
|
+
message: `Memory search failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
async handleMemoryStatus() {
|
|
2799
|
+
try {
|
|
2800
|
+
const { getChatIndexStatus } = await import("./chat-index-W2UZ34ZI.js");
|
|
2801
|
+
const s = getChatIndexStatus();
|
|
2802
|
+
this.send({
|
|
2803
|
+
type: "memory_status",
|
|
2804
|
+
exists: s.exists,
|
|
2805
|
+
chunks: s.chunks,
|
|
2806
|
+
sessions: s.sessions,
|
|
2807
|
+
built: s.built,
|
|
2808
|
+
model: s.model,
|
|
2809
|
+
vecFileSizeBytes: s.vecFileSizeBytes,
|
|
2810
|
+
chunksFileSizeBytes: s.chunksFileSizeBytes
|
|
2811
|
+
});
|
|
2812
|
+
} catch (err) {
|
|
2813
|
+
this.send({
|
|
2814
|
+
type: "error",
|
|
2815
|
+
message: `Memory status failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
async handleMemoryRebuild(full) {
|
|
2820
|
+
try {
|
|
2821
|
+
this.send({
|
|
2822
|
+
type: "info",
|
|
2823
|
+
message: full ? "\u{1F9E0} Rebuilding chat memory index (this may take a while on first run \u2014 ~117 MB embedder)." : "\u{1F9E0} Refreshing chat memory index (incremental)\u2026"
|
|
2824
|
+
});
|
|
2825
|
+
const { buildChatIndex } = await import("./chat-index-W2UZ34ZI.js");
|
|
2826
|
+
const stats = await buildChatIndex({
|
|
2827
|
+
full,
|
|
2828
|
+
onProgress: (p) => {
|
|
2829
|
+
this.send({
|
|
2830
|
+
type: "memory_rebuild_progress",
|
|
2831
|
+
stage: p.stage,
|
|
2832
|
+
processed: p.processed,
|
|
2833
|
+
total: p.total
|
|
2834
|
+
});
|
|
2835
|
+
}
|
|
2836
|
+
});
|
|
2837
|
+
this.send({
|
|
2838
|
+
type: "memory_rebuild_done",
|
|
2839
|
+
chunksTotal: stats.chunksTotal,
|
|
2840
|
+
chunksAdded: stats.chunksAdded,
|
|
2841
|
+
chunksRemoved: stats.chunksRemoved,
|
|
2842
|
+
sessionsIndexed: stats.sessionsIndexed,
|
|
2843
|
+
sessionsSkipped: stats.sessionsSkipped,
|
|
2844
|
+
durationMs: stats.durationMs
|
|
2845
|
+
});
|
|
2846
|
+
await this.handleMemoryStatus();
|
|
2847
|
+
} catch (err) {
|
|
2848
|
+
this.send({
|
|
2849
|
+
type: "error",
|
|
2850
|
+
message: `Memory rebuild failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2748
2854
|
sendSessionMessages() {
|
|
2749
2855
|
const session = this.sessions.current;
|
|
2750
2856
|
if (!session) return;
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
ToolRegistry,
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
schemaToJsonSchema
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-G65IDWVP.js";
|
|
7
7
|
import "./chunk-2ZD3YTVM.js";
|
|
8
8
|
import "./chunk-4BKXL7SM.js";
|
|
9
9
|
import "./chunk-ANYYM4CF.js";
|
|
@@ -12,10 +12,10 @@ import "./chunk-KJLJPUY2.js";
|
|
|
12
12
|
import "./chunk-6VRJGH25.js";
|
|
13
13
|
import "./chunk-2DXY7UGF.js";
|
|
14
14
|
import "./chunk-KHYD3WXE.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-EEEAFWNK.js";
|
|
16
16
|
import {
|
|
17
17
|
VERSION
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-WPQ4D6T3.js";
|
|
19
19
|
|
|
20
20
|
// src/mcp/server.ts
|
|
21
21
|
import { createInterface } from "readline";
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
googleSearchContext,
|
|
6
6
|
truncateOutput
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-G65IDWVP.js";
|
|
8
8
|
import "./chunk-2ZD3YTVM.js";
|
|
9
9
|
import "./chunk-4BKXL7SM.js";
|
|
10
10
|
import "./chunk-ANYYM4CF.js";
|
|
@@ -13,10 +13,10 @@ import "./chunk-KJLJPUY2.js";
|
|
|
13
13
|
import "./chunk-6VRJGH25.js";
|
|
14
14
|
import "./chunk-2DXY7UGF.js";
|
|
15
15
|
import "./chunk-KHYD3WXE.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-EEEAFWNK.js";
|
|
17
17
|
import {
|
|
18
18
|
SUBAGENT_ALLOWED_TOOLS
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-WPQ4D6T3.js";
|
|
20
20
|
|
|
21
21
|
// src/hub/task-orchestrator.ts
|
|
22
22
|
import { createInterface } from "readline";
|
package/dist/web/client/app.js
CHANGED
|
@@ -187,6 +187,10 @@ function handleServerMessage(msg) {
|
|
|
187
187
|
case 'tools_list': renderToolsList(msg); switchSidebarTab('tools'); break;
|
|
188
188
|
case 'export_data': handleExportData(msg); break;
|
|
189
189
|
case 'memory_content': handleMemoryContent(msg); break;
|
|
190
|
+
case 'memory_hits': renderMemoryHits(msg); break;
|
|
191
|
+
case 'memory_status': renderMemoryStatus(msg); break;
|
|
192
|
+
case 'memory_rebuild_progress': renderMemoryRebuildProgress(msg); break;
|
|
193
|
+
case 'memory_rebuild_done': renderMemoryRebuildDone(msg); break;
|
|
190
194
|
case 'auth_required': showAuthScreen(msg.hasUsers); break;
|
|
191
195
|
case 'auth_result': handleAuthResult(msg); break;
|
|
192
196
|
case 'info': addInfoMessage(msg.message); break;
|
|
@@ -2869,3 +2873,200 @@ function showEnableAuthDialog() {
|
|
|
2869
2873
|
})
|
|
2870
2874
|
.catch(err => alert('Failed: ' + err.message));
|
|
2871
2875
|
}
|
|
2876
|
+
|
|
2877
|
+
// ── B4 Chat Memory recall panel (v0.4.90+) ────────────────────────────
|
|
2878
|
+
// UI for the semantic index built from all past sessions. The tab is
|
|
2879
|
+
// opt-in: we only request a status refresh + wire handlers when the
|
|
2880
|
+
// user actually clicks 🧠 Memory, so unused sessions pay zero cost.
|
|
2881
|
+
|
|
2882
|
+
let memoryPanelInit = false;
|
|
2883
|
+
let memoryStatusCache = null;
|
|
2884
|
+
|
|
2885
|
+
function initMemoryPanel() {
|
|
2886
|
+
if (memoryPanelInit) return;
|
|
2887
|
+
memoryPanelInit = true;
|
|
2888
|
+
|
|
2889
|
+
const input = document.getElementById('memory-search-input');
|
|
2890
|
+
const btnSearch = document.getElementById('btn-memory-search');
|
|
2891
|
+
const btnRefresh = document.getElementById('btn-memory-refresh');
|
|
2892
|
+
const btnRebuild = document.getElementById('btn-memory-rebuild');
|
|
2893
|
+
|
|
2894
|
+
const runSearch = () => {
|
|
2895
|
+
const q = (input?.value || '').trim();
|
|
2896
|
+
if (!q) return;
|
|
2897
|
+
const excl = document.getElementById('memory-exclude-current')?.checked;
|
|
2898
|
+
const hitsEl = document.getElementById('memory-hits');
|
|
2899
|
+
if (hitsEl) {
|
|
2900
|
+
hitsEl.innerHTML = '<div class="text-xs opacity-40 text-center py-4">Searching…</div>';
|
|
2901
|
+
}
|
|
2902
|
+
send({
|
|
2903
|
+
type: 'memory_search',
|
|
2904
|
+
query: q,
|
|
2905
|
+
topK: 8,
|
|
2906
|
+
minScore: 0.2,
|
|
2907
|
+
excludeCurrentSession: !!excl,
|
|
2908
|
+
});
|
|
2909
|
+
};
|
|
2910
|
+
|
|
2911
|
+
btnSearch?.addEventListener('click', runSearch);
|
|
2912
|
+
input?.addEventListener('keydown', (e) => {
|
|
2913
|
+
if (e.key === 'Enter') {
|
|
2914
|
+
e.preventDefault();
|
|
2915
|
+
runSearch();
|
|
2916
|
+
}
|
|
2917
|
+
});
|
|
2918
|
+
|
|
2919
|
+
btnRefresh?.addEventListener('click', () => {
|
|
2920
|
+
send({ type: 'memory_rebuild', full: false });
|
|
2921
|
+
});
|
|
2922
|
+
btnRebuild?.addEventListener('click', () => {
|
|
2923
|
+
if (!confirm('Full rebuild re-embeds every chat chunk. On first run this downloads ~117 MB and can take minutes. Continue?')) return;
|
|
2924
|
+
send({ type: 'memory_rebuild', full: true });
|
|
2925
|
+
});
|
|
2926
|
+
|
|
2927
|
+
send({ type: 'memory_status_request' });
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
function renderMemoryStatus(msg) {
|
|
2931
|
+
memoryStatusCache = msg;
|
|
2932
|
+
const el = document.getElementById('memory-status');
|
|
2933
|
+
if (!el) return;
|
|
2934
|
+
if (!msg.exists) {
|
|
2935
|
+
el.textContent = 'Index not built — click ⟳ Rebuild to create it.';
|
|
2936
|
+
el.className = 'text-xs opacity-70 truncate';
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
const built = msg.built ? msg.built.replace('T', ' ').slice(0, 16) : '-';
|
|
2940
|
+
const mb = ((msg.vecFileSizeBytes + msg.chunksFileSizeBytes) / 1024 / 1024).toFixed(2);
|
|
2941
|
+
el.textContent = `${msg.chunks} chunks · ${msg.sessions} sessions · ${mb} MB · built ${built}`;
|
|
2942
|
+
el.className = 'text-xs opacity-50 truncate';
|
|
2943
|
+
el.title = `model: ${msg.model || '-'}`;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
function renderMemoryHits(msg) {
|
|
2947
|
+
const el = document.getElementById('memory-hits');
|
|
2948
|
+
if (!el) return;
|
|
2949
|
+
if (msg.indexMissing) {
|
|
2950
|
+
el.innerHTML = '<div class="text-xs opacity-60 text-center py-4">Chat index is empty — click ⟳ Rebuild first.</div>';
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
if (!msg.hits || msg.hits.length === 0) {
|
|
2954
|
+
el.innerHTML = `<div class="text-xs opacity-60 text-center py-4">No memories matched "${escapeHtml(msg.query)}".<br>Try a broader phrase or lower minScore.</div>`;
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
// Cache hit texts on the container so inject buttons can find them by id
|
|
2958
|
+
// without us having to embed full-text in data-* attrs (would bloat DOM).
|
|
2959
|
+
memoryHitsCache = new Map();
|
|
2960
|
+
for (const h of msg.hits) memoryHitsCache.set(h.id, h);
|
|
2961
|
+
|
|
2962
|
+
// Top toolbar: inject top-3 / clear results.
|
|
2963
|
+
const header = `
|
|
2964
|
+
<div class="flex items-center gap-1 text-xs pb-1 mb-1 border-b border-base-content/10">
|
|
2965
|
+
<span class="opacity-60 flex-1">${msg.hits.length} hit(s) for "${escapeHtml(msg.query)}"</span>
|
|
2966
|
+
<button class="btn btn-xs btn-primary btn-outline" id="btn-memory-inject-top3" title="Inject top 3 hits into chat input as a quoted context block">➕ Inject top 3</button>
|
|
2967
|
+
</div>
|
|
2968
|
+
`;
|
|
2969
|
+
|
|
2970
|
+
const items = msg.hits.map((h) => {
|
|
2971
|
+
const ts = (h.timestamp || '').replace('T', ' ').slice(0, 16);
|
|
2972
|
+
const title = h.sessionTitle || h.sessionId.slice(0, 8);
|
|
2973
|
+
const score = typeof h.score === 'number' ? h.score.toFixed(3) : '-';
|
|
2974
|
+
const preview = h.text.length > 500 ? h.text.slice(0, 500) + '…' : h.text;
|
|
2975
|
+
return `
|
|
2976
|
+
<div class="memory-hit card bg-base-100 border border-base-content/10 p-2 hover:border-primary/40"
|
|
2977
|
+
data-hit-id="${escapeHtml(h.id)}">
|
|
2978
|
+
<div class="flex items-center gap-1 text-xs opacity-70 mb-1">
|
|
2979
|
+
<span class="font-semibold truncate flex-1">${escapeHtml(title)}</span>
|
|
2980
|
+
<span class="badge badge-xs">${score}</span>
|
|
2981
|
+
</div>
|
|
2982
|
+
<div class="text-xs opacity-50 mb-1 flex items-center gap-2">
|
|
2983
|
+
<span class="flex-1">${ts} · session ${escapeHtml(h.sessionId.slice(0, 8))}</span>
|
|
2984
|
+
<button class="btn btn-xs btn-ghost memory-hit-inject" title="Inject this hit into chat input">➕</button>
|
|
2985
|
+
<button class="btn btn-xs btn-ghost memory-hit-load" title="Load this session in current tab">↗</button>
|
|
2986
|
+
</div>
|
|
2987
|
+
<div class="text-xs whitespace-pre-wrap leading-snug opacity-90" style="max-height: 8em; overflow: hidden;">${escapeHtml(preview)}</div>
|
|
2988
|
+
</div>
|
|
2989
|
+
`;
|
|
2990
|
+
}).join('');
|
|
2991
|
+
el.innerHTML = header + items;
|
|
2992
|
+
|
|
2993
|
+
document.getElementById('btn-memory-inject-top3')?.addEventListener('click', (e) => {
|
|
2994
|
+
e.stopPropagation();
|
|
2995
|
+
injectMemoryHits(msg.hits.slice(0, 3));
|
|
2996
|
+
});
|
|
2997
|
+
el.querySelectorAll('.memory-hit').forEach((card) => {
|
|
2998
|
+
const hitId = card.getAttribute('data-hit-id');
|
|
2999
|
+
const h = hitId ? memoryHitsCache.get(hitId) : null;
|
|
3000
|
+
if (!h) return;
|
|
3001
|
+
card.querySelector('.memory-hit-inject')?.addEventListener('click', (e) => {
|
|
3002
|
+
e.stopPropagation();
|
|
3003
|
+
injectMemoryHits([h]);
|
|
3004
|
+
});
|
|
3005
|
+
card.querySelector('.memory-hit-load')?.addEventListener('click', (e) => {
|
|
3006
|
+
e.stopPropagation();
|
|
3007
|
+
loadSessionInTab(h.sessionId, h.sessionTitle || '');
|
|
3008
|
+
});
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
/**
|
|
3013
|
+
* Inject one or more memory hits into the chat input as a quoted context
|
|
3014
|
+
* block. User can then edit or add their question before sending, giving
|
|
3015
|
+
* them full control over what lands in the AI's context (vs automatic
|
|
3016
|
+
* injection which is surprising).
|
|
3017
|
+
*/
|
|
3018
|
+
function injectMemoryHits(hits) {
|
|
3019
|
+
if (!hits || hits.length === 0) return;
|
|
3020
|
+
const lines = ['> 🧠 Recalled from past sessions:', '>'];
|
|
3021
|
+
for (const h of hits) {
|
|
3022
|
+
const ts = (h.timestamp || '').replace('T', ' ').slice(0, 16);
|
|
3023
|
+
const title = h.sessionTitle || h.sessionId.slice(0, 8);
|
|
3024
|
+
const body = h.text.length > 800 ? h.text.slice(0, 800) + '…' : h.text;
|
|
3025
|
+
lines.push(`> **${title}** · ${ts}`);
|
|
3026
|
+
for (const ln of body.split('\n')) lines.push(`> ${ln}`);
|
|
3027
|
+
lines.push('>');
|
|
3028
|
+
}
|
|
3029
|
+
lines.push('');
|
|
3030
|
+
const block = lines.join('\n');
|
|
3031
|
+
const existing = userInput?.value || '';
|
|
3032
|
+
if (userInput) {
|
|
3033
|
+
// Prepend — user's in-progress question stays after the quoted block.
|
|
3034
|
+
userInput.value = block + existing;
|
|
3035
|
+
// Fire input event so the auto-resize textarea adapts its height.
|
|
3036
|
+
userInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
3037
|
+
userInput.focus();
|
|
3038
|
+
// Move caret to end so user can keep typing.
|
|
3039
|
+
const len = userInput.value.length;
|
|
3040
|
+
try { userInput.setSelectionRange(len, len); } catch { /* ignore */ }
|
|
3041
|
+
}
|
|
3042
|
+
addInfoMessage(`🧠 Injected ${hits.length} memory hit(s) into input. Edit and send when ready.`);
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
let memoryHitsCache = new Map();
|
|
3046
|
+
|
|
3047
|
+
function renderMemoryRebuildProgress(msg) {
|
|
3048
|
+
const el = document.getElementById('memory-status');
|
|
3049
|
+
if (!el) return;
|
|
3050
|
+
if (msg.stage === 'embedding' && msg.total) {
|
|
3051
|
+
el.textContent = `Embedding ${msg.processed || 0} / ${msg.total} chunks…`;
|
|
3052
|
+
} else if (msg.stage !== 'done') {
|
|
3053
|
+
el.textContent = `${msg.stage}…`;
|
|
3054
|
+
}
|
|
3055
|
+
el.className = 'text-xs opacity-70 truncate';
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
function renderMemoryRebuildDone(msg) {
|
|
3059
|
+
const el = document.getElementById('memory-status');
|
|
3060
|
+
if (el) {
|
|
3061
|
+
el.textContent = `✓ ${msg.chunksTotal} chunks (+${msg.chunksAdded} / −${msg.chunksRemoved}) · ${msg.sessionsIndexed} re-indexed · ${(msg.durationMs / 1000).toFixed(1)}s`;
|
|
3062
|
+
el.className = 'text-xs opacity-70 truncate';
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
// Lazy-init memory panel on first click of the 🧠 Memory sidebar tab.
|
|
3067
|
+
// switchSidebarTab already toggles visibility — we just need to bootstrap.
|
|
3068
|
+
document.querySelectorAll('.sidebar-tab').forEach(btn => {
|
|
3069
|
+
if (btn.dataset.tab === 'memory') {
|
|
3070
|
+
btn.addEventListener('click', () => initMemoryPanel());
|
|
3071
|
+
}
|
|
3072
|
+
});
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="branches" title="Conversation branches (B2)">🌿 Branches</button>
|
|
109
109
|
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="tools">🔧 Tools</button>
|
|
110
110
|
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="files">📁 Files</button>
|
|
111
|
+
<button class="sidebar-tab flex-1 text-xs font-semibold py-2 px-1 text-center" data-tab="memory" title="Chat memory semantic recall (B4, v0.4.90)">🧠 Memory</button>
|
|
111
112
|
</div>
|
|
112
113
|
<!-- Sessions tab -->
|
|
113
114
|
<div id="tab-sessions" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden">
|
|
@@ -158,6 +159,29 @@
|
|
|
158
159
|
<div class="text-xs opacity-40 text-center py-4">Click tab to load</div>
|
|
159
160
|
</div>
|
|
160
161
|
</div>
|
|
162
|
+
|
|
163
|
+
<!-- Memory tab (B4, v0.4.90+) -->
|
|
164
|
+
<div id="tab-memory" class="sidebar-tab-content flex flex-col flex-1 overflow-hidden hidden">
|
|
165
|
+
<div class="p-2 border-b border-base-content/10 flex flex-col gap-1">
|
|
166
|
+
<div class="flex items-center gap-1">
|
|
167
|
+
<input id="memory-search-input" type="text" class="input input-xs input-bordered flex-1 min-w-0" placeholder="Search past chats (semantic)…">
|
|
168
|
+
<button id="btn-memory-search" class="btn btn-xs btn-primary btn-outline flex-shrink-0" title="Search">🔍</button>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="flex items-center gap-1 text-xs">
|
|
171
|
+
<label class="flex items-center gap-1 opacity-70 cursor-pointer">
|
|
172
|
+
<input id="memory-exclude-current" type="checkbox" class="checkbox checkbox-xs">
|
|
173
|
+
<span>exclude current</span>
|
|
174
|
+
</label>
|
|
175
|
+
<span class="flex-1"></span>
|
|
176
|
+
<button id="btn-memory-refresh" class="btn btn-xs btn-ghost" title="Incremental refresh">↻</button>
|
|
177
|
+
<button id="btn-memory-rebuild" class="btn btn-xs btn-ghost" title="Full rebuild (slow first run)">⟳ Rebuild</button>
|
|
178
|
+
</div>
|
|
179
|
+
<div id="memory-status" class="text-xs opacity-50 truncate">Loading index status…</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div id="memory-hits" class="flex-1 overflow-y-auto p-2 flex flex-col gap-2 text-sm">
|
|
182
|
+
<div class="text-xs opacity-40 text-center py-4">Type a query above to recall past chats.</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
161
185
|
</aside>
|
|
162
186
|
<!-- Sidebar resize handle -->
|
|
163
187
|
<div id="sidebar-resize" class="sidebar-resize-handle" title="Drag to resize sidebar"></div>
|