@memtensor/memos-local-openclaw-plugin 0.3.11 → 0.3.13

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.
@@ -2868,17 +2868,22 @@ async function loadAll(){
2868
2868
  }
2869
2869
 
2870
2870
  async function loadStats(){
2871
- const r=await fetch('/api/stats');
2872
- const d=await r.json();
2871
+ let d;
2872
+ try{
2873
+ const r=await fetch('/api/stats');
2874
+ d=await r.json();
2875
+ }catch(e){ d={}; }
2876
+ if(!d||typeof d!=='object') d={};
2877
+ const tm=d.totalMemories||0;
2873
2878
  const dedupB=d.dedupBreakdown||{};
2874
- const activeCount=dedupB.active||d.totalMemories;
2879
+ const activeCount=dedupB.active||tm;
2875
2880
  const inactiveCount=(dedupB.duplicate||0)+(dedupB.merged||0);
2876
- document.getElementById('statTotal').textContent=d.totalMemories;
2881
+ document.getElementById('statTotal').textContent=tm;
2877
2882
  if(inactiveCount>0){
2878
2883
  document.getElementById('statTotal').title=activeCount+' '+t('stat.active')+', '+inactiveCount+' '+t('stat.deduped');
2879
2884
  }
2880
- document.getElementById('statSessions').textContent=d.totalSessions;
2881
- document.getElementById('statEmbeddings').textContent=d.totalEmbeddings;
2885
+ document.getElementById('statSessions').textContent=d.totalSessions||0;
2886
+ document.getElementById('statEmbeddings').textContent=d.totalEmbeddings||0;
2882
2887
  let days=0;
2883
2888
  if(d.timeRange&&d.timeRange.earliest!=null&&d.timeRange.latest!=null){
2884
2889
  let e=Number(d.timeRange.earliest), l=Number(d.timeRange.latest);
@@ -2900,7 +2905,7 @@ async function loadStats(){
2900
2905
  }
2901
2906
 
2902
2907
  const sl=document.getElementById('sessionList');
2903
- sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>'+t('sidebar.allsessions')+'</span><span class="count">'+d.totalMemories+'</span></div>';
2908
+ sl.innerHTML='<div class="session-item'+(activeSession===null?' active':'')+'" onclick="filterSession(null)"><span>'+t('sidebar.allsessions')+'</span><span class="count">'+tm+'</span></div>';
2904
2909
  (d.sessions||[]).forEach(s=>{
2905
2910
  const isActive=activeSession===s.session_key;
2906
2911
  const name=s.session_key.length>20?s.session_key.slice(0,8)+'...'+s.session_key.slice(-8):s.session_key;
@@ -2927,16 +2932,23 @@ async function loadMemories(page){
2927
2932
  if(page) currentPage=page;
2928
2933
  const list=document.getElementById('memoryList');
2929
2934
  list.innerHTML='<div class="spinner"></div>';
2930
- const p=getFilterParams();
2931
- p.set('limit',PAGE_SIZE);
2932
- p.set('page',currentPage);
2933
- const r=await fetch('/api/memories?'+p.toString());
2934
- const d=await r.json();
2935
- totalPages=d.totalPages||1;
2936
- totalCount=d.total||0;
2937
- document.getElementById('searchMeta').textContent=totalCount+t('search.meta.total');
2938
- renderMemories(d.memories||[]);
2939
- renderPagination();
2935
+ try{
2936
+ const p=getFilterParams();
2937
+ p.set('limit',PAGE_SIZE);
2938
+ p.set('page',currentPage);
2939
+ const r=await fetch('/api/memories?'+p.toString());
2940
+ const d=await r.json();
2941
+ totalPages=d.totalPages||1;
2942
+ totalCount=d.total||0;
2943
+ document.getElementById('searchMeta').textContent=totalCount+t('search.meta.total');
2944
+ renderMemories(d.memories||[]);
2945
+ renderPagination();
2946
+ }catch(e){
2947
+ list.innerHTML='';
2948
+ totalPages=1;totalCount=0;
2949
+ renderMemories([]);
2950
+ renderPagination();
2951
+ }
2940
2952
  }
2941
2953
 
2942
2954
  async function doSearch(q){
@@ -3261,11 +3273,11 @@ async function migrateScan(){
3261
3273
 
3262
3274
  try{
3263
3275
  const r=await fetch('/api/migrate/scan');
3264
- if(!r.ok){ const err=await r.text(); throw new Error(err); }
3265
- const d=await r.json();
3276
+ const d=await r.json().catch(()=>({}));
3277
+ if(d.error && !d.sqliteFiles) throw new Error(d.error);
3266
3278
  migrateScanData=d;
3267
3279
 
3268
- const files=d.sqliteFiles||[];
3280
+ const files=Array.isArray(d.sqliteFiles)?d.sqliteFiles:[];
3269
3281
  const sess=d.sessions||{count:0,messages:0};
3270
3282
  const sqliteTotal=files.reduce((s,f)=>s+f.chunks,0);
3271
3283
  document.getElementById('migrateSqliteCount').textContent=sqliteTotal;
@@ -447,36 +447,55 @@ export class ViewerServer {
447
447
  }
448
448
 
449
449
  private serveStats(res: http.ServerResponse): void {
450
- const db = (this.store as any).db;
451
- const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get() as any;
452
- const sessions = db.prepare("SELECT COUNT(DISTINCT session_key) as count FROM chunks").get() as any;
453
- const roles = db.prepare("SELECT role, COUNT(*) as count FROM chunks GROUP BY role").all() as any[];
454
- const timeRange = db.prepare("SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks").get() as any;
455
- const embeddings = db.prepare("SELECT COUNT(*) as count FROM embeddings").get() as any;
456
- const kinds = db.prepare("SELECT kind, COUNT(*) as count FROM chunks GROUP BY kind").all() as any[];
457
- const sessionList = db.prepare(
458
- "SELECT session_key, COUNT(*) as count, MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks GROUP BY session_key ORDER BY latest DESC",
459
- ).all() as any[];
460
-
461
- let skillCount = 0;
462
- try { skillCount = (db.prepare("SELECT COUNT(*) as count FROM skills").get() as any).count; } catch { /* table may not exist yet */ }
463
-
464
- let dedupBreakdown: Record<string, number> = {};
450
+ const emptyStats = {
451
+ totalMemories: 0, totalSessions: 0, totalEmbeddings: 0, totalSkills: 0,
452
+ embeddingProvider: this.embedder?.provider ?? "none",
453
+ roleBreakdown: {}, kindBreakdown: {}, dedupBreakdown: {},
454
+ timeRange: { earliest: null, latest: null },
455
+ sessions: [],
456
+ };
457
+
458
+ if (!this.store || !(this.store as any).db) {
459
+ this.jsonResponse(res, emptyStats);
460
+ return;
461
+ }
462
+
465
463
  try {
466
- const dedupRows = db.prepare("SELECT dedup_status, COUNT(*) as count FROM chunks GROUP BY dedup_status").all() as any[];
467
- dedupBreakdown = Object.fromEntries(dedupRows.map((d: any) => [d.dedup_status ?? "active", d.count]));
468
- } catch { /* column may not exist yet */ }
464
+ const db = (this.store as any).db;
465
+ const total = db.prepare("SELECT COUNT(*) as count FROM chunks").get() as any;
466
+ const sessions = db.prepare("SELECT COUNT(DISTINCT session_key) as count FROM chunks").get() as any;
467
+ const roles = db.prepare("SELECT role, COUNT(*) as count FROM chunks GROUP BY role").all() as any[];
468
+ const timeRange = db.prepare("SELECT MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks").get() as any;
469
+ let embCount = 0;
470
+ try { embCount = (db.prepare("SELECT COUNT(*) as count FROM embeddings").get() as any).count; } catch { /* table may not exist */ }
471
+ const kinds = db.prepare("SELECT kind, COUNT(*) as count FROM chunks GROUP BY kind").all() as any[];
472
+ const sessionList = db.prepare(
473
+ "SELECT session_key, COUNT(*) as count, MIN(created_at) as earliest, MAX(created_at) as latest FROM chunks GROUP BY session_key ORDER BY latest DESC",
474
+ ).all() as any[];
475
+
476
+ let skillCount = 0;
477
+ try { skillCount = (db.prepare("SELECT COUNT(*) as count FROM skills").get() as any).count; } catch { /* table may not exist yet */ }
478
+
479
+ let dedupBreakdown: Record<string, number> = {};
480
+ try {
481
+ const dedupRows = db.prepare("SELECT dedup_status, COUNT(*) as count FROM chunks GROUP BY dedup_status").all() as any[];
482
+ dedupBreakdown = Object.fromEntries(dedupRows.map((d: any) => [d.dedup_status ?? "active", d.count]));
483
+ } catch { /* column may not exist yet */ }
469
484
 
470
- this.jsonResponse(res, {
471
- totalMemories: total.count, totalSessions: sessions.count, totalEmbeddings: embeddings.count,
472
- totalSkills: skillCount,
473
- embeddingProvider: this.embedder.provider,
474
- roleBreakdown: Object.fromEntries(roles.map((r: any) => [r.role, r.count])),
475
- kindBreakdown: Object.fromEntries(kinds.map((k: any) => [k.kind, k.count])),
476
- dedupBreakdown,
477
- timeRange: { earliest: timeRange.earliest, latest: timeRange.latest },
478
- sessions: sessionList,
479
- });
485
+ this.jsonResponse(res, {
486
+ totalMemories: total.count, totalSessions: sessions.count, totalEmbeddings: embCount,
487
+ totalSkills: skillCount,
488
+ embeddingProvider: this.embedder.provider,
489
+ roleBreakdown: Object.fromEntries(roles.map((r: any) => [r.role, r.count])),
490
+ kindBreakdown: Object.fromEntries(kinds.map((k: any) => [k.kind, k.count])),
491
+ dedupBreakdown,
492
+ timeRange: { earliest: timeRange.earliest, latest: timeRange.latest },
493
+ sessions: sessionList,
494
+ });
495
+ } catch (e) {
496
+ this.log.warn(`stats error: ${e}`);
497
+ this.jsonResponse(res, emptyStats);
498
+ }
480
499
  }
481
500
 
482
501
  private async serveSearch(_req: http.IncomingMessage, res: http.ServerResponse, url: URL): Promise<void> {
@@ -943,8 +962,17 @@ export class ViewerServer {
943
962
  });
944
963
  } catch (e) {
945
964
  this.log.warn(`migrate/scan error: ${e}`);
946
- res.writeHead(500, { "Content-Type": "application/json" });
947
- res.end(JSON.stringify({ error: String(e) }));
965
+ this.jsonResponse(res, {
966
+ sqliteFiles: [],
967
+ sessions: { count: 0, messages: 0 },
968
+ totalItems: 0,
969
+ configReady: false,
970
+ hasEmbedding: false,
971
+ hasSummarizer: false,
972
+ hasImportedData: false,
973
+ importedSessionCount: 0,
974
+ error: String(e),
975
+ });
948
976
  }
949
977
  }
950
978