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()">×</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