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>