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 }