ui: plus menu for additional chat actions
Replace the paperclip and the separate bottom action row with a single + control that opens an upward panel next to the chat input box. Menu content stays in bottom-actions.html so chat-input-bottom-actions-start / end extension points are unchanged for plugins using these slots. Added Files to the left sidebar dropdown for easy nav. polish plus menu for extra chat actions
Alessandro committed
Mar 25, 2026 at 22:12 UTC
8333f5b2762e2f35ccbeff36e67eb762fd3169bf
7 files changed
+300
-99
tests/test_webui_extension_surfaces.py
+2
-2
@@ -38,8 +38,8 @@ SURFACE_SCENARIOS: list[tuple[str, str]] = [
38
("chat-input-progress-end", "webui/components/chat/input/progress.html"),
39
("chat-input-box-start", "webui/components/chat/input/chat-bar-input.html"),
40
("chat-input-box-end", "webui/components/chat/input/chat-bar-input.html"),
41
- ("chat-input-bottom-actions-start", "webui/components/chat/input/bottom-actions.html"),
42
- ("chat-input-bottom-actions-end", "webui/components/chat/input/bottom-actions.html"),
41
+ ("chat-input-bottom-actions-start", "webui/components/chat/input/bottom-actions-bar.html"),
42
+ ("chat-input-bottom-actions-end", "webui/components/chat/input/bottom-actions-bar.html"),
43
("chat-top-start", "webui/components/chat/top-section/chat-top.html"),
44
("chat-top-end", "webui/components/chat/top-section/chat-top.html"),
45
("welcome-screen-start", "webui/components/welcome/welcome-screen.html"),
webui/components/chat/input/bottom-actions-bar.html
new
+98
@@ -0,0 +1,98 @@
1
+<html>
2
+<head>
3
+ <script type="module">
4
+ import { store } from "/components/chat/input/input-store.js";
5
+ </script>
6
+</head>
7
+<body>
8
+ <template x-if="$store.chatInput">
9
+ <div class="text-buttons-row chat-bottom-actions-bar">
10
+ <x-extension id="chat-input-bottom-actions-start"></x-extension>
11
+ <button type="button" class="text-button" @click="$store.chatInput.pauseAgent(!$store.chatInput.paused)">
12
+ <template x-if="!$store.chatInput.paused">
13
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="14" height="14" aria-hidden="true">
14
+ <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path>
15
+ </svg>
16
+ </template>
17
+ <template x-if="$store.chatInput.paused">
18
+ <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" width="14" height="14" aria-hidden="true">
19
+ <path d="M8 5v14l11-7z"></path>
20
+ </svg>
21
+ </template>
22
+ <span x-text="$store.chatInput.paused ? 'Resume Agent' : 'Pause Agent'"></span>
23
+ </button>
24
+
25
+ <button type="button" class="text-button" id="nudges_window" @click="$store.chatInput.nudge()">
26
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49 58" fill="currentColor" width="14" height="14" aria-hidden="true">
27
+ <path d="m11.97,16.32c-.46,0-.91-.25-1.15-.68-.9-1.63-1.36-3.34-1.36-5.1C9.45,4.73,14.18,0,20,0s10.55,4.73,10.55,10.55c0,.87-.13,1.75-.41,2.76-.19.7-.9,1.13-1.62.93-.7-.19-1.12-.92-.93-1.62.21-.79.31-1.44.31-2.07,0-4.36-3.55-7.91-7.91-7.91s-7.91,3.55-7.91,7.91c0,1.3.35,2.59,1.03,3.82.36.64.13,1.44-.51,1.79-.21.11-.42.17-.64.17Z" stroke-width="0.5" stroke="currentColor"/>
28
+ <path d="M34.5,58h-6.18c-3.17,0-6.15-1.23-8.39-3.47L1.16,35.75c-1.54-1.54-1.54-4.05,0-5.59,2.4-2.4,6.27-2.68,8.99-.64l4.58,3.44V10.55c0-2.91,2.36-5.27,5.27-5.27s5.27,2.36,5.27,5.27v8.62c.78-.45,1.68-.71,2.64-.71,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.91,0,5.27,2.36,5.27,5.27v14.5c0,8-6.51,14.5-14.5,14.5ZM6.03,30.79c-1.1,0-2.19.42-3.01,1.23-.51.51-.51,1.35,0,1.86l18.77,18.78c1.74,1.74,4.06,2.7,6.53,2.7h6.18c6.54,0,11.86-5.32,11.86-11.86v-14.5c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v1.32c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-3.95c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v3.95c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-6.59c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v6.59c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32V10.55c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v25.05c0,.5-.28.95-.73,1.18s-.98.18-1.38-.12l-6.69-5.02c-.75-.56-1.65-.84-2.54-.84Z" stroke-width="0.5" stroke="currentColor"/>
29
+ </svg>
30
+ <p>Nudge</p>
31
+ </button>
32
+ <x-extension id="chat-input-bottom-actions-end"></x-extension>
33
+ </div>
34
+ </template>
35
+
36
+ <style>
37
+ .text-buttons-row {
38
+ width: 100%;
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ gap: var(--spacing-xs);
42
+ padding-top: var(--spacing-xs);
43
+ margin-left: var(--spacing-xs);
44
+ }
45
+ .text-buttons-row > x-extension:not(:empty) { display: contents; }
46
+ .text-buttons-row > x-extension:empty { display: none; }
47
+ .text-buttons-row > x-extension > x-component,
48
+ .text-buttons-row > x-extension > x-component > div[x-data] { display: contents; }
49
+ .text-button {
50
+ background-color: transparent;
51
+ border: none;
52
+ border-radius: 5px;
53
+ color: var(--color-text);
54
+ font-family: "Rubik", Arial, Helvetica, sans-serif;
55
+ font-size: 0.6rem;
56
+ padding: 6px var(--spacing-sm);
57
+ cursor: pointer;
58
+ opacity: 0.8;
59
+ transition: all 0.3s;
60
+ display: flex;
61
+ align-items: center;
62
+ gap: var(--spacing-xs);
63
+ }
64
+ .text-button:hover:not(:disabled) {
65
+ opacity: 1;
66
+ background-color: var(--color-background-hover);
67
+ border-radius: 4px;
68
+ }
69
+ .text-button:active:not(:disabled) { opacity: 0.5; }
70
+ .text-button:disabled {
71
+ opacity: 0.4;
72
+ cursor: not-allowed;
73
+ }
74
+ .text-button svg {
75
+ width: 14px;
76
+ height: 14px;
77
+ flex-shrink: 0;
78
+ }
79
+ .text-button p { margin-block: 0; }
80
+
81
+ @media (max-width: 768px) {
82
+ .text-buttons-row {
83
+ width: 90%;
84
+ white-space: pre-wrap;
85
+ }
86
+ .text-button svg { width: 18px; height: 18px; }
87
+ }
88
+ @media (max-width: 640px) {
89
+ .text-buttons-row {
90
+ width: 90%;
91
+ white-space: pre-wrap;
92
+ }
93
+ .text-button { max-height: 25px; }
94
+ .text-button p { display: none; }
95
+ }
96
+ </style>
97
+</body>
98
+</html>
webui/components/chat/input/bottom-actions.html
+115
-83
@@ -2,100 +2,132 @@
2
<head>
3
<script type="module">
4
import { store } from "/components/chat/input/input-store.js";
5
+ import { store as attachmentsStore } from "/components/chat/attachments/attachmentsStore.js";
6
import { store as historyStore } from "/components/modals/history/history-store.js";
7
import { store as contextStore } from "/components/modals/context/context-store.js";
8
import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
9
</script>
10
</head>
11
<body>
11
- <div x-data>
12
- <template x-if="$store.chatInput">
13
- <div class="text-buttons-row">
14
- <x-extension id="chat-input-bottom-actions-start"></x-extension>
15
- <button class="text-button" @click="$store.chatInput.pauseAgent(!$store.chatInput.paused)">
16
- <template x-if="!$store.chatInput.paused">
17
- <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="14" height="14">
18
- <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path>
19
- </svg>
20
- </template>
21
- <template x-if="$store.chatInput.paused">
22
- <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.8" stroke="currentColor" width="14" height="14">
23
- <path d="M8 5v14l11-7z"></path>
24
- </svg>
25
- </template>
26
- <span x-text="$store.chatInput.paused ? 'Resume Agent' : 'Pause Agent'"></span>
27
- </button>
12
+ <template x-if="$store.chatInput">
13
+ <div class="chat-bottom-actions-menu">
14
+ <label class="chat-bottom-menu-item" @click.stop>
15
+ <input
16
+ type="file"
17
+ id="chat-file-input"
18
+ class="chat-bottom-menu-file-input"
19
+ accept="*"
20
+ multiple
21
+ @change="$store.chatAttachments.handleFileUpload($event); $store.chatInput.closeChatMoreMenu()"
22
+ >
23
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
24
+ <path 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" />
25
+ </svg>
26
+ <span>Add attachments</span>
27
+ </label>
28
29
- <button class="text-button" @click="$confirmClick($event, () => $store.chats.resetChat())" :disabled="!$store.chats.selected">
30
- <span class="material-symbols-outlined">delete_sweep</span>
31
- <p>Clear Chat</p>
32
- </button>
29
+ <button
30
+ type="button"
31
+ class="chat-bottom-menu-item"
32
+ @click="$confirmClick($event, () => { $store.chats.resetChat(); $store.chatInput.closeChatMoreMenu() })"
33
+ :disabled="!$store.chats.selected"
34
+ >
35
+ <span class="material-symbols-outlined" aria-hidden="true">delete_sweep</span>
36
+ <span>Clear Chat</span>
37
+ </button>
38
34
- <button class="text-button" @click="$store.chats.saveChat()" :disabled="!$store.chats.selected">
35
- <span class="material-symbols-outlined">save</span>
36
- <p>Save Chat</p>
37
- </button>
39
+ <button
40
+ type="button"
41
+ class="chat-bottom-menu-item"
42
+ id="history_inspect"
43
+ @click="$store.history.open(); $store.chatInput.closeChatMoreMenu()"
44
+ >
45
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="5 10 85 85" width="18" height="18" aria-hidden="true">
46
+ <path fill="currentColor" d="m59.572,57.949c-.41,0-.826-.105-1.207-.325l-9.574-5.528c-.749-.432-1.21-1.231-1.21-2.095v-14.923c0-1.336,1.083-2.419,2.419-2.419s2.419,1.083,2.419,2.419v13.526l8.364,4.829c1.157.668,1.554,2.148.886,3.305-.448.776-1.261,1.21-2.097,1.21Zm30.427-7.947c0,10.684-4.161,20.728-11.716,28.283-6.593,6.59-15.325,10.69-24.59,11.544-1.223.113-2.448.169-3.669.169-7.492,0-14.878-2.102-21.22-6.068l-15.356,5.733c-.888.331-1.887.114-2.557-.556s-.887-1.669-.556-2.557l5.733-15.351c-4.613-7.377-6.704-16.165-5.899-24.891.854-9.266,4.954-17.998,11.544-24.588,7.555-7.555,17.6-11.716,28.285-11.716s20.73,4.161,28.285,11.716c7.555,7.555,11.716,17.599,11.716,28.283Zm-15.137-24.861c-13.71-13.71-36.018-13.71-49.728,0-11.846,11.846-13.682,30.526-4.365,44.417.434.647.53,1.464.257,2.194l-4.303,11.523,11.528-4.304c.274-.102.561-.153.846-.153.474,0,.944.139,1.348.41,13.888,9.315,32.568,7.479,44.417-4.365,13.707-13.708,13.706-36.014,0-49.723Zm-24.861-4.13c-15.989,0-28.996,13.006-28.996,28.992s13.008,28.992,28.996,28.992c1.336,0,2.419-1.083,2.419-2.419s-1.083-2.419-2.419-2.419c-13.32,0-24.157-10.835-24.157-24.153s10.837-24.153,24.157-24.153,24.153,10.835,24.153,24.153c0,1.336,1.083,2.419,2.419,2.419c0-15.986-13.006-28.992-28.992-28.992Zm25.041,33.531c-1.294.347-2.057,1.673-1.71,2.963.343,1.289,1.669,2.057,2.963,1.71,1.289-.343,2.053-1.669,1.71-2.963-.347-1.289-1.673-2.057-2.963-1.71Zm-2.03,6.328c-1.335,0-2.419,1.084-2.419,2.419s1.084,2.419,2.419,2.419,2.419-1.084,2.419-2.419-1.084-2.419-2.419-2.419Zm-3.598,5.587c-1.289-.347-2.615.416-2.963,1.71-.343,1.289.421,2.615,1.71,2.963,1.294.347,2.62-.421,2.963-1.71.347-1.294-.416-2.62-1.71-2.963Zm-4.919,4.462c-1.157-.667-2.638-.27-3.306.887-.667,1.157-.27,2.638.887,3.305,1.157.668,2.638.27,3.306-.887.667-1.157.27-2.638-.887-3.306Zm-9.327,3.04c-.946.946-.946,2.478,0,3.42.942.946,2.473.946,3.42,0,.946-.942.946-2.473,0-3.42-.946-.942-2.478-.946-3.42,0Z"></path>
47
+ </svg>
48
+ <span>History</span>
49
+ </button>
50
39
- <button class="text-button" @click="$store.chatInput.loadKnowledge()">
40
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
41
- <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"></path>
42
- </svg>
43
- <p>Import knowledge</p>
44
- </button>
45
-
46
- <button class="text-button" id="work_dir_browser" @click="$store.chatInput.browseFiles()">
47
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 123.37 92.59">
48
- <path d="m5.72,11.5l-3.93,8.73h119.77s-3.96-8.73-3.96-8.73h-60.03c-1.59,0-2.88-1.29-2.88-2.88V1.75H13.72v6.87c0,1.59-1.29,2.88-2.88,2.88h-5.12Z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="7"></path>
49
- <path d="m6.38,20.23H1.75l7.03,67.03c.11,1.07.55,2.02,1.2,2.69.55.55,1.28.89,2.11.89h97.1c.82,0,1.51-.33,2.05-.87.68-.68,1.13-1.67,1.28-2.79l9.1-66.94H6.38Z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="8"></path>
50
- </svg>
51
- <p>Files</p>
52
- </button>
53
-
54
- <button class="text-button" id="history_inspect" @click="$store.history.open()">
55
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="5 10 85 85">
56
- <path fill="currentColor" d="m59.572,57.949c-.41,0-.826-.105-1.207-.325l-9.574-5.528c-.749-.432-1.21-1.231-1.21-2.095v-14.923c0-1.336,1.083-2.419,2.419-2.419s2.419,1.083,2.419,2.419v13.526l8.364,4.829c1.157.668,1.554,2.148.886,3.305-.448.776-1.261,1.21-2.097,1.21Zm30.427-7.947c0,10.684-4.161,20.728-11.716,28.283-6.593,6.59-15.325,10.69-24.59,11.544-1.223.113-2.448.169-3.669.169-7.492,0-14.878-2.102-21.22-6.068l-15.356,5.733c-.888.331-1.887.114-2.557-.556s-.887-1.669-.556-2.557l5.733-15.351c-4.613-7.377-6.704-16.165-5.899-24.891.854-9.266,4.954-17.998,11.544-24.588,7.555-7.555,17.6-11.716,28.285-11.716s20.73,4.161,28.285,11.716c7.555,7.555,11.716,17.599,11.716,28.283Zm-15.137-24.861c-13.71-13.71-36.018-13.71-49.728,0-11.846,11.846-13.682,30.526-4.365,44.417.434.647.53,1.464.257,2.194l-4.303,11.523,11.528-4.304c.274-.102.561-.153.846-.153.474,0,.944.139,1.348.41,13.888,9.315,32.568,7.479,44.417-4.365,13.707-13.708,13.706-36.014,0-49.723Zm-24.861-4.13c-15.989,0-28.996,13.006-28.996,28.992s13.008,28.992,28.996,28.992c1.336,0,2.419-1.083,2.419-2.419s-1.083-2.419-2.419-2.419c-13.32,0-24.157-10.835-24.157-24.153s10.837-24.153,24.157-24.153,24.153,10.835,24.153,24.153c0,1.336,1.083,2.419,2.419,2.419c0-15.986-13.006-28.992-28.992-28.992Zm25.041,33.531c-1.294.347-2.057,1.673-1.71,2.963.343,1.289,1.669,2.057,2.963,1.71,1.289-.343,2.053-1.669,1.71-2.963-.347-1.289-1.673-2.057-2.963-1.71Zm-2.03,6.328c-1.335,0-2.419,1.084-2.419,2.419s1.084,2.419,2.419,2.419,2.419-1.084,2.419-2.419-1.084-2.419-2.419-2.419Zm-3.598,5.587c-1.289-.347-2.615.416-2.963,1.71-.343,1.289.421,2.615,1.71,2.963,1.294.347,2.62-.421,2.963-1.71.347-1.294-.416-2.62-1.71-2.963Zm-4.919,4.462c-1.157-.667-2.638-.27-3.306.887-.667,1.157-.27,2.638.887,3.305,1.157.668,2.638.27,3.306-.887.667-1.157.27-2.638-.887-3.306Zm-9.327,3.04c-.946.946-.946,2.478,0,3.42.942.946,2.473.946,3.42,0,.946-.942.946-2.473,0-3.42-.946-.942-2.478-.946-3.42,0Z"></path>
57
- </svg>
58
- <p>History</p>
59
- </button>
60
-
61
- <button class="text-button" id="ctx_window" @click="$store.context.open()">
62
- <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="17 15 70 70" fill="currentColor">
63
- <path d="m63 25c1.1016 0 2-0.89844 2-2s-0.89844-2-2-2h-26c-1.1016 0-2 0.89844-2 2s0.89844 2 2 2z"></path>
64
- <path d="m63 79c1.1016 0 2-0.89844 2-2s-0.89844-2-2-2h-26c-1.1016 0-2 0.89844-2 2s0.89844 2 2 2z"></path>
65
- <path d="m68 39h-36c-6.0703 0-11 4.9297-11 11s4.9297 11 11 11h36c6.0703 0 11-4.9297 11-11s-4.9297-11-11-11zm0 18h-36c-3.8594 0-7-3.1406-7-7s3.1406-7 7-7h36c3.8594 0 7 3.1406 7 7s-3.1406 7-7 7z"></path>
66
- </svg>
67
- <p>Context</p>
68
- </button>
69
-
70
- <button class="text-button" id="nudges_window" @click="$store.chatInput.nudge()">
71
- <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49 58" fill="currentColor">
72
- <path d="m11.97,16.32c-.46,0-.91-.25-1.15-.68-.9-1.63-1.36-3.34-1.36-5.1C9.45,4.73,14.18,0,20,0s10.55,4.73,10.55,10.55c0,.87-.13,1.75-.41,2.76-.19.7-.9,1.13-1.62.93-.7-.19-1.12-.92-.93-1.62.21-.79.31-1.44.31-2.07,0-4.36-3.55-7.91-7.91-7.91s-7.91,3.55-7.91,7.91c0,1.3.35,2.59,1.03,3.82.36.64.13,1.44-.51,1.79-.21.11-.42.17-.64.17Z" stroke-width="0.5" stroke="currentColor"/>
73
- <path d="M34.5,58h-6.18c-3.17,0-6.15-1.23-8.39-3.47L1.16,35.75c-1.54-1.54-1.54-4.05,0-5.59,2.4-2.4,6.27-2.68,8.99-.64l4.58,3.44V10.55c0-2.91,2.36-5.27,5.27-5.27s5.27,2.36,5.27,5.27v8.62c.78-.45,1.68-.71,2.64-.71,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.91,0,5.27,2.36,5.27,5.27v14.5c0,8-6.51,14.5-14.5,14.5ZM6.03,30.79c-1.1,0-2.19.42-3.01,1.23-.51.51-.51,1.35,0,1.86l18.77,18.78c1.74,1.74,4.06,2.7,6.53,2.7h6.18c6.54,0,11.86-5.32,11.86-11.86v-14.5c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v1.32c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-3.95c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v3.95c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-6.59c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v6.59c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32V10.55c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v25.05c0,.5-.28.95-.73,1.18s-.98.18-1.38-.12l-6.69-5.02c-.75-.56-1.65-.84-2.54-.84Z" stroke-width="0.5" stroke="currentColor"/>
74
- </svg>
75
- <p>Nudge</p>
76
- </button>
77
- <x-extension id="chat-input-bottom-actions-end"></x-extension>
78
- </div>
79
- </template>
80
- </div>
51
+ <button
52
+ type="button"
53
+ class="chat-bottom-menu-item"
54
+ id="ctx_window"
55
+ @click="$store.context.open(); $store.chatInput.closeChatMoreMenu()"
56
+ >
57
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="17 15 70 70" width="18" height="18" fill="currentColor" aria-hidden="true">
58
+ <path d="m63 25c1.1016 0 2-0.89844 2-2s-0.89844-2-2-2h-26c-1.1016 0-2 0.89844-2 2s0.89844 2 2 2z"></path>
59
+ <path d="m63 79c1.1016 0 2-0.89844 2-2s-0.89844-2-2-2h-26c-1.1016 0-2 0.89844-2 2s0.89844 2 2 2z"></path>
60
+ <path d="m68 39h-36c-6.0703 0-11 4.9297-11 11s4.9297 11 11 11h36c6.0703 0 11-4.9297 11-11s-4.9297-11-11-11zm0 18h-36c-3.8594 0-7-3.1406-7-7s3.1406-7 7-7h36c3.8594 0 7 3.1406 7 7s-3.1406 7-7 7z"></path>
61
+ </svg>
62
+ <span>Context</span>
63
+ </button>
64
+ </div>
65
+ </template>
66
67
<style>
83
- .text-buttons-row { width: 100%; display: flex; flex-wrap: wrap; gap: var(--spacing-xs); padding-top: var(--spacing-xs); margin-left: var(--spacing-xs); }
84
- .text-buttons-row > x-extension:not(:empty) { display: contents; }
85
- .text-buttons-row > x-extension:empty { display: none; }
86
- .text-buttons-row > x-extension > x-component,
87
- .text-buttons-row > x-extension > x-component > div[x-data] { display: contents; }
88
- .text-button { background-color: transparent; border: none; border-radius: 5px; color: var(--color-text); font-family: "Rubik", Arial, Helvetica, sans-serif; font-size: 0.6rem; padding: 6px var(--spacing-sm); cursor: pointer; opacity: 0.8; -webkit-transition: all 0.3s; transition: all 0.3s; display: flex; align-items: center; gap: var(--spacing-xs); }
89
- .text-button:hover:not(:disabled) { opacity: 1; background-color: var(--color-background-hover); border-radius: 4px; }
90
- .text-button:active:not(:disabled) { opacity: 0.5; }
91
- .text-button:disabled { opacity: 0.4; cursor: not-allowed; }
92
- .text-button svg { width: 14px; height: 14px; flex-shrink: 0; }
93
- .text-button .material-symbols-outlined { font-size: 14px; flex-shrink: 0; }
94
- .text-button p { margin-block: 0; }
68
+ .chat-bottom-actions-menu {
69
+ display: flex;
70
+ flex-direction: column;
71
+ width: 100%;
72
+ font-family: var(--font-family-main, "Rubik", Arial, Helvetica, sans-serif);
73
+ font-size: 0.8125rem;
74
+ }
75
96
- @media (max-width: 768px) { .text-buttons-row { width: 90%; display: flex; flex-wrap: wrap; padding-top: var(--spacing-xs); gap: var(--spacing-xs); white-space: pre-wrap; } .text-button { font-size: 0.6rem; } .text-button svg { width: 18px; height: 18px; flex-shrink: 0; } .text-button .material-symbols-outlined { font-size: 18px; } }
97
- @media (max-width: 640px) { .text-buttons-row { display: flex; flex-wrap: wrap; width: 90%; padding-top: var(--spacing-xs); gap: var(--spacing-xs); white-space: pre-wrap; } .text-button { max-height: 25px; } .text-button p { display: none; } }
98
- </style>
76
+ .chat-bottom-menu-item {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 0.4rem;
80
+ width: 100%;
81
+ margin: 0;
82
+ padding: var(--spacing-xs) var(--spacing-sm);
83
+ border-radius: 0.25rem;
84
+ background: transparent;
85
+ color: var(--color-text);
86
+ font-family: 'Rubik', Arial, Helvetica, sans-serif;
87
+ text-align: left;
88
+ cursor: pointer;
89
+ opacity: 0.88;
90
+ transition: background-color 0.12s ease, opacity 0.12s ease;
91
+ white-space: nowrap;
92
+ box-sizing: border-box;
93
+ }
94
+ button.chat-bottom-menu-item {
95
+ border: none;
96
+ }
97
+ label.chat-bottom-menu-item {
98
+ position: relative;
99
+ font-weight: inherit;
100
+ }
101
+ .chat-bottom-menu-file-input {
102
+ position: absolute;
103
+ width: 1px;
104
+ height: 1px;
105
+ padding: 0;
106
+ margin: -1px;
107
+ overflow: hidden;
108
+ clip: rect(0, 0, 0, 0);
109
+ white-space: nowrap;
110
+ border: 0;
111
+ opacity: 0;
112
+ }
113
+ button.chat-bottom-menu-item:hover:not(:disabled),
114
+ label.chat-bottom-menu-item:hover {
115
+ opacity: 1;
116
+ background-color: var(--color-background-hover);
117
+ }
118
+ button.chat-bottom-menu-item:active:not(:disabled) { opacity: 0.72; }
119
+ label.chat-bottom-menu-item:active { opacity: 0.72; }
120
+ button.chat-bottom-menu-item:disabled {
121
+ opacity: 0.38;
122
+ cursor: not-allowed;
123
+ pointer-events: none;
124
+ }
125
+ .chat-bottom-menu-item .material-symbols-outlined {
126
+ font-size: 18px;
127
+ flex-shrink: 0;
128
+ opacity: 0.9;
129
+ }
130
+ .chat-bottom-menu-item svg { flex-shrink: 0; opacity: 0.88; }
131
+ </style>
132
</body>
133
</html>
101
-
webui/components/chat/input/chat-bar-input.html
+67
-13
@@ -2,7 +2,6 @@
2
<head>
3
<script type="module">
4
import { store as speechStore } from "/components/chat/speech/speech-store.js";
5
- import { store as attachmentsStore } from "/components/chat/attachments/attachmentsStore.js";
5
import { store as fullScreenStore } from "/components/modals/full-screen-input/full-screen-store.js";
6
import { store as messageQueueStore } from "/components/chat/message-queue/message-queue-store.js";
7
import { store as chatInputStore } from "/components/chat/input/input-store.js";
@@ -12,14 +11,29 @@
11
<div x-data>
12
<x-extension id="chat-input-box-start"></x-extension>
13
<div class="input-row">
15
- <!-- Attachment icon with tooltip -->
16
- <div class="attachment-wrapper">
17
- <label for="file-input" class="attachment-icon" title="Add attachments to the message" data-bs-placement="top" data-bs-trigger="hover">
18
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
19
- <path 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" />
20
- </svg>
21
- </label>
22
- <input type="file" id="file-input" accept="*" multiple style="display: none" @change="$store.chatAttachments.handleFileUpload($event)">
14
+ <!-- More actions (+): opens upward; content lives in bottom-actions.html -->
15
+ <div
16
+ class="attachment-wrapper chat-more-dropdown"
17
+ @click.outside="$store.chatInput.closeChatMoreMenu()"
18
+ @keydown.escape.window="$store.chatInput.closeChatMoreMenu()"
19
+ >
20
+ <button
21
+ type="button"
22
+ class="chat-more-trigger"
23
+ @click.stop="$store.chatInput.toggleChatMoreMenu()"
24
+ :aria-expanded="$store.chatInput.chatMoreMenuOpen.toString()"
25
+ aria-label="More actions"
26
+ >
27
+ <span class="material-symbols-outlined" aria-hidden="true">add</span>
28
+ </button>
29
+ <div
30
+ class="chat-more-panel"
31
+ x-show="$store.chatInput.chatMoreMenuOpen"
32
+ x-transition
33
+ style="display: none;"
34
+ >
35
+ <x-component path="chat/input/bottom-actions.html"></x-component>
36
+ </div>
37
</div>
38
39
<!-- Container for textarea and expand button -->
@@ -62,11 +76,51 @@
76
/* Layout rows */
77
.input-row { display: flex; align-items: center; gap: var(--spacing-xs); width: 100%; white-space: nowrap; }
78
65
- /* Attachments icon + tooltip */
79
.attachment-wrapper { position: relative; flex-shrink: 0; }
67
- .attachment-icon { cursor: pointer; color: var(--color-text); opacity: 0.7; transition: opacity 0.2s ease; display: flex; align-items: center; }
68
- .attachment-icon:hover { opacity: 1; }
69
- .attachment-icon:active { opacity: 0.5; }
80
+
81
+ /* Composer + menu: custom panel (edit here; not shared with file-browser dropdown) */
82
+ .chat-more-dropdown { z-index: 1; }
83
+ .chat-more-trigger {
84
+ cursor: pointer;
85
+ color: var(--color-text);
86
+ opacity: 0.75;
87
+ transition: opacity 0.2s ease;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ width: 2.25rem;
92
+ height: 2.25rem;
93
+ padding: 0;
94
+ border: none;
95
+ border-radius: 50%;
96
+ background: transparent;
97
+ }
98
+ .chat-more-trigger:hover { opacity: 1; }
99
+ .chat-more-trigger:active { opacity: 0.5; }
100
+ .chat-more-trigger .material-symbols-outlined {
101
+ font-size: 1.5rem;
102
+ line-height: 1;
103
+ }
104
+ .chat-more-panel {
105
+ position: absolute;
106
+ left: 0;
107
+ bottom: 100%;
108
+ margin-bottom: 0.25rem;
109
+ min-width: 11rem;
110
+ max-width: min(18rem, 92vw);
111
+ max-height: min(70vh, 20rem);
112
+ overflow-x: hidden;
113
+ overflow-y: auto;
114
+ background-color: var(--color-panel);
115
+ border: 1px solid var(--color-border);
116
+ border-radius: 0.4rem;
117
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
118
+ z-index: 1050;
119
+ padding: 0.2rem 0;
120
+ }
121
+ .light-mode .chat-more-panel {
122
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
123
+ }
124
/* Text input */
125
#chat-input-container {
126
position: relative;
webui/components/chat/input/chat-bar.html
+1
-1
@@ -19,7 +19,7 @@
19
</div>
20
21
<x-component path="chat/input/chat-bar-input.html"></x-component>
22
- <x-component path="chat/input/bottom-actions.html"></x-component>
22
+ <x-component path="chat/input/bottom-actions-bar.html"></x-component>
23
</div>
24
</template>
25
<x-extension id="chat-input-end"></x-extension>
webui/components/chat/input/input-store.js
+11
@@ -8,6 +8,16 @@ import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
8
const model = {
9
paused: false,
10
message: "",
11
+ /** Composer + menu (bottom actions moved into dropdown) */
12
+ chatMoreMenuOpen: false,
13
+
14
+ toggleChatMoreMenu() {
15
+ this.chatMoreMenuOpen = !this.chatMoreMenuOpen;
16
+ },
17
+
18
+ closeChatMoreMenu() {
19
+ this.chatMoreMenuOpen = false;
20
+ },
21
22
_getSendState() {
23
const hasInput = this.message.trim() || attachmentsStore?.attachments?.length > 0;
@@ -212,6 +222,7 @@ const model = {
222
reset() {
223
this.message = "";
224
attachmentsStore.clearAttachments();
225
+ this.chatMoreMenuOpen = false;
226
this.adjustTextareaHeight();
227
}
228
};
webui/components/sidebar/top-section/header-icons.html
+6
@@ -4,6 +4,7 @@
4
import { store as sidebarStore } from "/components/sidebar/sidebar-store.js";
5
import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
6
import { store as projectsStore } from "/components/projects/projects-store.js";
7
+ import { store as chatInputStore } from "/components/chat/input/input-store.js";
8
</script>
9
</head>
10
<body>
@@ -76,6 +77,11 @@
77
<span>Projects</span>
78
</button>
79
80
+ <button class="dropdown-item" @click="$store.chatInput.browseFiles(); $store.sidebar.menuClose()">
81
+ <span class="material-symbols-outlined">folder</span>
82
+ <span>Files</span>
83
+ </button>
84
+
85
<button class="dropdown-item" @click="openModal('components/modals/scheduler/scheduler-modal.html'); $store.sidebar.menuClose()">
86
<span class="material-symbols-outlined">schedule</span>
87
<span>Scheduler</span>