UI improvements for v0.8.1 (#299)

* ui: updated icons New icons for: - memory settings - browser agent model * ui: katex and copy button fix - Fix copy button appending "Copy" when copying user messages - Fix KaTeX math not displaying properly or at all * ui: drag and drop file attachments Drag and drop fullscreen modal for file attachments in the input message * ui: favicon, fullscreen input qol

Alessandro committed Jan 14, 2025 at 12:47 UTC 814b8e7dec99be17bdadf9d3390f5a6f0cd6a085
13 files changed +385 -23
prompts/default/agent.system.behaviour_default.md
+1 -1
@@ -1,2 +1,2 @@
1 - Favor linux commands for simple tasks where possible instead of python
2 -- Enclose any math with $$...$$
\ No newline at end of file
2 +- Enclose any math with $...$
\ No newline at end of file
prompts/reflection/agent.system.main.communication.md
+1 -1
@@ -4,7 +4,7 @@
4 respond valid json with fields
5 thoughts: decompose ➔ create decision trees ➔ forest of thoughts
6 reflection: question logical frameworks ➔ refine thoughts ➔ perform metareflection
7 -math requires katex $$...$$ delims
7 +math requires katex $...$ delims
8 tool_name: use tool name
9 tool_args: key value pairs tool arguments
10 no other text
python/helpers/settings.py
+20
@@ -437,6 +437,25 @@ def convert_out(settings: Settings) -> SettingsOutput:
437 "fields": browser_model_fields,
438 }
439
440 + # Memory settings section
441 + memory_fields: list[SettingsField] = []
442 + memory_fields.append(
443 + {
444 + "id": "memory_settings",
445 + "title": "Memory Settings",
446 + "description": "<settings for memory>",
447 + "type": "text",
448 + "value": "",
449 + }
450 + )
451 +
452 + memory_section: SettingsSection = {
453 + "id": "memory",
454 + "title": "Memory Settings",
455 + "description": "<settings for memory management here>",
456 + "fields": memory_fields,
457 + }
458 +
459 # basic auth section
460 auth_fields: list[SettingsField] = []
461
@@ -706,6 +725,7 @@ def convert_out(settings: Settings) -> SettingsOutput:
725 util_model_section,
726 embed_model_section,
727 browser_model_section,
728 + memory_section,
729 stt_section,
730 api_keys_section,
731 auth_section,
webui/css/modals.css
+75 -3
@@ -136,7 +136,7 @@
136
137 .full-screen-input-modal .modal-close {
138 position: absolute;
139 - top: 1rem;
139 + top: 1.2rem;
140 right: 1rem;
141 font-size: 1.5rem;
142 padding: 0 0.5rem;
@@ -161,7 +161,7 @@
161
162 #full-screen-input {
163 width: 100%;
164 - height: 100%;
164 + height: calc(100% - 50px);
165 border: none;
166 background-color: transparent;
167 color: var(--color-text);
@@ -375,4 +375,76 @@ h2 {
375 .btn-upload > svg {
376 width: 20px;
377 }
378 -}
\ No newline at end of file
378 +}
379 +
380 +/* Editor Toolbar */
381 +.editor-toolbar {
382 + display: flex;
383 + align-items: center;
384 + gap: 1rem;
385 + padding: 0.5rem 1rem;
386 + border-radius: 6px;
387 + background-color: rgba(30, 30, 30, 0.95);
388 + border-bottom: 1px solid var(--color-border);
389 +}
390 +
391 +.light-mode .editor-toolbar {
392 + background-color: rgba(240, 240, 240, 0.95);
393 +}
394 +
395 +.toolbar-group {
396 + display: flex;
397 + align-items: center;
398 + gap: 0.5rem;
399 + padding: 0 0.5rem;
400 + border-right: 1px solid var(--color-border);
401 +}
402 +
403 +.toolbar-group:last-child {
404 + border-right: none;
405 +}
406 +
407 +.toolbar-button {
408 + display: flex;
409 + align-items: center;
410 + justify-content: center;
411 + width: 32px;
412 + height: 32px;
413 + padding: 0.4rem;
414 + background: transparent;
415 + border: 1px solid transparent;
416 + border-radius: 4px;
417 + color: var(--color-text);
418 + opacity: 0.7;
419 + cursor: pointer;
420 + transition: all 0.2s ease;
421 +}
422 +
423 +.toolbar-button svg {
424 + width: 18px;
425 + height: 18px;
426 +}
427 +
428 +.toolbar-button:hover {
429 + opacity: 1;
430 + background-color: rgba(255, 255, 255, 0.1);
431 +}
432 +
433 +.toolbar-button:active {
434 + transform: translateY(1px);
435 +}
436 +
437 +.toolbar-button.active {
438 + background-color: rgba(255, 255, 255, 0.15);
439 + border-color: var(--color-border);
440 + opacity: 1;
441 +}
442 +
443 +.toolbar-button:disabled {
444 + opacity: 0.3;
445 + cursor: not-allowed;
446 +}
447 +
448 +.toolbar-button:disabled:hover {
449 + background-color: transparent;
450 +}
webui/index.css
+75 -6
@@ -771,11 +771,11 @@ pre {
771 font-family: 'Roboto Mono', monospace;
772 font-optical-sizing: auto;
773 -webkit-font-optical-sizing: auto;
774 - font-size: 0.875rem;
774 + font-size: 0.90rem;
775 max-height: 7rem;
776 - min-height: 3rem;
776 + min-height: 3.05rem;
777 width: 100%;
778 - padding: 0.5rem 40px var(--spacing-sm) var(--spacing-sm);
778 + padding: 0.48rem 40px var(--spacing-sm) var(--spacing-sm);
779 margin-right: var(--spacing-xs);
780 overflow-y: auto;
781 scroll-behavior: smooth;
@@ -842,7 +842,7 @@ pre {
842 #chat-input:focus {
843 outline: 0.05rem solid rgba(155, 155, 155, 0.5);
844 font-size: 0.955rem;
845 - padding-top: 0.5rem;
845 + padding-top: 0.45rem;
846 background-color: var(--color-input-focus);
847 }
848
@@ -1526,8 +1526,8 @@ input:checked + .slider:before {
1526
1527 /* Math (KaTeX) */
1528 .katex {
1529 - line-height: 1rem !important;
1530 - font-size: 3rem;
1529 + line-height: 1.2 !important;
1530 + font-size: 1.1em;
1531 }
1532
1533 /* Animations */
@@ -2003,4 +2003,73 @@ input:checked + .slider:before {
2003 100% {
2004 transform: scale(1);
2005 }
2006 +}
2007 +
2008 +.dragdrop-overlay {
2009 + position: fixed;
2010 + top: 0;
2011 + left: 0;
2012 + width: 100%;
2013 + height: 100%;
2014 + background: rgba(0, 0, 0, 0.85);
2015 + z-index: 9999;
2016 + display: flex;
2017 + justify-content: center;
2018 + align-items: center;
2019 + flex-direction: column;
2020 + gap: 1rem;
2021 +}
2022 +
2023 +.light-mode .dragdrop-overlay {
2024 + background: rgba(255, 255, 255, 0.92);
2025 +}
2026 +
2027 +/* Alpine transition classes */
2028 +.transition {
2029 + transition-property: all;
2030 + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2031 +}
2032 +
2033 +.duration-300 {
2034 + transition-duration: 300ms;
2035 +}
2036 +
2037 +.opacity-0 {
2038 + opacity: 0;
2039 +}
2040 +
2041 +.opacity-100 {
2042 + opacity: 1;
2043 +}
2044 +
2045 +/* Rest of dragdrop styles remain the same */
2046 +.dragdrop-icon {
2047 + width: 128px;
2048 + height: 128px;
2049 + opacity: 0.85;
2050 + filter: invert(1);
2051 +}
2052 +
2053 +.light-mode .dragdrop-icon {
2054 + filter: none;
2055 +}
2056 +
2057 +.dragdrop-text {
2058 + color: var(--color-text);
2059 + font-size: 1.2rem;
2060 + opacity: 0.85;
2061 + text-align: center;
2062 + max-width: 80%;
2063 + line-height: 1.5;
2064 +}
2065 +
2066 +.dragdrop-subtext {
2067 + color: var(--color-text);
2068 + font-size: 0.9rem;
2069 + opacity: 0.6;
2070 +}
2071 +
2072 +/* Alpine cloak to prevent FOUC */
2073 +[x-cloak] {
2074 + display: none !important;
2075 }
\ No newline at end of file
webui/index.html
+57 -1
@@ -5,6 +5,7 @@
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
7 <title>Agent Zero</title>
8 + <link rel="icon" type="image/svg+xml" href="public/darkSymbol.svg">
9 <link rel="stylesheet" href="index.css">
10 <link rel="stylesheet" href="css/toast.css">
11 <link rel="stylesheet" href="css/settings.css">
@@ -665,6 +666,7 @@
666 </div>
667 </div>
668 </div>
669 + </div>
670 </template>
671 </div>
672
@@ -700,9 +702,46 @@
702 <div class="modal-container full-screen-input-modal">
703 <div class="modal-content">
704 <button class="modal-close" @click="handleClose()">&times;</button>
705 +
706 + <!-- Add toolbar -->
707 + <div class="editor-toolbar">
708 + <div class="toolbar-group">
709 + <button class="toolbar-button" @click="undo()" :disabled="!canUndo" title="Undo (Ctrl+Z)">
710 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
711 + <path d="M3 7v6h6"></path>
712 + <path d="M21 17a9 9 0 00-9-9 9 9 0 00-6 2.3L3 13"></path>
713 + </svg>
714 + </button>
715 + <button class="toolbar-button" @click="redo()" :disabled="!canRedo" title="Redo (Ctrl+Shift+Z)">
716 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
717 + <path d="M21 7v6h-6"></path>
718 + <path d="M3 17a9 9 0 019-9 9 9 0 016 2.3l3 2.7"></path>
719 + </svg>
720 + </button>
721 + </div>
722 + <div class="toolbar-group">
723 + <button class="toolbar-button" @click="clearText()" title="Clear Text">
724 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
725 + <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"></path>
726 + <line x1="10" y1="11" x2="10" y2="17"></line>
727 + <line x1="14" y1="11" x2="14" y2="17"></line>
728 + </svg>
729 + </button>
730 + <button class="toolbar-button" @click="toggleWrap()" :class="{ active: wordWrap }" title="Toggle Word Wrap">
731 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
732 + <path d="M3 6h18M3 12h15l3 3-3 3M3 18h18"></path>
733 + </svg>
734 + </button>
735 + </div>
736 + </div>
737 +
738 <textarea id="full-screen-input" x-model="inputText"
739 placeholder="Type your message here..."
705 - @keydown.ctrl.enter="handleClose()"></textarea>
740 + @keydown.ctrl.enter="handleClose()"
741 + @keydown.ctrl.z.prevent="undo()"
742 + @keydown.ctrl.shift.z.prevent="redo()"
743 + :style="{ 'white-space': wordWrap ? 'pre-wrap' : 'pre' }"
744 + @input="updateHistory()"></textarea>
745 </div>
746 <div class="modal-footer">
747 <div id="buttons-container">
@@ -714,6 +753,23 @@
753 </template>
754 </div>
755
756 + <!-- Drag and Drop Overlay -->
757 + <div id="dragdrop-overlay"
758 + x-cloak
759 + x-data="{ isVisible: false }"
760 + x-show="isVisible"
761 + x-transition:enter="transition ease-out duration-300"
762 + x-transition:enter-start="opacity-0"
763 + x-transition:enter-end="opacity-100"
764 + x-transition:leave="transition ease-in duration-300"
765 + x-transition:leave-start="opacity-100"
766 + x-transition:leave-end="opacity-0"
767 + class="dragdrop-overlay">
768 + <img src="public/dragndrop.svg" alt="Drop files" class="dragdrop-icon">
769 + <div class="dragdrop-text">Drop files to attach them to your message</div>
770 + <div class="dragdrop-subtext">Supported formats: Images (JPG, PNG, BMP) and Documents (PDF, TXT, CSV, HTML, JSON, MD)</div>
771 + </div>
772 +
773 </body>
774
775 </html>
\ No newline at end of file
webui/index.js
+84 -1
@@ -869,4 +869,87 @@ async function startPolling() {
869 _doPoll();
870 }
871
872 -document.addEventListener("DOMContentLoaded", startPolling);
\ No newline at end of file
872 +document.addEventListener("DOMContentLoaded", startPolling);
873 +
874 +document.addEventListener('DOMContentLoaded', () => {
875 + const dragDropOverlay = document.getElementById('dragdrop-overlay');
876 + const inputSection = document.getElementById('input-section');
877 + let dragCounter = 0;
878 +
879 + // Prevent default drag behaviors
880 + ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
881 + document.addEventListener(eventName, (e) => {
882 + e.preventDefault();
883 + e.stopPropagation();
884 + }, false);
885 + });
886 +
887 + // Handle drag enter
888 + document.addEventListener('dragenter', (e) => {
889 + dragCounter++;
890 + if (dragCounter === 1) {
891 + Alpine.$data(dragDropOverlay).isVisible = true;
892 + }
893 + }, false);
894 +
895 + // Handle drag leave
896 + document.addEventListener('dragleave', (e) => {
897 + dragCounter--;
898 + if (dragCounter === 0) {
899 + Alpine.$data(dragDropOverlay).isVisible = false;
900 + }
901 + }, false);
902 +
903 + // Handle drop
904 + dragDropOverlay.addEventListener('drop', (e) => {
905 + dragCounter = 0;
906 + Alpine.$data(dragDropOverlay).isVisible = false;
907 +
908 + const inputAD = Alpine.$data(inputSection);
909 + const files = e.dataTransfer.files;
910 + handleFiles(files, inputAD);
911 + }, false);
912 +});
913 +
914 +// Separate file handling logic to be used by both drag-drop and file input
915 +function handleFiles(files, inputAD) {
916 + Array.from(files).forEach(file => {
917 + const ext = file.name.split('.').pop().toLowerCase();
918 + const allowedExts = new Set(['jpg', 'jpeg', 'png', 'bmp', 'md', 'py', 'js', 'sh',
919 + 'html', 'css', 'pdf', 'txt', 'csv', 'json']);
920 +
921 + if (allowedExts.has(ext)) {
922 + const isImage = ['jpg', 'jpeg', 'png', 'bmp'].includes(ext);
923 +
924 + if (isImage) {
925 + const reader = new FileReader();
926 + reader.onload = e => {
927 + inputAD.attachments.push({
928 + file: file,
929 + url: e.target.result,
930 + type: 'image',
931 + name: file.name,
932 + extension: ext
933 + });
934 + inputAD.hasAttachments = true;
935 + };
936 + reader.readAsDataURL(file);
937 + } else {
938 + inputAD.attachments.push({
939 + file: file,
940 + type: 'file',
941 + name: file.name,
942 + extension: ext
943 + });
944 + inputAD.hasAttachments = true;
945 + }
946 + }
947 + });
948 +}
949 +
950 +// Modify the existing handleFileUpload to use the new handleFiles function
951 +window.handleFileUpload = function(event) {
952 + const files = event.target.files;
953 + const inputAD = Alpine.$data(inputSection);
954 + handleFiles(files, inputAD);
955 +}
\ No newline at end of file
webui/js/messages.js
+8 -10
@@ -14,7 +14,7 @@ function createCopyButton() {
14 if (container.classList.contains("kvps-row")) {
15 textToCopy = container.querySelector(".kvps-val").textContent;
16 } else if (container.classList.contains("message-text")) {
17 - textToCopy = container.textContent.replace("copy", "");
17 + textToCopy = container.querySelector("span").textContent;
18 } else {
19 textToCopy = container.querySelector("span").textContent;
20 }
@@ -118,10 +118,7 @@ export function _drawMessage(
118 if (window.renderMathInElement && latex) {
119 renderMathInElement(spanElement, {
120 delimiters: [
121 - { left: "$", right: "$", display: true },
122 - { left: "\\$", right: "\\$", display: true },
123 - { left: "$", right: "$", display: false },
124 - { left: "\\$", right: "\\$", display: false },
121 + { left: "$", right: "$", display: true }
122 ],
123 throwOnError: false,
124 });
@@ -254,7 +251,11 @@ export function drawMessageUser(
251 if (content && content.trim().length > 0) {
252 const textDiv = document.createElement("div");
253 textDiv.classList.add("message-text");
257 - textDiv.textContent = content;
254 +
255 + // Create a span for the content
256 + const spanElement = document.createElement("span");
257 + spanElement.textContent = content;
258 + textDiv.appendChild(spanElement);
259
260 // Add click handler
261 textDiv.addEventListener("click", () => {
@@ -548,10 +549,7 @@ function drawKvps(container, kvps, latex) {
549 if (window.renderMathInElement && latex) {
550 renderMathInElement(span, {
551 delimiters: [
551 - { left: "$$", right: "$$", display: true },
552 - { left: "$$", right: "$$", display: true },
553 - { left: "$", right: "$", display: false },
554 - { left: "$$", right: "$$", display: false },
552 + { left: "$", right: "$", display: true }
553 ],
554 throwOnError: false,
555 });
webui/js/modal.js
+56
@@ -1,11 +1,19 @@
1 const fullScreenInputModalProxy = {
2 isOpen: false,
3 inputText: '',
4 + wordWrap: true,
5 + undoStack: [],
6 + redoStack: [],
7 + maxStackSize: 100,
8 + lastSavedState: '',
9
10 openModal() {
11 const chatInput = document.getElementById('chat-input');
12 this.inputText = chatInput.value;
13 + this.lastSavedState = this.inputText;
14 this.isOpen = true;
15 + this.undoStack = [];
16 + this.redoStack = [];
17
18 // Focus the full screen input after a short delay to ensure the modal is rendered
19 setTimeout(() => {
@@ -19,6 +27,54 @@ const fullScreenInputModalProxy = {
27 chatInput.value = this.inputText;
28 chatInput.dispatchEvent(new Event('input')); // Trigger input event for textarea auto-resize
29 this.isOpen = false;
30 + },
31 +
32 + updateHistory() {
33 + // Don't save if the text hasn't changed
34 + if (this.lastSavedState === this.inputText) return;
35 +
36 + this.undoStack.push(this.lastSavedState);
37 + if (this.undoStack.length > this.maxStackSize) {
38 + this.undoStack.shift();
39 + }
40 + this.redoStack = [];
41 + this.lastSavedState = this.inputText;
42 + },
43 +
44 + undo() {
45 + if (!this.canUndo) return;
46 +
47 + this.redoStack.push(this.inputText);
48 + this.inputText = this.undoStack.pop();
49 + this.lastSavedState = this.inputText;
50 + },
51 +
52 + redo() {
53 + if (!this.canRedo) return;
54 +
55 + this.undoStack.push(this.inputText);
56 + this.inputText = this.redoStack.pop();
57 + this.lastSavedState = this.inputText;
58 + },
59 +
60 + clearText() {
61 + if (this.inputText) {
62 + this.updateHistory(); // Save current state before clearing
63 + this.inputText = '';
64 + this.lastSavedState = '';
65 + }
66 + },
67 +
68 + toggleWrap() {
69 + this.wordWrap = !this.wordWrap;
70 + },
71 +
72 + get canUndo() {
73 + return this.undoStack.length > 0;
74 + },
75 +
76 + get canRedo() {
77 + return this.redoStack.length > 0;
78 }
79 };
80
webui/public/browser_model.svg new
+1
@@ -0,0 +1 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="Layer_1" x="0" y="0" version="1.1" viewBox="0 0 22 18"><style>.st0{fill:none;stroke:#000;stroke-width:.5;stroke-miterlimit:10}</style><path d="M9.7 2.2C9.2 2.1 8.6 2 8 2 3.8 2 .3 5.5.3 9.8s3.5 7.7 7.7 7.7c3.4 0 6.2-2.2 7.3-5.2" class="st0"/><path d="M2.2 4.7C3.6 6 5.7 6.9 8 6.9m-5.8 7.9c1.4-1.3 3.5-2.1 5.8-2.1s4.4.8 5.8 2.1" class="st0"/><path d="M11.8 12.3c-.6 3-2.1 5.2-3.8 5.2-2.2 0-4-3.5-4-7.7C4 5.5 5.8 2 8 2m0 0v15.5m0-7.7H.3" class="st0"/><path d="M20.2 10.5c-.4.4-.9.6-1.4.6h-6.7c-.6 0-1.1-.2-1.4-.6-.3-.3-.5-.8-.5-1.3V4.9c0-1 .8-1.9 1.9-1.9h6.7c1.1 0 1.9.9 1.9 1.9v4.3c.1.5-.1 1-.5 1.3z" class="st0"/><path d="M12.6 4.1h5.7c.8 0 1.4.6 1.4 1.4v3.1c0 .8-.6 1.4-1.4 1.4h-5.7c-.8 0-1.4-.6-1.4-1.4V5.5c0-.8.6-1.4 1.4-1.4zm8.2 4.5h.4c.2 0 .4-.2.4-.5V6c0-.3-.2-.5-.4-.5h-.4m-.7-2.1V1.6m.5-.5c0 .3-.2.5-.5.5s-.5-.2-.5-.5.2-.5.5-.5.5.2.5.5zm-9.8 2.3V1.6" class="st0"/><circle cx="10.8" cy="1.1" r=".5" class="st0"/><path d="M10.2 5.6h-.5c-.2 0-.5.2-.5.4v2.1c0 .3.2.5.5.5h.5" class="st0"/></svg>
\ No newline at end of file
webui/public/darkSymbol.svg new
+5
@@ -0,0 +1,5 @@
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
3 + <path d="M23.93,26.28c-2.63-4.51-5.26-9.04-7.95-13.67c-2.66,4.61-5.29,9.16-7.91,13.7h-4.07 c4-6.89,12-20.56,12-20.56h0s8.02,13.67,12.02,20.58h-4.07Z" fill="currentColor"/>
4 + <path d="M21.1,26.3H10.78c0.69-1.19,1.35-2.35,2.01-3.5h6.34c0.64,1.14,1.28,2.28,1.97,3.5Z" fill="currentColor"/>
5 +</svg>
\ No newline at end of file
webui/public/dragndrop.svg new
+1
@@ -0,0 +1 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="Layer_1" x="0" y="0" version="1.1" viewBox="0 0 128 128"><style>.st0{fill:none;stroke:#151515;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10}</style><path d="M64.8 88H18.4c-.8 0-1.5-.7-1.5-1.5v-49c0-.8.7-1.5 1.5-1.5h49.1c.8 0 1.5.7 1.5 1.5v42.2" class="st0"/><path d="M55 35.7V13c0-.8.7-1.5 1.5-1.5h49.1c.8 0 1.5.7 1.5 1.5v49.1c0 .8-.7 1.5-1.5 1.5H69.1M81 30.4v14.2m-7.1-7.1h14.2m-4.2 79.6c-.3-.5-.8-.9-1.4-1.2l-22.1-9.6c-2.1-.9-3-3.3-2.2-5.4.9-2.1 3.3-3.1 5.5-2.2l10.5 4.6-14.9-24.1c-1.2-1.9-.6-4.3 1.3-5.5s4.3-.6 5.5 1.3l9.8 15.8-1.8-3c-.8-1.3-.4-2.9.9-3.7l.6-.4c1.3-.8 2.9-.4 3.7.9l1.7 2.8-.3-.5c-.8-1.3-.4-2.9.9-3.7s2.9-.4 3.7.9l1.5 2.4c-1-1.6-.5-3.6 1.1-4.6h.1c1.6-1 3.6-.5 4.6 1.1l5.1 8.3c1.1 1.8 1.8 3.9 2 6.1l.5 7.8c0 .5.2 1 .5 1.5h0c.9 1.5.5 3.5-1.1 4.5l-11.4 7c-1.5.8-3.4.3-4.3-1.1" class="st0"/></svg>
\ No newline at end of file
webui/public/memory.svg new
+1
@@ -0,0 +1 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="Layer_1" x="0" y="0" version="1.1" viewBox="0 0 22 16"><style>.st1{fill:none;stroke:#000;stroke-width:.5;stroke-miterlimit:10;fill-rule:evenodd;clip-rule:evenodd}</style><path d="M14.1 13.8c-3.2 0-5.8-2.6-5.8-5.8s2.6-5.8 5.8-5.8 5.8 2.6 5.8 5.8-2.6 5.8-5.8 5.8zM11.5.2v2m2.6-2v2m2.6-2v2m-5.2 11.6v2m2.6-2v2m2.6-2v2m5.2-10.4h-2m2 2.6h-2m2 2.6h-2" style="fill:none;stroke:#000;stroke-width:.5;stroke-miterlimit:10"/><path d="M15.1 5.4c.1 0 .2.1.3.2s.2 0 .3 0l.3-.3s.2-.2.5 0l.5.5s.2.2.1.4c0 0 0 .1-.1.2l-.2.2c-.1.1-.1.2-.1.3s.1.2.1.4c0 .1.1.2.2.2h.4s.3 0 .3.3v.7s.1.4-.3.5h-.5c-.1 0-.2.1-.2.2s-.1.2-.2.3 0 .2 0 .3l.2.2s.2.2 0 .5l-.1.1-.4.4s-.3.2-.5 0l-.3-.3c-.1-.1-.2-.1-.3-.1s-.2.1-.4.1c-.1 0-.2.1-.2.2v.4s0 .3-.4.3h-.7s-.3 0-.3-.3v-.4c0-.1-.1-.2-.2-.2s-.2-.1-.3-.2-.2 0-.3 0l-.3.2s-.3.2-.5-.1l-.5-.5s-.3-.3.1-.6l.2-.2c.1-.1.1-.2.1-.3s-.1-.2-.1-.4c0-.1-.1-.2-.2-.2h-.3s-.4 0-.4-.4v-.8s0-.4.4-.3h.3c.1 0 .2-.1.3-.2 0-.1.1-.2.2-.3s0-.2 0-.3l-.3-.1s-.3-.3 0-.5l.6-.5s.3-.2.5 0l.3.3c.1.1.2.1.3.1s.2-.1.4-.1c.1 0 .2-.1.2-.2v-.3s0-.4.5-.4h.7s.3 0 .3.5v.3c-.2 0-.1.1 0 .2z" class="st1"/><circle cx="14.1" cy="8" r="1.6" class="st1"/><circle cx="1.6" cy="8" r="1.2" class="st1"/><path d="M2.8 8H7" class="st1"/><circle cx="3.5" cy="3.3" r="1.2" class="st1"/><path d="m3.9 4.4.4 1H7" class="st1"/><circle cx="3.5" cy="12.7" r="1.2" class="st1"/><path d="M7 10.6H4.3l-.4 1" class="st1"/></svg>
\ No newline at end of file