Rafael Uzarowski commited on
Commit
816b806
·
unverified ·
1 Parent(s): 875a333

feat: memory dashboard first version

Browse files
python/api/memory_dashboard.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from python.helpers.api import ApiHandler, Request, Response
2
+ from python.helpers.memory import Memory
3
+
4
+
5
+ class MemoryDashboard(ApiHandler):
6
+
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ try:
9
+ # Get filter parameters
10
+ area_filter = input.get("area", "") # Filter by memory area (MAIN, FRAGMENTS, SOLUTIONS, INSTRUMENTS)
11
+ search_query = input.get("search", "") # Full-text search query
12
+ limit = input.get("limit", 100) # Number of results to return
13
+
14
+ # Get context and agent
15
+ ctxid = input.get("context", "")
16
+ context = self.get_context(ctxid)
17
+
18
+ # Check if memory is already initialized to avoid triggering preload
19
+ memory_subdir = context.agent0.config.memory_subdir or "default"
20
+ if Memory.index.get(memory_subdir) is None:
21
+ # Memory not initialized yet, return empty results
22
+ return {
23
+ "success": True,
24
+ "memories": [],
25
+ "total_count": 0,
26
+ "knowledge_count": 0,
27
+ "conversation_count": 0,
28
+ "areas_count": {},
29
+ "search_query": search_query,
30
+ "area_filter": area_filter,
31
+ "message": "Memory database not yet initialized. Use the agent first to initialize memory."
32
+ }
33
+
34
+ # Get already initialized memory instance (no initialization triggered)
35
+ db = Memory(
36
+ agent=context.agent0,
37
+ db=Memory.index[memory_subdir],
38
+ memory_subdir=memory_subdir,
39
+ )
40
+
41
+ memories = []
42
+
43
+ if search_query:
44
+ # If search query provided, use similarity search
45
+ threshold = 0.6 # Lower threshold for broader search in dashboard
46
+ filter_expr = f"area == '{area_filter}'" if area_filter else ""
47
+
48
+ docs = await db.search_similarity_threshold(
49
+ query=search_query,
50
+ limit=limit,
51
+ threshold=threshold,
52
+ filter=filter_expr
53
+ )
54
+ memories = docs
55
+ else:
56
+ # If no search query, get all memories from specified area(s)
57
+ all_docs = db.db.get_all_docs()
58
+
59
+ for doc_id, doc in all_docs.items():
60
+ # Apply area filter if specified
61
+ if area_filter and doc.metadata.get("area", "") != area_filter:
62
+ continue
63
+
64
+ memories.append(doc)
65
+
66
+ # Apply limit
67
+ if len(memories) >= limit:
68
+ break
69
+
70
+ # Format memories for the dashboard
71
+ formatted_memories = []
72
+ for memory in memories:
73
+ metadata = memory.metadata
74
+
75
+ # Extract key information
76
+ memory_data = {
77
+ "id": metadata.get("id", "unknown"),
78
+ "area": metadata.get("area", "unknown"),
79
+ "timestamp": metadata.get("timestamp", "unknown"),
80
+ "content_preview": memory.page_content[:200] + ("..." if len(memory.page_content) > 200 else ""),
81
+ "content_full": memory.page_content,
82
+ "knowledge_source": metadata.get("knowledge_source", False),
83
+ "source_file": metadata.get("source_file", ""),
84
+ "file_type": metadata.get("file_type", ""),
85
+ "consolidation_action": metadata.get("consolidation_action", ""),
86
+ "tags": metadata.get("tags", []),
87
+ "metadata": metadata # Include full metadata for advanced users
88
+ }
89
+
90
+ formatted_memories.append(memory_data)
91
+
92
+ # Sort by timestamp (newest first) - handle "unknown" timestamps
93
+ def get_sort_key(memory):
94
+ timestamp = memory["timestamp"]
95
+ if timestamp == "unknown" or not timestamp:
96
+ return "0000-00-00 00:00:00" # Put unknown timestamps at the end
97
+ return timestamp
98
+
99
+ formatted_memories.sort(key=get_sort_key, reverse=True)
100
+
101
+ # Get summary statistics
102
+ total_memories = len(formatted_memories)
103
+ knowledge_count = sum(1 for m in formatted_memories if m["knowledge_source"])
104
+ conversation_count = total_memories - knowledge_count
105
+
106
+ areas_count: dict[str, int] = {}
107
+ for memory in formatted_memories:
108
+ area = memory["area"]
109
+ areas_count[area] = areas_count.get(area, 0) + 1
110
+
111
+ return {
112
+ "success": True,
113
+ "memories": formatted_memories,
114
+ "total_count": total_memories,
115
+ "knowledge_count": knowledge_count,
116
+ "conversation_count": conversation_count,
117
+ "areas_count": areas_count,
118
+ "search_query": search_query,
119
+ "area_filter": area_filter
120
+ }
121
+
122
+ except Exception as e:
123
+ return {
124
+ "success": False,
125
+ "error": str(e),
126
+ "memories": [],
127
+ "total_count": 0
128
+ }
python/api/memory_delete.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from python.helpers.api import ApiHandler, Request, Response
2
+ from python.helpers.memory import Memory
3
+
4
+
5
+ class MemoryDelete(ApiHandler):
6
+
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ try:
9
+ # Get memory ID to delete
10
+ memory_id = input.get("memory_id", "")
11
+ if not memory_id:
12
+ return {
13
+ "success": False,
14
+ "error": "Memory ID is required"
15
+ }
16
+
17
+ # Get context and agent
18
+ ctxid = input.get("context", "")
19
+ context = self.get_context(ctxid)
20
+
21
+ # Check if memory is initialized to avoid triggering preload
22
+ memory_subdir = context.agent0.config.memory_subdir or "default"
23
+ if Memory.index.get(memory_subdir) is None:
24
+ return {
25
+ "success": False,
26
+ "error": "Memory database not initialized"
27
+ }
28
+
29
+ # Get already initialized memory instance (no initialization triggered)
30
+ db = Memory(
31
+ agent=context.agent0,
32
+ db=Memory.index[memory_subdir],
33
+ memory_subdir=memory_subdir,
34
+ )
35
+
36
+ # Delete the memory by ID
37
+ deleted_docs = await db.delete_documents_by_ids([memory_id])
38
+
39
+ if deleted_docs:
40
+ return {
41
+ "success": True,
42
+ "message": f"Memory {memory_id} deleted successfully",
43
+ "deleted_count": len(deleted_docs)
44
+ }
45
+ else:
46
+ return {
47
+ "success": False,
48
+ "error": f"Memory {memory_id} not found or already deleted"
49
+ }
50
+
51
+ except Exception as e:
52
+ return {
53
+ "success": False,
54
+ "error": str(e)
55
+ }
python/helpers/settings.py CHANGED
@@ -638,6 +638,16 @@ def convert_out(settings: Settings) -> SettingsOutput:
638
  }
639
  )
640
 
 
 
 
 
 
 
 
 
 
 
641
  memory_fields.append(
642
  {
643
  "id": "memory_recall_enabled",
 
638
  }
639
  )
640
 
641
+ memory_fields.append(
642
+ {
643
+ "id": "memory_dashboard",
644
+ "title": "Memory Dashboard",
645
+ "description": "View and explore all stored memories in a table format with filtering and search capabilities.",
646
+ "type": "button",
647
+ "value": "Open Dashboard",
648
+ }
649
+ )
650
+
651
  memory_fields.append(
652
  {
653
  "id": "memory_recall_enabled",
webui/components/settings/memory/memory-dashboard-store.js ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createStore } from "/js/AlpineStore.js";
2
+ import { getContext } from "/index.js";
3
+ import * as API from "/js/api.js";
4
+
5
+ const model = {
6
+ memories: [],
7
+ loading: true,
8
+ error: null,
9
+
10
+ // Filter states
11
+ areaFilter: "",
12
+ searchQuery: "",
13
+ currentPage: 1,
14
+ itemsPerPage: 10,
15
+
16
+ // Statistics
17
+ totalCount: 0,
18
+ knowledgeCount: 0,
19
+ conversationCount: 0,
20
+ areasCount: {},
21
+
22
+ // UI state
23
+ selectedMemory: null,
24
+ showDetailModal: false,
25
+ message: null, // For displaying initialization messages
26
+
27
+ async initialize() {
28
+ await this.loadMemories();
29
+ },
30
+
31
+ async loadMemories() {
32
+ this.loading = true;
33
+ this.error = null;
34
+ this.message = null;
35
+
36
+ try {
37
+ const response = await API.callJsonApi("memory_dashboard", {
38
+ context: getContext(),
39
+ area: this.areaFilter,
40
+ search: this.searchQuery,
41
+ limit: 500 // Get more for client-side pagination
42
+ });
43
+
44
+ if (response.success) {
45
+ this.memories = response.memories || []; // Already sorted by API
46
+ this.totalCount = response.total_count || 0;
47
+ this.knowledgeCount = response.knowledge_count || 0;
48
+ this.conversationCount = response.conversation_count || 0;
49
+ this.areasCount = response.areas_count || {};
50
+ this.message = response.message || null; // Handle initialization messages
51
+ this.currentPage = 1; // Reset to first page when loading new data
52
+ } else {
53
+ this.error = response.error || "Failed to load memories";
54
+ this.memories = [];
55
+ this.message = null;
56
+ }
57
+ } catch (error) {
58
+ this.error = error.message || "Failed to load memories";
59
+ this.memories = [];
60
+ this.message = null;
61
+ console.error("Memory dashboard error:", error);
62
+ } finally {
63
+ this.loading = false;
64
+ }
65
+ },
66
+
67
+ async applyFilters() {
68
+ await this.loadMemories();
69
+ },
70
+
71
+ clearFilters() {
72
+ this.areaFilter = "";
73
+ this.searchQuery = "";
74
+ this.applyFilters();
75
+ },
76
+
77
+ // Pagination
78
+ get totalPages() {
79
+ return Math.ceil(this.memories.length / this.itemsPerPage);
80
+ },
81
+
82
+ get paginatedMemories() {
83
+ const startIndex = (this.currentPage - 1) * this.itemsPerPage;
84
+ const endIndex = startIndex + this.itemsPerPage;
85
+ return this.memories.slice(startIndex, endIndex);
86
+ },
87
+
88
+ goToPage(page) {
89
+ if (page >= 1 && page <= this.totalPages) {
90
+ this.currentPage = page;
91
+ }
92
+ },
93
+
94
+ nextPage() {
95
+ if (this.currentPage < this.totalPages) {
96
+ this.currentPage++;
97
+ }
98
+ },
99
+
100
+ prevPage() {
101
+ if (this.currentPage > 1) {
102
+ this.currentPage--;
103
+ }
104
+ },
105
+
106
+ // Memory details
107
+ showMemoryDetails(memory) {
108
+ this.selectedMemory = memory;
109
+ this.showDetailModal = true;
110
+ },
111
+
112
+ closeDetailModal() {
113
+ this.showDetailModal = false;
114
+ this.selectedMemory = null;
115
+ },
116
+
117
+
118
+
119
+ // Utility methods
120
+ formatTimestamp(timestamp, compact = false) {
121
+ if (!timestamp || timestamp === "unknown") return "Unknown";
122
+ try {
123
+ // Parse timestamp - assume UTC if no timezone specified
124
+ let date;
125
+ if (timestamp.includes('T') || timestamp.includes('Z') || timestamp.includes('+')) {
126
+ // ISO format with timezone info
127
+ date = new Date(timestamp);
128
+ } else {
129
+ // Assume UTC for plain format "YYYY-MM-DD HH:MM:SS"
130
+ date = new Date(timestamp + ' UTC');
131
+ }
132
+
133
+ // Convert to local time and format
134
+ if (compact) {
135
+ // Compact format for table view
136
+ return date.toLocaleString(undefined, {
137
+ month: '2-digit',
138
+ day: '2-digit',
139
+ hour: '2-digit',
140
+ minute: '2-digit',
141
+ hour12: false
142
+ });
143
+ } else {
144
+ // Full format for detail view
145
+ return date.toLocaleString(undefined, {
146
+ year: 'numeric',
147
+ month: '2-digit',
148
+ day: '2-digit',
149
+ hour: '2-digit',
150
+ minute: '2-digit',
151
+ second: '2-digit',
152
+ hour12: false
153
+ });
154
+ }
155
+ } catch {
156
+ return timestamp;
157
+ }
158
+ },
159
+
160
+ formatTags(tags) {
161
+ if (!Array.isArray(tags) || tags.length === 0) return "";
162
+ return tags.join(", ");
163
+ },
164
+
165
+ getAreaColor(area) {
166
+ if (!area) return '#757575'; // Default color for undefined/null area
167
+
168
+ const colors = {
169
+ 'main': '#2196F3',
170
+ 'fragments': '#4CAF50',
171
+ 'solutions': '#FF9800',
172
+ 'instruments': '#9C27B0'
173
+ };
174
+ return colors[area.toLowerCase()] || '#757575';
175
+ },
176
+
177
+ copyToClipboard(text) {
178
+ navigator.clipboard.writeText(text).then(() => {
179
+ // Could show a toast notification here
180
+ console.log("Copied to clipboard");
181
+ }).catch(err => {
182
+ console.error("Failed to copy: ", err);
183
+ });
184
+ },
185
+
186
+ async deleteMemory(memory) {
187
+ // Confirm deletion
188
+ const confirmMessage = `Are you sure you want to delete this memory?\n\nArea: ${memory.area}\nContent: ${memory.content_preview}`;
189
+ if (!confirm(confirmMessage)) {
190
+ return;
191
+ }
192
+
193
+ try {
194
+ const response = await API.callJsonApi("memory_delete", {
195
+ context: getContext(),
196
+ memory_id: memory.id
197
+ });
198
+
199
+ if (response.success) {
200
+ // Close detail modal if the deleted memory is currently shown
201
+ if (this.selectedMemory && this.selectedMemory.id === memory.id) {
202
+ this.closeDetailModal();
203
+ }
204
+
205
+ // Remove memory from local array
206
+ this.memories = this.memories.filter(m => m.id !== memory.id);
207
+ this.totalCount = this.memories.length;
208
+
209
+ // Update statistics
210
+ if (memory.knowledge_source) {
211
+ this.knowledgeCount = Math.max(0, this.knowledgeCount - 1);
212
+ } else {
213
+ this.conversationCount = Math.max(0, this.conversationCount - 1);
214
+ }
215
+
216
+ // Update areas count
217
+ if (this.areasCount[memory.area]) {
218
+ this.areasCount[memory.area] = Math.max(0, this.areasCount[memory.area] - 1);
219
+ if (this.areasCount[memory.area] === 0) {
220
+ delete this.areasCount[memory.area];
221
+ }
222
+ }
223
+
224
+ // Adjust current page if needed
225
+ if (this.paginatedMemories.length === 0 && this.currentPage > 1) {
226
+ this.currentPage--;
227
+ }
228
+
229
+ console.log("Memory deleted successfully");
230
+ } else {
231
+ alert("Failed to delete memory: " + (response.error || "Unknown error"));
232
+ }
233
+ } catch (error) {
234
+ console.error("Delete memory error:", error);
235
+ alert("Failed to delete memory: " + error.message);
236
+ }
237
+ },
238
+
239
+ // Export functionality (optional)
240
+ exportMemories() {
241
+ const dataStr = JSON.stringify(this.memories, null, 2);
242
+ const dataBlob = new Blob([dataStr], {type: 'application/json'});
243
+ const url = URL.createObjectURL(dataBlob);
244
+ const link = document.createElement('a');
245
+ link.href = url;
246
+ link.download = `memory-export-${new Date().toISOString().split('T')[0]}.json`;
247
+ link.click();
248
+ URL.revokeObjectURL(url);
249
+ }
250
+ };
251
+
252
+ const store = createStore("memoryDashboardStore", model);
253
+
254
+ export { store };
webui/components/settings/memory/memory-dashboard.html ADDED
@@ -0,0 +1,1171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html>
2
+
3
+ <head>
4
+ <title>Memory Dashboard</title>
5
+ <script type="module">
6
+ import { store } from "/components/settings/memory/memory-dashboard-store.js";
7
+ </script>
8
+ </head>
9
+
10
+ <body>
11
+ <div x-data>
12
+ <template x-if="$store.memoryDashboardStore">
13
+ <div x-init="$store.memoryDashboardStore.initialize()" class="memory-dashboard">
14
+
15
+ <!-- Header with title and statistics -->
16
+ <div class="dashboard-header">
17
+ <h3>Memory Dashboard</h3>
18
+ <div class="memory-stats" x-show="!$store.memoryDashboardStore.loading">
19
+ <div class="stat-item">
20
+ <span class="stat-label">Total:</span>
21
+ <span class="stat-value" x-text="$store.memoryDashboardStore.totalCount"></span>
22
+ </div>
23
+ <div class="stat-item">
24
+ <span class="stat-label">Knowledge:</span>
25
+ <span class="stat-value" x-text="$store.memoryDashboardStore.knowledgeCount"></span>
26
+ </div>
27
+ <div class="stat-item">
28
+ <span class="stat-label">Conversation:</span>
29
+ <span class="stat-value" x-text="$store.memoryDashboardStore.conversationCount"></span>
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <!-- Filters -->
35
+ <div class="filters-section">
36
+ <div class="filter-row">
37
+ <div class="filter-group">
38
+ <label for="area-filter">Area:</label>
39
+ <select id="area-filter" x-model="$store.memoryDashboardStore.areaFilter">
40
+ <option value="">All Areas</option>
41
+ <option value="main">Main</option>
42
+ <option value="fragments">Fragments</option>
43
+ <option value="solutions">Solutions</option>
44
+ <option value="instruments">Instruments</option>
45
+ </select>
46
+ </div>
47
+
48
+ <div class="filter-group">
49
+ <label for="search-input">Search:</label>
50
+ <input type="text" id="search-input" x-model="$store.memoryDashboardStore.searchQuery"
51
+ placeholder="Search memory content..." />
52
+ </div>
53
+
54
+ <div class="filter-actions">
55
+ <button class="btn primary slim" @click="$store.memoryDashboardStore.applyFilters()"
56
+ :disabled="$store.memoryDashboardStore.loading">
57
+ Apply Filters
58
+ </button>
59
+ <button class="btn slim" @click="$store.memoryDashboardStore.clearFilters()">
60
+ Clear
61
+ </button>
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ <!-- Loading state -->
67
+ <div x-show="$store.memoryDashboardStore.loading" class="loading-state">
68
+ <div class="loading-spinner"></div>
69
+ <span>Loading memories...</span>
70
+ </div>
71
+
72
+ <!-- Error state -->
73
+ <div x-show="$store.memoryDashboardStore.error" class="error-state">
74
+ <span x-text="$store.memoryDashboardStore.error"></span>
75
+ </div>
76
+
77
+ <!-- Memory table -->
78
+ <div x-show="!$store.memoryDashboardStore.loading && !$store.memoryDashboardStore.error"
79
+ class="memory-table-container">
80
+
81
+ <!-- Pagination controls at top -->
82
+ <div class="pagination-controls-top" x-show="$store.memoryDashboardStore.totalPages > 1">
83
+ <div class="pagination-info-inline">
84
+ <span>Page <span x-text="$store.memoryDashboardStore.currentPage"></span>
85
+ of <span x-text="$store.memoryDashboardStore.totalPages"></span>
86
+ (<span x-text="$store.memoryDashboardStore.memories.length"></span> total memories)
87
+ </span>
88
+ </div>
89
+
90
+ <div class="pagination-controls">
91
+ <button class="btn slim" @click="$store.memoryDashboardStore.prevPage()"
92
+ :disabled="$store.memoryDashboardStore.currentPage === 1">
93
+ Previous
94
+ </button>
95
+
96
+ <span class="page-numbers">
97
+ <template x-for="page in Array.from({length: $store.memoryDashboardStore.totalPages}, (_, i) => i + 1)" :key="page">
98
+ <button class="page-btn"
99
+ @click="$store.memoryDashboardStore.goToPage(page)"
100
+ :class="{'active': page === $store.memoryDashboardStore.currentPage}"
101
+ x-text="page">
102
+ </button>
103
+ </template>
104
+ </span>
105
+
106
+ <button class="btn slim" @click="$store.memoryDashboardStore.nextPage()"
107
+ :disabled="$store.memoryDashboardStore.currentPage === $store.memoryDashboardStore.totalPages">
108
+ Next
109
+ </button>
110
+ </div>
111
+ </div>
112
+
113
+ <div class="table-wrapper">
114
+ <table class="memory-table">
115
+ <thead>
116
+ <tr>
117
+ <th class="col-area">Area</th>
118
+ <th class="col-timestamp">Timestamp</th>
119
+ <th class="col-source">Source</th>
120
+ <th class="col-preview">Preview</th>
121
+ <th class="col-actions">Actions</th>
122
+ </tr>
123
+ </thead>
124
+ <tbody>
125
+ <template x-for="memory in $store.memoryDashboardStore.paginatedMemories" :key="memory.id">
126
+ <tr class="memory-row">
127
+ <!-- Area with color indicator -->
128
+ <td class="area-cell">
129
+ <span class="area-badge"
130
+ :style="`background-color: ${$store.memoryDashboardStore.getAreaColor(memory.area)}`"
131
+ x-text="memory.area.toUpperCase()"></span>
132
+ </td>
133
+
134
+ <!-- Timestamp -->
135
+ <td class="timestamp-cell">
136
+ <span x-text="$store.memoryDashboardStore.formatTimestamp(memory.timestamp, true)"
137
+ :title="$store.memoryDashboardStore.formatTimestamp(memory.timestamp, false)"></span>
138
+ </td>
139
+
140
+ <!-- Source -->
141
+ <td class="source-cell">
142
+ <template x-if="memory.knowledge_source">
143
+ <div class="source-info">
144
+ <span class="source-type knowledge">Knowledge</span>
145
+ <span class="source-file" x-text="memory.source_file" x-show="memory.source_file"></span>
146
+ </div>
147
+ </template>
148
+ <template x-if="!memory.knowledge_source">
149
+ <span class="source-type conversation">Conversation</span>
150
+ </template>
151
+ </td>
152
+
153
+ <!-- Content preview -->
154
+ <td class="preview-cell">
155
+ <div class="content-preview" x-text="memory.content_preview"></div>
156
+ <div class="tags" x-show="memory.tags && memory.tags.length > 0">
157
+ <template x-for="tag in memory.tags" :key="tag">
158
+ <span class="tag" x-text="tag"></span>
159
+ </template>
160
+ </div>
161
+ </td>
162
+
163
+ <!-- Actions -->
164
+ <td class="actions-cell">
165
+ <button class="btn-action view" @click="$store.memoryDashboardStore.showMemoryDetails(memory)"
166
+ title="View Details">
167
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
168
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
169
+ <circle cx="12" cy="12" r="3"></circle>
170
+ </svg>
171
+ </button>
172
+ <button class="btn-action copy" @click="$store.memoryDashboardStore.copyToClipboard(memory.content_full)"
173
+ title="Copy Content">
174
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
175
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
176
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
177
+ </svg>
178
+ </button>
179
+ <button class="btn-action delete" @click="$store.memoryDashboardStore.deleteMemory(memory)"
180
+ title="Delete Memory">
181
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
182
+ <polyline points="3,6 5,6 21,6"></polyline>
183
+ <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>
184
+ <line x1="10" y1="11" x2="10" y2="17"></line>
185
+ <line x1="14" y1="11" x2="14" y2="17"></line>
186
+ </svg>
187
+ </button>
188
+ </td>
189
+ </tr>
190
+ </template>
191
+ </tbody>
192
+ </table>
193
+ </div>
194
+
195
+ <!-- No memories message -->
196
+ <div x-show="$store.memoryDashboardStore.memories.length === 0 && !$store.memoryDashboardStore.message" class="no-memories">
197
+ No memories found matching the current filters.
198
+ </div>
199
+
200
+ <!-- Initialization message -->
201
+ <div x-show="$store.memoryDashboardStore.message" class="init-message">
202
+ <span x-text="$store.memoryDashboardStore.message"></span>
203
+ </div>
204
+
205
+
206
+
207
+ <!-- Export button -->
208
+ <div class="export-section">
209
+ <button class="btn slim" @click="$store.memoryDashboardStore.exportMemories()">
210
+ Export Memories (JSON)
211
+ </button>
212
+ </div>
213
+ </div>
214
+
215
+ <!-- Enhanced Detail Modal -->
216
+ <div x-show="$store.memoryDashboardStore.showDetailModal"
217
+ x-transition:enter="modal-enter"
218
+ x-transition:enter-start="modal-enter-start"
219
+ x-transition:enter-end="modal-enter-end"
220
+ x-transition:leave="modal-leave"
221
+ x-transition:leave-start="modal-leave-start"
222
+ x-transition:leave-end="modal-leave-end"
223
+ class="modal-overlay"
224
+ @click.self="$store.memoryDashboardStore.closeDetailModal()">
225
+ <div class="modal-content memory-detail-modal" x-show="$store.memoryDashboardStore.selectedMemory" x-cloak>
226
+
227
+ <!-- Modal Header with Memory Info -->
228
+ <div class="modal-header-enhanced">
229
+ <div class="header-left">
230
+ <div class="memory-title">
231
+ <span class="area-badge-large"
232
+ :style="`background: linear-gradient(135deg, ${$store.memoryDashboardStore.getAreaColor($store.memoryDashboardStore.selectedMemory?.area)} 0%, ${$store.memoryDashboardStore.getAreaColor($store.memoryDashboardStore.selectedMemory?.area)}cc 100%)`"
233
+ x-text="$store.memoryDashboardStore.selectedMemory?.area?.toUpperCase() || 'UNKNOWN'"></span>
234
+ <h3>Memory Details</h3>
235
+ </div>
236
+ <div class="memory-meta-quick">
237
+ <span class="timestamp-badge" x-text="$store.memoryDashboardStore.formatTimestamp($store.memoryDashboardStore.selectedMemory?.timestamp, true)"></span>
238
+ <span class="source-badge"
239
+ :class="$store.memoryDashboardStore.selectedMemory?.knowledge_source ? 'knowledge' : 'conversation'"
240
+ x-text="$store.memoryDashboardStore.selectedMemory?.knowledge_source ? 'Knowledge' : 'Conversation'"></span>
241
+ </div>
242
+ </div>
243
+ <div class="header-actions">
244
+ <button class="btn-action-header copy" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.selectedMemory?.content_full)"
245
+ title="Copy Full Content">
246
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
247
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
248
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
249
+ </svg>
250
+ </button>
251
+ <button class="btn-action-header delete" @click="$store.memoryDashboardStore.deleteMemory($store.memoryDashboardStore.selectedMemory)"
252
+ title="Delete Memory">
253
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
254
+ <polyline points="3,6 5,6 21,6"></polyline>
255
+ <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>
256
+ <line x1="10" y1="11" x2="10" y2="17"></line>
257
+ <line x1="14" y1="11" x2="14" y2="17"></line>
258
+ </svg>
259
+ </button>
260
+ <button class="btn-close-enhanced" @click="$store.memoryDashboardStore.closeDetailModal()">
261
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
262
+ <line x1="18" y1="6" x2="6" y2="18"></line>
263
+ <line x1="6" y1="6" x2="18" y2="18"></line>
264
+ </svg>
265
+ </button>
266
+ </div>
267
+ </div>
268
+
269
+ <!-- Modal Body with Enhanced Layout -->
270
+ <div class="modal-body-enhanced">
271
+
272
+ <!-- Content Section -->
273
+ <div class="content-section-main">
274
+ <div class="section-header">
275
+ <h4>
276
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
277
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
278
+ <polyline points="14,2 14,8 20,8"></polyline>
279
+ </svg>
280
+ Content
281
+ </h4>
282
+ </div>
283
+ <div class="content-display" x-text="$store.memoryDashboardStore.selectedMemory?.content_full"></div>
284
+ </div>
285
+
286
+ <!-- Metadata Sidebar -->
287
+ <div class="metadata-sidebar">
288
+ <div class="section-header">
289
+ <h4>
290
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
291
+ <circle cx="12" cy="12" r="3"></circle>
292
+ <path d="M12 1v6m0 6v6m11-7h-6m-6 0H1"></path>
293
+ </svg>
294
+ Metadata
295
+ </h4>
296
+ </div>
297
+
298
+ <div class="metadata-grid">
299
+ <div class="metadata-item">
300
+ <label>Memory ID</label>
301
+ <value class="monospace" x-text="$store.memoryDashboardStore.selectedMemory?.id"></value>
302
+ </div>
303
+
304
+ <div class="metadata-item">
305
+ <label>Area</label>
306
+ <value x-text="$store.memoryDashboardStore.selectedMemory?.area"></value>
307
+ </div>
308
+
309
+ <div class="metadata-item">
310
+ <label>Created</label>
311
+ <value x-text="$store.memoryDashboardStore.formatTimestamp($store.memoryDashboardStore.selectedMemory?.timestamp)"></value>
312
+ </div>
313
+
314
+ <div class="metadata-item">
315
+ <label>Source Type</label>
316
+ <value x-text="$store.memoryDashboardStore.selectedMemory?.knowledge_source ? 'Knowledge File' : 'Conversation'"></value>
317
+ </div>
318
+
319
+ <div class="metadata-item" x-show="$store.memoryDashboardStore.selectedMemory?.source_file">
320
+ <label>Source File</label>
321
+ <value class="monospace" x-text="$store.memoryDashboardStore.selectedMemory?.source_file"></value>
322
+ </div>
323
+
324
+ <div class="metadata-item" x-show="$store.memoryDashboardStore.selectedMemory?.file_type">
325
+ <label>File Type</label>
326
+ <value x-text="$store.memoryDashboardStore.selectedMemory?.file_type"></value>
327
+ </div>
328
+
329
+ <div class="metadata-item" x-show="$store.memoryDashboardStore.selectedMemory?.consolidation_action">
330
+ <label>Consolidation</label>
331
+ <value class="consolidation-badge" x-text="$store.memoryDashboardStore.selectedMemory?.consolidation_action"></value>
332
+ </div>
333
+
334
+ <div class="metadata-item" x-show="$store.memoryDashboardStore.selectedMemory?.tags && $store.memoryDashboardStore.selectedMemory?.tags.length > 0">
335
+ <label>Tags</label>
336
+ <div class="tags-display">
337
+ <template x-for="tag in $store.memoryDashboardStore.selectedMemory?.tags" :key="tag">
338
+ <span class="tag-pill" x-text="tag"></span>
339
+ </template>
340
+ </div>
341
+ </div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ </div>
347
+
348
+ </div>
349
+ </template>
350
+ </div>
351
+
352
+ <style>
353
+ [x-cloak] {
354
+ display: none !important;
355
+ }
356
+
357
+ .memory-dashboard {
358
+ padding: 1rem;
359
+ max-width: 100%;
360
+ background: var(--color-background);
361
+ color: var(--color-text);
362
+ }
363
+
364
+ .dashboard-header {
365
+ display: flex;
366
+ justify-content: space-between;
367
+ align-items: center;
368
+ margin-bottom: 1.5rem;
369
+ padding-bottom: 1rem;
370
+ border-bottom: 1px solid var(--color-border);
371
+ }
372
+
373
+ .memory-stats {
374
+ display: flex;
375
+ gap: 1rem;
376
+ }
377
+
378
+ .stat-item {
379
+ display: flex;
380
+ flex-direction: column;
381
+ align-items: center;
382
+ padding: 0.5rem;
383
+ background: var(--color-panel);
384
+ border: 1px solid var(--color-border);
385
+ border-radius: 8px;
386
+ min-width: 80px;
387
+ }
388
+
389
+ .stat-label {
390
+ font-size: 0.8rem;
391
+ color: var(--color-secondary);
392
+ margin-bottom: 0.25rem;
393
+ }
394
+
395
+ .stat-value {
396
+ font-size: 1.1rem;
397
+ font-weight: bold;
398
+ color: var(--color-primary);
399
+ }
400
+
401
+ .filters-section {
402
+ margin-bottom: 1.5rem;
403
+ padding: 1rem;
404
+ background: var(--color-panel);
405
+ border: 1px solid var(--color-border);
406
+ border-radius: 8px;
407
+ }
408
+
409
+ .filter-row {
410
+ display: flex;
411
+ gap: 1rem;
412
+ align-items: end;
413
+ flex-wrap: wrap;
414
+ }
415
+
416
+ .filter-group {
417
+ display: flex;
418
+ flex-direction: column;
419
+ gap: 0.25rem;
420
+ }
421
+
422
+ .filter-group label {
423
+ font-size: 0.9rem;
424
+ font-weight: 500;
425
+ color: var(--color-text);
426
+ }
427
+
428
+ .filter-group input, .filter-group select {
429
+ padding: 0.5rem;
430
+ border: 1px solid var(--color-border);
431
+ background: var(--color-background);
432
+ color: var(--color-text);
433
+ border-radius: 6px;
434
+ font-size: 0.9rem;
435
+ min-width: 200px;
436
+ }
437
+
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.1);
442
+ }
443
+
444
+ .filter-actions {
445
+ display: flex;
446
+ gap: 0.5rem;
447
+ }
448
+
449
+ .loading-state, .error-state, .no-memories, .init-message {
450
+ text-align: center;
451
+ padding: 2rem;
452
+ color: var(--color-secondary);
453
+ background: var(--color-panel);
454
+ border: 1px solid var(--color-border);
455
+ border-radius: 8px;
456
+ margin: 1rem 0;
457
+ }
458
+
459
+ .init-message {
460
+ color: var(--color-primary);
461
+ background: rgba(115, 122, 129, 0.1);
462
+ border-color: var(--color-primary);
463
+ }
464
+
465
+ .error-state {
466
+ color: var(--color-accent);
467
+ background: rgba(207, 102, 121, 0.1);
468
+ border-color: var(--color-accent);
469
+ }
470
+
471
+ .loading-spinner {
472
+ width: 20px;
473
+ height: 20px;
474
+ border: 2px solid var(--color-border);
475
+ border-left-color: var(--color-primary);
476
+ border-radius: 50%;
477
+ animation: spin 1s linear infinite;
478
+ margin: 0 auto 0.5rem;
479
+ }
480
+
481
+ @keyframes spin {
482
+ to { transform: rotate(360deg); }
483
+ }
484
+
485
+ .memory-table-container {
486
+ background: var(--color-background);
487
+ border: 1px solid var(--color-border);
488
+ border-radius: 8px;
489
+ overflow: hidden;
490
+ margin-bottom: 1rem;
491
+ }
492
+
493
+ .table-wrapper {
494
+ width: 100%;
495
+ overflow-x: auto;
496
+ }
497
+
498
+ .memory-table {
499
+ width: 100%;
500
+ border-collapse: collapse;
501
+ table-layout: fixed;
502
+ }
503
+
504
+ /* Fixed column widths for proper fit */
505
+ .col-area { width: 15%; }
506
+ .col-timestamp { width: 18%; }
507
+ .col-source { width: 15%; }
508
+ .col-preview { width: 30%; }
509
+ .col-actions { width: 22%; }
510
+
511
+ .memory-table th,
512
+ .memory-table td {
513
+ padding: 1rem 0.75rem;
514
+ text-align: left;
515
+ vertical-align: top;
516
+ border-bottom: 1px solid var(--color-border);
517
+ overflow: hidden;
518
+ }
519
+
520
+ /* Allow source column to wrap properly */
521
+ .source-cell {
522
+ overflow: visible;
523
+ white-space: normal;
524
+ word-wrap: break-word;
525
+ }
526
+
527
+ .memory-table th {
528
+ background: var(--color-panel);
529
+ font-weight: 600;
530
+ color: var(--color-text);
531
+ position: sticky;
532
+ top: 0;
533
+ z-index: 10;
534
+ border-bottom: 2px solid var(--color-border);
535
+ }
536
+
537
+ .memory-table tbody tr {
538
+ border-bottom: 1px solid var(--color-border);
539
+ transition: background-color 0.2s ease;
540
+ }
541
+
542
+ .memory-table tbody tr:nth-child(even) {
543
+ background: var(--color-panel);
544
+ }
545
+
546
+ .memory-table tbody tr:hover {
547
+ background: rgba(255, 255, 255, 0.1);
548
+ }
549
+
550
+ .memory-table tbody tr:last-child {
551
+ border-bottom: none;
552
+ }
553
+
554
+ .area-badge {
555
+ padding: 0.25rem 0.5rem;
556
+ border-radius: 12px;
557
+ color: white;
558
+ font-size: 0.75rem;
559
+ font-weight: bold;
560
+ text-transform: uppercase;
561
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
562
+ }
563
+
564
+ .timestamp-cell {
565
+ font-size: 0.85rem;
566
+ color: var(--color-secondary);
567
+ white-space: normal;
568
+ word-break: break-word;
569
+ font-family: monospace;
570
+ line-height: 1.2;
571
+ }
572
+
573
+ .source-info {
574
+ display: flex;
575
+ flex-direction: column;
576
+ gap: 0.25rem;
577
+ align-items: flex-start;
578
+ }
579
+
580
+ .source-type {
581
+ padding: 0.25rem 0.5rem;
582
+ border-radius: 6px;
583
+ font-size: 0.75rem;
584
+ font-weight: 500;
585
+ text-align: center;
586
+ border: 1px solid;
587
+ white-space: nowrap;
588
+ flex-shrink: 0;
589
+ }
590
+
591
+ .source-type.knowledge {
592
+ background: rgba(115, 122, 129, 0.1);
593
+ color: var(--color-primary);
594
+ border-color: var(--color-primary);
595
+ }
596
+
597
+ .source-type.conversation {
598
+ background: rgba(101, 101, 101, 0.1);
599
+ color: var(--color-secondary);
600
+ border-color: var(--color-secondary);
601
+ }
602
+
603
+ .source-file {
604
+ font-size: 0.7rem;
605
+ color: var(--color-secondary);
606
+ font-family: monospace;
607
+ background: var(--color-panel);
608
+ padding: 0.2rem 0.4rem;
609
+ border-radius: 4px;
610
+ border: 1px solid var(--color-border);
611
+ word-break: break-all;
612
+ white-space: normal;
613
+ max-width: 100%;
614
+ overflow-wrap: break-word;
615
+ }
616
+
617
+ .content-preview {
618
+ line-height: 1.4;
619
+ font-size: 0.9rem;
620
+ word-wrap: break-word;
621
+ overflow-wrap: break-word;
622
+ max-width: 100%;
623
+ overflow: hidden;
624
+ text-overflow: ellipsis;
625
+ }
626
+
627
+ .tags {
628
+ display: flex;
629
+ gap: 0.25rem;
630
+ margin-top: 0.5rem;
631
+ flex-wrap: wrap;
632
+ }
633
+
634
+ .tag {
635
+ background: var(--color-panel);
636
+ border: 1px solid var(--color-border);
637
+ padding: 0.2rem 0.4rem;
638
+ border-radius: 6px;
639
+ font-size: 0.7rem;
640
+ color: var(--color-secondary);
641
+ }
642
+
643
+ .actions-cell {
644
+ white-space: nowrap;
645
+ text-align: center;
646
+ min-width: 140px;
647
+ padding: 0.5rem 0.25rem !important;
648
+ }
649
+
650
+ .btn-action {
651
+ background: none;
652
+ border: 1px solid var(--color-border);
653
+ padding: 0.3rem;
654
+ margin: 0 0.1rem;
655
+ cursor: pointer;
656
+ border-radius: 6px;
657
+ color: var(--color-secondary);
658
+ transition: all 0.2s ease;
659
+ display: inline-flex;
660
+ align-items: center;
661
+ justify-content: center;
662
+ min-width: 32px;
663
+ height: 32px;
664
+ }
665
+
666
+ .btn-action:hover {
667
+ background: rgba(0, 0, 0, 0.05);
668
+ border-color: rgba(0, 0, 0, 0.2);
669
+ color: var(--color-text);
670
+ transform: translateY(-1px);
671
+ }
672
+
673
+ .btn-action.view:hover {
674
+ background: rgba(33, 150, 243, 0.1);
675
+ border-color: #2196F3;
676
+ color: #2196F3;
677
+ }
678
+
679
+ .btn-action.copy:hover {
680
+ background: rgba(76, 175, 80, 0.1);
681
+ border-color: #4CAF50;
682
+ color: #4CAF50;
683
+ }
684
+
685
+ .btn-action.delete:hover {
686
+ background: rgba(244, 67, 54, 0.1);
687
+ border-color: #f44336;
688
+ color: #f44336;
689
+ }
690
+
691
+ .pagination-controls-top {
692
+ display: flex;
693
+ justify-content: space-between;
694
+ align-items: center;
695
+ margin-bottom: 1rem;
696
+ padding: 1rem;
697
+ background: var(--color-panel);
698
+ border: 1px solid var(--color-border);
699
+ border-radius: 8px;
700
+ }
701
+
702
+ .pagination-info-inline {
703
+ color: var(--color-secondary);
704
+ font-size: 0.9rem;
705
+ }
706
+
707
+ .pagination-controls {
708
+ display: flex;
709
+ justify-content: center;
710
+ align-items: center;
711
+ gap: 0.5rem;
712
+ margin: 1rem 0;
713
+ padding: 1rem;
714
+ background: var(--color-panel);
715
+ border: 1px solid var(--color-border);
716
+ border-radius: 8px;
717
+ }
718
+
719
+ .page-numbers {
720
+ display: flex;
721
+ gap: 0.25rem;
722
+ }
723
+
724
+ .page-btn {
725
+ background: var(--color-background);
726
+ border: 1px solid var(--color-border);
727
+ color: var(--color-text);
728
+ padding: 0.5rem 0.75rem;
729
+ cursor: pointer;
730
+ border-radius: 6px;
731
+ font-size: 0.9rem;
732
+ transition: all 0.2s ease;
733
+ }
734
+
735
+ .page-btn:hover {
736
+ background: rgba(255, 255, 255, 0.1);
737
+ border-color: var(--color-primary);
738
+ }
739
+
740
+ .page-btn.active {
741
+ background: var(--color-primary);
742
+ color: white;
743
+ border-color: var(--color-primary);
744
+ }
745
+
746
+ .export-section {
747
+ text-align: center;
748
+ margin-top: 1rem;
749
+ padding: 1rem;
750
+ background: var(--color-panel);
751
+ border: 1px solid var(--color-border);
752
+ border-radius: 8px;
753
+ border-top: 1px solid var(--color-border);
754
+ }
755
+
756
+ /* Enhanced Modal Styles */
757
+ .modal-overlay {
758
+ position: fixed;
759
+ top: 0;
760
+ left: 0;
761
+ right: 0;
762
+ bottom: 0;
763
+ background: rgba(0, 0, 0, 0.6);
764
+ backdrop-filter: blur(4px);
765
+ display: flex;
766
+ align-items: flex-start;
767
+ justify-content: center;
768
+ z-index: 1000;
769
+ overflow-y: auto;
770
+ padding: 2rem 0;
771
+ }
772
+
773
+ .modal-enter {
774
+ transition: all 0.3s ease;
775
+ }
776
+
777
+ .modal-enter-start {
778
+ opacity: 0;
779
+ }
780
+
781
+ .modal-enter-end {
782
+ opacity: 1;
783
+ }
784
+
785
+ .modal-leave {
786
+ transition: all 0.2s ease;
787
+ }
788
+
789
+ .modal-leave-start {
790
+ opacity: 1;
791
+ }
792
+
793
+ .modal-leave-end {
794
+ opacity: 0;
795
+ }
796
+
797
+ .memory-detail-modal {
798
+ background: var(--color-background);
799
+ border: 1px solid var(--color-border);
800
+ border-radius: 16px;
801
+ width: 95%;
802
+ max-width: 1200px;
803
+ max-height: 90vh;
804
+ min-height: 60vh;
805
+ overflow: hidden;
806
+ display: flex;
807
+ flex-direction: column;
808
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
809
+ animation: modalSlideIn 0.3s ease-out;
810
+ margin: 0 auto;
811
+ }
812
+
813
+ @keyframes modalSlideIn {
814
+ from {
815
+ transform: translateY(20px) scale(0.95);
816
+ opacity: 0;
817
+ }
818
+ to {
819
+ transform: translateY(0) scale(1);
820
+ opacity: 1;
821
+ }
822
+ }
823
+
824
+ .modal-header-enhanced {
825
+ background: var(--color-panel);
826
+ padding: 1.5rem 2rem;
827
+ border-bottom: 2px solid var(--color-border);
828
+ display: flex;
829
+ justify-content: space-between;
830
+ align-items: flex-start;
831
+ }
832
+
833
+ .header-left {
834
+ flex: 1;
835
+ }
836
+
837
+ .memory-title {
838
+ display: flex;
839
+ align-items: center;
840
+ gap: 1rem;
841
+ margin-bottom: 0.75rem;
842
+ }
843
+
844
+ .memory-title h3 {
845
+ margin: 0;
846
+ font-size: 1.5rem;
847
+ font-weight: 600;
848
+ color: var(--color-text);
849
+ }
850
+
851
+ .area-badge-large {
852
+ padding: 0.5rem 1rem;
853
+ border-radius: 20px;
854
+ color: white;
855
+ font-size: 0.75rem;
856
+ font-weight: 700;
857
+ text-transform: uppercase;
858
+ letter-spacing: 0.5px;
859
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
860
+ }
861
+
862
+ .memory-meta-quick {
863
+ display: flex;
864
+ gap: 0.75rem;
865
+ align-items: center;
866
+ flex-wrap: wrap;
867
+ }
868
+
869
+ .timestamp-badge {
870
+ background: var(--color-panel);
871
+ border: 1px solid var(--color-border);
872
+ color: var(--color-secondary);
873
+ padding: 0.25rem 0.75rem;
874
+ border-radius: 12px;
875
+ font-size: 0.8rem;
876
+ font-weight: 500;
877
+ }
878
+
879
+ .source-badge {
880
+ padding: 0.25rem 0.75rem;
881
+ border-radius: 12px;
882
+ font-size: 0.8rem;
883
+ font-weight: 500;
884
+ border: 1px solid;
885
+ }
886
+
887
+ .source-badge.knowledge {
888
+ background: rgba(115, 122, 129, 0.1);
889
+ color: var(--color-primary);
890
+ border-color: var(--color-primary);
891
+ }
892
+
893
+ .source-badge.conversation {
894
+ background: rgba(101, 101, 101, 0.1);
895
+ color: var(--color-secondary);
896
+ border-color: var(--color-secondary);
897
+ }
898
+
899
+ .header-actions {
900
+ display: flex;
901
+ gap: 0.5rem;
902
+ align-items: flex-start;
903
+ }
904
+
905
+ .btn-action-header {
906
+ background: var(--color-background);
907
+ border: 1px solid var(--color-border);
908
+ padding: 0.75rem;
909
+ border-radius: 12px;
910
+ cursor: pointer;
911
+ color: var(--color-secondary);
912
+ transition: all 0.2s ease;
913
+ display: flex;
914
+ align-items: center;
915
+ justify-content: center;
916
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
917
+ }
918
+
919
+ .btn-action-header:hover {
920
+ background: rgba(255, 255, 255, 0.1);
921
+ border-color: var(--color-primary);
922
+ transform: translateY(-1px);
923
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
924
+ }
925
+
926
+ .btn-action-header.copy:hover {
927
+ background: rgba(76, 175, 80, 0.05);
928
+ border-color: #4CAF50;
929
+ color: #4CAF50;
930
+ }
931
+
932
+ .btn-action-header.delete:hover {
933
+ background: rgba(244, 67, 54, 0.05);
934
+ border-color: #f44336;
935
+ color: #f44336;
936
+ }
937
+
938
+ .btn-close-enhanced {
939
+ background: rgba(207, 102, 121, 0.1);
940
+ border: 1px solid var(--color-accent);
941
+ padding: 0.75rem;
942
+ border-radius: 12px;
943
+ cursor: pointer;
944
+ color: var(--color-accent);
945
+ transition: all 0.2s ease;
946
+ display: flex;
947
+ align-items: center;
948
+ justify-content: center;
949
+ }
950
+
951
+ .btn-close-enhanced:hover {
952
+ background: rgba(207, 102, 121, 0.15);
953
+ border-color: var(--color-accent);
954
+ transform: translateY(-1px);
955
+ }
956
+
957
+ .modal-body-enhanced {
958
+ display: flex;
959
+ flex: 1;
960
+ overflow: hidden;
961
+ min-height: 0;
962
+ }
963
+
964
+ .content-section-main {
965
+ flex: 2;
966
+ padding: 2rem;
967
+ overflow-y: auto;
968
+ background: var(--color-background);
969
+ }
970
+
971
+ .metadata-sidebar {
972
+ flex: 1;
973
+ min-width: 320px;
974
+ background: var(--color-panel);
975
+ border-left: 1px solid var(--color-border);
976
+ padding: 2rem;
977
+ overflow-y: auto;
978
+ }
979
+
980
+ .section-header {
981
+ margin-bottom: 1.5rem;
982
+ }
983
+
984
+ .section-header h4 {
985
+ margin: 0;
986
+ font-size: 1.1rem;
987
+ font-weight: 600;
988
+ color: var(--color-text);
989
+ display: flex;
990
+ align-items: center;
991
+ gap: 0.5rem;
992
+ }
993
+
994
+ .content-display {
995
+ background: var(--color-background);
996
+ border: 1px solid var(--color-border);
997
+ border-radius: 12px;
998
+ padding: 1.5rem;
999
+ font-size: 0.95rem;
1000
+ line-height: 1.6;
1001
+ color: var(--color-text);
1002
+ white-space: pre-wrap;
1003
+ max-height: 60vh;
1004
+ overflow-y: auto;
1005
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
1006
+ }
1007
+
1008
+ .metadata-grid {
1009
+ display: flex;
1010
+ flex-direction: column;
1011
+ gap: 1.25rem;
1012
+ }
1013
+
1014
+ .metadata-item {
1015
+ display: flex;
1016
+ flex-direction: column;
1017
+ gap: 0.5rem;
1018
+ }
1019
+
1020
+ .metadata-item label {
1021
+ font-size: 0.8rem;
1022
+ font-weight: 600;
1023
+ color: var(--color-secondary);
1024
+ text-transform: uppercase;
1025
+ letter-spacing: 0.5px;
1026
+ }
1027
+
1028
+ .metadata-item value {
1029
+ font-size: 0.9rem;
1030
+ color: var(--color-text);
1031
+ background: var(--color-background);
1032
+ padding: 0.75rem;
1033
+ border-radius: 8px;
1034
+ border: 1px solid var(--color-border);
1035
+ word-break: break-all;
1036
+ }
1037
+
1038
+ .metadata-item value.monospace {
1039
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
1040
+ font-size: 0.85rem;
1041
+ }
1042
+
1043
+ .consolidation-badge {
1044
+ background: var(--color-accent) !important;
1045
+ color: white !important;
1046
+ padding: 0.5rem 1rem !important;
1047
+ border-radius: 20px !important;
1048
+ font-weight: 600 !important;
1049
+ text-transform: capitalize !important;
1050
+ border: none !important;
1051
+ }
1052
+
1053
+ .tags-display {
1054
+ display: flex;
1055
+ flex-wrap: wrap;
1056
+ gap: 0.5rem;
1057
+ background: var(--color-background);
1058
+ padding: 0.75rem;
1059
+ border-radius: 8px;
1060
+ border: 1px solid var(--color-border);
1061
+ }
1062
+
1063
+ .tag-pill {
1064
+ background: var(--color-primary);
1065
+ color: white;
1066
+ padding: 0.25rem 0.75rem;
1067
+ border-radius: 12px;
1068
+ font-size: 0.75rem;
1069
+ font-weight: 500;
1070
+ }
1071
+
1072
+ /* Responsive design */
1073
+ @media (max-width: 768px) {
1074
+ .modal-overlay {
1075
+ padding: 1rem 0;
1076
+ }
1077
+
1078
+ .memory-dashboard {
1079
+ padding: 0.5rem;
1080
+ }
1081
+
1082
+ .dashboard-header {
1083
+ flex-direction: column;
1084
+ align-items: flex-start;
1085
+ gap: 1rem;
1086
+ }
1087
+
1088
+ .memory-stats {
1089
+ width: 100%;
1090
+ justify-content: space-between;
1091
+ }
1092
+
1093
+ .filter-row {
1094
+ flex-direction: column;
1095
+ align-items: stretch;
1096
+ }
1097
+
1098
+ .filter-group input, .filter-group select {
1099
+ min-width: unset;
1100
+ width: 100%;
1101
+ }
1102
+
1103
+ .memory-table th,
1104
+ .memory-table td {
1105
+ padding: 0.5rem 0.25rem;
1106
+ font-size: 0.85rem;
1107
+ }
1108
+
1109
+ .actions-cell {
1110
+ min-width: 120px;
1111
+ }
1112
+
1113
+ .col-area { width: 15%; }
1114
+ .col-timestamp { width: 15%; }
1115
+ .col-source { width: 25%; }
1116
+ .col-preview { width: 25%; }
1117
+ .col-actions { width: 20%; }
1118
+
1119
+ .memory-detail-modal {
1120
+ width: 98%;
1121
+ max-height: 95vh;
1122
+ min-height: 50vh;
1123
+ margin: 0;
1124
+ }
1125
+
1126
+ .modal-body-enhanced {
1127
+ flex-direction: column;
1128
+ }
1129
+
1130
+ .metadata-sidebar {
1131
+ min-width: unset;
1132
+ border-left: none;
1133
+ border-top: 1px solid var(--color-border);
1134
+ }
1135
+
1136
+ .modal-header-enhanced {
1137
+ padding: 1rem;
1138
+ }
1139
+
1140
+ .memory-title {
1141
+ flex-direction: column;
1142
+ align-items: flex-start;
1143
+ gap: 0.5rem;
1144
+ }
1145
+
1146
+ .content-section-main,
1147
+ .metadata-sidebar {
1148
+ padding: 1rem;
1149
+ }
1150
+
1151
+ .pagination-controls-top {
1152
+ flex-direction: column;
1153
+ gap: 1rem;
1154
+ align-items: stretch;
1155
+ }
1156
+
1157
+ .pagination-controls {
1158
+ flex-wrap: wrap;
1159
+ justify-content: center;
1160
+ }
1161
+
1162
+ .page-numbers {
1163
+ flex-wrap: wrap;
1164
+ justify-content: center;
1165
+ }
1166
+ }
1167
+ </style>
1168
+
1169
+ </body>
1170
+
1171
+ </html>
webui/js/settings.js CHANGED
@@ -281,6 +281,8 @@ const settingsModalProxy = {
281
  openModal("settings/external/a2a-connection.html");
282
  } else if (field.id === "external_api_examples") {
283
  openModal("settings/external/api-examples.html");
 
 
284
  }
285
  }
286
  };
 
281
  openModal("settings/external/a2a-connection.html");
282
  } else if (field.id === "external_api_examples") {
283
  openModal("settings/external/api-examples.html");
284
+ } else if (field.id === "memory_dashboard") {
285
+ openModal("settings/memory/memory-dashboard.html");
286
  }
287
  }
288
  };