remove unecessary stuff
TerminallyLazy committed
Aug 4, 2025 at 23:49 UTC
c61bc30de8cea9b0cb7b8563e5e957943b16cdb8
7 files changed
+236
-756
webui/components/messages/action-buttons/message-action-buttons.css
deleted
-245
@@ -1,245 +0,0 @@
1
-/* Message Action Buttons Styles */
2
-
3
-/* Main action buttons container */
4
-.action-buttons {
5
- position: sticky;
6
- top: var(--spacing-sm);
7
- right: 0;
8
- display: flex;
9
- align-items: center;
10
- gap: var(--spacing-xs);
11
- opacity: 0;
12
- transition: opacity var(--transition-speed) ease-in-out;
13
- z-index: 10;
14
- align-self: flex-start;
15
- margin-left: auto;
16
- margin-top: var(--spacing-sm);
17
- margin-right: var(--spacing-xs);
18
-}
19
-
20
-/* Individual action button */
21
-.action-button {
22
- position: relative;
23
- display: flex;
24
- align-items: center;
25
- justify-content: center;
26
- width: 32px;
27
- height: 32px;
28
- background: var(--color-panel);
29
- border: 1px solid var(--color-border);
30
- border-radius: 6px;
31
- cursor: pointer;
32
- transition: all var(--transition-speed) ease-in-out;
33
- color: var(--color-text);
34
- padding: 0;
35
- font-size: 16px;
36
- opacity: 0.7;
37
-}
38
-
39
-.action-button:hover {
40
- opacity: 1;
41
- background: var(--color-background);
42
- border-color: var(--color-primary);
43
- transform: translateY(-1px);
44
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
45
-}
46
-
47
-.action-button:active {
48
- transform: translateY(0);
49
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
50
-}
51
-
52
-/* Material icons in buttons */
53
-.action-button .material-symbols-outlined {
54
- font-size: 16px;
55
- line-height: 1;
56
- font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
57
-}
58
-
59
-/* Success state */
60
-.action-button.success {
61
- background: #4CAF50;
62
- border-color: #4CAF50;
63
- color: white;
64
-}
65
-
66
-.action-button.success .material-symbols-outlined {
67
- font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 20;
68
-}
69
-
70
-/* Error state */
71
-.action-button.error {
72
- background: var(--color-accent);
73
- border-color: var(--color-accent);
74
- color: white;
75
-}
76
-
77
-.action-button.error .material-symbols-outlined {
78
- font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 20;
79
-}
80
-
81
-/* Speaking state */
82
-.action-button.speaking {
83
- background: var(--color-primary);
84
- border-color: var(--color-primary);
85
- color: white;
86
- animation: pulse 2s infinite;
87
-}
88
-
89
-@keyframes pulse {
90
- 0% { opacity: 1; }
91
- 50% { opacity: 0.7; }
92
- 100% { opacity: 1; }
93
-}
94
-
95
-/* Tooltips for desktop */
96
-.action-tooltip {
97
- position: absolute;
98
- bottom: calc(100% + 8px);
99
- left: 50%;
100
- transform: translateX(-50%);
101
- background: rgba(0, 0, 0, 0.9);
102
- color: white;
103
- padding: var(--spacing-xs) var(--spacing-sm);
104
- border-radius: 4px;
105
- font-size: var(--font-size-small);
106
- white-space: nowrap;
107
- opacity: 0;
108
- visibility: hidden;
109
- transition: all var(--transition-speed) ease-in-out;
110
- pointer-events: none;
111
- z-index: 20;
112
- font-family: "Rubik", Arial, Helvetica, sans-serif;
113
-}
114
-
115
-.action-tooltip::after {
116
- content: '';
117
- position: absolute;
118
- top: 100%;
119
- left: 50%;
120
- transform: translateX(-50%);
121
- border: 4px solid transparent;
122
- border-top-color: rgba(0, 0, 0, 0.9);
123
-}
124
-
125
-/* Show action buttons on hover for desktop */
126
-.device-pointer .msg-content:hover .action-buttons,
127
-.device-pointer .kvps-row:hover .action-buttons,
128
-.device-pointer .message-text:hover .action-buttons {
129
- opacity: 1;
130
-}
131
-
132
-/* Show tooltips on button hover for desktop only */
133
-.device-pointer .action-button:hover .action-tooltip {
134
- opacity: 1;
135
- visibility: visible;
136
-}
137
-
138
-/* Touch device behavior - buttons shown when parent has active class */
139
-.device-touch .msg-content.show-actions .action-buttons,
140
-.device-touch .kvps-row.show-actions .action-buttons,
141
-.device-touch .message-text.show-actions .action-buttons {
142
- opacity: 1;
143
-}
144
-
145
-/* Hide tooltips on touch devices */
146
-.device-touch .action-tooltip {
147
- display: none;
148
-}
149
-
150
-/* Viewport sticky positioning for long messages */
151
-.action-buttons.viewport-sticky {
152
- position: fixed;
153
- top: var(--spacing-md);
154
- right: var(--spacing-md);
155
- background: var(--color-panel);
156
- border: 1px solid var(--color-border);
157
- border-radius: 8px;
158
- padding: var(--spacing-xs);
159
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
160
- opacity: 1;
161
- z-index: 100;
162
-}
163
-
164
-.action-buttons.viewport-sticky .action-button {
165
- opacity: 1;
166
-}
167
-
168
-/* Extra bottom padding for mobile browser bars */
169
-.device-touch .action-buttons {
170
- margin-bottom: 70px;
171
-}
172
-
173
-/* Specific positioning adjustments for different message types */
174
-.message-user .action-buttons {
175
- margin-right: 0;
176
- align-self: flex-end;
177
-}
178
-
179
-.message-agent .action-buttons,
180
-.message-agent-response .action-buttons {
181
- margin-right: 0;
182
-}
183
-
184
-.message-tool .action-buttons,
185
-.message-info .action-buttons {
186
- margin-top: 0;
187
-}
188
-
189
-.msg-thoughts .action-buttons {
190
- margin-top: 0;
191
-}
192
-
193
-/* Ensure message containers are positioned for sticky children */
194
-.msg-content,
195
-.kvps-row,
196
-.message-text {
197
- position: relative;
198
- display: flex;
199
- flex-direction: column;
200
-}
201
-
202
-/* Responsive adjustments for smaller screens */
203
-@media (max-width: 768px) {
204
- .action-button {
205
- width: 36px;
206
- height: 36px;
207
- }
208
-
209
- .action-button .material-symbols-outlined {
210
- font-size: 18px;
211
- }
212
-
213
- .action-buttons.viewport-sticky {
214
- right: var(--spacing-sm);
215
- top: var(--spacing-sm);
216
- }
217
-}
218
-
219
-/* High contrast mode support */
220
-@media (prefers-contrast: high) {
221
- .action-button {
222
- border-width: 2px;
223
- }
224
-
225
- .action-button:hover {
226
- border-width: 2px;
227
- }
228
-}
229
-
230
-/* Reduced motion support */
231
-@media (prefers-reduced-motion: reduce) {
232
- .action-button,
233
- .action-buttons,
234
- .action-tooltip {
235
- transition: none;
236
- }
237
-
238
- .action-button.speaking {
239
- animation: none;
240
- }
241
-
242
- .action-button:hover {
243
- transform: none;
244
- }
245
-}
\ No newline at end of file
webui/components/messages/action-buttons/message-action-buttons.js
deleted
-250
@@ -1,250 +0,0 @@
1
-// Message Action Buttons Component
2
-import { store as speechStore } from "/components/chat/speech/speech-store.js";
3
-
4
-// Extract text content based on container type
5
-function extractTextContent(container) {
6
- if (container.classList.contains("kvps-row")) {
7
- return container.querySelector(".kvps-val")?.innerText || "";
8
- } else if (container.classList.contains("message-text")) {
9
- // User messages use <pre> elements, not <span>
10
- return container.querySelector("pre")?.innerText || container.querySelector("span")?.innerText || "";
11
- } else {
12
- // Other message types use <span> elements
13
- return container.querySelector("span")?.innerText || "";
14
- }
15
-}
16
-
17
-// Copy text with fallback for local development
18
-async function copyToClipboard(text, button) {
19
- try {
20
- // Try modern clipboard API first
21
- if (navigator.clipboard && window.isSecureContext) {
22
- await navigator.clipboard.writeText(text);
23
- showCopyFeedback(button, true);
24
- } else {
25
- // Fallback for local development or non-HTTPS
26
- const textarea = document.createElement("textarea");
27
- textarea.value = text;
28
- textarea.style.position = "fixed";
29
- textarea.style.left = "-999999px";
30
- document.body.appendChild(textarea);
31
- textarea.select();
32
-
33
- try {
34
- document.execCommand("copy");
35
- showCopyFeedback(button, true);
36
- } catch (err) {
37
- console.error("Fallback copy failed:", err);
38
- showCopyFeedback(button, false);
39
- } finally {
40
- document.body.removeChild(textarea);
41
- }
42
- }
43
- } catch (err) {
44
- console.error("Copy failed:", err);
45
- showCopyFeedback(button, false);
46
- }
47
-}
48
-
49
-// Show visual feedback for copy action
50
-function showCopyFeedback(button, success) {
51
-
52
-
53
- if (success) {
54
-
55
- button.classList.add("success");
56
- } else {
57
- button.classList.add("error");
58
- }
59
-
60
- setTimeout(() => {
61
- button.classList.remove("success", "error");
62
- }, 2000);
63
-}
64
-
65
-// Speak text using speech store
66
-async function speakContent(text, button) {
67
- try {
68
- // Validate input text
69
- if (!text || typeof text !== 'string' || text.trim().length === 0) {
70
- console.warn("TTS: No valid text to speak");
71
- return;
72
- }
73
-
74
- // Update button state
75
- button.classList.add("speaking");
76
- const icon = button.querySelector(".material-symbols-outlined");
77
- const originalIcon = icon.textContent;
78
-
79
- // Check if already speaking
80
- if (speechStore.isSpeaking) {
81
- // Stop speaking using the correct speech store method
82
- if (speechStore.stop) {
83
- speechStore.stop();
84
- } else {
85
- // Fallback if stop doesn't exist
86
- speechSynthesis.cancel();
87
- }
88
- icon.textContent = originalIcon;
89
- button.classList.remove("speaking");
90
- return;
91
- }
92
-
93
- // Clean and speak text
94
- const cleanedText = speechStore.cleanText ? speechStore.cleanText(text.trim()) : text.trim();
95
-
96
- // Validate cleaned text
97
- if (!cleanedText || cleanedText.length === 0) {
98
- console.warn("TTS: Text was empty after cleaning");
99
- button.classList.remove("speaking");
100
- return;
101
- }
102
-
103
- // Update icon to show speaking state
104
- icon.textContent = "stop_circle";
105
-
106
- // Start speaking - use the speak function from speech store
107
- if (speechStore.speak) {
108
- await speechStore.speak(cleanedText);
109
- } else {
110
- // Fallback to browser TTS if speech store doesn't have speak function
111
- const utterance = new SpeechSynthesisUtterance(cleanedText);
112
- utterance.lang = 'en-US'; // Ensure English language
113
- utterance.rate = 1.0;
114
- utterance.pitch = 1.0;
115
- utterance.volume = 1.0;
116
-
117
- // Handle utterance events
118
- utterance.onend = () => {
119
- icon.textContent = originalIcon;
120
- button.classList.remove("speaking");
121
- };
122
-
123
- utterance.onerror = (event) => {
124
- console.error("TTS utterance error:", event.error);
125
- icon.textContent = originalIcon;
126
- button.classList.remove("speaking");
127
- };
128
-
129
- speechSynthesis.speak(utterance);
130
- return; // Don't reset button state immediately for browser TTS
131
- }
132
-
133
- // Reset button state when done (for speech store)
134
- icon.textContent = originalIcon;
135
- button.classList.remove("speaking");
136
-
137
- } catch (err) {
138
- console.error("Speech failed:", err);
139
- button.classList.remove("speaking");
140
-
141
- // Reset icon
142
- const icon = button.querySelector(".material-symbols-outlined");
143
- if (icon) {
144
- icon.textContent = "volume_up";
145
- }
146
-
147
- // Show error feedback
148
- button.classList.add("error");
149
-
150
- setTimeout(() => {
151
- button.classList.remove("error");
152
- }, 2000);
153
- }
154
-}
155
-
156
-// Create action buttons for a message or KVP
157
-export function createActionButtons() {
158
- const container = document.createElement("div");
159
- container.className = "action-buttons";
160
-
161
- // Copy button
162
- const copyButton = document.createElement("button");
163
- copyButton.className = "action-button copy-action";
164
- copyButton.setAttribute("aria-label", "Copy text");
165
- copyButton.innerHTML = `
166
- <span class="material-symbols-outlined">content_copy</span>
167
- `;
168
-
169
- // Speak button
170
- const speakButton = document.createElement("button");
171
- speakButton.className = "action-button speak-action";
172
- speakButton.setAttribute("aria-label", "Speak text");
173
- speakButton.innerHTML = `
174
- <span class="material-symbols-outlined">volume_up</span>
175
- `;
176
-
177
- // Add event listeners
178
- copyButton.addEventListener("click", async function(e) {
179
- e.stopPropagation();
180
- const messageContainer = this.closest(".msg-content, .kvps-row, .message-text");
181
- const textToCopy = extractTextContent(messageContainer);
182
- await copyToClipboard(textToCopy, this);
183
- });
184
-
185
- speakButton.addEventListener("click", async function(e) {
186
- e.stopPropagation();
187
- const messageContainer = this.closest(".msg-content, .kvps-row, .message-text");
188
- const textToSpeak = extractTextContent(messageContainer);
189
- await speakContent(textToSpeak, this);
190
- });
191
-
192
- // Add buttons to container
193
- container.appendChild(copyButton);
194
- container.appendChild(speakButton);
195
-
196
- return container;
197
-}
198
-
199
-// Add action buttons to an element
200
-export function addActionButtonsToElement(element) {
201
- // Remove any existing action buttons first
202
- const existingButtons = element.querySelector(".action-buttons");
203
- if (existingButtons) {
204
- existingButtons.remove();
205
- }
206
-
207
-
208
- // Create and add new action buttons
209
- const actionButtons = createActionButtons();
210
- element.appendChild(actionButtons);
211
-
212
- // Set up intersection observer for viewport detection
213
- setupViewportObserver(element);
214
-}
215
-
216
-// Set up intersection observer for keeping buttons in viewport
217
-function setupViewportObserver(element) {
218
- const actionButtons = element.querySelector(".action-buttons");
219
- if (!actionButtons) return;
220
-
221
- // Create observer for viewport detection
222
- const observer = new IntersectionObserver((entries) => {
223
- entries.forEach(entry => {
224
- const buttons = entry.target.querySelector(".action-buttons");
225
- if (buttons && !entry.isIntersecting) {
226
-
227
- } else if (buttons) {
228
-
229
- }
230
- });
231
- }, {
232
- root: null,
233
- rootMargin: "-50px 0px",
234
- threshold: 0
235
- });
236
-
237
- // Start observing
238
- observer.observe(element);
239
-
240
- // Store observer reference for cleanup
241
- element._actionButtonsObserver = observer;
242
-}
243
-
244
-// Cleanup function to disconnect observer
245
-export function cleanupActionButtons(element) {
246
- if (element._actionButtonsObserver) {
247
- element._actionButtonsObserver.disconnect();
248
- delete element._actionButtonsObserver;
249
- }
250
-}
\ No newline at end of file
webui/components/messages/action-buttons/simple-action-buttons.css
new
+109
@@ -0,0 +1,109 @@
1
+/* Simplified Action Buttons - Keeping the Great Look & Feel */
2
+
3
+/* Main action buttons container - matches original positioning */
4
+.action-buttons {
5
+ position: sticky;
6
+ top: var(--spacing-sm);
7
+ right: 0;
8
+ display: flex;
9
+ align-items: center;
10
+ gap: var(--spacing-xs);
11
+ opacity: 0;
12
+ transition: opacity var(--transition-speed) ease-in-out;
13
+ z-index: 10;
14
+ align-self: flex-start;
15
+ margin-left: auto;
16
+ margin-top: var(--spacing-sm);
17
+ margin-right: var(--spacing-xs);
18
+}
19
+
20
+/* Individual action button - exact same styling as original */
21
+.action-button {
22
+ position: relative;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ width: 32px;
27
+ height: 32px;
28
+ background: var(--color-panel);
29
+ border: 1px solid var(--color-border);
30
+ border-radius: 6px;
31
+ cursor: pointer;
32
+ transition: all var(--transition-speed) ease-in-out;
33
+ color: var(--color-text);
34
+ padding: 0;
35
+ font-size: 16px;
36
+ opacity: 0.7;
37
+}
38
+
39
+.action-button:hover {
40
+ opacity: 1;
41
+ background: var(--color-background);
42
+ border-color: var(--color-primary);
43
+ transform: translateY(-1px);
44
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
45
+}
46
+
47
+.action-button:active {
48
+ transform: translateY(0);
49
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
50
+}
51
+
52
+/* Material icons - same as original */
53
+.action-button .material-symbols-outlined {
54
+ font-size: 16px;
55
+ line-height: 1;
56
+ font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
57
+}
58
+
59
+/* Success state - same as original */
60
+.action-button.success {
61
+ background: #4CAF50;
62
+ border-color: #4CAF50;
63
+ color: white;
64
+}
65
+
66
+.action-button.success .material-symbols-outlined {
67
+ font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 20;
68
+}
69
+
70
+/* Error state - same as original */
71
+.action-button.error {
72
+ background: var(--color-accent);
73
+ border-color: var(--color-accent);
74
+ color: white;
75
+}
76
+
77
+.action-button.error .material-symbols-outlined {
78
+ font-variation-settings: 'FILL' 1, 'wght' 500, 'GRAD' 0, 'opsz' 20;
79
+}
80
+
81
+/* Speaking state - same as original */
82
+.action-button.speaking {
83
+ background: var(--color-primary);
84
+ border-color: var(--color-primary);
85
+ color: white;
86
+ animation: pulse 2s infinite;
87
+}
88
+
89
+@keyframes pulse {
90
+ 0% { opacity: 1; }
91
+ 50% { opacity: 0.7; }
92
+ 100% { opacity: 1; }
93
+}
94
+
95
+/* Show action buttons on hover - simplified, no device detection needed */
96
+.msg-content:hover .action-buttons,
97
+.kvps-row:hover .action-buttons,
98
+.message-text:hover .action-buttons {
99
+ opacity: 1;
100
+}
101
+
102
+/* Ensure message containers are positioned for sticky children */
103
+.msg-content,
104
+.kvps-row,
105
+.message-text {
106
+ position: relative;
107
+ display: flex;
108
+ flex-direction: column;
109
+}
\ No newline at end of file
webui/components/messages/action-buttons/simple-action-buttons.js
new
+123
@@ -0,0 +1,123 @@
1
+// Simplified Message Action Buttons - Keeping the Great Look & Feel
2
+import { store as speechStore } from "/components/chat/speech/speech-store.js";
3
+
4
+// Extract text content from different message types
5
+function getTextContent(element) {
6
+ if (element.classList.contains("kvps-row")) {
7
+ return element.querySelector(".kvps-val")?.innerText || "";
8
+ } else if (element.classList.contains("message-text")) {
9
+ return element.querySelector("pre")?.innerText || element.querySelector("span")?.innerText || "";
10
+ } else {
11
+ return element.querySelector("span")?.innerText || "";
12
+ }
13
+}
14
+
15
+// Create and add action buttons to element
16
+export function addActionButtonsToElement(element) {
17
+ // Skip if buttons already exist
18
+ if (element.querySelector(".action-buttons")) return;
19
+
20
+ // Create container with same styling as original
21
+ const container = document.createElement("div");
22
+ container.className = "action-buttons";
23
+
24
+ // Copy button - matches original design
25
+ const copyBtn = document.createElement("button");
26
+ copyBtn.className = "action-button copy-action";
27
+ copyBtn.setAttribute("aria-label", "Copy text");
28
+ copyBtn.innerHTML = '<span class="material-symbols-outlined">content_copy</span>';
29
+
30
+ copyBtn.onclick = async (e) => {
31
+ e.stopPropagation();
32
+ const text = getTextContent(element);
33
+ const icon = copyBtn.querySelector(".material-symbols-outlined");
34
+
35
+ try {
36
+ // Try modern clipboard API
37
+ if (navigator.clipboard && window.isSecureContext) {
38
+ await navigator.clipboard.writeText(text);
39
+ } else {
40
+ // Fallback for local dev
41
+ const textarea = document.createElement("textarea");
42
+ textarea.value = text;
43
+ textarea.style.position = "fixed";
44
+ textarea.style.left = "-999999px";
45
+ document.body.appendChild(textarea);
46
+ textarea.select();
47
+ document.execCommand("copy");
48
+ document.body.removeChild(textarea);
49
+ }
50
+
51
+ // Visual feedback - matches original
52
+ icon.textContent = "check";
53
+ copyBtn.classList.add("success");
54
+ setTimeout(() => {
55
+ icon.textContent = "content_copy";
56
+ copyBtn.classList.remove("success");
57
+ }, 2000);
58
+
59
+ } catch (err) {
60
+ console.error("Copy failed:", err);
61
+ icon.textContent = "error";
62
+ copyBtn.classList.add("error");
63
+ setTimeout(() => {
64
+ icon.textContent = "content_copy";
65
+ copyBtn.classList.remove("error");
66
+ }, 2000);
67
+ }
68
+ };
69
+
70
+ // Speak button - matches original design
71
+ const speakBtn = document.createElement("button");
72
+ speakBtn.className = "action-button speak-action";
73
+ speakBtn.setAttribute("aria-label", "Speak text");
74
+ speakBtn.innerHTML = '<span class="material-symbols-outlined">volume_up</span>';
75
+
76
+ speakBtn.onclick = async (e) => {
77
+ e.stopPropagation();
78
+ const text = getTextContent(element);
79
+ const icon = speakBtn.querySelector(".material-symbols-outlined");
80
+
81
+ if (!text || text.trim().length === 0) return;
82
+
83
+ try {
84
+ // Check if already speaking and stop it
85
+ if (speechStore.isSpeaking) {
86
+ if (speechStore.stop) speechStore.stop();
87
+ else speechSynthesis.cancel();
88
+ return;
89
+ }
90
+
91
+ // Visual feedback - matches original
92
+ speakBtn.classList.add("speaking");
93
+ icon.textContent = "stop_circle";
94
+
95
+ // Use speech store if available, otherwise browser TTS
96
+ if (speechStore?.speak) {
97
+ const cleanedText = speechStore.cleanText ? speechStore.cleanText(text) : text;
98
+ await speechStore.speak(cleanedText);
99
+ } else {
100
+ const utterance = new SpeechSynthesisUtterance(text);
101
+ utterance.lang = 'en-US';
102
+ utterance.onend = () => {
103
+ icon.textContent = "volume_up";
104
+ speakBtn.classList.remove("speaking");
105
+ };
106
+ speechSynthesis.speak(utterance);
107
+ return; // Don't reset immediately for browser TTS
108
+ }
109
+
110
+ // Reset for speech store
111
+ icon.textContent = "volume_up";
112
+ speakBtn.classList.remove("speaking");
113
+
114
+ } catch (err) {
115
+ console.error("Speech failed:", err);
116
+ icon.textContent = "volume_up";
117
+ speakBtn.classList.remove("speaking");
118
+ }
119
+ };
120
+
121
+ container.append(copyBtn, speakBtn);
122
+ element.appendChild(container);
123
+}
\ No newline at end of file
webui/index.html
+1
-1
@@ -8,7 +8,7 @@
8
<link rel="icon" type="image/svg+xml" href="public/favicon.svg">
9
<link rel="stylesheet" href="index.css">
10
<link rel="stylesheet" href="css/messages.css">
11
- <link rel="stylesheet" href="components/messages/action-buttons/message-action-buttons.css">
11
+ <link rel="stylesheet" href="components/messages/action-buttons/simple-action-buttons.css">
12
<link rel="stylesheet" href="css/toast.css">
13
<link rel="stylesheet" href="css/settings.css">
14
<link rel="stylesheet" href="css/file_browser.css">
webui/js/message-interactions.js
deleted
-243
@@ -1,243 +0,0 @@
1
-// Message Interactions Handler for Touch/Pointer Devices
2
-import { getInputType } from "./device.js";
3
-
4
-// Track which messages currently have visible action buttons
5
-const visibleActionStates = new WeakSet();
6
-
7
-// Initialize interaction handlers for touch devices
8
-export function initializeMessageInteractions() {
9
- const inputType = getInputType();
10
-
11
- if (inputType === "touch") {
12
- setupTouchInteractions();
13
- }
14
- // Pointer devices use hover states from CSS, no JS needed
15
-}
16
-
17
-// Set up touch device interactions
18
-function setupTouchInteractions() {
19
- // Use event delegation on the chat history container
20
- const chatHistory = document.getElementById("chat-history");
21
- if (!chatHistory) return;
22
-
23
- // Handle touch interactions for showing/hiding action buttons
24
- chatHistory.addEventListener("click", handleTouchInteraction, { passive: false });
25
-
26
- // Handle clicks outside messages to hide action buttons
27
- document.addEventListener("click", handleOutsideClick, { passive: true });
28
-
29
- // Prevent text selection when tapping action buttons area
30
- chatHistory.addEventListener("selectstart", handleSelectStart, { passive: false });
31
-}
32
-
33
-// Handle touch interactions on messages
34
-function handleTouchInteraction(e) {
35
- // Find the closest message container
36
- const messageContainer = e.target.closest(".msg-content, .kvps-row, .message-text");
37
- if (!messageContainer) return;
38
-
39
- // If clicking on action buttons, let them handle their own events
40
- if (e.target.closest(".action-buttons")) {
41
- return;
42
- }
43
-
44
- // Toggle action buttons visibility
45
- toggleActionButtons(messageContainer, e);
46
-}
47
-
48
-// Handle clicks outside of messages to hide action buttons
49
-function handleOutsideClick(e) {
50
- // If clicking on a message or action button, don't hide
51
- if (e.target.closest(".msg-content, .kvps-row, .message-text, .action-buttons")) {
52
- return;
53
- }
54
-
55
- // Hide all visible action buttons
56
- hideAllActionButtons();
57
-}
58
-
59
-// Prevent text selection when interacting with action area
60
-function handleSelectStart(e) {
61
- // Allow text selection unless we're in the action buttons area
62
- const actionButtons = e.target.closest(".action-buttons");
63
- const messageContainer = e.target.closest(".msg-content, .kvps-row, .message-text");
64
-
65
- if (actionButtons) {
66
- e.preventDefault();
67
- return false;
68
- }
69
-
70
- // If a message has visible action buttons, prevent selection to avoid conflicts
71
- if (messageContainer && visibleActionStates.has(messageContainer)) {
72
- // Allow selection after a short delay to distinguish from tap-to-show-buttons
73
- setTimeout(() => {
74
- if (visibleActionStates.has(messageContainer)) {
75
- // Still visible, user might want to select text
76
- return true;
77
- }
78
- }, 200);
79
- }
80
-}
81
-
82
-// Toggle action buttons visibility for a message
83
-export function toggleActionButtons(messageContainer, event = null) {
84
- if (!messageContainer) return;
85
-
86
- const isCurrentlyVisible = visibleActionStates.has(messageContainer);
87
-
88
- if (isCurrentlyVisible) {
89
- hideActionButtons(messageContainer);
90
- } else {
91
- // Hide all other visible action buttons first
92
- hideAllActionButtons();
93
-
94
- // Show action buttons for this message
95
- showActionButtons(messageContainer);
96
- }
97
-
98
- // Prevent event bubbling to avoid triggering document click handler
99
- if (event) {
100
- event.stopPropagation();
101
- }
102
-}
103
-
104
-// Show action buttons for a message
105
-export function showActionButtons(messageContainer) {
106
- if (!messageContainer) return;
107
-
108
- messageContainer.classList.add("show-actions");
109
- visibleActionStates.add(messageContainer);
110
-
111
- // Add a slight delay to prevent immediate hiding from document click
112
- setTimeout(() => {
113
- messageContainer.setAttribute("data-actions-visible", "true");
114
- }, 50);
115
-}
116
-
117
-// Hide action buttons for a message
118
-export function hideActionButtons(messageContainer) {
119
- if (!messageContainer) return;
120
-
121
- messageContainer.classList.remove("show-actions");
122
- messageContainer.removeAttribute("data-actions-visible");
123
- visibleActionStates.delete(messageContainer);
124
-}
125
-
126
-// Hide all visible action buttons
127
-export function hideAllActionButtons() {
128
- const chatHistory = document.getElementById("chat-history");
129
- if (!chatHistory) return;
130
-
131
- // Find all messages with visible action buttons
132
- const visibleMessages = chatHistory.querySelectorAll(".show-actions");
133
- visibleMessages.forEach(message => {
134
- hideActionButtons(message);
135
- });
136
-}
137
-
138
-// Setup interaction for a specific message (called when message is created)
139
-export function setupMessageInteraction(messageContainer) {
140
- if (!messageContainer) return;
141
-
142
- const inputType = getInputType();
143
-
144
- if (inputType === "touch") {
145
- // For touch devices, add click handler
146
- messageContainer.addEventListener("click", (e) => {
147
- // Only handle if not clicking on action buttons
148
- if (!e.target.closest(".action-buttons")) {
149
- toggleActionButtons(messageContainer, e);
150
- }
151
- }, { passive: false });
152
-
153
- // Prevent context menu on long press for action area
154
- messageContainer.addEventListener("contextmenu", (e) => {
155
- if (e.target.closest(".action-buttons")) {
156
- e.preventDefault();
157
- }
158
- });
159
- }
160
-
161
- // For both touch and pointer devices, handle keyboard navigation
162
- setupKeyboardNavigation(messageContainer);
163
-}
164
-
165
-// Set up keyboard navigation for accessibility
166
-function setupKeyboardNavigation(messageContainer) {
167
- const actionButtons = messageContainer.querySelector(".action-buttons");
168
- if (!actionButtons) return;
169
-
170
- const buttons = actionButtons.querySelectorAll(".action-button");
171
-
172
- buttons.forEach((button, index) => {
173
- button.setAttribute("tabindex", "0");
174
-
175
- button.addEventListener("keydown", (e) => {
176
- switch (e.key) {
177
- case "Enter":
178
- case " ":
179
- e.preventDefault();
180
- button.click();
181
- break;
182
- case "ArrowLeft":
183
- e.preventDefault();
184
- const prevButton = buttons[index - 1] || buttons[buttons.length - 1];
185
- prevButton.focus();
186
- break;
187
- case "ArrowRight":
188
- e.preventDefault();
189
- const nextButton = buttons[index + 1] || buttons[0];
190
- nextButton.focus();
191
- break;
192
- case "Escape":
193
- hideActionButtons(messageContainer);
194
- messageContainer.focus();
195
- break;
196
- }
197
- });
198
- });
199
-}
200
-
201
-// Debounced scroll handler to manage viewport sticky buttons
202
-let scrollTimeout;
203
-export function handleScroll() {
204
- clearTimeout(scrollTimeout);
205
- scrollTimeout = setTimeout(() => {
206
- // Check if any viewport-sticky buttons should be hidden
207
- const stickyButtons = document.querySelectorAll(".action-buttons.viewport-sticky");
208
- stickyButtons.forEach(buttons => {
209
- const parent = buttons.closest(".msg-content, .kvps-row, .message-text");
210
- if (parent) {
211
- const rect = parent.getBoundingClientRect();
212
- const isVisible = rect.top < window.innerHeight && rect.bottom > 0;
213
-
214
- if (isVisible) {
215
- buttons.classList.remove("viewport-sticky");
216
- }
217
- }
218
- });
219
- }, 100);
220
-}
221
-
222
-// Initialize scroll handler
223
-export function initializeScrollHandler() {
224
- window.addEventListener("scroll", handleScroll, { passive: true });
225
- window.addEventListener("resize", handleScroll, { passive: true });
226
-}
227
-
228
-// Cleanup function
229
-export function cleanupMessageInteractions() {
230
- window.removeEventListener("scroll", handleScroll);
231
- window.removeEventListener("resize", handleScroll);
232
-
233
- const chatHistory = document.getElementById("chat-history");
234
- if (chatHistory) {
235
- chatHistory.removeEventListener("click", handleTouchInteraction);
236
- chatHistory.removeEventListener("selectstart", handleSelectStart);
237
- }
238
-
239
- document.removeEventListener("click", handleOutsideClick);
240
-
241
- // Clear visible states
242
- visibleActionStates.clear?.();
243
-}
\ No newline at end of file
webui/js/messages.js
+3
-17
@@ -3,26 +3,15 @@ import { openImageModal } from "./image_modal.js";
3
import { marked } from "../vendor/marked/marked.esm.js";
4
import { store as _messageResizeStore } from "/components/messages/resize/message-resize-store.js"; // keep here, required in html
5
import { store as attachmentsStore } from "/components/chat/attachments/attachmentsStore.js";
6
-import { addActionButtonsToElement, cleanupActionButtons } from "/components/messages/action-buttons/message-action-buttons.js";
7
-import { setupMessageInteraction, initializeMessageInteractions, initializeScrollHandler } from "./message-interactions.js";
6
+import { addActionButtonsToElement } from "/components/messages/action-buttons/simple-action-buttons.js";
7
8
const chatHistory = document.getElementById("chat-history");
9
10
let messageGroup = null;
11
13
-// Initialize message interactions when DOM is ready
14
-let interactionsInitialized = false;
15
-function ensureInteractionsInitialized() {
16
- if (!interactionsInitialized && chatHistory) {
17
- initializeMessageInteractions();
18
- initializeScrollHandler();
19
- interactionsInitialized = true;
20
- }
21
-}
12
+// Simplified implementation - no complex interactions needed
13
14
export function setMessage(id, type, heading, content, temp, kvps = null) {
24
- // Ensure interactions are initialized
25
- ensureInteractionsInitialized();
15
16
// Search for the existing message container by id
17
let messageContainer = document.getElementById(`message-${id}`);
@@ -82,10 +71,7 @@ export function setMessage(id, type, heading, content, temp, kvps = null) {
71
chatHistory.appendChild(messageGroup);
72
}
73
85
- // Set up interactions for new messages
86
- if (isNewMessage) {
87
- setupMessageInteraction(messageContainer);
88
- }
74
+ // Simplified implementation - no setup needed
75
76
return messageContainer;
77
}