memory dashboard editing

frdel committed Sep 24, 2025 at 20:43 UTC 062aff7b556ed0219965d19753545412cbaf8ff1
6 files changed +664 -378
python/api/memory_dashboard.py
+48 -24
@@ -2,6 +2,7 @@ from python.helpers.api import ApiHandler, Request, Response
2 from python.helpers.memory import Memory
3 from python.helpers import files
4 from models import ModelConfig, ModelType
5 +from langchain_core.documents import Document
6
7
8 class MemoryDashboard(ApiHandler):
@@ -12,13 +13,15 @@ class MemoryDashboard(ApiHandler):
13 if action == "get_memory_subdirs":
14 return await self._get_memory_subdirs()
15 elif action == "get_current_memory_subdir":
15 - return await self._get_current_memory_subdir(request)
16 + return await self._get_current_memory_subdir(input)
17 elif action == "search":
18 return await self._search_memories(input)
19 elif action == "delete":
20 return await self._delete_memory(input)
21 elif action == "bulk_delete":
22 return await self._bulk_delete_memories(input)
23 + elif action == "update":
24 + return await self._update_memory(input)
25 else:
26 return {
27 "success": False,
@@ -101,11 +104,11 @@ class MemoryDashboard(ApiHandler):
104 "error": f"Failed to bulk delete memories: {str(e)}",
105 }
106
104 - async def _get_current_memory_subdir(self, request: Request) -> dict:
107 + async def _get_current_memory_subdir(self, input: dict) -> dict:
108 """Get the current memory subdirectory from the active context."""
109 try:
110 # Try to get the context from the request
108 - context_id = getattr(request, "context_id", None)
111 + context_id = input.get("context_id", None)
112 if not context_id:
113 # Fallback to default if no context available
114 return {"success": True, "memory_subdir": "default"}
@@ -192,27 +195,7 @@ class MemoryDashboard(ApiHandler):
195 memories = memories[:limit]
196
197 # Format memories for the dashboard
195 - formatted_memories: list[dict] = []
196 - for m in memories:
197 - metadata = m.metadata
198 -
199 - # Extract key information
200 - memory_data = {
201 - "id": metadata.get("id", "unknown"),
202 - "area": metadata.get("area", "unknown"),
203 - "timestamp": metadata.get("timestamp", "unknown"),
204 - "content_preview": m.page_content[:200]
205 - + ("..." if len(m.page_content) > 200 else ""),
206 - "content_full": m.page_content,
207 - "knowledge_source": metadata.get("knowledge_source", False),
208 - "source_file": metadata.get("source_file", ""),
209 - "file_type": metadata.get("file_type", ""),
210 - "consolidation_action": metadata.get("consolidation_action", ""),
211 - "tags": metadata.get("tags", []),
212 - "metadata": metadata, # Include full metadata for advanced users
213 - }
214 -
215 - formatted_memories.append(memory_data)
198 + formatted_memories = [self._format_memory_for_dashboard(m) for m in memories]
199
200 # Get summary statistics
201 total_memories = len(formatted_memories)
@@ -238,3 +221,44 @@ class MemoryDashboard(ApiHandler):
221
222 except Exception as e:
223 return {"success": False, "error": str(e), "memories": [], "total_count": 0}
224 +
225 + def _format_memory_for_dashboard(self, m: Document) -> dict:
226 + """Format a memory document for the dashboard."""
227 + metadata = m.metadata
228 + return {
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 ""),
234 + "content_full": m.page_content,
235 + "knowledge_source": metadata.get("knowledge_source", False),
236 + "source_file": metadata.get("source_file", ""),
237 + "file_type": metadata.get("file_type", ""),
238 + "consolidation_action": metadata.get("consolidation_action", ""),
239 + "tags": metadata.get("tags", []),
240 + "metadata": metadata, # Include full metadata for advanced users
241 + }
242 +
243 + async def _update_memory(self, input: dict) -> dict:
244 + try:
245 + memory_subdir = input.get("memory_subdir")
246 + original = input.get("original")
247 + edited = input.get("edited")
248 +
249 + if not memory_subdir or not original or not edited:
250 + return {"success": False, "error": "Missing required parameters"}
251 +
252 + doc = Document(
253 + page_content=edited["content_full"],
254 + metadata=edited["metadata"],
255 + )
256 +
257 + memory = await Memory.get_by_subdir(memory_subdir, preload_knowledge=False)
258 + id = (await memory.update_documents([doc]))[0]
259 + doc = memory.get_document_by_id(id)
260 + formatted_doc = self._format_memory_for_dashboard(doc) if doc else None
261 +
262 + return {"success": formatted_doc is not None, "memory": formatted_doc}
263 + except Exception as e:
264 + return {"success": False, "error": str(e), "memory": None}
python/helpers/memory.py
+18 -4
@@ -89,9 +89,14 @@ class Memory:
89 )
90
91 @staticmethod
92 - async def get_by_subdir(memory_subdir: str, log_item: LogItem | None = None, preload_knowledge: bool = True):
92 + async def get_by_subdir(
93 + memory_subdir: str,
94 + log_item: LogItem | None = None,
95 + preload_knowledge: bool = True,
96 + ):
97 if not Memory.index.get(memory_subdir):
98 import initialize
99 +
100 agent_config = initialize.initialize_agent()
101 model_config = agent_config.embeddings_model
102 db, _created = Memory.initialize(
@@ -312,6 +317,9 @@ class Memory:
317
318 return index
319
320 + def get_document_by_id(self, id: str) -> Document | None:
321 + return self.db.get_by_ids(id)[0]
322 +
323 async def search_similarity_threshold(
324 self, query: str, limit: int, threshold: float, filter: str = ""
325 ):
@@ -392,15 +400,21 @@ class Memory:
400 self._save_db() # persist
401 return ids
402
403 + async def update_documents(self, docs: list[Document]):
404 + ids = [doc.metadata["id"] for doc in docs]
405 + await self.db.adelete(ids=ids) # delete originals
406 + ins = await self.db.aadd_documents(documents=docs, ids=ids) # add updated
407 + self._save_db() # persist
408 + return ins
409 +
410 def _save_db(self):
411 Memory._save_db_file(self.db, self.memory_subdir)
412
413 def _generate_doc_id(self):
414 while True:
400 - doc_id = guids.generate_id(10) # random ID
401 - if not self.db.get_by_ids(doc_id): # check if exists
415 + doc_id = guids.generate_id(10) # random ID
416 + if not self.db.get_by_ids(doc_id): # check if exists
417 return doc_id
403 -
418
419 @staticmethod
420 def _save_db_file(db: MyFaiss, memory_subdir: str):
webui/components/settings/memory/memory-dashboard-store.js
+184 -88
@@ -26,13 +26,15 @@ const memoryDashboardStore = {
26 // Memory subdirectories
27 memorySubdirs: [],
28 selectedMemorySubdir: "default",
29 - memoryInitialized: {}, // Track which subdirs have been initialized
29 + memoryInitialized: {}, // Track which subdirs have been initialized
30
31 // Search and filters
32 searchQuery: "",
33 areaFilter: "",
34 - threshold: parseFloat(localStorage.getItem('memoryDashboard_threshold') || '0.6'),
35 - limit: parseInt(localStorage.getItem('memoryDashboard_limit') || '1000'),
34 + threshold: parseFloat(
35 + localStorage.getItem("memoryDashboard_threshold") || "0.6"
36 + ),
37 + limit: parseInt(localStorage.getItem("memoryDashboard_limit") || "1000"),
38
39 // Stats
40 totalCount: 0,
@@ -43,40 +45,55 @@ const memoryDashboardStore = {
45
46 // Memory detail modal (standard modal approach)
47 detailMemory: null,
48 + editMode: false,
49 + editMemoryBackup: null,
50
51 // Polling
52 pollingInterval: null,
53 pollingEnabled: false,
54
51 - init(){
55 + async openModal() {
56 + await openModal("settings/memory/memory-dashboard.html");
57 + },
58 +
59 + init() {
60 this.initialize();
61 },
62
63 + async onOpen() {
64 + await this.getCurrentMemorySubdir();
65 + await this.loadMemorySubdirs();
66 + await this.searchMemories();
67 + // Start polling for live updates as soon as dashboard is open
68 + this.startPolling();
69 + },
70 +
71 async initialize() {
72 // Reset state when opening (but keep directory from context)
73 this.currentPage = 1;
74 this.searchQuery = "";
75 this.areaFilter = "";
76
61 - // Get current memory subdirectory from application context
62 - await this.getCurrentMemorySubdir();
77 + // // Get current memory subdirectory from application context
78 + // await this.getCurrentMemorySubdir();
79
64 - await this.loadMemorySubdirs();
80 + // await this.loadMemorySubdirs();
81
66 - // Automatically search with selected subdirectory
67 - if (this.selectedMemorySubdir) {
68 - await this.searchMemories();
69 - }
82 + // // Automatically search with selected subdirectory
83 + // if (this.selectedMemorySubdir) {
84 + // await this.searchMemories();
85 + // }
86
71 - // Start polling for live updates as soon as dashboard is open
72 - this.startPolling();
87 + // // Start polling for live updates as soon as dashboard is open
88 + // this.startPolling();
89 },
90
91 async getCurrentMemorySubdir() {
92 try {
93 // Try to get current memory subdirectory from the backend
94 const response = await API.callJsonApi("memory_dashboard", {
79 - action: "get_current_memory_subdir"
95 + action: "get_current_memory_subdir",
96 + context_id: getContext(),
97 });
98
99 if (response.success && response.memory_subdir) {
@@ -97,14 +114,14 @@ const memoryDashboardStore = {
114
115 try {
116 const response = await API.callJsonApi("memory_dashboard", {
100 - action: "get_memory_subdirs"
117 + action: "get_memory_subdirs",
118 });
119
103 - if (response.success) {
120 + if (response.success) {
121 let subdirs = response.subdirs || ["default"];
122
123 // Sort alphabetically but ensure "default" is always first
107 - subdirs = subdirs.filter(dir => dir !== "default").sort();
124 + subdirs = subdirs.filter((dir) => dir !== "default").sort();
125 if (response.subdirs && response.subdirs.includes("default")) {
126 subdirs.unshift("default");
127 } else {
@@ -137,8 +154,11 @@ const memoryDashboardStore = {
154
155 async searchMemories(silent = false) {
156 // Save limit to localStorage for persistence
140 - localStorage.setItem('memoryDashboard_limit', this.limit.toString());
141 - localStorage.setItem('memoryDashboard_threshold', this.threshold.toString());
157 + localStorage.setItem("memoryDashboard_limit", this.limit.toString());
158 + localStorage.setItem(
159 + "memoryDashboard_threshold",
160 + this.threshold.toString()
161 + );
162
163 if (!silent) {
164 this.loading = true;
@@ -158,15 +178,15 @@ const memoryDashboardStore = {
178 area: this.areaFilter,
179 search: this.searchQuery,
180 limit: this.limit,
161 - threshold: this.threshold
181 + threshold: this.threshold,
182 });
183
164 - if (response.success) {
184 + if (response.success) {
185 // Preserve existing selections when updating memories during polling
186 const existingSelections = {};
187 if (silent && this.memories) {
188 // Build a map of existing selections by memory ID
169 - this.memories.forEach(memory => {
189 + this.memories.forEach((memory) => {
190 if (memory.selected) {
191 existingSelections[memory.id] = true;
192 }
@@ -174,9 +194,9 @@ const memoryDashboardStore = {
194 }
195
196 // Add selected property to each memory item for mass selection
177 - this.memories = (response.memories || []).map(memory => ({
197 + this.memories = (response.memories || []).map((memory) => ({
198 ...memory,
179 - selected: existingSelections[memory.id] || false
199 + selected: existingSelections[memory.id] || false,
200 }));
201 this.totalCount = response.total_count || 0;
202 this.totalDbCount = response.total_db_count || 0;
@@ -268,7 +288,7 @@ const memoryDashboardStore = {
288
289 // Mass selection
290 get selectedMemories() {
271 - return this.memories.filter(memory => memory.selected);
291 + return this.memories.filter((memory) => memory.selected);
292 },
293
294 get selectedCount() {
@@ -276,24 +296,25 @@ const memoryDashboardStore = {
296 },
297
298 get allSelected() {
279 - return this.memories.length > 0 && this.memories.every(memory => memory.selected);
299 + return (
300 + this.memories.length > 0 &&
301 + this.memories.every((memory) => memory.selected)
302 + );
303 },
304
305 get someSelected() {
283 - return this.memories.some(memory => memory.selected);
306 + return this.memories.some((memory) => memory.selected);
307 },
308
309 toggleSelectAll() {
310 const shouldSelectAll = !this.allSelected;
288 - this.memories.forEach(memory => {
311 + this.memories.forEach((memory) => {
312 memory.selected = shouldSelectAll;
313 });
314 },
315
293 -
294 -
316 clearSelection() {
296 - this.memories.forEach(memory => {
317 + this.memories.forEach((memory) => {
318 memory.selected = false;
319 });
320 },
@@ -311,17 +332,23 @@ const memoryDashboardStore = {
332 const response = await API.callJsonApi("memory_dashboard", {
333 action: "bulk_delete",
334 memory_subdir: this.selectedMemorySubdir,
314 - memory_ids: selectedMemories.map(memory => memory.id)
335 + memory_ids: selectedMemories.map((memory) => memory.id),
336 });
337
338 if (response.success) {
318 - justToast(`Successfully deleted ${selectedMemories.length} memories`, "success");
339 + justToast(
340 + `Successfully deleted ${selectedMemories.length} memories`,
341 + "success"
342 + );
343
344 // Let polling refresh the data instead of manual manipulation
345 // Trigger an immediate refresh to get updated state from backend
346 await this.searchMemories(true); // silent refresh
347 } else {
324 - justToast(response.error || "Failed to delete selected memories", "error");
348 + justToast(
349 + response.error || "Failed to delete selected memories",
350 + "error"
351 + );
352 }
353 } catch (error) {
354 justToast(error.message || "Failed to delete selected memories", "error");
@@ -335,15 +362,22 @@ const memoryDashboardStore = {
362 let formatted = `=== Memory ID: ${memory.id} ===
363 Area: ${memory.area}
364 Timestamp: ${this.formatTimestamp(memory.timestamp)}
338 -Source: ${memory.knowledge_source ? 'Knowledge' : 'Conversation'}
339 -${memory.source_file ? `File: ${memory.source_file}` : ''}
340 -${memory.tags && memory.tags.length > 0 ? `Tags: ${memory.tags.join(', ')}` : ''}`;
365 +Source: ${memory.knowledge_source ? "Knowledge" : "Conversation"}
366 +${memory.source_file ? `File: ${memory.source_file}` : ""}
367 +${
368 + memory.tags && memory.tags.length > 0 ? `Tags: ${memory.tags.join(", ")}` : ""
369 +}`;
370
371 // Add custom metadata if present
343 - if (memory.metadata && typeof memory.metadata === 'object' && Object.keys(memory.metadata).length > 0) {
344 - formatted += '\n\nMetadata:';
372 + if (
373 + memory.metadata &&
374 + typeof memory.metadata === "object" &&
375 + Object.keys(memory.metadata).length > 0
376 + ) {
377 + formatted += "\n\nMetadata:";
378 for (const [key, value] of Object.entries(memory.metadata)) {
346 - const displayValue = typeof value === 'object' ? JSON.stringify(value, null, 2) : value;
379 + const displayValue =
380 + typeof value === "object" ? JSON.stringify(value, null, 2) : value;
381 formatted += `\n${key}: ${displayValue}`;
382 }
383 }
@@ -359,10 +393,15 @@ ${memory.content_full}
393 const selectedMemories = this.selectedMemories;
394 if (selectedMemories.length === 0) return;
395
362 - const content = selectedMemories.map(memory => this.formatMemoryForCopy(memory)).join('\n');
396 + const content = selectedMemories
397 + .map((memory) => this.formatMemoryForCopy(memory))
398 + .join("\n");
399
400 this.copyToClipboard(content);
365 - justToast(`Copied ${selectedMemories.length} memories with metadata to clipboard`, "success");
401 + justToast(
402 + `Copied ${selectedMemories.length} memories with metadata to clipboard`,
403 + "success"
404 + );
405 },
406
407 bulkExportMemories() {
@@ -373,7 +412,7 @@ ${memory.content_full}
412 export_timestamp: new Date().toISOString(),
413 memory_subdir: this.selectedMemorySubdir,
414 total_memories: selectedMemories.length,
376 - memories: selectedMemories.map(memory => ({
415 + memories: selectedMemories.map((memory) => ({
416 id: memory.id,
417 area: memory.area,
418 timestamp: memory.timestamp,
@@ -381,18 +420,18 @@ ${memory.content_full}
420 tags: memory.tags || [],
421 knowledge_source: memory.knowledge_source,
422 source_file: memory.source_file || null,
384 - metadata: memory.metadata || {}
385 - }))
423 + metadata: memory.metadata || {},
424 + })),
425 };
426
427 const jsonString = JSON.stringify(exportData, null, 2);
389 - const blob = new Blob([jsonString], { type: 'application/json' });
428 + const blob = new Blob([jsonString], { type: "application/json" });
429 const url = URL.createObjectURL(blob);
430
392 - const timestamp = new Date().toISOString().split('T')[0];
431 + const timestamp = new Date().toISOString().split("T")[0];
432 const filename = `memories_${this.selectedMemorySubdir}_selected_${selectedMemories.length}_${timestamp}.json`;
433
395 - const a = document.createElement('a');
434 + const a = document.createElement("a");
435 a.href = url;
436 a.download = filename;
437 document.body.appendChild(a);
@@ -400,12 +439,17 @@ ${memory.content_full}
439 document.body.removeChild(a);
440 URL.revokeObjectURL(url);
441
403 - justToast(`Exported ${selectedMemories.length} selected memories to ${filename}`, "success");
442 + justToast(
443 + `Exported ${selectedMemories.length} selected memories to ${filename}`,
444 + "success"
445 + );
446 },
447
448 // Memory detail modal (standard approach)
449 showMemoryDetails(memory) {
450 this.detailMemory = memory;
451 + this.editMode = false;
452 + this.editMemoryBackup = null;
453 // Use global modal system
454 openModal("settings/memory/memory-detail-modal.html");
455 },
@@ -427,25 +471,33 @@ ${memory.content_full}
471
472 if (compact) {
473 // For table display: MM/DD HH:mm
430 - return date.toLocaleDateString("en-US", {
431 - month: "2-digit",
432 - day: "2-digit",
433 - }) + " " + date.toLocaleTimeString("en-US", {
434 - hour12: false,
435 - hour: "2-digit",
436 - minute: "2-digit"
437 - });
474 + return (
475 + date.toLocaleDateString("en-US", {
476 + month: "2-digit",
477 + day: "2-digit",
478 + }) +
479 + " " +
480 + date.toLocaleTimeString("en-US", {
481 + hour12: false,
482 + hour: "2-digit",
483 + minute: "2-digit",
484 + })
485 + );
486 } else {
487 // For details: Full format
440 - return date.toLocaleDateString("en-US", {
441 - year: "numeric",
442 - month: "long",
443 - day: "numeric",
444 - }) + " at " + date.toLocaleTimeString("en-US", {
445 - hour12: true,
446 - hour: "numeric",
447 - minute: "2-digit"
448 - });
488 + return (
489 + date.toLocaleDateString("en-US", {
490 + year: "numeric",
491 + month: "long",
492 + day: "numeric",
493 + }) +
494 + " at " +
495 + date.toLocaleTimeString("en-US", {
496 + hour12: true,
497 + hour: "numeric",
498 + minute: "2-digit",
499 + })
500 + );
501 }
502 },
503
@@ -456,22 +508,25 @@ ${memory.content_full}
508
509 getAreaColor(area) {
510 const colors = {
459 - "main": "#3b82f6",
460 - "fragments": "#10b981",
461 - "solutions": "#8b5cf6",
462 - "instruments": "#f59e0b"
511 + main: "#3b82f6",
512 + fragments: "#10b981",
513 + solutions: "#8b5cf6",
514 + instruments: "#f59e0b",
515 };
516 return colors[area] || "#6c757d";
517 },
518
519 copyToClipboard(text) {
520 if (navigator.clipboard && window.isSecureContext) {
469 - navigator.clipboard.writeText(text).then(() => {
470 - justToast("Copied to clipboard!", "success");
471 - }).catch(err => {
472 - console.error("Clipboard copy failed:", err);
473 - this.fallbackCopyToClipboard(text);
474 - });
521 + navigator.clipboard
522 + .writeText(text)
523 + .then(() => {
524 + justToast("Copied to clipboard!", "success");
525 + })
526 + .catch((err) => {
527 + console.error("Clipboard copy failed:", err);
528 + this.fallbackCopyToClipboard(text);
529 + });
530 } else {
531 this.fallbackCopyToClipboard(text);
532 }
@@ -487,7 +542,7 @@ ${memory.content_full}
542 textArea.focus();
543 textArea.select();
544 try {
490 - document.execCommand('copy');
545 + document.execCommand("copy");
546 justToast("Copied to clipboard!", "success");
547 } catch (err) {
548 console.error("Fallback clipboard copy failed:", err);
@@ -496,19 +551,24 @@ ${memory.content_full}
551 document.body.removeChild(textArea);
552 },
553
499 - async deleteMemory(memory) {
500 - if (!confirm(`Are you sure you want to delete this memory from ${memory.area}?`)) {
554 + async deleteMemory(memory) {
555 + if (
556 + !confirm(
557 + `Are you sure you want to delete this memory from ${memory.area}?`
558 + )
559 + ) {
560 return;
561 }
562
563 try {
564 // Check if this is the memory currently being viewed in detail modal
506 - const isViewingThisMemory = this.detailMemory && this.detailMemory.id === memory.id;
565 + const isViewingThisMemory =
566 + this.detailMemory && this.detailMemory.id === memory.id;
567
568 const response = await API.callJsonApi("memory_dashboard", {
569 action: "delete",
570 memory_subdir: this.selectedMemorySubdir,
511 - memory_id: memory.id
571 + memory_id: memory.id,
572 });
573
574 if (response.success) {
@@ -545,22 +605,24 @@ ${memory.content_full}
605 total_memories: this.memories.length,
606 search_query: this.searchQuery,
607 area_filter: this.areaFilter,
548 - memories: this.memories.map(memory => ({
608 + memories: this.memories.map((memory) => ({
609 id: memory.id,
610 area: memory.area,
611 timestamp: memory.timestamp,
612 content: memory.content_full,
553 - metadata: memory.metadata
554 - }))
613 + metadata: memory.metadata,
614 + })),
615 };
616
617 const blob = new Blob([JSON.stringify(exportData, null, 2)], {
558 - type: 'application/json'
618 + type: "application/json",
619 });
620 const url = URL.createObjectURL(blob);
561 - const a = document.createElement('a');
621 + const a = document.createElement("a");
622 a.href = url;
563 - a.download = `memory-export-${this.selectedMemorySubdir}-${new Date().toISOString().split('T')[0]}.json`;
623 + a.download = `memory-export-${this.selectedMemorySubdir}-${
624 + new Date().toISOString().split("T")[0]
625 + }.json`;
626 document.body.appendChild(a);
627 a.click();
628 document.body.removeChild(a);
@@ -573,7 +635,7 @@ ${memory.content_full}
635 }
636 },
637
576 - startPolling() {
638 + startPolling() {
639 if (!this.pollingEnabled || this.pollingInterval) {
640 return; // Already polling or disabled
641 }
@@ -605,9 +667,43 @@ ${memory.content_full}
667 this.areasCount = {};
668 this.message = null;
669 this.currentPage = 1;
670 + this.editMemoryBackup;
671 + },
672 +
673 + enableEditMode() {
674 + this.editMode = true;
675 + this.editMemoryBackup = JSON.stringify(this.detailMemory); // store backup
676 },
677
678 + cancelEditMode() {
679 + this.editMode = false;
680 + this.detailMemory = JSON.parse(this.editMemoryBackup); // restore backup
681 + },
682 +
683 + async confirmEditMode() {
684 + try {
685 +
686 + const response = await API.callJsonApi("memory_dashboard", {
687 + action: "update",
688 + memory_subdir: this.selectedMemorySubdir,
689 + original: JSON.parse(this.editMemoryBackup),
690 + edited: this.detailMemory,
691 + });
692
693 + if(response.success){
694 + justToast("Memory updated successfully", "success");
695 + await this.searchMemories(true); // silent refresh
696 + }else{
697 + justToast(`Failed to update memory: ${response.error}`, "error");
698 + }
699 +
700 + this.editMode = false;
701 + this.editMemoryBackup = null; // discard backup
702 + } catch (error) {
703 + console.error("Error confirming edit mode:", error);
704 + justToast("Failed to save memory changes.", "error");
705 + }
706 + },
707 };
708
709 const store = createStore("memoryDashboardStore", memoryDashboardStore);
webui/components/settings/memory/memory-dashboard.html
+1 -1
@@ -10,7 +10,7 @@
10 <body>
11 <div x-data>
12 <template x-if="$store.memoryDashboardStore">
13 - <div x-create="$store.memoryDashboardStore.searchMemories()"
13 + <div x-create="$store.memoryDashboardStore.onOpen()"
14 x-destroy="$store.memoryDashboardStore.cleanup()" class="memory-dashboard">
15
16 <!-- Search and Filters -->
webui/components/settings/memory/memory-detail-modal.html
+43 -40
@@ -265,13 +265,43 @@
265 </div>
266 </div>
267 <div class="header-actions">
268 - <button class="btn-action-header copy" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.formatMemoryForCopy($store.memoryDashboardStore.detailMemory))"
269 - title="Copy Complete Memory with Metadata">
270 - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
271 - <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
272 - <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
273 - </svg>
274 - </button>
268 + <!-- View Mode Buttons -->
269 + <template x-if="!$store.memoryDashboardStore.editMode">
270 + <div style="display: flex; gap: 0.5rem;">
271 +
272 + <button class="btn-action-header copy-all" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.formatMemoryForCopy($store.memoryDashboardStore.detailMemory))"
273 + title="Copy Complete Memory with Metadata">
274 + <span class="material-symbols-outlined">copy_all</span> All
275 + </button>
276 + <button class="btn-action-header copy-content" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.detailMemory?.content_full)"
277 + title="Copy Content Only">
278 + <span class="material-symbols-outlined">content_copy</span> Content
279 + </button>
280 + <div style="width:2em"></div>
281 + <button class="btn-action-header edit" @click="$store.memoryDashboardStore.enableEditMode()"
282 + title="Edit Memory">
283 + <span class="material-symbols-outlined">edit_document</span>
284 + </button>
285 + <button class="btn-action-header delete danger" @click="$store.memoryDashboardStore.deleteMemory($store.memoryDashboardStore.detailMemory)"
286 + title="Delete Memory">
287 + <span class="material-symbols-outlined">delete</span>
288 + </button>
289 + </div>
290 + </template>
291 + <!-- Edit Mode Buttons -->
292 + <template x-if="$store.memoryDashboardStore.editMode">
293 + <div style="display: flex; gap: 0.5rem;">
294 + <button class="btn-action-header cancel" @click="$store.memoryDashboardStore.cancelEditMode()"
295 + title="Cancel Edit">
296 + <span class="material-symbols-outlined">close</span>
297 + </button>
298 + <button class="btn-action-header confirm" @click="$store.memoryDashboardStore.confirmEditMode()"
299 + title="Confirm Changes">
300 + <span class="material-symbols-outlined">check</span>
301 + </button>
302 +
303 + </div>
304 + </template>
305 </div>
306 </div>
307
@@ -281,7 +311,12 @@
311 <div class="content-section-main">
312 <div class="content-block">
313 <h4>Memory Content</h4>
284 - <div class="memory-content-display" x-text="$store.memoryDashboardStore.detailMemory?.content_full"></div>
314 + <template x-if="!$store.memoryDashboardStore.editMode">
315 + <div class="memory-content-display" x-text="$store.memoryDashboardStore.detailMemory?.content_full"></div>
316 + </template>
317 + <template x-if="$store.memoryDashboardStore.editMode">
318 + <textarea class="memory-content-display" style="width: 100%; height: 300px;" x-model="$store.memoryDashboardStore.detailMemory.content_full"></textarea>
319 + </template>
320 </div>
321
322 <!-- Tags Section -->
@@ -343,38 +378,6 @@
378 </template>
379 </div>
380
346 - <!-- Actions -->
347 - <div class="metadata-group">
348 - <h5>Actions</h5>
349 - <div class="metadata-actions">
350 - <button class="btn slim" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.formatMemoryForCopy($store.memoryDashboardStore.detailMemory))"
351 - title="Copy Complete Memory with Metadata">
352 - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
353 - <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
354 - <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
355 - </svg>
356 - Copy All
357 - </button>
358 - <button class="btn slim" @click="$store.memoryDashboardStore.copyToClipboard($store.memoryDashboardStore.detailMemory?.content_full)"
359 - title="Copy Content Only">
360 - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
361 - <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
362 - <polyline points="14,2 14,8 20,8"></polyline>
363 - </svg>
364 - Copy Content
365 - </button>
366 - <button class="btn slim danger" @click="$store.memoryDashboardStore.deleteMemory($store.memoryDashboardStore.detailMemory)"
367 - title="Delete Memory">
368 - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
369 - <polyline points="3,6 5,6 21,6"></polyline>
370 - <path d="M19,6V20a2,2,0,0,1-2,2H7a2,2,0,0,1-2-2V6m3,0V4a2,2,0,0,1,2-2h4a2,2,0,0,1,2,2V6"></path>
371 - <line x1="10" y1="11" x2="10" y2="17"></line>
372 - <line x1="14" y1="11" x2="14" y2="17"></line>
373 - </svg>
374 - Delete
375 - </button>
376 - </div>
377 - </div>
381 </div>
382 </div>
383 </div>
webui/index.html
+370 -221
@@ -32,7 +32,7 @@
32 <!-- Pre-initialize schedulerSettings to ensure Alpine doesn't miss it -->
33 <script>
34 // Pre-define schedulerSettings skeleton to ensure it's available to Alpine
35 - globalThis.schedulerSettings = function() {
35 + globalThis.schedulerSettings = function () {
36 return {
37 tasks: [],
38 isLoading: true,
@@ -114,8 +114,7 @@
114
115 <!-- KaTeX javascript -->
116 <script defer src="vendor/katex/katex.min.js" crossorigin="anonymous"></script>
117 - <script defer src="vendor/katex/katex.auto-render.min.js"
118 - crossorigin="anonymous"></script>
117 + <script defer src="vendor/katex/katex.auto-render.min.js" crossorigin="anonymous"></script>
118
119 <!-- QR Code Library -->
120 <script src="vendor/qrcode.min.js"></script>
@@ -174,6 +173,9 @@
173 d="m50.301 66.5c-9 0-16.398-7.3008-16.398-16.398 0-9.1016 7.3008-16.398 16.398-16.398 9.1016 0 16.398 7.3008 16.398 16.398 0 9.0977-7.3984 16.398-16.398 16.398zm0-25.5c-5 0-9.1016 4.1016-9.1016 9.1016s4.1016 9.1016 9.1016 9.1016 9.1016-4.1016 9.1016-9.1016c-0.003906-5-4.1016-9.1016-9.1016-9.1016z">
174 </path>
175 </svg>Settings</button>
176 + <button class="config-button" id="memory-dash"
177 + @click="openModal('settings/memory/memory-dashboard.html');">Memory</button>
178 +
179
180 </div>
181
@@ -194,7 +196,8 @@
196 <li>
197 <div :class="{'chat-list-button': true, 'font-bold': context.id === selected}"
198 @click="selected = context.id; selectChat(context.id)">
197 - <span class="chat-name" :title="context.name ? context.name : 'Chat #' + context.no"
199 + <span class="chat-name"
200 + :title="context.name ? context.name : 'Chat #' + context.no"
201 x-text="context.name ? context.name : 'Chat #' + context.no"></span>
202 </div>
203 <button class="edit-button" @click="killChat(context.id)">X</button>
@@ -214,8 +217,7 @@
217 openTaskDetail(taskId) {
218 globalThis.openTaskDetail(taskId);
219 }
217 - }"
218 - style="display: none;">
220 + }" style="display: none;">
221 <div class="tasks-list-container">
222 <ul class="config-list" x-show="tasks.length > 0">
223 <template x-for="task in tasks">
@@ -225,32 +227,40 @@
227 <!-- Task container with a vertical layout -->
228 <div class="task-container task-container-vertical">
229 <!-- Task name on its own line with full width -->
228 - <span class="task-name" :title="(task.task_name || `Task #${task.no}`) + ' (' + (task.name || `Chat #${task.no}`) + ')'"
230 + <span class="task-name"
231 + :title="(task.task_name || `Task #${task.no}`) + ' (' + (task.name || `Chat #${task.no}`) + ')'"
232 x-text="(task.task_name || `Task #${task.no}`) + ' (' + (task.name || `Chat #${task.no}`) + ')'"
233 :data-task-id="task.id"></span>
234 <!-- Second line with status badge and action button -->
235 <div class="task-info-line">
236 <!-- Status badge (reusing scheduler styling) -->
237 <span class="scheduler-status-badge scheduler-status-badge-small"
235 - :class="task.state ? `scheduler-status-${task.state}` : 'scheduler-status-idle'"
236 - x-text="task.state || 'idle'"></span>
238 + :class="task.state ? `scheduler-status-${task.state}` : 'scheduler-status-idle'"
239 + x-text="task.state || 'idle'"></span>
240 <!-- Action buttons -->
238 - <button class="edit-button"
239 - @click.stop="openTaskDetail(task.id)"
240 - title="View task details" style="margin-left: auto; margin-right: 5px;">
241 - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 512 512" fill="var(--color-primary)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
242 - <path d="M256 0c70.69 0 134.69 28.66 181.02 74.98C483.34 121.3 512 185.31 512 256c0 70.69-28.66 134.7-74.98 181.02C390.69 483.34 326.69 512 256 512c-70.69 0-134.69-28.66-181.02-74.98C28.66 390.69 0 326.69 0 256c0-70.69 28.66-134.69 74.98-181.02C121.31 28.66 185.31 0 256 0zm-9.96 161.03c0-4.28.76-8.26 2.27-11.91 1.5-3.63 3.77-6.94 6.79-9.91 3-2.95 6.29-5.2 9.84-6.7 3.57-1.5 7.41-2.28 11.52-2.28 4.12 0 7.96.78 11.49 2.27 3.54 1.51 6.78 3.76 9.75 6.73 2.95 2.97 5.16 6.26 6.64 9.91 1.49 3.63 2.22 7.61 2.22 11.89 0 4.17-.73 8.08-2.21 11.69-1.48 3.6-3.68 6.94-6.65 9.97-2.94 3.03-6.18 5.32-9.72 6.84-3.54 1.51-7.38 2.29-11.52 2.29-4.22 0-8.14-.76-11.75-2.26-3.58-1.51-6.86-3.79-9.83-6.79-2.94-3.02-5.16-6.34-6.63-9.97-1.48-3.62-2.21-7.54-2.21-11.77zm13.4 178.16c-1.11 3.97-3.35 11.76 3.3 11.76 1.44 0 3.27-.81 5.46-2.4 2.37-1.71 5.09-4.31 8.13-7.75 3.09-3.5 6.32-7.65 9.67-12.42 3.33-4.76 6.84-10.22 10.49-16.31.37-.65 1.23-.87 1.89-.48l12.36 9.18c.6.43.73 1.25.35 1.86-5.69 9.88-11.44 18.51-17.26 25.88-5.85 7.41-11.79 13.57-17.8 18.43l-.1.06c-6.02 4.88-12.19 8.55-18.51 11.01-17.58 6.81-45.36 5.7-53.32-14.83-5.02-12.96-.9-27.69 3.06-40.37l19.96-60.44c1.28-4.58 2.89-9.62 3.47-14.33.97-7.87-2.49-12.96-11.06-12.96h-17.45c-.76 0-1.38-.62-1.38-1.38l.08-.48 4.58-16.68c.16-.62.73-1.04 1.35-1.02l89.12-2.79c.76-.03 1.41.57 1.44 1.33l-.07.43-37.76 124.7zm158.3-244.93c-41.39-41.39-98.58-67-161.74-67-63.16 0-120.35 25.61-161.74 67-41.39 41.39-67 98.58-67 161.74 0 63.16 25.61 120.35 67 161.74 41.39 41.39 98.58 67 161.74 67 63.16 0 120.35-25.61 161.74-67 41.39-41.39 67-98.58 67-161.74 0-63.16-25.61-120.35-67-161.74z"/>
241 + <button class="edit-button" @click.stop="openTaskDetail(task.id)"
242 + title="View task details"
243 + style="margin-left: auto; margin-right: 5px;">
244 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
245 + viewBox="0 0 512 512" fill="var(--color-primary)"
246 + stroke="currentColor" stroke-width="2" stroke-linecap="round"
247 + stroke-linejoin="round">
248 + <path
249 + d="M256 0c70.69 0 134.69 28.66 181.02 74.98C483.34 121.3 512 185.31 512 256c0 70.69-28.66 134.7-74.98 181.02C390.69 483.34 326.69 512 256 512c-70.69 0-134.69-28.66-181.02-74.98C28.66 390.69 0 326.69 0 256c0-70.69 28.66-134.69 74.98-181.02C121.31 28.66 185.31 0 256 0zm-9.96 161.03c0-4.28.76-8.26 2.27-11.91 1.5-3.63 3.77-6.94 6.79-9.91 3-2.95 6.29-5.2 9.84-6.7 3.57-1.5 7.41-2.28 11.52-2.28 4.12 0 7.96.78 11.49 2.27 3.54 1.51 6.78 3.76 9.75 6.73 2.95 2.97 5.16 6.26 6.64 9.91 1.49 3.63 2.22 7.61 2.22 11.89 0 4.17-.73 8.08-2.21 11.69-1.48 3.6-3.68 6.94-6.65 9.97-2.94 3.03-6.18 5.32-9.72 6.84-3.54 1.51-7.38 2.29-11.52 2.29-4.22 0-8.14-.76-11.75-2.26-3.58-1.51-6.86-3.79-9.83-6.79-2.94-3.02-5.16-6.34-6.63-9.97-1.48-3.62-2.21-7.54-2.21-11.77zm13.4 178.16c-1.11 3.97-3.35 11.76 3.3 11.76 1.44 0 3.27-.81 5.46-2.4 2.37-1.71 5.09-4.31 8.13-7.75 3.09-3.5 6.32-7.65 9.67-12.42 3.33-4.76 6.84-10.22 10.49-16.31.37-.65 1.23-.87 1.89-.48l12.36 9.18c.6.43.73 1.25.35 1.86-5.69 9.88-11.44 18.51-17.26 25.88-5.85 7.41-11.79 13.57-17.8 18.43l-.1.06c-6.02 4.88-12.19 8.55-18.51 11.01-17.58 6.81-45.36 5.7-53.32-14.83-5.02-12.96-.9-27.69 3.06-40.37l19.96-60.44c1.28-4.58 2.89-9.62 3.47-14.33.97-7.87-2.49-12.96-11.06-12.96h-17.45c-.76 0-1.38-.62-1.38-1.38l.08-.48 4.58-16.68c.16-.62.73-1.04 1.35-1.02l89.12-2.79c.76-.03 1.41.57 1.44 1.33l-.07.43-37.76 124.7zm158.3-244.93c-41.39-41.39-98.58-67-161.74-67-63.16 0-120.35 25.61-161.74 67-41.39 41.39-67 98.58-67 161.74 0 63.16 25.61 120.35 67 161.74 41.39 41.39 98.58 67 161.74 67 63.16 0 120.35-25.61 161.74-67 41.39-41.39 67-98.58 67-161.74 0-63.16-25.61-120.35-67-161.74z" />
250 </svg>
251 </button>
245 - <button class="edit-button"
246 - @click.stop="resetChat(task.id)"
247 - style="margin-right: 5px;"
248 - title="Clear task chat">
249 - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 122.88 121.1" fill="var(--color-primary)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
250 - <path d="M62.89,56.03c1.11-0.35,2.34-0.25,3.72,0.37l10.4,7.87c2.26,1.71,4.24,3.78,2.73,6.9 c-0.51,1.06-1.4,2.1-2.38,3.49l-0.53,0.75c-1.97,2.8-2.61,2-5.71,1.83c0.56,13.37,1.75,27.82-2.64,40.88 c-0.87,2.7-3.32,3.44-6.95,2.71l-6.1-2.03c4.11-6.14,6.16-13.85,6.44-22.89c-3.46,8.58-6.8,16.96-10.68,20.86l-6.28-2.08 c0.61-3.05,1.05-5.43,0.35-6.9l-4.07,4.24l-9.33-5.77c6.36-3.36,11.62-7.87,15.6-13.73c-6.69,5.01-12.76,8.1-18.14,8.99 c-2.75,0.83-4.49,0.35-5.16-1.53c-0.48-1.34-0.05-1.77,0.81-2.86c1.11-1.41,2.61-2.67,4.35-3.79c-3.13,1.1-4.64,0.95-6.37,1.51 c-4.9,1.59-9.94-1.86-8.26-6.9c1.07-3.23,3.54-3.09,6.67-4.07l5.42-1.69c-5.19,0.28-10.32,0.45-15.02-0.25 c-5.4-0.8-5.31-0.99-8.24-5.38c-3.94-5.91-6.25-11.45,2.52-9.16c16.73,3.18,33.56,5.34,51.25-0.98c-0.76-1.32-0.9-2.57-0.5-3.73 C57.37,60.94,61.13,56.58,62.89,56.03L62.89,56.03z M113.8,2.42L74.45,51.53c-4.71,6.68,3.2,11.91,8.39,5.64l39.2-49.27 C125.12,1.86,119.13-3.16,113.8,2.42L113.8,2.42z"/>
252 + <button class="edit-button" @click.stop="resetChat(task.id)"
253 + style="margin-right: 5px;" title="Clear task chat">
254 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
255 + viewBox="0 0 122.88 121.1" fill="var(--color-primary)"
256 + stroke="currentColor" stroke-width="2" stroke-linecap="round"
257 + stroke-linejoin="round">
258 + <path
259 + d="M62.89,56.03c1.11-0.35,2.34-0.25,3.72,0.37l10.4,7.87c2.26,1.71,4.24,3.78,2.73,6.9 c-0.51,1.06-1.4,2.1-2.38,3.49l-0.53,0.75c-1.97,2.8-2.61,2-5.71,1.83c0.56,13.37,1.75,27.82-2.64,40.88 c-0.87,2.7-3.32,3.44-6.95,2.71l-6.1-2.03c4.11-6.14,6.16-13.85,6.44-22.89c-3.46,8.58-6.8,16.96-10.68,20.86l-6.28-2.08 c0.61-3.05,1.05-5.43,0.35-6.9l-4.07,4.24l-9.33-5.77c6.36-3.36,11.62-7.87,15.6-13.73c-6.69,5.01-12.76,8.1-18.14,8.99 c-2.75,0.83-4.49,0.35-5.16-1.53c-0.48-1.34-0.05-1.77,0.81-2.86c1.11-1.41,2.61-2.67,4.35-3.79c-3.13,1.1-4.64,0.95-6.37,1.51 c-4.9,1.59-9.94-1.86-8.26-6.9c1.07-3.23,3.54-3.09,6.67-4.07l5.42-1.69c-5.19,0.28-10.32,0.45-15.02-0.25 c-5.4-0.8-5.31-0.99-8.24-5.38c-3.94-5.91-6.25-11.45,2.52-9.16c16.73,3.18,33.56,5.34,51.25-0.98c-0.76-1.32-0.9-2.57-0.5-3.73 C57.37,60.94,61.13,56.58,62.89,56.03L62.89,56.03z M113.8,2.42L74.45,51.53c-4.71,6.68,3.2,11.91,8.39,5.64l39.2-49.27 C125.12,1.86,119.13-3.16,113.8,2.42L113.8,2.42z" />
260 </svg>
261 </button>
253 - <button title="Delete task" class="edit-button" @click.stop="deleteTaskGlobal(task.id)">X</button>
262 + <button title="Delete task" class="edit-button"
263 + @click.stop="deleteTaskGlobal(task.id)">X</button>
264
265 </div>
266 </div>
@@ -400,7 +410,8 @@
410 d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
411 </svg>
412 </label>
403 - <input type="file" id="file-input" accept="*" multiple style="display: none" @change="$store.chatAttachments.handleFileUpload($event)">
413 + <input type="file" id="file-input" accept="*" multiple style="display: none"
414 + @change="$store.chatAttachments.handleFileUpload($event)">
415
416 <div x-show="showTooltip" class="tooltip">
417 Add attachments to the message
@@ -412,9 +423,11 @@
423 <textarea id="chat-input" placeholder="Type your message here..." rows="1"></textarea>
424
425 <!-- Expand button inside the textarea container -->
415 - <button id="expand-button" @click="$store.fullScreenInputModal.openModal()" aria-label="Expand input">
426 + <button id="expand-button" @click="$store.fullScreenInputModal.openModal()"
427 + aria-label="Expand input">
428 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
417 - <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
429 + <path
430 + d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
431 </svg>
432 </button>
433 </div>
@@ -431,8 +444,7 @@
444
445 <!-- Microphone button -->
446 <button class="chat-button mic-inactive" id="microphone-button"
434 - aria-label="Start/Stop recording"
435 - @click="$store.speech.handleMicrophoneClick()"
447 + aria-label="Start/Stop recording" @click="$store.speech.handleMicrophoneClick()"
448 x-effect="$store.speech.updateMicrophoneButtonUI()">
449 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 18" fill="currentColor">
450 <path
@@ -540,30 +552,18 @@
552 <!-- Tab Navigation -->
553 <div class="settings-tabs-container">
554 <div class="settings-tabs">
543 - <div class="settings-tab"
544 - :class="{'active': activeTab === 'agent'}"
545 - @click="switchTab('agent')"
546 - title="Agent Settings">Agent Settings</div>
547 - <div class="settings-tab"
548 - :class="{'active': activeTab === 'external'}"
549 - @click="switchTab('external')"
550 - title="External Services">External Services</div>
551 - <div class="settings-tab"
552 - :class="{'active': activeTab === 'mcp'}"
553 - @click="switchTab('mcp')"
554 - title="MCP">MCP/A2A</div>
555 - <div class="settings-tab"
556 - :class="{'active': activeTab === 'developer'}"
557 - @click="switchTab('developer')"
558 - title="Developer">Developer</div>
559 - <div class="settings-tab"
560 - :class="{'active': activeTab === 'scheduler'}"
561 - @click="switchTab('scheduler')"
562 - title="Task Scheduler">Task Scheduler</div>
563 - <div class="settings-tab"
564 - :class="{'active': activeTab === 'backup'}"
565 - @click="switchTab('backup')"
566 - title="Backup & Restore">Backup & Restore</div>
555 + <div class="settings-tab" :class="{'active': activeTab === 'agent'}"
556 + @click="switchTab('agent')" title="Agent Settings">Agent Settings</div>
557 + <div class="settings-tab" :class="{'active': activeTab === 'external'}"
558 + @click="switchTab('external')" title="External Services">External Services</div>
559 + <div class="settings-tab" :class="{'active': activeTab === 'mcp'}"
560 + @click="switchTab('mcp')" title="MCP">MCP/A2A</div>
561 + <div class="settings-tab" :class="{'active': activeTab === 'developer'}"
562 + @click="switchTab('developer')" title="Developer">Developer</div>
563 + <div class="settings-tab" :class="{'active': activeTab === 'scheduler'}"
564 + @click="switchTab('scheduler')" title="Task Scheduler">Task Scheduler</div>
565 + <div class="settings-tab" :class="{'active': activeTab === 'backup'}"
566 + @click="switchTab('backup')" title="Backup & Restore">Backup & Restore</div>
567 </div>
568 </div>
569
@@ -574,8 +574,7 @@
574 <template x-for="(section, index) in filteredSections" :key="section.title">
575 <li>
576 <a :href="'#section' + (index + 1)">
577 - <img :src="'/public/' + section.id +'.svg'"
578 - :alt="section.title">
577 + <img :src="'/public/' + section.id +'.svg'" :alt="section.title">
578 <span x-text="section.title"></span>
579 </a>
580 </li>
@@ -595,11 +594,14 @@
594 <div class="section-title" x-text="section.title"></div>
595 <div class="section-description" x-html="section.description"></div>
596
598 - <template x-for="(field, fieldIndex) in section.fields.filter(f => !f.hidden)" :key="fieldIndex">
597 + <template x-for="(field, fieldIndex) in section.fields.filter(f => !f.hidden)"
598 + :key="fieldIndex">
599 <div :class="{'field': true, 'field-full': field.type === 'textarea'}">
600 <div class="field-label" x-show="field.title || field.description">
601 - <div class="field-title" x-text="field.title" x-show="field.title"></div>
602 - <div class="field-description" x-html="field.description || ''" x-show="field.description"></div>
601 + <div class="field-title" x-text="field.title" x-show="field.title">
602 + </div>
603 + <div class="field-description" x-html="field.description || ''"
604 + x-show="field.description"></div>
605 </div>
606
607 <div class="field-control">
@@ -614,18 +616,16 @@
616 <template x-if="field.type === 'number'">
617 <input type="number" :class="field.classes" :value="field.value"
618 :readonly="field.readonly === true"
617 - @input="field.value = $event.target.value"
618 - :min="field.min" :max="field.max" :step="field.step">
619 + @input="field.value = $event.target.value" :min="field.min"
620 + :max="field.max" :step="field.step">
621 </template>
622
623
624 <!-- Password field -->
625 <template x-if="field.type === 'password'">
626 <input type="password" :class="field.classes" :value="field.value"
625 - :readonly="field.readonly === true"
626 - :id="field.id"
627 - autocomplete="off"
628 - @input="field.value = $event.target.value">
627 + :readonly="field.readonly === true" :id="field.id"
628 + autocomplete="off" @input="field.value = $event.target.value">
629 </template>
630
631 <!-- Textarea field -->
@@ -633,8 +633,7 @@
633 <textarea :class="field.classes" :value="field.value"
634 :readonly="field.readonly === true"
635 @input="field.value = $event.target.value"
636 - :style="field.style"
637 - ></textarea>
636 + :style="field.style"></textarea>
637 </template>
638
639 <!-- Switch field -->
@@ -707,10 +706,10 @@
706 </ul>
707 </nav>
708
710 - <div id="section-task-scheduler" class="section"
711 - x-data="schedulerSettings">
709 + <div id="section-task-scheduler" class="section" x-data="schedulerSettings">
710 <div class="section-title">Task Scheduler</div>
713 - <div class="section-description">Manage scheduled tasks and automated processes for Agent Zero.</div>
711 + <div class="section-description">Manage scheduled tasks and automated processes for
712 + Agent Zero.</div>
713
714 <!-- Create Task Form -->
715 <div class="scheduler-form" x-show="isCreating">
@@ -731,7 +730,8 @@
730 <div class="scheduler-form-field">
731 <div class="label-help-wrapper">
732 <label class="scheduler-form-label">Task Name</label>
734 - <div class="scheduler-form-help">A unique name to identify this task</div>
733 + <div class="scheduler-form-help">A unique name to identify this task
734 + </div>
735 </div>
736 <input type="text" x-model="editingTask.name" placeholder="Enter task name">
737 </div>
@@ -753,78 +753,102 @@
753 <div class="scheduler-form-field" x-show="isCreating">
754 <div class="label-help-wrapper">
755 <label class="scheduler-form-label">State</label>
756 - <div class="scheduler-form-help">Select the initial state of the task</div>
756 + <div class="scheduler-form-help">Select the initial state of the task
757 + </div>
758 </div>
759 <div>
760 <div class="scheduler-state-selector">
761 <span class="scheduler-status-badge scheduler-status-idle"
761 - :class="{'scheduler-status-selected': editingTask.state === 'idle'}"
762 - @click="editingTask.state = 'idle'">idle</span>
762 + :class="{'scheduler-status-selected': editingTask.state === 'idle'}"
763 + @click="editingTask.state = 'idle'">idle</span>
764 <span class="scheduler-status-badge scheduler-status-running"
764 - :class="{'scheduler-status-selected': editingTask.state === 'running'}"
765 - @click="editingTask.state = 'running'">running</span>
765 + :class="{'scheduler-status-selected': editingTask.state === 'running'}"
766 + @click="editingTask.state = 'running'">running</span>
767 <span class="scheduler-status-badge scheduler-status-disabled"
767 - :class="{'scheduler-status-selected': editingTask.state === 'disabled'}"
768 - @click="editingTask.state = 'disabled'">disabled</span>
768 + :class="{'scheduler-status-selected': editingTask.state === 'disabled'}"
769 + @click="editingTask.state = 'disabled'">disabled</span>
770 <span class="scheduler-status-badge scheduler-status-error"
770 - :class="{'scheduler-status-selected': editingTask.state === 'error'}"
771 - @click="editingTask.state = 'error'">error</span>
771 + :class="{'scheduler-status-selected': editingTask.state === 'error'}"
772 + @click="editingTask.state = 'error'">error</span>
773 </div>
774 <div class="scheduler-state-explanation">
774 - <span x-show="editingTask.state === 'idle'"><strong>idle</strong>: ready to run</span>
775 - <span x-show="editingTask.state === 'running'"><strong>running</strong>: currently executing</span>
776 - <span x-show="editingTask.state === 'disabled'"><strong>disabled</strong>: won't execute automatically</span>
777 - <span x-show="editingTask.state === 'error'"><strong>error</strong>: task encountered an error</span>
775 + <span x-show="editingTask.state === 'idle'"><strong>idle</strong>:
776 + ready to run</span>
777 + <span
778 + x-show="editingTask.state === 'running'"><strong>running</strong>:
779 + currently executing</span>
780 + <span
781 + x-show="editingTask.state === 'disabled'"><strong>disabled</strong>:
782 + won't execute automatically</span>
783 + <span x-show="editingTask.state === 'error'"><strong>error</strong>:
784 + task encountered an error</span>
785 </div>
786 </div>
787 </div>
788
789 <!-- Schedule (only for scheduled tasks) -->
783 - <div class="scheduler-form-field full-width" x-show="editingTask.type === 'scheduled'">
790 + <div class="scheduler-form-field full-width"
791 + x-show="editingTask.type === 'scheduled'">
792 <div class="label-help-wrapper">
793 <label class="scheduler-form-label">Schedule</label>
786 - <div class="scheduler-form-help">Cron schedule for automated execution (minute hour day month weekday)</div>
794 + <div class="scheduler-form-help">Cron schedule for automated execution
795 + (minute hour day month weekday)</div>
796 </div>
797 <div class="scheduler-schedule-builder">
798 <div class="scheduler-schedule-field">
799 <span class="scheduler-schedule-label">Minute</span>
791 - <input type="text" x-model="editingTask.schedule.minute" placeholder="*" maxlength="9">
800 + <input type="text" x-model="editingTask.schedule.minute"
801 + placeholder="*" maxlength="9">
802 </div>
803 <div class="scheduler-schedule-field">
804 <span class="scheduler-schedule-label">Hour</span>
795 - <input type="text" x-model="editingTask.schedule.hour" placeholder="*" maxlength="9">
805 + <input type="text" x-model="editingTask.schedule.hour"
806 + placeholder="*" maxlength="9">
807 </div>
808 <div class="scheduler-schedule-field">
809 <span class="scheduler-schedule-label">Day</span>
799 - <input type="text" x-model="editingTask.schedule.day" placeholder="*" maxlength="9">
810 + <input type="text" x-model="editingTask.schedule.day"
811 + placeholder="*" maxlength="9">
812 </div>
813 <div class="scheduler-schedule-field">
814 <span class="scheduler-schedule-label">Month</span>
803 - <input type="text" x-model="editingTask.schedule.month" placeholder="*" maxlength="9">
815 + <input type="text" x-model="editingTask.schedule.month"
816 + placeholder="*" maxlength="9">
817 </div>
818 <div class="scheduler-schedule-field">
819 <span class="scheduler-schedule-label">Weekday</span>
807 - <input type="text" x-model="editingTask.schedule.weekday" placeholder="*" maxlength="9">
820 + <input type="text" x-model="editingTask.schedule.weekday"
821 + placeholder="*" maxlength="9">
822 </div>
823 </div>
824 </div>
825
826 <!-- Plan (for planned tasks) -->
813 - <div class="scheduler-form-field full-width" x-show="editingTask.type === 'planned'">
827 + <div class="scheduler-form-field full-width"
828 + x-show="editingTask.type === 'planned'">
829 <div class="label-help-wrapper">
830 <label class="scheduler-form-label">Plan</label>
816 - <div class="scheduler-form-help">Specific execution times for this task</div>
831 + <div class="scheduler-form-help">Specific execution times for this task
832 + </div>
833 </div>
834 <div class="scheduler-plan-builder">
835 <div class="scheduler-plan-todo">
836 <span class="scheduler-plan-label">Upcoming Executions</span>
837 <div class="scheduler-todo-list">
822 - <template x-if="editingTask.plan && Array.isArray(editingTask.plan.todo) && editingTask.plan.todo.length > 0">
823 - <template x-for="(time, index) in editingTask.plan.todo" :key="index">
838 + <template
839 + x-if="editingTask.plan && Array.isArray(editingTask.plan.todo) && editingTask.plan.todo.length > 0">
840 + <template x-for="(time, index) in editingTask.plan.todo"
841 + :key="index">
842 <div class="scheduler-todo-item">
843 <span x-text="formatDate(time)"></span>
826 - <button @click.prevent="editingTask.plan.todo.splice(index, 1)" class="scheduler-todo-remove">
827 - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
844 + <button
845 + @click.prevent="editingTask.plan.todo.splice(index, 1)"
846 + class="scheduler-todo-remove">
847 + <svg xmlns="http://www.w3.org/2000/svg"
848 + width="16" height="16" viewBox="0 0 24 24"
849 + fill="none" stroke="currentColor"
850 + stroke-width="2" stroke-linecap="round"
851 + stroke-linejoin="round">
852 <line x1="18" y1="6" x2="6" y2="18"></line>
853 <line x1="6" y1="6" x2="18" y2="18"></line>
854 </svg>
@@ -832,7 +856,8 @@
856 </div>
857 </template>
858 </template>
835 - <template x-if="!editingTask.plan || !Array.isArray(editingTask.plan.todo) || editingTask.plan.todo.length === 0">
859 + <template
860 + x-if="!editingTask.plan || !Array.isArray(editingTask.plan.todo) || editingTask.plan.todo.length === 0">
861 <div class="scheduler-empty-plan">
862 No scheduled execution times yet. Add one below.
863 </div>
@@ -885,7 +910,11 @@
910 input.value = '';
911 }
912 " class="scheduler-add-todo-button">
888 - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 4px;">
913 + <svg xmlns="http://www.w3.org/2000/svg" width="16"
914 + height="16" viewBox="0 0 24 24" fill="none"
915 + stroke="currentColor" stroke-width="2"
916 + stroke-linecap="round" stroke-linejoin="round"
917 + style="margin-right: 4px;">
918 <line x1="12" y1="5" x2="12" y2="19"></line>
919 <line x1="5" y1="12" x2="19" y2="12"></line>
920 </svg>
@@ -898,14 +927,18 @@
927 </div>
928
929 <!-- Token (for ad-hoc tasks) -->
901 - <div class="scheduler-form-field full-width" x-show="editingTask.type === 'adhoc'">
930 + <div class="scheduler-form-field full-width"
931 + x-show="editingTask.type === 'adhoc'">
932 <div class="label-help-wrapper">
933 <label class="scheduler-form-label">Token</label>
904 - <div class="scheduler-form-help">Token used to trigger this task externally</div>
934 + <div class="scheduler-form-help">Token used to trigger this task
935 + externally</div>
936 </div>
937 <div class="input-group">
907 - <input type="text" x-model="editingTask.token" placeholder="Token for ad-hoc task">
908 - <button class="scheduler-task-action" @click="editingTask.token = generateRandomToken()">
938 + <input type="text" x-model="editingTask.token"
939 + placeholder="Token for ad-hoc task">
940 + <button class="scheduler-task-action"
941 + @click="editingTask.token = generateRandomToken()">
942 Generate
943 </button>
944 </div>
@@ -915,27 +948,33 @@
948 <div class="scheduler-form-field full-width">
949 <div class="label-help-wrapper">
950 <label class="scheduler-form-label">System Prompt</label>
918 - <div class="scheduler-form-help">System-level instructions for the assistant</div>
951 + <div class="scheduler-form-help">System-level instructions for the
952 + assistant</div>
953 </div>
920 - <textarea x-model="editingTask.system_prompt" placeholder="System instructions for the AI"></textarea>
954 + <textarea x-model="editingTask.system_prompt"
955 + placeholder="System instructions for the AI"></textarea>
956 </div>
957
958 <!-- User Prompt -->
959 <div class="scheduler-form-field full-width">
960 <div class="label-help-wrapper">
961 <label class="scheduler-form-label">User Prompt</label>
927 - <div class="scheduler-form-help">The main task prompt that will be executed</div>
962 + <div class="scheduler-form-help">The main task prompt that will be
963 + executed</div>
964 </div>
929 - <textarea x-model="editingTask.prompt" placeholder="User message for the AI"></textarea>
965 + <textarea x-model="editingTask.prompt"
966 + placeholder="User message for the AI"></textarea>
967 </div>
968
969 <!-- Attachments Field -->
970 <div class="scheduler-form-field full-width">
971 <div class="label-help-wrapper">
972 <label class="scheduler-form-label">Attachments</label>
936 - <div class="scheduler-form-help">Container file paths or URLs, one per line</div>
973 + <div class="scheduler-form-help">Container file paths or URLs, one per
974 + line</div>
975 </div>
938 - <textarea x-model="attachmentsText" placeholder="Enter file paths or URLs, one per line"></textarea>
976 + <textarea x-model="attachmentsText"
977 + placeholder="Enter file paths or URLs, one per line"></textarea>
978 </div>
979 </div>
980 </div>
@@ -959,7 +998,8 @@
998 <div class="scheduler-form-field">
999 <div class="label-help-wrapper">
1000 <label class="scheduler-form-label">Task Name</label>
962 - <div class="scheduler-form-help">A unique name to identify this task</div>
1001 + <div class="scheduler-form-help">A unique name to identify this task
1002 + </div>
1003 </div>
1004 <input type="text" x-model="editingTask.name" placeholder="Enter task name">
1005 </div>
@@ -968,7 +1008,8 @@
1008 <div class="scheduler-form-field">
1009 <div class="label-help-wrapper">
1010 <label class="scheduler-form-label">Task Type</label>
971 - <div class="scheduler-form-help">Task type cannot be changed after creation</div>
1011 + <div class="scheduler-form-help">Task type cannot be changed after
1012 + creation</div>
1013 </div>
1014 <select x-model="editingTask.type" disabled>
1015 <option value="scheduled">Scheduled Task</option>
@@ -986,73 +1027,96 @@
1027 <div>
1028 <div class="scheduler-state-selector">
1029 <span class="scheduler-status-badge scheduler-status-idle"
989 - :class="{'scheduler-status-selected': editingTask.state === 'idle'}"
990 - @click="editingTask.state = 'idle'">idle</span>
1030 + :class="{'scheduler-status-selected': editingTask.state === 'idle'}"
1031 + @click="editingTask.state = 'idle'">idle</span>
1032 <span class="scheduler-status-badge scheduler-status-running"
992 - :class="{'scheduler-status-selected': editingTask.state === 'running'}"
993 - @click="editingTask.state = 'running'">running</span>
1033 + :class="{'scheduler-status-selected': editingTask.state === 'running'}"
1034 + @click="editingTask.state = 'running'">running</span>
1035 <span class="scheduler-status-badge scheduler-status-disabled"
995 - :class="{'scheduler-status-selected': editingTask.state === 'disabled'}"
996 - @click="editingTask.state = 'disabled'">disabled</span>
1036 + :class="{'scheduler-status-selected': editingTask.state === 'disabled'}"
1037 + @click="editingTask.state = 'disabled'">disabled</span>
1038 <span class="scheduler-status-badge scheduler-status-error"
998 - :class="{'scheduler-status-selected': editingTask.state === 'error'}"
999 - @click="editingTask.state = 'error'">error</span>
1039 + :class="{'scheduler-status-selected': editingTask.state === 'error'}"
1040 + @click="editingTask.state = 'error'">error</span>
1041 </div>
1042 <div class="scheduler-state-explanation">
1002 - <span x-show="editingTask.state === 'idle'"><strong>idle</strong>: ready to run</span>
1003 - <span x-show="editingTask.state === 'running'"><strong>running</strong>: currently executing</span>
1004 - <span x-show="editingTask.state === 'disabled'"><strong>disabled</strong>: won't execute automatically</span>
1005 - <span x-show="editingTask.state === 'error'"><strong>error</strong>: task encountered an error</span>
1043 + <span x-show="editingTask.state === 'idle'"><strong>idle</strong>:
1044 + ready to run</span>
1045 + <span
1046 + x-show="editingTask.state === 'running'"><strong>running</strong>:
1047 + currently executing</span>
1048 + <span
1049 + x-show="editingTask.state === 'disabled'"><strong>disabled</strong>:
1050 + won't execute automatically</span>
1051 + <span x-show="editingTask.state === 'error'"><strong>error</strong>:
1052 + task encountered an error</span>
1053 </div>
1054 </div>
1055 </div>
1056
1057 <!-- Schedule (for scheduled tasks) -->
1011 - <div class="scheduler-form-field full-width" x-show="editingTask && editingTask.type === 'scheduled'">
1058 + <div class="scheduler-form-field full-width"
1059 + x-show="editingTask && editingTask.type === 'scheduled'">
1060 <div class="label-help-wrapper">
1061 <label class="scheduler-form-label">Schedule (Cron Expression)</label>
1014 - <div class="scheduler-form-help">Format: minute hour day month weekday (e.g., "* * * * *" for every minute)</div>
1062 + <div class="scheduler-form-help">Format: minute hour day month weekday
1063 + (e.g., "* * * * *" for every minute)</div>
1064 </div>
1065 <div class="scheduler-schedule-builder">
1066 <div class="scheduler-schedule-field">
1067 <span class="scheduler-schedule-label">Minute</span>
1019 - <input type="text" x-model="editingTask.schedule.minute" placeholder="*" maxlength="9">
1068 + <input type="text" x-model="editingTask.schedule.minute"
1069 + placeholder="*" maxlength="9">
1070 </div>
1071 <div class="scheduler-schedule-field">
1072 <span class="scheduler-schedule-label">Hour</span>
1023 - <input type="text" x-model="editingTask.schedule.hour" placeholder="*" maxlength="9">
1073 + <input type="text" x-model="editingTask.schedule.hour"
1074 + placeholder="*" maxlength="9">
1075 </div>
1076 <div class="scheduler-schedule-field">
1077 <span class="scheduler-schedule-label">Day</span>
1027 - <input type="text" x-model="editingTask.schedule.day" placeholder="*" maxlength="9">
1078 + <input type="text" x-model="editingTask.schedule.day"
1079 + placeholder="*" maxlength="9">
1080 </div>
1081 <div class="scheduler-schedule-field">
1082 <span class="scheduler-schedule-label">Month</span>
1031 - <input type="text" x-model="editingTask.schedule.month" placeholder="*" maxlength="9">
1083 + <input type="text" x-model="editingTask.schedule.month"
1084 + placeholder="*" maxlength="9">
1085 </div>
1086 <div class="scheduler-schedule-field">
1087 <span class="scheduler-schedule-label">Weekday</span>
1035 - <input type="text" x-model="editingTask.schedule.weekday" placeholder="*" maxlength="9">
1088 + <input type="text" x-model="editingTask.schedule.weekday"
1089 + placeholder="*" maxlength="9">
1090 </div>
1091 </div>
1092 </div>
1093
1094 <!-- Plan (for planned tasks) -->
1041 - <div class="scheduler-form-field full-width" x-show="editingTask.type === 'planned'">
1095 + <div class="scheduler-form-field full-width"
1096 + x-show="editingTask.type === 'planned'">
1097 <div class="label-help-wrapper">
1098 <label class="scheduler-form-label">Plan</label>
1044 - <div class="scheduler-form-help">Specific execution times for this task</div>
1099 + <div class="scheduler-form-help">Specific execution times for this task
1100 + </div>
1101 </div>
1102 <div class="scheduler-plan-builder">
1103 <div class="scheduler-plan-todo">
1104 <span class="scheduler-plan-label">Upcoming Executions</span>
1105 <div class="scheduler-todo-list">
1050 - <template x-if="editingTask.plan && Array.isArray(editingTask.plan.todo) && editingTask.plan.todo.length > 0">
1051 - <template x-for="(time, index) in editingTask.plan.todo" :key="index">
1106 + <template
1107 + x-if="editingTask.plan && Array.isArray(editingTask.plan.todo) && editingTask.plan.todo.length > 0">
1108 + <template x-for="(time, index) in editingTask.plan.todo"
1109 + :key="index">
1110 <div class="scheduler-todo-item">
1111 <span x-text="formatDate(time)"></span>
1054 - <button @click.prevent="editingTask.plan.todo.splice(index, 1)" class="scheduler-todo-remove">
1055 - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1112 + <button
1113 + @click.prevent="editingTask.plan.todo.splice(index, 1)"
1114 + class="scheduler-todo-remove">
1115 + <svg xmlns="http://www.w3.org/2000/svg"
1116 + width="16" height="16" viewBox="0 0 24 24"
1117 + fill="none" stroke="currentColor"
1118 + stroke-width="2" stroke-linecap="round"
1119 + stroke-linejoin="round">
1120 <line x1="18" y1="6" x2="6" y2="18"></line>
1121 <line x1="6" y1="6" x2="18" y2="18"></line>
1122 </svg>
@@ -1060,7 +1124,8 @@
1124 </div>
1125 </template>
1126 </template>
1063 - <template x-if="!editingTask.plan || !Array.isArray(editingTask.plan.todo) || editingTask.plan.todo.length === 0">
1127 + <template
1128 + x-if="!editingTask.plan || !Array.isArray(editingTask.plan.todo) || editingTask.plan.todo.length === 0">
1129 <div class="scheduler-empty-plan">
1130 No scheduled execution times yet. Add one below.
1131 </div>
@@ -1113,7 +1178,11 @@
1178 input.value = '';
1179 }
1180 " class="scheduler-add-todo-button">
1116 - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 4px;">
1181 + <svg xmlns="http://www.w3.org/2000/svg" width="16"
1182 + height="16" viewBox="0 0 24 24" fill="none"
1183 + stroke="currentColor" stroke-width="2"
1184 + stroke-linecap="round" stroke-linejoin="round"
1185 + style="margin-right: 4px;">
1186 <line x1="12" y1="5" x2="12" y2="19"></line>
1187 <line x1="5" y1="12" x2="19" y2="12"></line>
1188 </svg>
@@ -1126,14 +1195,18 @@
1195 </div>
1196
1197 <!-- Token (for ad-hoc tasks) -->
1129 - <div class="scheduler-form-field full-width" x-show="editingTask.type === 'adhoc'">
1198 + <div class="scheduler-form-field full-width"
1199 + x-show="editingTask.type === 'adhoc'">
1200 <div class="label-help-wrapper">
1201 <label class="scheduler-form-label">Token</label>
1132 - <div class="scheduler-form-help">Token used to trigger this task externally</div>
1202 + <div class="scheduler-form-help">Token used to trigger this task
1203 + externally</div>
1204 </div>
1205 <div class="input-group">
1135 - <input type="text" x-model="editingTask.token" placeholder="Token for ad-hoc task">
1136 - <button class="scheduler-task-action" @click="editingTask.token = generateRandomToken()">
1206 + <input type="text" x-model="editingTask.token"
1207 + placeholder="Token for ad-hoc task">
1208 + <button class="scheduler-task-action"
1209 + @click="editingTask.token = generateRandomToken()">
1210 Generate
1211 </button>
1212 </div>
@@ -1143,33 +1216,40 @@
1216 <div class="scheduler-form-field full-width">
1217 <div class="label-help-wrapper">
1218 <label class="scheduler-form-label">System Prompt</label>
1146 - <div class="scheduler-form-help">System-level instructions for the assistant</div>
1219 + <div class="scheduler-form-help">System-level instructions for the
1220 + assistant</div>
1221 </div>
1148 - <textarea x-model="editingTask.system_prompt" placeholder="System instructions for the AI"></textarea>
1222 + <textarea x-model="editingTask.system_prompt"
1223 + placeholder="System instructions for the AI"></textarea>
1224 </div>
1225
1226 <!-- User Prompt -->
1227 <div class="scheduler-form-field full-width">
1228 <div class="label-help-wrapper">
1229 <label class="scheduler-form-label">User Prompt</label>
1155 - <div class="scheduler-form-help">The main task prompt that will be executed</div>
1230 + <div class="scheduler-form-help">The main task prompt that will be
1231 + executed</div>
1232 </div>
1157 - <textarea x-model="editingTask.prompt" placeholder="User message for the AI"></textarea>
1233 + <textarea x-model="editingTask.prompt"
1234 + placeholder="User message for the AI"></textarea>
1235 </div>
1236
1237 <!-- Attachments Field -->
1238 <div class="scheduler-form-field full-width">
1239 <div class="label-help-wrapper">
1240 <label class="scheduler-form-label">Attachments</label>
1164 - <div class="scheduler-form-help">Container file paths or URLs, one per line</div>
1241 + <div class="scheduler-form-help">Container file paths or URLs, one per
1242 + line</div>
1243 </div>
1166 - <textarea x-model="attachmentsText" placeholder="Enter file paths or URLs, one per line"></textarea>
1244 + <textarea x-model="attachmentsText"
1245 + placeholder="Enter file paths or URLs, one per line"></textarea>
1246 </div>
1247 </div>
1248 </div>
1249
1250 <!-- Task List View -->
1172 - <div class="scheduler-container" x-show="!isCreating && !isEditing && viewMode === 'list'">
1251 + <div class="scheduler-container"
1252 + x-show="!isCreating && !isEditing && viewMode === 'list'">
1253 <!-- Header with Actions -->
1254 <div class="scheduler-header">
1255 <h2>Task Management</h2>
@@ -1210,36 +1290,37 @@
1290 </div> -->
1291
1292 <!-- Empty State -->
1213 - <div class="scheduler-empty"
1214 - x-show="!isLoading && filteredTasks.length === 0"
1215 - x-effect="$el.style.display = (!isLoading && filteredTasks.length === 0) ? '' : 'none'">
1293 + <div class="scheduler-empty" x-show="!isLoading && filteredTasks.length === 0"
1294 + x-effect="$el.style.display = (!isLoading && filteredTasks.length === 0) ? '' : 'none'">
1295 <!-- <div class="scheduler-empty-icon">📋</div> -->
1296 <div class="scheduler-empty-text">No tasks found</div>
1218 - <button class="btn btn-ok" @click="startCreateTask()">Create your first task</button>
1297 + <button class="btn btn-ok" @click="startCreateTask()">Create your first
1298 + task</button>
1299 </div>
1300
1301 <!-- Task List Table -->
1222 - <table class="scheduler-task-list"
1223 - x-show="!isLoading && filteredTasks.length > 0"
1224 - x-effect="$el.style.display = (!isLoading && filteredTasks.length > 0) ? '' : 'none'">
1302 + <table class="scheduler-task-list" x-show="!isLoading && filteredTasks.length > 0"
1303 + x-effect="$el.style.display = (!isLoading && filteredTasks.length > 0) ? '' : 'none'">
1304 <thead>
1305 <tr>
1306 <th @click="changeSort('name')">
1307 Name
1308 <span class="scheduler-sort-indicator" x-show="sortField === 'name'"
1230 - :class="{'scheduler-sort-desc': sortDirection === 'desc'}">↑</span>
1309 + :class="{'scheduler-sort-desc': sortDirection === 'desc'}">↑</span>
1310 </th>
1311 <th @click="changeSort('state')">
1312 State
1234 - <span class="scheduler-sort-indicator" x-show="sortField === 'state'"
1235 - :class="{'scheduler-sort-desc': sortDirection === 'desc'}">↑</span>
1313 + <span class="scheduler-sort-indicator"
1314 + x-show="sortField === 'state'"
1315 + :class="{'scheduler-sort-desc': sortDirection === 'desc'}">↑</span>
1316 </th>
1317 <th>Type</th>
1318 <th>Schedule</th>
1319 <th @click="changeSort('last_run')">
1320 Last Run
1241 - <span class="scheduler-sort-indicator" x-show="sortField === 'last_run'"
1242 - :class="{'scheduler-sort-desc': sortDirection === 'desc'}">↑</span>
1321 + <span class="scheduler-sort-indicator"
1322 + x-show="sortField === 'last_run'"
1323 + :class="{'scheduler-sort-desc': sortDirection === 'desc'}">↑</span>
1324 </th>
1325 <th>Actions</th>
1326 </tr>
@@ -1251,35 +1332,55 @@
1332 <span x-text="task.name"></span>
1333 </td>
1334 <td>
1254 - <span class="scheduler-status-badge" :class="getStateBadgeClass(task.state)" x-text="task.state"></span>
1335 + <span class="scheduler-status-badge"
1336 + :class="getStateBadgeClass(task.state)"
1337 + x-text="task.state"></span>
1338 </td>
1339 <td x-text="task.type"></td>
1340 <td>
1258 - <span x-show="task.type === 'scheduled'" x-text="formatSchedule(task)"></span>
1259 - <span x-show="task.type === 'adhoc'" class="scheduler-no-schedule">—</span>
1260 - <span x-show="task.type === 'planned'" x-html="formatPlan(task).replace(/\n/g, '<br>')"></span>
1341 + <span x-show="task.type === 'scheduled'"
1342 + x-text="formatSchedule(task)"></span>
1343 + <span x-show="task.type === 'adhoc'"
1344 + class="scheduler-no-schedule">—</span>
1345 + <span x-show="task.type === 'planned'"
1346 + x-html="formatPlan(task).replace(/\n/g, '<br>')"></span>
1347 </td>
1348 <td x-text="formatDate(task.last_run)"></td>
1349 <td @click.stop>
1350 <div class="scheduler-task-actions">
1265 - <button class="scheduler-task-action" @click="runTask(task.uuid)" title="Run Task">
1266 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16">
1267 - <path d="M8 5v14l11-7z"/>
1351 + <button class="scheduler-task-action"
1352 + @click="runTask(task.uuid)" title="Run Task">
1353 + <svg xmlns="http://www.w3.org/2000/svg"
1354 + viewBox="0 0 24 24" fill="currentColor" width="16"
1355 + height="16">
1356 + <path d="M8 5v14l11-7z" />
1357 </svg>
1358 </button>
1270 - <button class="scheduler-task-action" @click="resetTaskState(task.uuid)" title="Reset State">
1271 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16">
1272 - <path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
1359 + <button class="scheduler-task-action"
1360 + @click="resetTaskState(task.uuid)" title="Reset State">
1361 + <svg xmlns="http://www.w3.org/2000/svg"
1362 + viewBox="0 0 24 24" fill="currentColor" width="16"
1363 + height="16">
1364 + <path
1365 + d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" />
1366 </svg>
1367 </button>
1275 - <button class="scheduler-task-action" @click="startEditTask(task.uuid)" title="Edit Task">
1276 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16">
1277 - <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
1368 + <button class="scheduler-task-action"
1369 + @click="startEditTask(task.uuid)" title="Edit Task">
1370 + <svg xmlns="http://www.w3.org/2000/svg"
1371 + viewBox="0 0 24 24" fill="currentColor" width="16"
1372 + height="16">
1373 + <path
1374 + d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
1375 </svg>
1376 </button>
1280 - <button class="scheduler-task-action" @click="deleteTask(task.uuid)" title="Delete Task">
1281 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16">
1282 - <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
1377 + <button class="scheduler-task-action"
1378 + @click="deleteTask(task.uuid)" title="Delete Task">
1379 + <svg xmlns="http://www.w3.org/2000/svg"
1380 + viewBox="0 0 24 24" fill="currentColor" width="16"
1381 + height="16">
1382 + <path
1383 + d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
1384 </svg>
1385 </button>
1386 </div>
@@ -1291,9 +1392,11 @@
1392 </div>
1393
1394 <!-- Task Detail View -->
1294 - <div class="scheduler-detail-view" x-show="!isCreating && !isEditing && viewMode === 'detail' && selectedTaskForDetail">
1395 + <div class="scheduler-detail-view"
1396 + x-show="!isCreating && !isEditing && viewMode === 'detail' && selectedTaskForDetail">
1397 <div class="scheduler-detail-header">
1296 - <h2 class="scheduler-detail-title" x-text="selectedTaskForDetail ? selectedTaskForDetail.name : ''"></h2>
1398 + <h2 class="scheduler-detail-title"
1399 + x-text="selectedTaskForDetail ? selectedTaskForDetail.name : ''"></h2>
1400 <div class="scheduler-status-badge"
1401 :class="selectedTaskForDetail ? getStateBadgeClass(selectedTaskForDetail.state) : ''"
1402 x-text="selectedTaskForDetail ? selectedTaskForDetail.state : ''">
@@ -1304,74 +1407,113 @@
1407 <div class="scheduler-detail-content">
1408 <div class="scheduler-details-grid">
1409 <div class="scheduler-details-label">Type:</div>
1307 - <div class="scheduler-details-value" x-text="selectedTaskForDetail ? selectedTaskForDetail.type : ''"></div>
1410 + <div class="scheduler-details-value"
1411 + x-text="selectedTaskForDetail ? selectedTaskForDetail.type : ''"></div>
1412
1413 <div class="scheduler-details-label">Created:</div>
1310 - <div class="scheduler-details-value" x-text="selectedTaskForDetail ? formatDate(selectedTaskForDetail.created_at) : ''"></div>
1414 + <div class="scheduler-details-value"
1415 + x-text="selectedTaskForDetail ? formatDate(selectedTaskForDetail.created_at) : ''">
1416 + </div>
1417
1418 <div class="scheduler-details-label">Last Updated:</div>
1313 - <div class="scheduler-details-value" x-text="selectedTaskForDetail ? formatDate(selectedTaskForDetail.updated_at) : ''"></div>
1419 + <div class="scheduler-details-value"
1420 + x-text="selectedTaskForDetail ? formatDate(selectedTaskForDetail.updated_at) : ''">
1421 + </div>
1422
1423 <div class="scheduler-details-label">Last Run:</div>
1316 - <div class="scheduler-details-value" x-text="selectedTaskForDetail ? formatDate(selectedTaskForDetail.last_run) : ''"></div>
1317 -
1318 - <div class="scheduler-details-label" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'scheduled'">Schedule:</div>
1319 - <div class="scheduler-details-value" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'scheduled'" x-text="selectedTaskForDetail ? formatSchedule(selectedTaskForDetail) : ''"></div>
1424 + <div class="scheduler-details-value"
1425 + x-text="selectedTaskForDetail ? formatDate(selectedTaskForDetail.last_run) : ''">
1426 + </div>
1427
1321 - <div class="scheduler-details-label" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'adhoc'">Token:</div>
1322 - <div class="scheduler-details-value" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'adhoc'" x-text="selectedTaskForDetail ? selectedTaskForDetail.token : ''"></div>
1428 + <div class="scheduler-details-label"
1429 + x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'scheduled'">
1430 + Schedule:</div>
1431 + <div class="scheduler-details-value"
1432 + x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'scheduled'"
1433 + x-text="selectedTaskForDetail ? formatSchedule(selectedTaskForDetail) : ''">
1434 + </div>
1435
1324 - <div class="scheduler-details-label" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'planned'">Plan:</div>
1325 - <div class="scheduler-details-value" x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'planned'">
1436 + <div class="scheduler-details-label"
1437 + x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'adhoc'">
1438 + Token:</div>
1439 + <div class="scheduler-details-value"
1440 + x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'adhoc'"
1441 + x-text="selectedTaskForDetail ? selectedTaskForDetail.token : ''"></div>
1442 +
1443 + <div class="scheduler-details-label"
1444 + x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'planned'">
1445 + Plan:</div>
1446 + <div class="scheduler-details-value"
1447 + x-show="selectedTaskForDetail && selectedTaskForDetail.type === 'planned'">
1448 <div x-show="selectedTaskForDetail && selectedTaskForDetail.plan">
1449 <div><strong>Upcoming:</strong></div>
1328 - <template x-if="selectedTaskForDetail && selectedTaskForDetail.plan && selectedTaskForDetail.plan.todo && selectedTaskForDetail.plan.todo.length > 0">
1450 + <template
1451 + x-if="selectedTaskForDetail && selectedTaskForDetail.plan && selectedTaskForDetail.plan.todo && selectedTaskForDetail.plan.todo.length > 0">
1452 <div>
1330 - <template x-for="(time, index) in selectedTaskForDetail.plan.todo" :key="index">
1453 + <template
1454 + x-for="(time, index) in selectedTaskForDetail.plan.todo"
1455 + :key="index">
1456 <div x-text="formatDate(time)"></div>
1457 </template>
1458 </div>
1459 </template>
1335 - <template x-if="!selectedTaskForDetail || !selectedTaskForDetail.plan || !selectedTaskForDetail.plan.todo || selectedTaskForDetail.plan.todo.length === 0">
1460 + <template
1461 + x-if="!selectedTaskForDetail || !selectedTaskForDetail.plan || !selectedTaskForDetail.plan.todo || selectedTaskForDetail.plan.todo.length === 0">
1462 <div>No upcoming executions</div>
1463 </template>
1464
1465 <div><strong>In Progress:</strong></div>
1340 - <div x-text="selectedTaskForDetail && selectedTaskForDetail.plan && selectedTaskForDetail.plan.in_progress ? formatDate(selectedTaskForDetail.plan.in_progress) : 'None'"></div>
1466 + <div
1467 + x-text="selectedTaskForDetail && selectedTaskForDetail.plan && selectedTaskForDetail.plan.in_progress ? formatDate(selectedTaskForDetail.plan.in_progress) : 'None'">
1468 + </div>
1469
1470 <div><strong>Completed:</strong></div>
1343 - <template x-if="selectedTaskForDetail && selectedTaskForDetail.plan && selectedTaskForDetail.plan.done && selectedTaskForDetail.plan.done.length > 0">
1471 + <template
1472 + x-if="selectedTaskForDetail && selectedTaskForDetail.plan && selectedTaskForDetail.plan.done && selectedTaskForDetail.plan.done.length > 0">
1473 <div>
1345 - <template x-for="(time, index) in selectedTaskForDetail.plan.done" :key="index">
1474 + <template
1475 + x-for="(time, index) in selectedTaskForDetail.plan.done"
1476 + :key="index">
1477 <div x-text="formatDate(time)"></div>
1478 </template>
1479 </div>
1480 </template>
1350 - <template x-if="!selectedTaskForDetail || !selectedTaskForDetail.plan || !selectedTaskForDetail.plan.done || selectedTaskForDetail.plan.done.length === 0">
1481 + <template
1482 + x-if="!selectedTaskForDetail || !selectedTaskForDetail.plan || !selectedTaskForDetail.plan.done || selectedTaskForDetail.plan.done.length === 0">
1483 <div>No completed executions</div>
1484 </template>
1485 </div>
1486 </div>
1487
1488 <div class="scheduler-details-label">Last Result:</div>
1357 - <div class="scheduler-details-value" x-text="selectedTaskForDetail && selectedTaskForDetail.last_result ? selectedTaskForDetail.last_result : 'No results yet'"></div>
1489 + <div class="scheduler-details-value"
1490 + x-text="selectedTaskForDetail && selectedTaskForDetail.last_result ? selectedTaskForDetail.last_result : 'No results yet'">
1491 + </div>
1492
1493 <div class="scheduler-details-label">System Prompt:</div>
1360 - <div class="scheduler-details-value" x-text="selectedTaskForDetail ? selectedTaskForDetail.system_prompt : ''"></div>
1494 + <div class="scheduler-details-value"
1495 + x-text="selectedTaskForDetail ? selectedTaskForDetail.system_prompt : ''">
1496 + </div>
1497
1498 <div class="scheduler-details-label">User Prompt:</div>
1363 - <div class="scheduler-details-value" x-text="selectedTaskForDetail ? selectedTaskForDetail.prompt : ''"></div>
1499 + <div class="scheduler-details-value"
1500 + x-text="selectedTaskForDetail ? selectedTaskForDetail.prompt : ''">
1501 + </div>
1502
1503 <div class="scheduler-details-label">Attachments:</div>
1504 <div class="scheduler-details-value">
1367 - <template x-if="selectedTaskForDetail && selectedTaskForDetail.attachments && selectedTaskForDetail.attachments.length > 0">
1505 + <template
1506 + x-if="selectedTaskForDetail && selectedTaskForDetail.attachments && selectedTaskForDetail.attachments.length > 0">
1507 <div>
1369 - <template x-for="(attachment, index) in selectedTaskForDetail.attachments" :key="index">
1508 + <template
1509 + x-for="(attachment, index) in selectedTaskForDetail.attachments"
1510 + :key="index">
1511 <div x-text="attachment"></div>
1512 </template>
1513 </div>
1514 </template>
1374 - <template x-if="!selectedTaskForDetail || !selectedTaskForDetail.attachments || selectedTaskForDetail.attachments.length === 0">
1515 + <template
1516 + x-if="!selectedTaskForDetail || !selectedTaskForDetail.attachments || selectedTaskForDetail.attachments.length === 0">
1517 <div>No attachments</div>
1518 </template>
1519 </div>
@@ -1551,14 +1693,18 @@
1693 <!-- Add toolbar -->
1694 <div class="editor-toolbar">
1695 <div class="toolbar-group">
1554 - <button class="toolbar-button" @click="undo()" :disabled="!canUndo" title="Undo (Ctrl+Z)">
1555 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1696 + <button class="toolbar-button" @click="undo()" :disabled="!canUndo"
1697 + title="Undo (Ctrl+Z)">
1698 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
1699 + stroke="currentColor" stroke-width="2">
1700 <path d="M3 7v6h6"></path>
1701 <path d="M21 17a9 9 0 00-9-9 9 9 0 00-6 2.3L3 13"></path>
1702 </svg>
1703 </button>
1560 - <button class="toolbar-button" @click="redo()" :disabled="!canRedo" title="Redo (Ctrl+Shift+Z)">
1561 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1704 + <button class="toolbar-button" @click="redo()" :disabled="!canRedo"
1705 + title="Redo (Ctrl+Shift+Z)">
1706 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
1707 + stroke="currentColor" stroke-width="2">
1708 <path d="M21 7v6h-6"></path>
1709 <path d="M3 17a9 9 0 019-9 9 9 0 016 2.3l3 2.7"></path>
1710 </svg>
@@ -1566,24 +1712,27 @@
1712 </div>
1713 <div class="toolbar-group">
1714 <button class="toolbar-button" @click="clearText()" title="Clear Text">
1569 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1570 - <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path>
1715 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
1716 + stroke="currentColor" stroke-width="2">
1717 + <path
1718 + d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2">
1719 + </path>
1720 <line x1="10" y1="11" x2="10" y2="17"></line>
1721 <line x1="14" y1="11" x2="14" y2="17"></line>
1722 </svg>
1723 </button>
1575 - <button class="toolbar-button" @click="toggleWrap()" :class="{ active: wordWrap }" title="Toggle Word Wrap">
1576 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1724 + <button class="toolbar-button" @click="toggleWrap()" :class="{ active: wordWrap }"
1725 + title="Toggle Word Wrap">
1726 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
1727 + stroke="currentColor" stroke-width="2">
1728 <path d="M3 6h18M3 12h15l3 3-3 3M3 18h18"></path>
1729 </svg>
1730 </button>
1731 </div>
1732 </div>
1733
1583 - <textarea id="full-screen-input" x-model="inputText"
1584 - placeholder="Type your message here..."
1585 - @keydown.ctrl.enter="handleClose()"
1586 - @keydown.ctrl.z.prevent="undo()"
1734 + <textarea id="full-screen-input" x-model="inputText" placeholder="Type your message here..."
1735 + @keydown.ctrl.enter="handleClose()" @keydown.ctrl.z.prevent="undo()"
1736 @keydown.ctrl.shift.z.prevent="redo()"
1737 :style="{ 'white-space': wordWrap ? 'pre-wrap' : 'pre' }"
1738 @input="updateHistory()"></textarea>
@@ -1603,17 +1752,17 @@
1752
1753 <!-- Register Service Worker for offline support and caching -->
1754 <script>
1606 - if ('serviceWorker' in navigator) {
1607 - window.addEventListener('load', () => {
1608 - navigator.serviceWorker.register('js/sw.js').then(registration => {
1609 - console.log('SW registered: ', registration);
1610 - }).catch(registrationError => {
1611 - console.log('SW registration failed: ', registrationError);
1612 - });
1613 - });
1614 - }
1755 + if ('serviceWorker' in navigator) {
1756 + window.addEventListener('load', () => {
1757 + navigator.serviceWorker.register('js/sw.js').then(registration => {
1758 + console.log('SW registered: ', registration);
1759 + }).catch(registrationError => {
1760 + console.log('SW registration failed: ', registrationError);
1761 + });
1762 + });
1763 + }
1764 </script>
1765
1766 </body>
1767
1619 -</html>
1768 +</html>
\ No newline at end of file