Rafael Uzarowski commited on
Commit
460fc82
·
unverified ·
1 Parent(s): 9056e66

fix: eview refinements for memory dashboard

Browse files

Adressed:

1) When selecting memories using checkbox, it unchecks one second later automatically (Brave+Firefox)
2) Area filter does not work. When All is selecter, it shows me memories from fragments, solutions, etc., but when I select an area, no memories are shown.
3) Modal does not have a title, it shows a path
4) In light mode, buttons and some labels are almost invisible - we should not use custom colors anywhere, we should always use one of the color vars in the screenshot, those are automatically adjusted based on current theme. Also in light mode there's a lot of gray areas that blend, again, we should use our color vars.
5) The loading spinner is stretched into an oval
6) We should merge the first div (title and memory counts) with the third one (pagination) to save space. + we should also show the total number of memories in the DB (unfiltered, just the total count of docs)
7) Action buttons should stack vertically, we can remove the title to make it more narrow
8) We should not use emojis for icons, they break the design, it's used in the source badge (knowledge, conversation...)

python/api/memory_dashboard.py CHANGED
@@ -253,10 +253,11 @@ class MemoryDashboard(ApiHandler):
253
  threshold = 0.6 # Lower threshold for broader search in dashboard
254
  comparator = Memory._get_comparator(f"area == '{area_filter}'") if area_filter else None
255
 
 
256
  docs = await myFaiss_db.asearch(
257
  search_query,
258
  search_type="similarity_score_threshold",
259
- k=limit,
260
  score_threshold=threshold,
261
  filter=comparator,
262
  )
@@ -272,10 +273,6 @@ class MemoryDashboard(ApiHandler):
272
 
273
  memories.append(doc)
274
 
275
- # Apply limit
276
- if len(memories) >= limit:
277
- break
278
-
279
  # Format memories for the dashboard
280
  formatted_memories: list[dict] = []
281
  for memory in memories:
@@ -298,7 +295,7 @@ class MemoryDashboard(ApiHandler):
298
 
299
  formatted_memories.append(memory_data)
300
 
301
- # Sort by timestamp (newest first) - handle "unknown" timestamps
302
  def get_sort_key(memory):
303
  timestamp = memory["timestamp"]
304
  if timestamp == "unknown" or not timestamp:
@@ -307,11 +304,19 @@ class MemoryDashboard(ApiHandler):
307
 
308
  formatted_memories.sort(key=get_sort_key, reverse=True)
309
 
 
 
 
 
310
  # Get summary statistics
311
  total_memories = len(formatted_memories)
312
  knowledge_count = sum(1 for m in formatted_memories if m["knowledge_source"])
313
  conversation_count = total_memories - knowledge_count
314
 
 
 
 
 
315
  areas_count: dict[str, int] = {}
316
  for memory_dict in formatted_memories:
317
  area = memory_dict["area"]
@@ -321,6 +326,7 @@ class MemoryDashboard(ApiHandler):
321
  "success": True,
322
  "memories": formatted_memories,
323
  "total_count": total_memories,
 
324
  "knowledge_count": knowledge_count,
325
  "conversation_count": conversation_count,
326
  "areas_count": areas_count,
 
253
  threshold = 0.6 # Lower threshold for broader search in dashboard
254
  comparator = Memory._get_comparator(f"area == '{area_filter}'") if area_filter else None
255
 
256
+ # Get ALL matching results, don't limit in query
257
  docs = await myFaiss_db.asearch(
258
  search_query,
259
  search_type="similarity_score_threshold",
260
+ k=10000, # Get all matches up to reasonable max
261
  score_threshold=threshold,
262
  filter=comparator,
263
  )
 
273
 
274
  memories.append(doc)
275
 
 
 
 
 
276
  # Format memories for the dashboard
277
  formatted_memories: list[dict] = []
278
  for memory in memories:
 
295
 
296
  formatted_memories.append(memory_data)
297
 
298
+ # Sort ALL results by timestamp (newest first) - handle "unknown" timestamps
299
  def get_sort_key(memory):
300
  timestamp = memory["timestamp"]
301
  if timestamp == "unknown" or not timestamp:
 
304
 
305
  formatted_memories.sort(key=get_sort_key, reverse=True)
306
 
307
+ # Apply limit AFTER sorting to get the newest entries
308
+ if limit and len(formatted_memories) > limit:
309
+ formatted_memories = formatted_memories[:limit]
310
+
311
  # Get summary statistics
312
  total_memories = len(formatted_memories)
313
  knowledge_count = sum(1 for m in formatted_memories if m["knowledge_source"])
314
  conversation_count = total_memories - knowledge_count
315
 
316
+ # Get total count of all memories in database (unfiltered)
317
+ all_docs = myFaiss_db.get_all_docs()
318
+ total_db_count = len(all_docs)
319
+
320
  areas_count: dict[str, int] = {}
321
  for memory_dict in formatted_memories:
322
  area = memory_dict["area"]
 
326
  "success": True,
327
  "memories": formatted_memories,
328
  "total_count": total_memories,
329
+ "total_db_count": total_db_count,
330
  "knowledge_count": knowledge_count,
331
  "conversation_count": conversation_count,
332
  "areas_count": areas_count,
webui/components/settings/memory/memory-dashboard-store.js CHANGED
@@ -2,6 +2,12 @@ import { createStore } from "/js/AlpineStore.js";
2
  import { getContext } from "/index.js";
3
  import * as API from "/js/api.js";
4
  import { openModal, closeModal } from "/js/modals.js";
 
 
 
 
 
 
5
 
6
  // Memory Dashboard Store
7
  const memoryDashboardStore = {
@@ -29,6 +35,7 @@ const memoryDashboardStore = {
29
 
30
  // Stats
31
  totalCount: 0,
 
32
  knowledgeCount: 0,
33
  conversationCount: 0,
34
  areasCount: {},
@@ -147,13 +154,25 @@ const memoryDashboardStore = {
147
  limit: this.limit
148
  });
149
 
150
- if (response.success) {
 
 
 
 
 
 
 
 
 
 
 
151
  // Add selected property to each memory item for mass selection
152
  this.memories = (response.memories || []).map(memory => ({
153
  ...memory,
154
- selected: memory.selected || false
155
  }));
156
  this.totalCount = response.total_count || 0;
 
157
  this.knowledgeCount = response.knowledge_count || 0;
158
  this.conversationCount = response.conversation_count || 0;
159
  this.areasCount = response.areas_count || {};
@@ -290,16 +309,16 @@ const memoryDashboardStore = {
290
  });
291
 
292
  if (response.success) {
293
- this.showToast(`Successfully deleted ${selectedMemories.length} memories`, "success");
294
 
295
  // Let polling refresh the data instead of manual manipulation
296
  // Trigger an immediate refresh to get updated state from backend
297
  await this.searchMemories(true); // silent refresh
298
  } else {
299
- this.showToast(response.error || "Failed to delete selected memories", "error");
300
  }
301
  } catch (error) {
302
- this.showToast(error.message || "Failed to delete selected memories", "error");
303
  } finally {
304
  this.loading = false;
305
  }
@@ -337,7 +356,7 @@ ${memory.content_full}
337
  const content = selectedMemories.map(memory => this.formatMemoryForCopy(memory)).join('\n');
338
 
339
  this.copyToClipboard(content);
340
- this.showToast(`Copied ${selectedMemories.length} memories with metadata to clipboard`, "success");
341
  },
342
 
343
  bulkExportMemories() {
@@ -375,7 +394,7 @@ ${memory.content_full}
375
  document.body.removeChild(a);
376
  URL.revokeObjectURL(url);
377
 
378
- this.showToast(`Exported ${selectedMemories.length} selected memories to ${filename}`, "success");
379
  },
380
 
381
  // Memory detail modal (standard approach)
@@ -431,18 +450,18 @@ ${memory.content_full}
431
 
432
  getAreaColor(area) {
433
  const colors = {
434
- "MAIN": "#3b82f6", // blue
435
- "FRAGMENTS": "#10b981", // emerald
436
- "SOLUTIONS": "#8b5cf6", // violet
437
- "INSTRUMENTS": "#f59e0b" // amber
438
  };
439
- return colors[area] || "#6b7280"; // gray for unknown
440
  },
441
 
442
  copyToClipboard(text) {
443
  if (navigator.clipboard && window.isSecureContext) {
444
  navigator.clipboard.writeText(text).then(() => {
445
- this.showToast("Copied to clipboard!", "success");
446
  }).catch(err => {
447
  console.error("Clipboard copy failed:", err);
448
  this.fallbackCopyToClipboard(text);
@@ -463,10 +482,10 @@ ${memory.content_full}
463
  textArea.select();
464
  try {
465
  document.execCommand('copy');
466
- this.showToast("Copied to clipboard!", "success");
467
  } catch (err) {
468
  console.error("Fallback clipboard copy failed:", err);
469
- this.showToast("Failed to copy to clipboard", "error");
470
  }
471
  document.body.removeChild(textArea);
472
  },
@@ -487,7 +506,7 @@ ${memory.content_full}
487
  });
488
 
489
  if (response.success) {
490
- this.showToast("Memory deleted successfully", "success");
491
 
492
  // If we were viewing this memory in detail modal, close it
493
  if (isViewingThisMemory) {
@@ -499,17 +518,17 @@ ${memory.content_full}
499
  // Trigger an immediate refresh to get updated state from backend
500
  await this.searchMemories(true); // silent refresh
501
  } else {
502
- this.showToast(`Failed to delete memory: ${response.error}`, "error");
503
  }
504
  } catch (error) {
505
  console.error("Memory deletion error:", error);
506
- this.showToast("Failed to delete memory", "error");
507
  }
508
  },
509
 
510
  exportMemories() {
511
  if (this.memories.length === 0) {
512
- this.showToast("No memories to export", "warning");
513
  return;
514
  }
515
 
@@ -541,10 +560,10 @@ ${memory.content_full}
541
  document.body.removeChild(a);
542
  URL.revokeObjectURL(url);
543
 
544
- this.showToast("Memory export completed", "success");
545
  } catch (error) {
546
  console.error("Memory export error:", error);
547
- this.showToast("Failed to export memories", "error");
548
  }
549
  },
550
 
@@ -574,6 +593,7 @@ ${memory.content_full}
574
  this.searchQuery = "";
575
  this.memories = [];
576
  this.totalCount = 0;
 
577
  this.knowledgeCount = 0;
578
  this.conversationCount = 0;
579
  this.areasCount = {};
@@ -581,14 +601,7 @@ ${memory.content_full}
581
  this.currentPage = 1;
582
  },
583
 
584
- showToast(message, type = "info") {
585
- // Use global toast function if available
586
- if (typeof toast === 'function') {
587
- toast(message, type);
588
- } else {
589
- console.log(`[${type.toUpperCase()}] ${message}`);
590
- }
591
- }
592
  };
593
 
594
  const store = createStore("memoryDashboardStore", memoryDashboardStore);
 
2
  import { getContext } from "/index.js";
3
  import * as API from "/js/api.js";
4
  import { openModal, closeModal } from "/js/modals.js";
5
+ import { store as notificationStore } from "/components/notifications/notification-store.js";
6
+
7
+ // Helper function for toasts
8
+ function justToast(text, type = "info", timeout = 5000) {
9
+ notificationStore.addFrontendToastOnly(type, text, "", timeout / 1000);
10
+ }
11
 
12
  // Memory Dashboard Store
13
  const memoryDashboardStore = {
 
35
 
36
  // Stats
37
  totalCount: 0,
38
+ totalDbCount: 0,
39
  knowledgeCount: 0,
40
  conversationCount: 0,
41
  areasCount: {},
 
154
  limit: this.limit
155
  });
156
 
157
+ if (response.success) {
158
+ // Preserve existing selections when updating memories during polling
159
+ const existingSelections = {};
160
+ if (silent && this.memories) {
161
+ // Build a map of existing selections by memory ID
162
+ this.memories.forEach(memory => {
163
+ if (memory.selected) {
164
+ existingSelections[memory.id] = true;
165
+ }
166
+ });
167
+ }
168
+
169
  // Add selected property to each memory item for mass selection
170
  this.memories = (response.memories || []).map(memory => ({
171
  ...memory,
172
+ selected: existingSelections[memory.id] || false
173
  }));
174
  this.totalCount = response.total_count || 0;
175
+ this.totalDbCount = response.total_db_count || 0;
176
  this.knowledgeCount = response.knowledge_count || 0;
177
  this.conversationCount = response.conversation_count || 0;
178
  this.areasCount = response.areas_count || {};
 
309
  });
310
 
311
  if (response.success) {
312
+ justToast(`Successfully deleted ${selectedMemories.length} memories`, "success");
313
 
314
  // Let polling refresh the data instead of manual manipulation
315
  // Trigger an immediate refresh to get updated state from backend
316
  await this.searchMemories(true); // silent refresh
317
  } else {
318
+ justToast(response.error || "Failed to delete selected memories", "error");
319
  }
320
  } catch (error) {
321
+ justToast(error.message || "Failed to delete selected memories", "error");
322
  } finally {
323
  this.loading = false;
324
  }
 
356
  const content = selectedMemories.map(memory => this.formatMemoryForCopy(memory)).join('\n');
357
 
358
  this.copyToClipboard(content);
359
+ justToast(`Copied ${selectedMemories.length} memories with metadata to clipboard`, "success");
360
  },
361
 
362
  bulkExportMemories() {
 
394
  document.body.removeChild(a);
395
  URL.revokeObjectURL(url);
396
 
397
+ justToast(`Exported ${selectedMemories.length} selected memories to ${filename}`, "success");
398
  },
399
 
400
  // Memory detail modal (standard approach)
 
450
 
451
  getAreaColor(area) {
452
  const colors = {
453
+ "main": "#3b82f6",
454
+ "fragments": "#10b981",
455
+ "solutions": "#8b5cf6",
456
+ "instruments": "#f59e0b"
457
  };
458
+ return colors[area] || "#6c757d";
459
  },
460
 
461
  copyToClipboard(text) {
462
  if (navigator.clipboard && window.isSecureContext) {
463
  navigator.clipboard.writeText(text).then(() => {
464
+ justToast("Copied to clipboard!", "success");
465
  }).catch(err => {
466
  console.error("Clipboard copy failed:", err);
467
  this.fallbackCopyToClipboard(text);
 
482
  textArea.select();
483
  try {
484
  document.execCommand('copy');
485
+ justToast("Copied to clipboard!", "success");
486
  } catch (err) {
487
  console.error("Fallback clipboard copy failed:", err);
488
+ justToast("Failed to copy to clipboard", "error");
489
  }
490
  document.body.removeChild(textArea);
491
  },
 
506
  });
507
 
508
  if (response.success) {
509
+ justToast("Memory deleted successfully", "success");
510
 
511
  // If we were viewing this memory in detail modal, close it
512
  if (isViewingThisMemory) {
 
518
  // Trigger an immediate refresh to get updated state from backend
519
  await this.searchMemories(true); // silent refresh
520
  } else {
521
+ justToast(`Failed to delete memory: ${response.error}`, "error");
522
  }
523
  } catch (error) {
524
  console.error("Memory deletion error:", error);
525
+ justToast("Failed to delete memory", "error");
526
  }
527
  },
528
 
529
  exportMemories() {
530
  if (this.memories.length === 0) {
531
+ justToast("No memories to export", "warning");
532
  return;
533
  }
534
 
 
560
  document.body.removeChild(a);
561
  URL.revokeObjectURL(url);
562
 
563
+ justToast("Memory export completed", "success");
564
  } catch (error) {
565
  console.error("Memory export error:", error);
566
+ justToast("Failed to export memories", "error");
567
  }
568
  },
569
 
 
593
  this.searchQuery = "";
594
  this.memories = [];
595
  this.totalCount = 0;
596
+ this.totalDbCount = 0;
597
  this.knowledgeCount = 0;
598
  this.conversationCount = 0;
599
  this.areasCount = {};
 
601
  this.currentPage = 1;
602
  },
603
 
604
+
 
 
 
 
 
 
 
605
  };
606
 
607
  const store = createStore("memoryDashboardStore", memoryDashboardStore);
webui/components/settings/memory/memory-dashboard.html CHANGED
@@ -1,29 +1,39 @@
1
- <script type="module">
2
- import { store } from "/components/settings/memory/memory-dashboard-store.js";
3
- </script>
4
-
 
 
 
 
5
  <div x-data>
6
  <template x-if="$store.memoryDashboardStore">
7
- <div x-init="$store.memoryDashboardStore.initialize()" class="memory-dashboard">
8
-
9
- <!-- Header with title and statistics -->
10
- <div class="dashboard-header">
11
- <h3>Memory Dashboard</h3>
12
- <div class="memory-stats" x-show="!$store.memoryDashboardStore.loading">
13
- <div class="stat-item">
14
- <span class="stat-label">Total:</span>
15
- <span class="stat-value" x-text="$store.memoryDashboardStore.totalCount"></span>
16
- </div>
17
- <div class="stat-item">
18
- <span class="stat-label">Knowledge:</span>
19
- <span class="stat-value" x-text="$store.memoryDashboardStore.knowledgeCount"></span>
20
- </div>
21
- <div class="stat-item">
22
- <span class="stat-label">Conversation:</span>
23
- <span class="stat-value" x-text="$store.memoryDashboardStore.conversationCount"></span>
24
- </div>
25
- </div>
26
- </div>
 
 
 
 
 
 
27
 
28
  <!-- Search and Filters -->
29
  <div class="filters-section">
@@ -45,10 +55,10 @@
45
  <label for="area-filter">Area:</label>
46
  <select id="area-filter" x-model="$store.memoryDashboardStore.areaFilter">
47
  <option value="">All Areas</option>
48
- <option value="MAIN">Main</option>
49
- <option value="FRAGMENTS">Fragments</option>
50
- <option value="SOLUTIONS">Solutions</option>
51
- <option value="INSTRUMENTS">Instruments</option>
52
  </select>
53
  </div>
54
 
@@ -95,33 +105,55 @@
95
  <div x-show="!$store.memoryDashboardStore.loading && !$store.memoryDashboardStore.error"
96
  class="memory-table-container">
97
 
98
- <!-- Pagination controls at top -->
99
- <div class="pagination-controls-top" x-show="$store.memoryDashboardStore.totalPages > 1">
100
- <div class="pagination-info-inline">
101
- <span>Page <span x-text="$store.memoryDashboardStore.currentPage"></span>
102
- of <span x-text="$store.memoryDashboardStore.totalPages"></span>
103
- (<span x-text="$store.memoryDashboardStore.memories.length"></span> total memories)
104
- </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </div>
106
 
107
- <div class="pagination-controls">
108
- <button class="btn slim" @click="$store.memoryDashboardStore.prevPage()"
109
- :disabled="$store.memoryDashboardStore.currentPage === 1">
110
- Previous
111
- </button>
112
-
113
- <select class="page-select"
114
- x-model.number="$store.memoryDashboardStore.currentPage"
115
- @change="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)">
116
- <template x-for="page in Array.from({length: $store.memoryDashboardStore.totalPages}, (_, i) => i + 1)" :key="page">
117
- <option :value="page" x-text="'Page ' + page"></option>
118
- </template>
119
- </select>
120
-
121
- <button class="btn slim" @click="$store.memoryDashboardStore.nextPage()"
122
- :disabled="$store.memoryDashboardStore.currentPage === $store.memoryDashboardStore.totalPages">
123
- Next
124
- </button>
 
 
 
 
 
 
 
 
 
125
  </div>
126
  </div>
127
 
@@ -186,7 +218,7 @@
186
  </th>
187
  <th class="col-metadata">Metadata</th>
188
  <th class="col-preview">Preview</th>
189
- <th class="col-actions">Actions</th>
190
  </tr>
191
  </thead>
192
  <tbody>
@@ -204,9 +236,9 @@
204
  <td class="metadata-cell">
205
  <div class="metadata-info">
206
  <div class="metadata-row">
207
- <span class="area-badge"
208
- :style="`background-color: ${$store.memoryDashboardStore.getAreaColor(memory.area)}`"
209
- x-text="memory.area"></span>
210
  </div>
211
  <div class="metadata-row metadata-timestamp">
212
  <span x-text="$store.memoryDashboardStore.formatTimestamp(memory.timestamp, true)"
@@ -214,10 +246,10 @@
214
  </div>
215
  <div class="metadata-row">
216
  <template x-if="memory.knowledge_source">
217
- <span class="source-type knowledge">📚 Knowledge</span>
218
  </template>
219
  <template x-if="!memory.knowledge_source">
220
- <span class="source-type conversation">💬 Conversation</span>
221
  </template>
222
  </div>
223
  </div>
@@ -235,58 +267,32 @@
235
 
236
  <!-- Actions -->
237
  <td class="actions-cell" @click.stop>
238
- <button class="btn-action copy" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.formatMemoryForCopy(memory))"
239
- title="Copy Memory">
240
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
241
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
242
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
243
- </svg>
244
- </button>
245
-
246
- <button class="btn-action delete" @click="$store.memoryDashboardStore.deleteMemory(memory)"
247
- title="Delete Memory">
248
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
249
- <polyline points="3,6 5,6 21,6"></polyline>
250
- <path d="M19,6V20a2,2,0,0,1-2,2H7a2,2,0,0,1-2-2V6M8,6V4a2,2,0,0,1,2-2h4a2,2,0,0,1,2,2V6"></path>
251
- <line x1="10" y1="11" x2="10" y2="17"></line>
252
- <line x1="14" y1="11" x2="14" y2="17"></line>
253
- </svg>
254
- </button>
 
 
255
  </td>
256
  </tr>
257
  </template>
258
  </tbody>
259
  </table>
260
 
261
- <!-- Pagination controls at bottom -->
262
- <div class="pagination-controls-bottom" x-show="$store.memoryDashboardStore.totalPages > 1">
263
- <div class="pagination-info-inline">
264
- <span>Page <span x-text="$store.memoryDashboardStore.currentPage"></span>
265
- of <span x-text="$store.memoryDashboardStore.totalPages"></span>
266
- (<span x-text="$store.memoryDashboardStore.memories.length"></span> total memories)
267
- </span>
268
- </div>
269
 
270
- <div class="pagination-controls">
271
- <button class="btn slim" @click="$store.memoryDashboardStore.prevPage()"
272
- :disabled="$store.memoryDashboardStore.currentPage === 1">
273
- Previous
274
- </button>
275
-
276
- <select class="page-select"
277
- x-model.number="$store.memoryDashboardStore.currentPage"
278
- @change="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)">
279
- <template x-for="page in Array.from({length: $store.memoryDashboardStore.totalPages}, (_, i) => i + 1)" :key="page">
280
- <option :value="page" x-text="'Page ' + page"></option>
281
- </template>
282
- </select>
283
-
284
- <button class="btn slim" @click="$store.memoryDashboardStore.nextPage()"
285
- :disabled="$store.memoryDashboardStore.currentPage === $store.memoryDashboardStore.totalPages">
286
- Next
287
- </button>
288
- </div>
289
- </div>
290
 
291
  </div>
292
 
@@ -328,18 +334,38 @@
328
  color: var(--color-text);
329
  }
330
 
331
- .dashboard-header {
 
 
 
 
 
 
 
 
 
332
  display: flex;
333
  justify-content: space-between;
334
  align-items: center;
335
- margin-bottom: 1.5rem;
336
- padding-bottom: 1rem;
337
- border-bottom: 1px solid var(--color-border);
 
 
 
 
338
  }
339
 
340
- .memory-stats {
341
  display: flex;
342
  gap: 1rem;
 
 
 
 
 
 
 
343
  }
344
 
345
  .stat-item {
@@ -355,10 +381,15 @@
355
 
356
  .stat-label {
357
  font-size: 0.8rem;
358
- color: var(--color-secondary);
 
359
  margin-bottom: 0.25rem;
360
  }
361
 
 
 
 
 
362
  .stat-value {
363
  font-size: 1.1rem;
364
  font-weight: bold;
@@ -407,7 +438,13 @@
407
  .filter-group input:focus, .filter-group select:focus {
408
  outline: none;
409
  border-color: var(--color-primary);
410
- box-shadow: 0 0 0 2px rgba(115, 122, 129, 0.1);
 
 
 
 
 
 
411
  }
412
 
413
  .filter-actions {
@@ -425,14 +462,16 @@
425
 
426
  .loading-text {
427
  font-size: 0.85rem;
428
- color: var(--color-secondary);
 
429
  font-style: italic;
430
  }
431
 
432
  .loading-state, .error-state, .no-memories, .init-message {
433
  text-align: center;
434
  padding: 2rem;
435
- color: var(--color-secondary);
 
436
  background: var(--color-panel);
437
  border: 1px solid var(--color-border);
438
  border-radius: 8px;
@@ -441,24 +480,41 @@
441
 
442
  .init-message {
443
  color: var(--color-primary);
444
- background: rgba(115, 122, 129, 0.1);
445
  border-color: var(--color-primary);
446
  }
447
 
 
 
 
 
448
  .error-state {
449
  color: var(--color-accent);
450
- background: rgba(207, 102, 121, 0.1);
451
  border-color: var(--color-accent);
452
  }
453
 
 
 
 
 
454
  .loading-spinner {
455
- width: 20px;
456
- height: 20px;
457
- border: 2px solid var(--color-border);
458
- border-left-color: var(--color-primary);
 
 
 
 
459
  border-radius: 50%;
460
  animation: spin 1s linear infinite;
461
  margin: 0 auto 0.5rem;
 
 
 
 
 
462
  }
463
 
464
  @keyframes spin {
@@ -487,8 +543,8 @@
487
  /* Fixed column widths for proper fit */
488
  .col-select { width: 5%; }
489
  .col-metadata { width: 25%; }
490
- .col-preview { width: 55%; }
491
- .col-actions { width: 15%; }
492
 
493
  .memory-table th,
494
  .memory-table td {
@@ -544,12 +600,12 @@
544
 
545
  /* Selected row styling */
546
  .memory-row.selected {
547
- background: rgba(115, 122, 129, 0.1);
548
  border-left: 3px solid var(--color-primary);
549
  }
550
 
551
  .light-mode .memory-row.selected {
552
- background: rgba(115, 122, 129, 0.05);
553
  }
554
 
555
  /* Mass action toolbar */
@@ -558,7 +614,7 @@
558
  justify-content: space-between;
559
  align-items: center;
560
  padding: 1rem;
561
- background: rgba(115, 122, 129, 0.1);
562
  border: 1px solid var(--color-primary);
563
  border-radius: 8px;
564
  margin: 1rem 0;
@@ -566,7 +622,7 @@
566
  }
567
 
568
  .light-mode .mass-action-toolbar {
569
- background: rgba(115, 122, 129, 0.05);
570
  }
571
 
572
  .selection-info {
@@ -596,27 +652,27 @@
596
 
597
  .btn-mass:hover {
598
  border-color: var(--color-primary);
599
- background: rgba(115, 122, 129, 0.1);
600
  }
601
 
602
  .btn-mass.copy:hover {
603
- border-color: #4caf50;
604
- color: #4caf50;
605
  }
606
 
607
  .btn-mass.export:hover {
608
- border-color: #2196f3;
609
- color: #2196f3;
610
  }
611
 
612
  .btn-mass.delete:hover {
613
- border-color: #f44336;
614
- color: #f44336;
615
  }
616
 
617
  .btn-mass.clear:hover {
618
- border-color: #ff9800;
619
- color: #ff9800;
620
  }
621
 
622
  @keyframes slideDown {
@@ -650,7 +706,7 @@
650
  }
651
 
652
  .memory-table tbody tr:hover {
653
- background: rgba(255, 255, 255, 0.1);
654
  }
655
 
656
  .memory-table tbody tr:last-child {
@@ -663,12 +719,19 @@
663
  color: white;
664
  font-size: 0.75rem;
665
  font-weight: bold;
666
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
667
  }
668
 
669
  .timestamp-cell {
670
  font-size: 0.85rem;
671
- color: var(--color-secondary);
 
672
  white-space: normal;
673
  word-break: break-word;
674
  font-family: monospace;
@@ -694,20 +757,33 @@
694
  }
695
 
696
  .source-type.knowledge {
697
- background: rgba(115, 122, 129, 0.1);
698
  color: var(--color-primary);
699
  border-color: var(--color-primary);
700
  }
701
 
 
 
 
 
 
702
  .source-type.conversation {
703
- background: rgba(101, 101, 101, 0.1);
704
- color: var(--color-secondary);
705
- border-color: var(--color-secondary);
 
 
 
 
 
 
 
706
  }
707
 
708
  .source-file {
709
  font-size: 0.7rem;
710
- color: var(--color-secondary);
 
711
  font-family: monospace;
712
  background: var(--color-panel);
713
  padding: 0.2rem 0.4rem;
@@ -743,14 +819,35 @@
743
  padding: 0.2rem 0.4rem;
744
  border-radius: 6px;
745
  font-size: 0.7rem;
746
- color: var(--color-secondary);
 
747
  }
748
 
749
  .actions-cell {
750
- white-space: nowrap;
751
- text-align: center;
752
- min-width: 140px;
753
- padding: 0.5rem 0.25rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754
  }
755
 
756
  .btn-action {
@@ -760,7 +857,8 @@
760
  margin: 0 0.1rem;
761
  cursor: pointer;
762
  border-radius: 6px;
763
- color: var(--color-secondary);
 
764
  transition: all 0.2s ease;
765
  display: inline-flex;
766
  align-items: center;
@@ -770,56 +868,43 @@
770
  }
771
 
772
  .btn-action:hover {
773
- background: rgba(0, 0, 0, 0.05);
774
- border-color: rgba(0, 0, 0, 0.2);
775
- color: var(--color-text);
 
776
  transform: translateY(-1px);
777
  }
778
 
779
  .btn-action.view:hover {
780
- background: rgba(33, 150, 243, 0.1);
781
- border-color: #2196F3;
782
- color: #2196F3;
783
  }
784
 
785
  .btn-action.copy:hover {
786
- background: rgba(76, 175, 80, 0.1);
787
- border-color: #4CAF50;
788
- color: #4CAF50;
789
  }
790
 
791
  .btn-action.delete:hover {
792
- background: rgba(244, 67, 54, 0.1);
793
- border-color: #f44336;
794
- color: #f44336;
795
- }
796
-
797
- .pagination-controls-top {
798
- display: flex;
799
- justify-content: space-between;
800
- align-items: center;
801
- margin-bottom: 1rem;
802
- padding: 1rem;
803
  background: var(--color-panel);
804
- border: 1px solid var(--color-border);
805
- border-radius: 8px;
806
  }
807
 
 
 
808
  .pagination-info-inline {
809
- color: var(--color-secondary);
 
810
  font-size: 0.9rem;
811
  }
812
 
813
  .pagination-controls {
814
  display: flex;
815
- justify-content: center;
816
  align-items: center;
817
  gap: 0.5rem;
818
- margin: 1rem 0;
819
- padding: 1rem;
820
- background: var(--color-panel);
821
- border: 1px solid var(--color-border);
822
- border-radius: 8px;
823
  }
824
 
825
  .page-select {
@@ -836,22 +921,15 @@
836
  .page-select:focus {
837
  outline: none;
838
  border-color: var(--color-primary);
839
- box-shadow: 0 0 0 2px rgba(115, 122, 129, 0.1);
 
840
  }
841
 
842
- .pagination-controls-top, .pagination-controls-bottom {
843
- display: flex;
844
- justify-content: space-between;
845
- align-items: center;
846
- padding: 1rem;
847
- background: var(--color-panel);
848
- border-bottom: 1px solid var(--color-border);
849
  }
850
 
851
- .pagination-controls-bottom {
852
- border-top: 1px solid var(--color-border);
853
- border-bottom: none;
854
- }
855
 
856
  .export-section {
857
  text-align: center;
@@ -979,7 +1057,8 @@
979
  .timestamp-badge {
980
  background: var(--color-panel);
981
  border: 1px solid var(--color-border);
982
- color: var(--color-secondary);
 
983
  padding: 0.25rem 0.75rem;
984
  border-radius: 12px;
985
  font-size: 0.8rem;
@@ -1002,8 +1081,9 @@
1002
 
1003
  .source-badge.conversation {
1004
  background: rgba(101, 101, 101, 0.1);
1005
- color: var(--color-secondary);
1006
- border-color: var(--color-secondary);
 
1007
  }
1008
 
1009
  .header-actions {
@@ -1189,17 +1269,22 @@
1189
  padding: 0.5rem;
1190
  }
1191
 
1192
- .dashboard-header {
1193
  flex-direction: column;
1194
  align-items: flex-start;
1195
  gap: 1rem;
1196
  }
1197
 
1198
- .memory-stats {
1199
  width: 100%;
1200
  justify-content: space-between;
1201
  }
1202
 
 
 
 
 
 
1203
  .filter-row {
1204
  flex-direction: column;
1205
  align-items: stretch;
@@ -1217,14 +1302,14 @@
1217
  }
1218
 
1219
  .actions-cell {
1220
- min-width: 120px;
 
1221
  }
1222
 
1223
- .col-area { width: 15%; }
1224
- .col-timestamp { width: 15%; }
1225
- .col-source { width: 25%; }
1226
- .col-preview { width: 25%; }
1227
- .col-actions { width: 20%; }
1228
 
1229
  .memory-detail-modal {
1230
  width: 98%;
@@ -1258,20 +1343,10 @@
1258
  padding: 1rem;
1259
  }
1260
 
1261
- .pagination-controls-top {
1262
- flex-direction: column;
1263
- gap: 1rem;
1264
- align-items: stretch;
1265
- }
1266
 
1267
- .pagination-controls {
1268
- flex-wrap: wrap;
1269
- justify-content: center;
1270
- }
1271
 
1272
- .page-numbers {
1273
  flex-wrap: wrap;
1274
- justify-content: center;
1275
  }
1276
  }
1277
 
@@ -1324,8 +1399,9 @@
1324
 
1325
  .source-badge.conversation {
1326
  background: rgba(101, 101, 101, 0.1);
1327
- color: var(--color-secondary);
1328
- border-color: var(--color-secondary);
 
1329
  }
1330
 
1331
  .header-actions {
@@ -1423,7 +1499,8 @@
1423
 
1424
  .metadata-group h5 {
1425
  margin: 0 0 0.75rem 0;
1426
- color: var(--color-secondary);
 
1427
  font-size: 0.9rem;
1428
  font-weight: 600;
1429
  text-transform: uppercase;
@@ -1444,7 +1521,8 @@
1444
  .metadata-label {
1445
  font-size: 0.8rem;
1446
  font-weight: 600;
1447
- color: var(--color-secondary);
 
1448
  text-transform: uppercase;
1449
  letter-spacing: 0.3px;
1450
  }
@@ -1528,4 +1606,5 @@
1528
 
1529
  </template>
1530
  </div>
1531
- </div>
 
 
1
+ <html>
2
+ <head>
3
+ <title>Memory Dashboard</title>
4
+ <script type="module">
5
+ import { store } from "/components/settings/memory/memory-dashboard-store.js";
6
+ </script>
7
+ </head>
8
+ <body>
9
  <div x-data>
10
  <template x-if="$store.memoryDashboardStore">
11
+ <div x-init="
12
+ $store.memoryDashboardStore.initialize();
13
+
14
+ // Setup cleanup when component is removed from DOM
15
+ const observer = new MutationObserver((mutations) => {
16
+ mutations.forEach((mutation) => {
17
+ if (mutation.type === 'childList') {
18
+ mutation.removedNodes.forEach((node) => {
19
+ if (node.contains && node.contains($el)) {
20
+ // Dashboard is being removed, cleanup polling
21
+ $store.memoryDashboardStore.cleanup();
22
+ observer.disconnect();
23
+ }
24
+ });
25
+ }
26
+ });
27
+ });
28
+
29
+ // Watch for DOM changes
30
+ observer.observe(document.body, {
31
+ childList: true,
32
+ subtree: true
33
+ });
34
+ " class="memory-dashboard">
35
+
36
+
37
 
38
  <!-- Search and Filters -->
39
  <div class="filters-section">
 
55
  <label for="area-filter">Area:</label>
56
  <select id="area-filter" x-model="$store.memoryDashboardStore.areaFilter">
57
  <option value="">All Areas</option>
58
+ <option value="main">Main</option>
59
+ <option value="fragments">Fragments</option>
60
+ <option value="solutions">Solutions</option>
61
+ <option value="instruments">Instruments</option>
62
  </select>
63
  </div>
64
 
 
105
  <div x-show="!$store.memoryDashboardStore.loading && !$store.memoryDashboardStore.error"
106
  class="memory-table-container">
107
 
108
+ <!-- Combined stats and pagination header -->
109
+ <div class="stats-pagination-header" x-show="!$store.memoryDashboardStore.loading">
110
+ <!-- Statistics -->
111
+ <div class="memory-stats-compact">
112
+ <div class="stat-item">
113
+ <span class="stat-label">DB Total:</span>
114
+ <span class="stat-value" x-text="$store.memoryDashboardStore.totalDbCount"></span>
115
+ </div>
116
+ <div class="stat-item">
117
+ <span class="stat-label">Filtered:</span>
118
+ <span class="stat-value" x-text="$store.memoryDashboardStore.totalCount"></span>
119
+ </div>
120
+ <div class="stat-item">
121
+ <span class="stat-label">Knowledge:</span>
122
+ <span class="stat-value" x-text="$store.memoryDashboardStore.knowledgeCount"></span>
123
+ </div>
124
+ <div class="stat-item">
125
+ <span class="stat-label">Conversation:</span>
126
+ <span class="stat-value" x-text="$store.memoryDashboardStore.conversationCount"></span>
127
+ </div>
128
  </div>
129
 
130
+ <!-- Pagination controls -->
131
+ <div class="pagination-controls-compact" x-show="$store.memoryDashboardStore.totalPages > 1">
132
+ <div class="pagination-info-inline">
133
+ <span>Page <span x-text="$store.memoryDashboardStore.currentPage"></span>
134
+ of <span x-text="$store.memoryDashboardStore.totalPages"></span>
135
+ </span>
136
+ </div>
137
+
138
+ <div class="pagination-controls">
139
+ <button class="btn slim" @click="$store.memoryDashboardStore.prevPage()"
140
+ :disabled="$store.memoryDashboardStore.currentPage === 1">
141
+ Previous
142
+ </button>
143
+
144
+ <select class="page-select"
145
+ x-model.number="$store.memoryDashboardStore.currentPage"
146
+ @change="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)">
147
+ <template x-for="page in Array.from({length: $store.memoryDashboardStore.totalPages}, (_, i) => i + 1)" :key="page">
148
+ <option :value="page" x-text="'Page ' + page"></option>
149
+ </template>
150
+ </select>
151
+
152
+ <button class="btn slim" @click="$store.memoryDashboardStore.nextPage()"
153
+ :disabled="$store.memoryDashboardStore.currentPage === $store.memoryDashboardStore.totalPages">
154
+ Next
155
+ </button>
156
+ </div>
157
  </div>
158
  </div>
159
 
 
218
  </th>
219
  <th class="col-metadata">Metadata</th>
220
  <th class="col-preview">Preview</th>
221
+ <th class="col-actions"></th>
222
  </tr>
223
  </thead>
224
  <tbody>
 
236
  <td class="metadata-cell">
237
  <div class="metadata-info">
238
  <div class="metadata-row">
239
+ <span class="area-badge"
240
+ :style="`background-color: ${$store.memoryDashboardStore.getAreaColor(memory.area)}`"
241
+ x-text="(memory.area || 'UNKNOWN').toUpperCase()"></span>
242
  </div>
243
  <div class="metadata-row metadata-timestamp">
244
  <span x-text="$store.memoryDashboardStore.formatTimestamp(memory.timestamp, true)"
 
246
  </div>
247
  <div class="metadata-row">
248
  <template x-if="memory.knowledge_source">
249
+ <span class="source-type knowledge">Knowledge</span>
250
  </template>
251
  <template x-if="!memory.knowledge_source">
252
+ <span class="source-type conversation">Conversation</span>
253
  </template>
254
  </div>
255
  </div>
 
267
 
268
  <!-- Actions -->
269
  <td class="actions-cell" @click.stop>
270
+ <div class="actions-wrapper">
271
+ <button class="btn-action copy" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.formatMemoryForCopy(memory))"
272
+ title="Copy Memory">
273
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
274
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
275
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
276
+ </svg>
277
+ </button>
278
+
279
+ <button class="btn-action delete" @click="$store.memoryDashboardStore.deleteMemory(memory)"
280
+ title="Delete Memory">
281
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
282
+ <polyline points="3,6 5,6 21,6"></polyline>
283
+ <path d="M19,6V20a2,2,0,0,1-2,2H7a2,2,0,0,1-2-2V6M8,6V4a2,2,0,0,1,2-2h4a2,2,0,0,1,2,2V6"></path>
284
+ <line x1="10" y1="11" x2="10" y2="17"></line>
285
+ <line x1="14" y1="11" x2="14" y2="17"></line>
286
+ </svg>
287
+ </button>
288
+ </div>
289
  </td>
290
  </tr>
291
  </template>
292
  </tbody>
293
  </table>
294
 
 
 
 
 
 
 
 
 
295
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
  </div>
298
 
 
334
  color: var(--color-text);
335
  }
336
 
337
+ .memory-dashboard h3 {
338
+ color: var(--color-text);
339
+ margin: 0;
340
+ font-size: 1.5rem;
341
+ font-weight: 600;
342
+ }
343
+
344
+
345
+
346
+ .stats-pagination-header {
347
  display: flex;
348
  justify-content: space-between;
349
  align-items: center;
350
+ padding: 1rem;
351
+ background: var(--color-panel);
352
+ border: 1px solid var(--color-border);
353
+ border-radius: 8px;
354
+ border-bottom: none;
355
+ border-bottom-left-radius: 0;
356
+ border-bottom-right-radius: 0;
357
  }
358
 
359
+ .memory-stats-compact {
360
  display: flex;
361
  gap: 1rem;
362
+ flex-wrap: wrap;
363
+ }
364
+
365
+ .pagination-controls-compact {
366
+ display: flex;
367
+ align-items: center;
368
+ gap: 1rem;
369
  }
370
 
371
  .stat-item {
 
381
 
382
  .stat-label {
383
  font-size: 0.8rem;
384
+ color: var(--color-text);
385
+ opacity: 0.7;
386
  margin-bottom: 0.25rem;
387
  }
388
 
389
+ .light-mode .stat-label {
390
+ opacity: 0.8;
391
+ }
392
+
393
  .stat-value {
394
  font-size: 1.1rem;
395
  font-weight: bold;
 
438
  .filter-group input:focus, .filter-group select:focus {
439
  outline: none;
440
  border-color: var(--color-primary);
441
+ box-shadow: 0 0 0 2px rgba(115, 122, 129, 0.2);
442
+ background: var(--color-input-focus);
443
+ }
444
+
445
+ .light-mode .filter-group input:focus,
446
+ .light-mode .filter-group select:focus {
447
+ box-shadow: 0 0 0 2px rgba(56, 70, 83, 0.1);
448
  }
449
 
450
  .filter-actions {
 
462
 
463
  .loading-text {
464
  font-size: 0.85rem;
465
+ color: var(--color-text);
466
+ opacity: 0.7;
467
  font-style: italic;
468
  }
469
 
470
  .loading-state, .error-state, .no-memories, .init-message {
471
  text-align: center;
472
  padding: 2rem;
473
+ color: var(--color-text);
474
+ opacity: 0.8;
475
  background: var(--color-panel);
476
  border: 1px solid var(--color-border);
477
  border-radius: 8px;
 
480
 
481
  .init-message {
482
  color: var(--color-primary);
483
+ background: rgba(115, 122, 129, 0.2);
484
  border-color: var(--color-primary);
485
  }
486
 
487
+ .light-mode .init-message {
488
+ background: rgba(56, 70, 83, 0.1);
489
+ }
490
+
491
  .error-state {
492
  color: var(--color-accent);
493
+ background: rgba(207, 102, 121, 0.2);
494
  border-color: var(--color-accent);
495
  }
496
 
497
+ .light-mode .error-state {
498
+ background: rgba(176, 0, 32, 0.1);
499
+ }
500
+
501
  .loading-spinner {
502
+ width: 24px;
503
+ height: 24px;
504
+ min-width: 24px;
505
+ min-height: 24px;
506
+ max-width: 24px;
507
+ max-height: 24px;
508
+ border: 3px solid var(--color-border);
509
+ border-top-color: var(--color-primary);
510
  border-radius: 50%;
511
  animation: spin 1s linear infinite;
512
  margin: 0 auto 0.5rem;
513
+ flex-shrink: 0;
514
+ flex-grow: 0;
515
+ display: inline-block;
516
+ position: relative;
517
+ box-sizing: border-box;
518
  }
519
 
520
  @keyframes spin {
 
543
  /* Fixed column widths for proper fit */
544
  .col-select { width: 5%; }
545
  .col-metadata { width: 25%; }
546
+ .col-preview { width: 60%; }
547
+ .col-actions { width: 10%; }
548
 
549
  .memory-table th,
550
  .memory-table td {
 
600
 
601
  /* Selected row styling */
602
  .memory-row.selected {
603
+ background: rgba(115, 122, 129, 0.2);
604
  border-left: 3px solid var(--color-primary);
605
  }
606
 
607
  .light-mode .memory-row.selected {
608
+ background: rgba(56, 70, 83, 0.1);
609
  }
610
 
611
  /* Mass action toolbar */
 
614
  justify-content: space-between;
615
  align-items: center;
616
  padding: 1rem;
617
+ background: rgba(115, 122, 129, 0.2);
618
  border: 1px solid var(--color-primary);
619
  border-radius: 8px;
620
  margin: 1rem 0;
 
622
  }
623
 
624
  .light-mode .mass-action-toolbar {
625
+ background: rgba(56, 70, 83, 0.1);
626
  }
627
 
628
  .selection-info {
 
652
 
653
  .btn-mass:hover {
654
  border-color: var(--color-primary);
655
+ background: var(--color-panel);
656
  }
657
 
658
  .btn-mass.copy:hover {
659
+ border-color: var(--color-primary);
660
+ color: var(--color-primary);
661
  }
662
 
663
  .btn-mass.export:hover {
664
+ border-color: var(--color-primary);
665
+ color: var(--color-primary);
666
  }
667
 
668
  .btn-mass.delete:hover {
669
+ border-color: var(--color-accent);
670
+ color: var(--color-accent);
671
  }
672
 
673
  .btn-mass.clear:hover {
674
+ border-color: var(--color-primary);
675
+ color: var(--color-primary);
676
  }
677
 
678
  @keyframes slideDown {
 
706
  }
707
 
708
  .memory-table tbody tr:hover {
709
+ background: var(--color-panel);
710
  }
711
 
712
  .memory-table tbody tr:last-child {
 
719
  color: white;
720
  font-size: 0.75rem;
721
  font-weight: bold;
722
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
723
+ border: 1px solid rgba(255, 255, 255, 0.1);
724
+ }
725
+
726
+ .light-mode .area-badge {
727
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
728
+ border: 1px solid rgba(0, 0, 0, 0.1);
729
  }
730
 
731
  .timestamp-cell {
732
  font-size: 0.85rem;
733
+ color: var(--color-text);
734
+ opacity: 0.7;
735
  white-space: normal;
736
  word-break: break-word;
737
  font-family: monospace;
 
757
  }
758
 
759
  .source-type.knowledge {
760
+ background: rgba(115, 122, 129, 0.2);
761
  color: var(--color-primary);
762
  border-color: var(--color-primary);
763
  }
764
 
765
+ .light-mode .source-type.knowledge {
766
+ background: rgba(56, 70, 83, 0.1);
767
+ color: var(--color-primary);
768
+ }
769
+
770
  .source-type.conversation {
771
+ background: rgba(101, 101, 101, 0.2);
772
+ color: var(--color-text);
773
+ opacity: 0.8;
774
+ border-color: var(--color-border);
775
+ }
776
+
777
+ .light-mode .source-type.conversation {
778
+ background: rgba(232, 234, 246, 0.5);
779
+ color: var(--color-text);
780
+ opacity: 0.9;
781
  }
782
 
783
  .source-file {
784
  font-size: 0.7rem;
785
+ color: var(--color-text);
786
+ opacity: 0.7;
787
  font-family: monospace;
788
  background: var(--color-panel);
789
  padding: 0.2rem 0.4rem;
 
819
  padding: 0.2rem 0.4rem;
820
  border-radius: 6px;
821
  font-size: 0.7rem;
822
+ color: var(--color-text);
823
+ opacity: 0.8;
824
  }
825
 
826
  .actions-cell {
827
+ min-width: 60px;
828
+ width: 60px;
829
+ padding: 0 !important;
830
+ height: 60px;
831
+ position: relative;
832
+ }
833
+
834
+ .actions-wrapper {
835
+ position: absolute;
836
+ top: 0;
837
+ left: 0;
838
+ right: 0;
839
+ bottom: 0;
840
+ display: flex;
841
+ flex-direction: column;
842
+ align-items: center;
843
+ justify-content: center;
844
+ }
845
+
846
+ .actions-cell .btn-action {
847
+ display: block;
848
+ margin: 0.1rem 0;
849
+ width: 28px;
850
+ height: 28px;
851
  }
852
 
853
  .btn-action {
 
857
  margin: 0 0.1rem;
858
  cursor: pointer;
859
  border-radius: 6px;
860
+ color: var(--color-text);
861
+ opacity: 0.7;
862
  transition: all 0.2s ease;
863
  display: inline-flex;
864
  align-items: center;
 
868
  }
869
 
870
  .btn-action:hover {
871
+ opacity: 1;
872
+ background: var(--color-panel);
873
+ border-color: var(--color-primary);
874
+ color: var(--color-primary);
875
  transform: translateY(-1px);
876
  }
877
 
878
  .btn-action.view:hover {
879
+ background: var(--color-panel);
880
+ border-color: var(--color-primary);
881
+ color: var(--color-primary);
882
  }
883
 
884
  .btn-action.copy:hover {
885
+ background: var(--color-panel);
886
+ border-color: var(--color-primary);
887
+ color: var(--color-primary);
888
  }
889
 
890
  .btn-action.delete:hover {
 
 
 
 
 
 
 
 
 
 
 
891
  background: var(--color-panel);
892
+ border-color: var(--color-accent);
893
+ color: var(--color-accent);
894
  }
895
 
896
+
897
+
898
  .pagination-info-inline {
899
+ color: var(--color-text);
900
+ opacity: 0.7;
901
  font-size: 0.9rem;
902
  }
903
 
904
  .pagination-controls {
905
  display: flex;
 
906
  align-items: center;
907
  gap: 0.5rem;
 
 
 
 
 
908
  }
909
 
910
  .page-select {
 
921
  .page-select:focus {
922
  outline: none;
923
  border-color: var(--color-primary);
924
+ box-shadow: 0 0 0 2px rgba(115, 122, 129, 0.2);
925
+ background: var(--color-input-focus);
926
  }
927
 
928
+ .light-mode .page-select:focus {
929
+ box-shadow: 0 0 0 2px rgba(56, 70, 83, 0.1);
 
 
 
 
 
930
  }
931
 
932
+
 
 
 
933
 
934
  .export-section {
935
  text-align: center;
 
1057
  .timestamp-badge {
1058
  background: var(--color-panel);
1059
  border: 1px solid var(--color-border);
1060
+ color: var(--color-text);
1061
+ opacity: 0.8;
1062
  padding: 0.25rem 0.75rem;
1063
  border-radius: 12px;
1064
  font-size: 0.8rem;
 
1081
 
1082
  .source-badge.conversation {
1083
  background: rgba(101, 101, 101, 0.1);
1084
+ color: var(--color-text);
1085
+ opacity: 0.8;
1086
+ border-color: var(--color-border);
1087
  }
1088
 
1089
  .header-actions {
 
1269
  padding: 0.5rem;
1270
  }
1271
 
1272
+ .stats-pagination-header {
1273
  flex-direction: column;
1274
  align-items: flex-start;
1275
  gap: 1rem;
1276
  }
1277
 
1278
+ .memory-stats-compact {
1279
  width: 100%;
1280
  justify-content: space-between;
1281
  }
1282
 
1283
+ .pagination-controls-compact {
1284
+ align-self: stretch;
1285
+ justify-content: center;
1286
+ }
1287
+
1288
  .filter-row {
1289
  flex-direction: column;
1290
  align-items: stretch;
 
1302
  }
1303
 
1304
  .actions-cell {
1305
+ min-width: 60px;
1306
+ width: 60px;
1307
  }
1308
 
1309
+ .col-select { width: 8%; }
1310
+ .col-metadata { width: 27%; }
1311
+ .col-preview { width: 55%; }
1312
+ .col-actions { width: 10%; }
 
1313
 
1314
  .memory-detail-modal {
1315
  width: 98%;
 
1343
  padding: 1rem;
1344
  }
1345
 
 
 
 
 
 
1346
 
 
 
 
 
1347
 
1348
+ .pagination-controls {
1349
  flex-wrap: wrap;
 
1350
  }
1351
  }
1352
 
 
1399
 
1400
  .source-badge.conversation {
1401
  background: rgba(101, 101, 101, 0.1);
1402
+ color: var(--color-text);
1403
+ opacity: 0.8;
1404
+ border-color: var(--color-border);
1405
  }
1406
 
1407
  .header-actions {
 
1499
 
1500
  .metadata-group h5 {
1501
  margin: 0 0 0.75rem 0;
1502
+ color: var(--color-text);
1503
+ opacity: 0.7;
1504
  font-size: 0.9rem;
1505
  font-weight: 600;
1506
  text-transform: uppercase;
 
1521
  .metadata-label {
1522
  font-size: 0.8rem;
1523
  font-weight: 600;
1524
+ color: var(--color-text);
1525
+ opacity: 0.7;
1526
  text-transform: uppercase;
1527
  letter-spacing: 0.3px;
1528
  }
 
1606
 
1607
  </template>
1608
  </div>
1609
+ </body>
1610
+ </html>
webui/components/settings/memory/memory-detail-modal.html CHANGED
@@ -1,14 +1,261 @@
1
- <div x-data>
2
- <template x-if="$store.memoryDashboardStore && $store.memoryDashboardStore.detailMemory">
3
- <div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  <!-- Modal Header with Memory Info -->
5
  <div class="modal-header-enhanced">
6
  <div class="header-left">
7
  <div class="memory-title">
8
  <span class="area-badge-large"
9
- :style="`background: linear-gradient(135deg, ${$store.memoryDashboardStore.getAreaColor($store.memoryDashboardStore.detailMemory?.area)} 0%, ${$store.memoryDashboardStore.getAreaColor($store.memoryDashboardStore.detailMemory?.area)}cc 100%)`"
10
  x-text="$store.memoryDashboardStore.detailMemory?.area?.toUpperCase() || 'UNKNOWN'"></span>
11
- <h3>Memory Details</h3>
12
  </div>
13
  <div class="memory-meta-quick">
14
  <span class="timestamp-badge" x-text="$store.memoryDashboardStore.formatTimestamp($store.memoryDashboardStore.detailMemory?.timestamp, true)"></span>
@@ -34,7 +281,7 @@
34
  <div class="content-section-main">
35
  <div class="content-block">
36
  <h4>Memory Content</h4>
37
- <div class="memory-content-display" x-html="$store.memoryDashboardStore.detailMemory?.content_full?.replace(/\n/g, '<br>')"></div>
38
  </div>
39
 
40
  <!-- Tags Section -->
@@ -133,5 +380,6 @@
133
  </div>
134
  </template>
135
  </div>
136
- </template>
137
- </div>
 
 
1
+ <html>
2
+
3
+ <head>
4
+ <title>Memory Details</title>
5
+ </head>
6
+
7
+ <style>
8
+ /* Memory Detail Modal Styles */
9
+ .modal-header-enhanced {
10
+ display: flex;
11
+ justify-content: space-between;
12
+ align-items: flex-start;
13
+ padding: 1.5rem;
14
+ border-bottom: 2px solid var(--color-border);
15
+ background: var(--color-panel);
16
+ }
17
+
18
+ .memory-title {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: 1rem;
22
+ margin-bottom: 0.5rem;
23
+ }
24
+
25
+ .area-badge-large {
26
+ color: white;
27
+ padding: 0.5rem 1rem;
28
+ border-radius: 20px;
29
+ font-size: 0.8rem;
30
+ font-weight: bold;
31
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
32
+ }
33
+
34
+ .memory-meta-quick {
35
+ display: flex;
36
+ gap: 1rem;
37
+ align-items: center;
38
+ }
39
+
40
+ .timestamp-badge, .source-badge {
41
+ padding: 0.25rem 0.75rem;
42
+ border-radius: 12px;
43
+ font-size: 0.75rem;
44
+ font-weight: 500;
45
+ background: var(--color-panel);
46
+ border: 1px solid var(--color-border);
47
+ color: var(--color-text);
48
+ opacity: 0.8;
49
+ }
50
+
51
+ .source-badge.knowledge {
52
+ background: rgba(115, 122, 129, 0.1);
53
+ color: var(--color-primary);
54
+ border-color: var(--color-primary);
55
+ opacity: 1;
56
+ }
57
+
58
+ .source-badge.conversation {
59
+ background: rgba(101, 101, 101, 0.1);
60
+ color: var(--color-text);
61
+ opacity: 0.8;
62
+ border-color: var(--color-border);
63
+ }
64
+
65
+ .header-actions {
66
+ display: flex;
67
+ gap: 0.5rem;
68
+ }
69
+
70
+ .btn-action-header {
71
+ background: var(--color-background);
72
+ border: 1px solid var(--color-border);
73
+ color: var(--color-text);
74
+ padding: 0.5rem;
75
+ border-radius: 8px;
76
+ cursor: pointer;
77
+ transition: all 0.2s ease;
78
+ }
79
+
80
+ .btn-action-header:hover {
81
+ border-color: var(--color-primary);
82
+ background: var(--color-panel);
83
+ }
84
+
85
+ .modal-body-enhanced {
86
+ display: flex;
87
+ min-height: 60vh;
88
+ max-height: 70vh;
89
+ }
90
+
91
+ .content-section-main {
92
+ flex: 1;
93
+ padding: 1.5rem;
94
+ overflow-y: auto;
95
+ }
96
+
97
+ .content-block {
98
+ margin-bottom: 2rem;
99
+ }
100
+
101
+ .content-block h4 {
102
+ margin: 0 0 1rem 0;
103
+ color: var(--color-text);
104
+ font-size: 1.1rem;
105
+ font-weight: 600;
106
+ border-bottom: 1px solid var(--color-border);
107
+ padding-bottom: 0.5rem;
108
+ }
109
+
110
+ .memory-content-display {
111
+ background: var(--color-panel);
112
+ border: 1px solid var(--color-border);
113
+ border-radius: 8px;
114
+ padding: 1.5rem;
115
+ line-height: 1.6;
116
+ color: var(--color-text);
117
+ white-space: pre-wrap;
118
+ word-break: break-word;
119
+ max-height: 300px;
120
+ overflow-y: auto;
121
+ }
122
+
123
+ .tags-display {
124
+ display: flex;
125
+ flex-wrap: wrap;
126
+ gap: 0.5rem;
127
+ }
128
+
129
+ .tag-display {
130
+ background: var(--color-primary);
131
+ color: white;
132
+ padding: 0.25rem 0.75rem;
133
+ border-radius: 12px;
134
+ font-size: 0.75rem;
135
+ font-weight: 500;
136
+ }
137
+
138
+ .metadata-sidebar {
139
+ min-width: 300px;
140
+ max-width: 350px;
141
+ background: var(--color-panel);
142
+ border-left: 1px solid var(--color-border);
143
+ padding: 1.5rem;
144
+ overflow-y: auto;
145
+ }
146
+
147
+ .metadata-sidebar h4 {
148
+ margin: 0 0 1rem 0;
149
+ color: var(--color-text);
150
+ font-size: 1.1rem;
151
+ font-weight: 600;
152
+ }
153
+
154
+ .metadata-group {
155
+ margin-bottom: 1.5rem;
156
+ }
157
+
158
+ .metadata-group h5 {
159
+ margin: 0 0 0.75rem 0;
160
+ color: var(--color-text);
161
+ opacity: 0.7;
162
+ font-size: 0.9rem;
163
+ font-weight: 600;
164
+ text-transform: uppercase;
165
+ letter-spacing: 0.5px;
166
+ }
167
+
168
+ .metadata-item {
169
+ display: flex;
170
+ flex-direction: column;
171
+ gap: 0.25rem;
172
+ margin-bottom: 0.75rem;
173
+ padding: 0.5rem;
174
+ background: var(--color-background);
175
+ border: 1px solid var(--color-border);
176
+ border-radius: 6px;
177
+ }
178
+
179
+ .metadata-label {
180
+ font-size: 0.8rem;
181
+ font-weight: 600;
182
+ color: var(--color-text);
183
+ opacity: 0.7;
184
+ text-transform: uppercase;
185
+ letter-spacing: 0.3px;
186
+ }
187
+
188
+ .metadata-value {
189
+ font-size: 0.9rem;
190
+ color: var(--color-text);
191
+ word-break: break-all;
192
+ line-height: 1.4;
193
+ }
194
+
195
+ .source-file-display {
196
+ font-family: monospace;
197
+ background: var(--color-panel);
198
+ padding: 0.5rem;
199
+ border-radius: 4px;
200
+ border: 1px solid var(--color-border);
201
+ }
202
+
203
+ .metadata-actions {
204
+ display: flex;
205
+ flex-direction: column;
206
+ gap: 0.5rem;
207
+ }
208
+
209
+ .metadata-actions .btn {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 0.5rem;
213
+ justify-content: flex-start;
214
+ font-size: 0.9rem;
215
+ }
216
+
217
+ /* Responsive design for modal */
218
+ @media (max-width: 768px) {
219
+ .modal-body-enhanced {
220
+ flex-direction: column;
221
+ max-height: 80vh;
222
+ }
223
+
224
+ .metadata-sidebar {
225
+ min-width: unset;
226
+ max-width: unset;
227
+ border-left: none;
228
+ border-top: 1px solid var(--color-border);
229
+ }
230
+
231
+ .modal-header-enhanced {
232
+ padding: 1rem;
233
+ }
234
+
235
+ .memory-title {
236
+ flex-direction: column;
237
+ align-items: flex-start;
238
+ gap: 0.5rem;
239
+ }
240
+
241
+ .content-section-main,
242
+ .metadata-sidebar {
243
+ padding: 1rem;
244
+ }
245
+ }
246
+ </style>
247
+
248
+ <body>
249
+ <div x-data>
250
+ <template x-if="$store.memoryDashboardStore && $store.memoryDashboardStore.detailMemory">
251
+ <div>
252
  <!-- Modal Header with Memory Info -->
253
  <div class="modal-header-enhanced">
254
  <div class="header-left">
255
  <div class="memory-title">
256
  <span class="area-badge-large"
257
+ :style="`background-color: ${$store.memoryDashboardStore.getAreaColor($store.memoryDashboardStore.detailMemory?.area)}`"
258
  x-text="$store.memoryDashboardStore.detailMemory?.area?.toUpperCase() || 'UNKNOWN'"></span>
 
259
  </div>
260
  <div class="memory-meta-quick">
261
  <span class="timestamp-badge" x-text="$store.memoryDashboardStore.formatTimestamp($store.memoryDashboardStore.detailMemory?.timestamp, true)"></span>
 
281
  <div class="content-section-main">
282
  <div class="content-block">
283
  <h4>Memory Content</h4>
284
+ <div class="memory-content-display" x-text="$store.memoryDashboardStore.detailMemory?.content_full"></div>
285
  </div>
286
 
287
  <!-- Tags Section -->
 
380
  </div>
381
  </template>
382
  </div>
383
+ </body>
384
+
385
+ </html>
webui/index.html CHANGED
@@ -1606,7 +1606,7 @@
1606
  <script>
1607
  if ('serviceWorker' in navigator) {
1608
  window.addEventListener('load', () => {
1609
- navigator.service-worker.register('js/sw.js').then(registration => {
1610
  console.log('SW registered: ', registration);
1611
  }).catch(registrationError => {
1612
  console.log('SW registration failed: ', registrationError);
 
1606
  <script>
1607
  if ('serviceWorker' in navigator) {
1608
  window.addEventListener('load', () => {
1609
+ navigator.serviceWorker.register('js/sw.js').then(registration => {
1610
  console.log('SW registered: ', registration);
1611
  }).catch(registrationError => {
1612
  console.log('SW registration failed: ', registrationError);