main
html 726 lines 30.6 KB
Raw
1 <html>
2
3 <head>
4 <title>Memory Dashboard</title>
5 <script type="module">
6 import { store } from "/plugins/_memory/webui/memory-dashboard-store.js";
7 </script>
8 </head>
9
10 <body>
11 <div x-data>
12 <template x-if="$store.memoryDashboardStore">
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="filters-header 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>
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="skills">Skills</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>
47
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
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 -->
71 <div x-show="$store.memoryDashboardStore.loading" class="loading-state">
72 <div class="loading-spinner"></div>
73 <span x-show="!$store.memoryDashboardStore.initializingMemory">Searching memories...</span>
74 <span x-show="$store.memoryDashboardStore.initializingMemory">Initializing memory database...</span>
75 </div>
76
77 <!-- Error state -->
78 <div x-show="$store.memoryDashboardStore.error" class="error-state">
79 <span x-text="$store.memoryDashboardStore.error"></span>
80 </div>
81
82 <!-- Memory table -->
83 <div x-show="!$store.memoryDashboardStore.loading && !$store.memoryDashboardStore.error"
84 class="data-table-container data-table-container-standalone memory-table-container">
85
86 <!-- Status Bar -->
87 <div class="data-status-bar" x-show="!$store.memoryDashboardStore.loading">
88 <div class="status-info">
89 <span class="status-item">
90 <x-icon name="database"></x-icon>
91 Total: <strong x-text="$store.memoryDashboardStore.totalDbCount"></strong>
92 </span>
93 <span class="status-separator"></span>
94 <span class="status-item">
95 Filtered: <strong x-text="$store.memoryDashboardStore.totalCount"></strong>
96 </span>
97 <span class="status-separator"></span>
98 <span class="status-item">
99 Knowledge: <strong x-text="$store.memoryDashboardStore.knowledgeCount"></strong>
100 </span>
101 <span class="status-separator"></span>
102 <span class="status-item">
103 Conversation: <strong x-text="$store.memoryDashboardStore.conversationCount"></strong>
104 </span>
105 </div>
106
107 <!-- Pagination -->
108 <div class="status-pagination" x-show="$store.memoryDashboardStore.totalPages > 1">
109 <button class="btn btn-icon" @click="$store.memoryDashboardStore.prevPage()"
110 :disabled="$store.memoryDashboardStore.currentPage === 1" title="Previous Page">
111 <x-icon name="chevron_left"></x-icon>
112 </button>
113 <div class="page-input-group">
114 <label>Page</label>
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"
125 title="Next Page">
126 <x-icon name="chevron_right"></x-icon>
127 </button>
128 </div>
129 </div>
130
131 <!-- Mass action toolbar -->
132 <div x-show="$store.memoryDashboardStore.selectedCount > 0" class="mass-action-toolbar">
133 <div class="selection-info">
134 <span x-text="$store.memoryDashboardStore.selectedCount"></span> memories selected
135 </div>
136
137 <div class="mass-actions">
138 <button class="btn btn-mass copy" @click="$store.memoryDashboardStore.bulkCopyMemories()"
139 title="Copy Selected Content">
140 <x-icon name="content_copy"></x-icon>
141 Copy
142 </button>
143
144 <button class="btn btn-mass export"
145 @click="$store.memoryDashboardStore.bulkExportMemories()"
146 title="Export Selected Memories">
147 <x-icon name="download"></x-icon>
148 Export
149 </button>
150
151 <button class="btn btn-mass delete"
152 @click="$confirmClick($event, () => $store.memoryDashboardStore.bulkDeleteMemories())"
153 title="Delete Selected Memories">
154 <x-icon name="delete"></x-icon>
155 Delete
156 </button>
157
158 <button class="btn btn-mass clear" @click="$store.memoryDashboardStore.clearSelection()"
159 title="Clear Selection">
160 <x-icon name="close"></x-icon>
161 Clear
162 </button>
163 </div>
164 </div>
165
166 <div class="table-wrapper">
167 <table class="data-table memory-table">
168 <thead>
169 <tr>
170 <th class="col-select">
171 <input type="checkbox" :checked="$store.memoryDashboardStore.allSelected"
172 :indeterminate="$store.memoryDashboardStore.someSelected && !$store.memoryDashboardStore.allSelected"
173 @change="$store.memoryDashboardStore.toggleSelectAll()"
174 title="Select/Deselect All" />
175 </th>
176 <th class="col-metadata">Metadata</th>
177 <th class="col-preview">Preview</th>
178 <th class="col-actions"></th>
179 </tr>
180 </thead>
181 <tbody>
182 <template x-for="memory in $store.memoryDashboardStore.paginatedMemories"
183 :key="memory.id">
184 <tr class="memory-row" :class="{'selected': memory.selected}"
185 @click="$store.memoryDashboardStore.showMemoryDetails(memory)"
186 style="cursor: pointer;">
187 <!-- Selection checkbox -->
188 <td class="select-cell" @click.stop="memory.selected = !memory.selected">
189 <input type="checkbox" x-model="memory.selected" />
190 </td>
191
192 <!-- Metadata (merged Area, Timestamp, Source) -->
193 <td class="metadata-cell">
194 <div class="metadata-info">
195 <div class="metadata-row">
196 <span class="area-badge"
197 :style="`background-color: ${$store.memoryDashboardStore.getAreaColor(memory.area)}`"
198 x-text="(memory.area || 'UNKNOWN').toUpperCase()"></span>
199 </div>
200 <div class="metadata-row">
201 <template x-if="memory.knowledge_source">
202 <span class="source-type knowledge">Knowledge</span>
203 </template>
204 <template x-if="!memory.knowledge_source">
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">
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>
225 </template>
226 </div>
227 </td>
228
229 <!-- Actions -->
230 <td class="actions-cell" @click.stop>
231 <div class="actions-wrapper">
232 <button class="btn btn-action copy"
233 @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.formatMemoryForCopy(memory))"
234 title="Copy Memory">
235 <x-icon name="content_copy"></x-icon>
236 </button>
237
238 <button class="btn btn-action delete"
239 @click="$confirmClick($event, () => $store.memoryDashboardStore.deleteMemory(memory))"
240 title="Delete Memory">
241 <x-icon name="delete"></x-icon>
242 </button>
243 </div>
244 </td>
245 </tr>
246 </template>
247 </tbody>
248 </table>
249
250
251
252 </div>
253
254 <!-- Mass action toolbar -->
255 <div x-show="$store.memoryDashboardStore.selectedCount > 0" class="mass-action-toolbar">
256 <div class="selection-info">
257 <span x-text="$store.memoryDashboardStore.selectedCount"></span> memories selected
258 </div>
259
260 <div class="mass-actions">
261 <button class="btn btn-mass copy" @click="$store.memoryDashboardStore.bulkCopyMemories()"
262 title="Copy Selected Content">
263 <x-icon name="content_copy"></x-icon>
264 Copy
265 </button>
266
267 <button class="btn btn-mass export"
268 @click="$store.memoryDashboardStore.bulkExportMemories()"
269 title="Export Selected Memories">
270 <x-icon name="download"></x-icon>
271 Export
272 </button>
273
274 <button class="btn btn-mass delete"
275 @click="$confirmClick($event, () => $store.memoryDashboardStore.bulkDeleteMemories())"
276 title="Delete Selected Memories">
277 <x-icon name="delete"></x-icon>
278 Delete
279 </button>
280
281 <button class="btn btn-mass clear" @click="$store.memoryDashboardStore.clearSelection()"
282 title="Clear Selection">
283 <x-icon name="close"></x-icon>
284 Clear
285 </button>
286 </div>
287 </div>
288
289
290 <!-- Status Bar -->
291 <div class="data-status-bar data-status-bar-bottom" x-show="!$store.memoryDashboardStore.loading">
292 <div class="status-info">
293 <span class="status-item">
294 <x-icon name="database"></x-icon>
295 Total: <strong x-text="$store.memoryDashboardStore.totalDbCount"></strong>
296 </span>
297 <span class="status-separator"></span>
298 <span class="status-item">
299 Filtered: <strong x-text="$store.memoryDashboardStore.totalCount"></strong>
300 </span>
301 <span class="status-separator"></span>
302 <span class="status-item">
303 Knowledge: <strong x-text="$store.memoryDashboardStore.knowledgeCount"></strong>
304 </span>
305 <span class="status-separator"></span>
306 <span class="status-item">
307 Conversation: <strong x-text="$store.memoryDashboardStore.conversationCount"></strong>
308 </span>
309 </div>
310
311 <!-- Pagination -->
312 <div class="status-pagination" x-show="$store.memoryDashboardStore.totalPages > 1">
313 <button class="btn btn-icon" @click="$store.memoryDashboardStore.prevPage()"
314 :disabled="$store.memoryDashboardStore.currentPage === 1" title="Previous Page">
315 <x-icon name="chevron_left"></x-icon>
316 </button>
317 <div class="page-input-group">
318 <label>Page</label>
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"
329 title="Next Page">
330 <x-icon name="chevron_right"></x-icon>
331 </button>
332 </div>
333 </div>
334
335
336 <!-- No memories message -->
337 <div x-show="$store.memoryDashboardStore.memories.length === 0 && !$store.memoryDashboardStore.message"
338 class="data-empty-state">
339 No memories found matching the current filters.
340 </div>
341
342 <!-- Initialization message -->
343 <div x-show="$store.memoryDashboardStore.message" class="init-message">
344 <span x-text="$store.memoryDashboardStore.message"></span>
345 </div>
346
347
348
349 </div>
350
351
352
353 </div>
354 </template>
355 </div>
356
357 <style>
358 /* Modal footer layout */
359 .modal-inner.modal-with-footer {
360 display: flex;
361 flex-direction: column;
362 }
363 .modal-inner.modal-with-footer .modal-scroll {
364 flex: 1;
365 min-height: 0;
366 }
367 .modal-inner.modal-with-footer .modal-footer {
368 flex-shrink: 0;
369 }
370
371 /* Memory-specific: Grid-based filter header layout */
372 .memory-filters-header {
373 display: grid;
374 grid-template-columns: 35% 25% 10% auto;
375 grid-template-rows: auto auto;
376 margin: 0 0 1rem 0;
377 }
378
379 /* Grid positioning for desktop - 2 rows */
380 .filter-memory-dir {
381 grid-column: 1;
382 grid-row: 1;
383 }
384 .filter-area {
385 grid-column: 2;
386 grid-row: 1;
387 }
388 .filter-limit {
389 grid-column: 3;
390 grid-row: 1;
391 }
392 .filter-threshold {
393 grid-column: 4;
394 grid-row: 2;
395 }
396 #search-input {
397 grid-column: 1 / 3;
398 grid-row: 2;
399 flex: 1;
400 border: 1px solid var(--color-border);
401 font-size: 0.9rem;
402 height: 38px;
403 }
404 #search-input:focus {
405 outline: none;
406 border-color: var(--color-primary);
407 background: var(--color-input-focus);
408 }
409 .btn-search {
410 grid-column: 3;
411 grid-row: 2;
412 justify-content: center;
413 }
414 .btn-clear {
415 grid-column: 4;
416 grid-row: 1;
417 justify-self: end;
418 }
419
420 /* Memory-specific filter overrides */
421 .memory-filters-header .filter-group-inline {
422 flex: 1;
423 }
424 .memory-filters-header .filter-group-inline select,
425 .memory-filters-header .filter-group-inline input {
426 flex: 0 auto;
427 }
428 .memory-filters-header .filter-group-inline.filter-limit {
429 flex: 0 0 auto;
430 }
431 .filter-limit input {
432 flex: 0 0 150% !important;
433 }
434
435 /* Memory-specific: Table layout overrides */
436 .memory-table {
437 table-layout: fixed;
438 }
439 .memory-table th,
440 .memory-table td {
441 padding: 1rem 0.75rem;
442 vertical-align: top;
443 }
444 .memory-table th {
445 position: sticky;
446 top: 0;
447 z-index: 10;
448 border-bottom: 2px solid var(--color-border);
449 }
450 .memory-table tbody tr.memory-row > td {
451 height: 5em;
452 }
453
454 /* Memory-specific column widths */
455 .col-select {
456 width: 5%;
457 text-align: center;
458 padding: 0.65rem 0.5rem !important;
459 }
460 .col-metadata {
461 width: 10em;
462 }
463 .col-actions {
464 width: 4em;
465 }
466 .col-select input[type="checkbox"] {
467 cursor: pointer;
468 transform: scale(1.2);
469 }
470
471 /* Memory-specific: Metadata display */
472 .metadata-cell {
473 overflow: visible;
474 text-wrap: wrap;
475 word-wrap: break-word;
476 }
477 .preview-cell {
478 overflow: visible;
479 text-wrap: wrap;
480 word-wrap: break-word;
481 }
482 .metadata-info {
483 display: flex;
484 flex-direction: column;
485 gap: 0.5rem;
486 height: 5.5em;
487 overflow: hidden;
488 }
489 .metadata-row {
490 display: flex;
491 align-items: center;
492 gap: 0.5rem;
493 }
494 .metadata-timestamp {
495 font-size: 0.85rem;
496 color: var(--color-text);
497 font-family: monospace;
498 }
499
500 /* Memory-specific: Area badge */
501 .area-badge {
502 color: white;
503 padding: 0.35rem 0.75rem;
504 border-radius: 16px;
505 font-size: 0.75rem;
506 font-weight: bold;
507 text-transform: uppercase;
508 }
509
510 /* Memory-specific: Source type badge */
511 .source-type {
512 padding: 0.25rem 0.5rem;
513 border-radius: var(--border-radius);
514 font-size: 0.75rem;
515 font-weight: 500;
516 text-align: center;
517 border: 1px solid;
518 white-space: nowrap;
519 flex-shrink: 0;
520 }
521
522 /* Memory-specific: Content preview */
523 .content-preview {
524 height: 5.5em;
525 overflow: hidden;
526 word-wrap: break-word;
527 overflow-wrap: break-word;
528 text-wrap: wrap;
529 }
530
531 /* Memory-specific: Tags */
532 .tags {
533 display: flex;
534 gap: 0.25rem;
535 margin-top: 0.5rem;
536 flex-wrap: wrap;
537 }
538 .tag {
539 background: var(--color-panel);
540 border: 1px solid var(--color-border);
541 padding: 0.2rem 0.4rem;
542 border-radius: 6px;
543 font-size: 0.7rem;
544 color: var(--color-text);
545 opacity: 0.8;
546 }
547
548 /* Memory-specific: Selection state */
549 .memory-row.selected {
550 background: var(--color-panel);
551 border-left: 3px solid var(--color-primary);
552 }
553 .select-cell {
554 text-align: center;
555 padding: 0.65rem 0.5rem !important;
556 }
557 .select-cell input[type="checkbox"] {
558 cursor: pointer;
559 transform: scale(1.2);
560 }
561
562 /* Memory-specific: Actions cell vertical layout */
563 .actions-cell {
564 padding: 0 !important;
565 position: relative;
566 }
567 .memory-table .actions-wrapper {
568 position: absolute;
569 top: 0;
570 left: 0;
571 right: 0;
572 bottom: 0;
573 flex-direction: column;
574 justify-content: center;
575 }
576
577 /* Responsive design - Memory-specific */
578 @media (max-width: 870px) {
579 .memory-dashboard {
580 padding: 0.5rem;
581 }
582
583 /* Switch to flex layout on mobile */
584 .memory-filters-header {
585 display: flex;
586 flex-wrap: wrap;
587 padding: 0.75rem;
588 }
589 .content-preview,
590 .metadata-info {
591 height: 6em;
592 }
593
594 /* Reset grid positioning */
595 .filter-memory-dir,
596 .filter-area,
597 .filter-limit,
598 .filter-threshold,
599 #search-input,
600 .btn-search,
601 .btn-clear {
602 grid-column: auto;
603 grid-row: auto;
604 }
605
606 /* Mobile order and layout */
607 .filter-memory-dir {
608 order: 1;
609 flex: 1 1 100%;
610 }
611 .filter-area {
612 order: 2;
613 flex: 1 1 100%;
614 }
615 .filter-limit {
616 order: 3;
617 flex: 1 1 100%;
618 }
619 .filter-threshold {
620 order: 4;
621 flex: 40%;
622 flex-direction: column;
623 align-items: stretch;
624 gap: 0.5rem;
625 padding: 0;
626 }
627 .filter-threshold label {
628 font-size: 0.9rem;
629 width: 100%;
630 display: flex;
631 justify-content: space-between;
632 }
633 .filter-threshold input[type="range"] {
634 width: 100%;
635 }
636 #search-input {
637 order: 5;
638 flex: auto;
639 width: 100%;
640 min-height: 42px;
641 }
642 .btn-clear {
643 order: 6;
644 flex: 40%;
645 min-width: 80px;
646 padding: 0;
647 min-height: 42px;
648 justify-content: center;
649 }
650 .btn-search {
651 order: 7;
652 flex: 40%;
653 min-height: 42px;
654 padding: 0 1rem;
655 min-width: 80px;
656 }
657
658 .memory-filters-header .filter-group-inline {
659 width: 100%;
660 flex-direction: row;
661 align-items: center;
662 gap: 0.5rem;
663 }
664 .memory-filters-header .filter-group-inline label {
665 font-size: 0.9rem;
666 margin-bottom: 0.25rem;
667 }
668 .memory-filters-header .filter-group-inline select,
669 .memory-filters-header .filter-group-inline input {
670 height: 42px;
671 font-size: 1rem;
672 }
673 .memory-filters-header .filter-group-inline.filter-limit {
674 flex: 33%;
675 }
676 .filter-limit input {
677 flex: 1;
678 }
679
680 .data-status-bar {
681 flex-direction: column;
682 gap: 0.75rem;
683 }
684 .status-info {
685 width: 100%;
686 justify-content: center;
687 flex-wrap: wrap;
688 gap: 0.5rem 1rem;
689 }
690 .status-info .material-symbols-outlined {
691 font-size: 18px;
692 }
693 .status-pagination {
694 width: 100%;
695 justify-content: center;
696 padding-top: 0.5rem;
697 border-top: 1px solid var(--color-border);
698 }
699
700 .memory-table th,
701 .memory-table td {
702 padding: 0.5rem 0.25rem;
703 font-size: 0.85rem;
704 }
705 .actions-cell {
706 min-width: 60px;
707 width: 60px;
708 }
709 .col-select {
710 width: 8%;
711 }
712 .col-metadata {
713 width: 27%;
714 }
715 .col-preview {
716 width: 55%;
717 }
718 .col-actions {
719 width: 10%;
720 }
721 }
722 </style>
723 </template>
724 </div>
725 </body>
726 </html>