memory dashboard polishing

frdel committed Sep 29, 2025 at 21:39 UTC bd09a89beff531e63521452a63bd998d13424b94
2 files changed +150 -120
python/api/memory_dashboard.py
+2 -2
@@ -229,8 +229,8 @@ class MemoryDashboard(ApiHandler):
229 "id": metadata.get("id", "unknown"),
230 "area": metadata.get("area", "unknown"),
231 "timestamp": metadata.get("timestamp", "unknown"),
232 - "content_preview": m.page_content[:200]
233 - + ("..." if len(m.page_content) > 200 else ""),
232 + # "content_preview": m.page_content[:200]
233 + # + ("..." if len(m.page_content) > 200 else ""),
234 "content_full": m.page_content,
235 "knowledge_source": metadata.get("knowledge_source", False),
236 "source_file": metadata.get("source_file", ""),
webui/components/settings/memory/memory-dashboard.html
+148 -118
@@ -10,60 +10,61 @@
10 <body>
11 <div x-data>
12 <template x-if="$store.memoryDashboardStore">
13 - <div x-create="$store.memoryDashboardStore.onOpen()"
14 - x-destroy="$store.memoryDashboardStore.cleanup()" class="memory-dashboard">
13 + <div x-create="$store.memoryDashboardStore.onOpen()" x-destroy="$store.memoryDashboardStore.cleanup()"
14 + class="memory-dashboard">
15
16 <!-- Search and Filters Section -->
17 <div class="memory-filters-header">
18 - <div class="filter-group-inline filter-memory-dir">
19 - <label for="memory-subdir-select">Memory Directory</label>
20 - <select id="memory-subdir-select" x-model="$store.memoryDashboardStore.selectedMemorySubdir"
21 - @change="$store.memoryDashboardStore.onMemorySubdirChange()"
22 - :disabled="$store.memoryDashboardStore.loadingSubdirs">
23 - <template x-for="subdir in $store.memoryDashboardStore.memorySubdirs" :key="subdir">
24 - <option :value="subdir" x-text="subdir"
25 - :selected="subdir === $store.memoryDashboardStore.selectedMemorySubdir">
26 - </option>
27 - </template>
28 - </select>
29 - </div>
18 + <div class="filter-group-inline filter-memory-dir">
19 + <label for="memory-subdir-select">Memory Directory</label>
20 + <select id="memory-subdir-select" x-model="$store.memoryDashboardStore.selectedMemorySubdir"
21 + @change="$store.memoryDashboardStore.onMemorySubdirChange()"
22 + :disabled="$store.memoryDashboardStore.loadingSubdirs">
23 + <template x-for="subdir in $store.memoryDashboardStore.memorySubdirs" :key="subdir">
24 + <option :value="subdir" x-text="subdir"
25 + :selected="subdir === $store.memoryDashboardStore.selectedMemorySubdir">
26 + </option>
27 + </template>
28 + </select>
29 + </div>
30
31 - <div class="filter-group-inline filter-area">
32 - <label for="area-filter">Area</label>
33 - <select id="area-filter" x-model="$store.memoryDashboardStore.areaFilter">
34 - <option value="">All Areas</option>
35 - <option value="main">Main</option>
36 - <option value="fragments">Fragments</option>
37 - <option value="solutions">Solutions</option>
38 - <option value="instruments">Instruments</option>
39 - </select>
40 - </div>
31 + <div class="filter-group-inline filter-area">
32 + <label for="area-filter">Area</label>
33 + <select id="area-filter" x-model="$store.memoryDashboardStore.areaFilter">
34 + <option value="">All Areas</option>
35 + <option value="main">Main</option>
36 + <option value="fragments">Fragments</option>
37 + <option value="solutions">Solutions</option>
38 + <option value="instruments">Instruments</option>
39 + </select>
40 + </div>
41
42 - <div class="filter-group-inline filter-limit">
43 - <label for="limit-input">Limit:</label>
44 - <input type="number" id="limit-input" x-model.number="$store.memoryDashboardStore.limit"
45 - min="10" max="1000" step="10" />
46 - </div>
42 + <div class="filter-group-inline filter-limit">
43 + <label for="limit-input">Limit:</label>
44 + <input type="number" id="limit-input" x-model.number="$store.memoryDashboardStore.limit"
45 + min="10" max="1000" step="10" />
46 + </div>
47
48 - <div class="filter-threshold">
49 - <label for="threshold-input">Threshold: <strong x-text="$store.memoryDashboardStore.threshold.toFixed(2)"></strong></label>
50 - <input type="range" id="threshold-input" x-model.number="$store.memoryDashboardStore.threshold"
51 - min="0" max="1" step="0.01" />
52 - </div>
48 + <div class="filter-threshold">
49 + <label for="threshold-input">Threshold: <strong
50 + x-text="$store.memoryDashboardStore.threshold.toFixed(2)"></strong></label>
51 + <input type="range" id="threshold-input" x-model.number="$store.memoryDashboardStore.threshold"
52 + min="0" max="1" step="0.01" />
53 + </div>
54 +
55 + <input type="text" id="search-input" x-model="$store.memoryDashboardStore.searchQuery"
56 + placeholder="Search memory content..."
57 + @keyup.enter="$store.memoryDashboardStore.searchMemories()" />
58
54 - <input type="text" id="search-input" x-model="$store.memoryDashboardStore.searchQuery"
55 - placeholder="Search memory content..."
56 - @keyup.enter="$store.memoryDashboardStore.searchMemories()" />
57 -
58 - <button class="btn btn-ok btn-search" @click="$store.memoryDashboardStore.searchMemories()"
59 - :disabled="$store.memoryDashboardStore.loading || $store.memoryDashboardStore.loadingSubdirs">
60 - <span x-show="!$store.memoryDashboardStore.initializingMemory">Search</span>
61 - <span x-show="$store.memoryDashboardStore.initializingMemory">Init...</span>
62 - </button>
63 -
64 - <button class="btn btn-cancel btn-clear" @click="$store.memoryDashboardStore.clearSearch()">
65 - Clear
66 - </button>
59 + <button class="btn btn-ok btn-search" @click="$store.memoryDashboardStore.searchMemories()"
60 + :disabled="$store.memoryDashboardStore.loading || $store.memoryDashboardStore.loadingSubdirs">
61 + <span x-show="!$store.memoryDashboardStore.initializingMemory">Search</span>
62 + <span x-show="$store.memoryDashboardStore.initializingMemory">Init...</span>
63 + </button>
64 +
65 + <button class="btn btn-cancel btn-clear" @click="$store.memoryDashboardStore.clearSearch()">
66 + Clear
67 + </button>
68 </div>
69
70 <!-- Loading state -->
@@ -106,20 +107,18 @@
107 <!-- Pagination -->
108 <div class="status-pagination" x-show="$store.memoryDashboardStore.totalPages > 1">
109 <button class="btn btn-icon" @click="$store.memoryDashboardStore.prevPage()"
109 - :disabled="$store.memoryDashboardStore.currentPage === 1"
110 - title="Previous Page">
110 + :disabled="$store.memoryDashboardStore.currentPage === 1" title="Previous Page">
111 <span class="material-symbols-outlined">chevron_left</span>
112 </button>
113 <div class="page-input-group">
114 <label>Page</label>
115 - <input type="number"
116 - class="page-input"
117 - x-model.number="$store.memoryDashboardStore.currentPage"
118 - @change="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)"
119 - @keyup.enter="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)"
120 - :min="1"
121 - :max="$store.memoryDashboardStore.totalPages" />
122 - <span class="page-total">of <strong x-text="$store.memoryDashboardStore.totalPages"></strong></span>
115 + <input type="number" class="page-input"
116 + x-model.number="$store.memoryDashboardStore.currentPage"
117 + @change="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)"
118 + @keyup.enter="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)"
119 + :min="1" :max="$store.memoryDashboardStore.totalPages" />
120 + <span class="page-total">of <strong
121 + x-text="$store.memoryDashboardStore.totalPages"></strong></span>
122 </div>
123 <button class="btn btn-icon" @click="$store.memoryDashboardStore.nextPage()"
124 :disabled="$store.memoryDashboardStore.currentPage === $store.memoryDashboardStore.totalPages"
@@ -142,13 +141,15 @@
141 Copy
142 </button>
143
145 - <button class="btn btn-mass export" @click="$store.memoryDashboardStore.bulkExportMemories()"
144 + <button class="btn btn-mass export"
145 + @click="$store.memoryDashboardStore.bulkExportMemories()"
146 title="Export Selected Memories">
147 <span class="material-symbols-outlined">download</span>
148 Export
149 </button>
150
151 - <button class="btn btn-mass delete" @click="$store.memoryDashboardStore.bulkDeleteMemories()"
151 + <button class="btn btn-mass delete"
152 + @click="$store.memoryDashboardStore.bulkDeleteMemories()"
153 title="Delete Selected Memories">
154 <span class="material-symbols-outlined">delete</span>
155 Delete
@@ -196,11 +197,6 @@
197 :style="`background-color: ${$store.memoryDashboardStore.getAreaColor(memory.area)}`"
198 x-text="(memory.area || 'UNKNOWN').toUpperCase()"></span>
199 </div>
199 - <div class="metadata-row metadata-timestamp">
200 - <span
201 - x-text="$store.memoryDashboardStore.formatTimestamp(memory.timestamp, true)"
202 - :title="$store.memoryDashboardStore.formatTimestamp(memory.timestamp, false)"></span>
203 - </div>
200 <div class="metadata-row">
201 <template x-if="memory.knowledge_source">
202 <span class="source-type knowledge">Knowledge</span>
@@ -209,12 +205,20 @@
205 <span class="source-type conversation">Conversation</span>
206 </template>
207 </div>
208 + <div class="metadata-row metadata-timestamp">
209 + <span
210 + x-text="$store.memoryDashboardStore.formatTimestamp(memory.timestamp, true)"
211 + :title="$store.memoryDashboardStore.formatTimestamp(memory.timestamp, false)"></span>
212 + </div>
213 +
214 </div>
215 </td>
216
217 <!-- Content preview -->
218 <td class="preview-cell">
217 - <div class="content-preview" x-text="memory.content_preview"></div>
219 + <div class="content-preview"
220 + x-text="memory.content_full.substring(0, 200) + (memory.content_full.length > 200 ? '...' : '')">
221 + </div>
222 <div class="tags" x-show="memory.tags && memory.tags.length > 0">
223 <template x-for="tag in memory.tags" :key="tag">
224 <span class="tag" x-text="tag"></span>
@@ -260,13 +264,15 @@
264 Copy
265 </button>
266
263 - <button class="btn btn-mass export" @click="$store.memoryDashboardStore.bulkExportMemories()"
267 + <button class="btn btn-mass export"
268 + @click="$store.memoryDashboardStore.bulkExportMemories()"
269 title="Export Selected Memories">
270 <span class="material-symbols-outlined">download</span>
271 Export
272 </button>
273
269 - <button class="btn btn-mass delete" @click="$store.memoryDashboardStore.bulkDeleteMemories()"
274 + <button class="btn btn-mass delete"
275 + @click="$store.memoryDashboardStore.bulkDeleteMemories()"
276 title="Delete Selected Memories">
277 <span class="material-symbols-outlined">delete</span>
278 Delete
@@ -305,20 +311,18 @@
311 <!-- Pagination -->
312 <div class="status-pagination" x-show="$store.memoryDashboardStore.totalPages > 1">
313 <button class="btn btn-icon" @click="$store.memoryDashboardStore.prevPage()"
308 - :disabled="$store.memoryDashboardStore.currentPage === 1"
309 - title="Previous Page">
314 + :disabled="$store.memoryDashboardStore.currentPage === 1" title="Previous Page">
315 <span class="material-symbols-outlined">chevron_left</span>
316 </button>
317 <div class="page-input-group">
318 <label>Page</label>
314 - <input type="number"
315 - class="page-input"
316 - x-model.number="$store.memoryDashboardStore.currentPage"
317 - @change="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)"
318 - @keyup.enter="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)"
319 - :min="1"
320 - :max="$store.memoryDashboardStore.totalPages" />
321 - <span class="page-total">of <strong x-text="$store.memoryDashboardStore.totalPages"></strong></span>
319 + <input type="number" class="page-input"
320 + x-model.number="$store.memoryDashboardStore.currentPage"
321 + @change="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)"
322 + @keyup.enter="$store.memoryDashboardStore.goToPage($store.memoryDashboardStore.currentPage)"
323 + :min="1" :max="$store.memoryDashboardStore.totalPages" />
324 + <span class="page-total">of <strong
325 + x-text="$store.memoryDashboardStore.totalPages"></strong></span>
326 </div>
327 <button class="btn btn-icon" @click="$store.memoryDashboardStore.nextPage()"
328 :disabled="$store.memoryDashboardStore.currentPage === $store.memoryDashboardStore.totalPages"
@@ -355,12 +359,12 @@
359 display: flex;
360 flex-direction: column;
361 }
358 -
362 +
363 .modal-inner.modal-with-footer .modal-scroll {
364 flex: 1;
365 min-height: 0;
366 }
363 -
367 +
368 .modal-inner.modal-with-footer .modal-footer {
369 flex-shrink: 0;
370 }
@@ -379,15 +383,39 @@
383 }
384
385 /* Grid positioning for desktop - 2 rows */
382 - .filter-memory-dir { grid-column: 1; grid-row: 1; }
383 - .filter-area { grid-column: 2; grid-row: 1; }
384 - .filter-limit { grid-column: 3; grid-row: 1; }
385 - .filter-threshold { grid-column: 4; grid-row: 2; }
386 -
387 - #search-input { grid-column: 1 / 3; grid-row: 2; }
388 - .btn-search { grid-column: 3; grid-row: 2; justify-content: center; }
389 - .btn-clear {
390 - grid-column: 4;
386 + .filter-memory-dir {
387 + grid-column: 1;
388 + grid-row: 1;
389 + }
390 +
391 + .filter-area {
392 + grid-column: 2;
393 + grid-row: 1;
394 + }
395 +
396 + .filter-limit {
397 + grid-column: 3;
398 + grid-row: 1;
399 + }
400 +
401 + .filter-threshold {
402 + grid-column: 4;
403 + grid-row: 2;
404 + }
405 +
406 + #search-input {
407 + grid-column: 1 / 3;
408 + grid-row: 2;
409 + }
410 +
411 + .btn-search {
412 + grid-column: 3;
413 + grid-row: 2;
414 + justify-content: center;
415 + }
416 +
417 + .btn-clear {
418 + grid-column: 4;
419 grid-row: 1;
420 justify-self: end;
421 }
@@ -424,7 +452,7 @@
452 .filter-group-inline.filter-limit {
453 flex: 0 0 auto;
454 }
427 -
455 +
456 .filter-limit input {
457 flex: 0 0 150%;
458 }
@@ -641,15 +669,13 @@
669 }
670
671 .col-metadata {
644 - width: 25%;
672 + width: 10em;
673 }
674
647 - .col-preview {
648 - width: 60%;
649 - }
675 + .col-preview {}
676
677 .col-actions {
652 - width: 10%;
678 + width: 4em;
679 }
680
681 .memory-table th,
@@ -686,13 +712,13 @@
712 }
713
714 .area-badge {
689 - color: white;
690 - padding: 0.35rem 0.75rem;
691 - border-radius: 16px;
692 - font-size: 0.75rem;
693 - font-weight: bold;
694 - text-transform: uppercase;
695 - }
715 + color: white;
716 + padding: 0.35rem 0.75rem;
717 + border-radius: 16px;
718 + font-size: 0.75rem;
719 + font-weight: bold;
720 + text-transform: uppercase;
721 + }
722
723 .select-cell,
724 .col-select {
@@ -720,7 +746,7 @@
746 /* border: 1px solid var(--color-primary); */
747 /* border-radius: 8px; */
748 /* margin: 1rem 0; */
723 - margin:0;
749 + margin: 0;
750 animation: slideDown 0.3s ease;
751 }
752
@@ -771,6 +797,7 @@
797 opacity: 0;
798 transform: translateY(-10px);
799 }
800 +
801 to {
802 opacity: 1;
803 transform: translateY(0);
@@ -795,8 +822,8 @@
822 border-bottom: none;
823 }
824
798 - .memory-table tbody tr.memory-row {
799 - background: var(--color-panel);
825 + .memory-table tbody tr.memory-row>td {
826 + height: 5em;
827 }
828
829 .source-type {
@@ -810,12 +837,12 @@
837 flex-shrink: 0;
838 }
839
813 - .content-preview {
814 - line-height: 1.4;
840 + .content-preview,
841 + .metadata-info {
842 + height: 5.5em;
843 + overflow: hidden;
844 word-wrap: break-word;
845 overflow-wrap: break-word;
817 - overflow: hidden;
818 - text-overflow: ellipsis;
846 }
847
848 .tags {
@@ -908,7 +935,7 @@
935 flex-wrap: wrap;
936 gap: 0.75rem;
937 }
911 -
938 +
939 /* Reset grid positioning */
940 .filter-memory-dir,
941 .filter-area,
@@ -948,19 +975,22 @@
975 }
976
977 /* Mobile order and layout - force items to specific widths */
951 - .filter-memory-dir {
978 + .filter-memory-dir {
979 order: 1;
980 flex: 1 1 100%;
981 }
955 - .filter-area {
982 +
983 + .filter-area {
984 order: 2;
985 flex: 1 1 100%;
986 }
959 - .filter-limit {
987 +
988 + .filter-limit {
989 order: 3;
990 flex: 1 1 100%;
991 }
963 - .btn-clear {
992 +
993 + .btn-clear {
994 order: 6;
995 flex: 40%;
996 min-width: 80px;
@@ -968,22 +998,23 @@
998 height: 42px;
999 justify-content: center;
1000 }
971 - #search-input {
1001 +
1002 + #search-input {
1003 order: 5;
1004 flex: auto;
1005 width: 100%;
1006 height: 42px;
1007 }
977 -
1008 +
1009 /* Search button and threshold on same row */
979 - .btn-search {
1010 + .btn-search {
1011 order: 7;
1012 flex: 40%;
1013 height: 42px;
1014 padding: 0 1rem;
1015 min-width: 80px;
1016 }
986 -
1017 +
1018 .filter-threshold {
1019 order: 4;
1020 flex: 40%;
@@ -1054,7 +1085,6 @@
1085 width: 10%;
1086 }
1087 }
1057 -
1088 </style>
1089
1090 </template>