Skip to content

Commit 3eed508

Browse files
committed
feat: AI panel UI redesign — centered chat, unified attach button, merged header
- Centered initial state (Claude-like welcome with input + model selector in middle) - Merged Attach File + Screenshot buttons into single '+' dropdown menu - Merged header bar and status bar into one compact header - Fixed: download progress bar stuck at 96% after model load - Fixed: API key error link targeting removed standalone bar
1 parent 30fea23 commit 3eed508

File tree

7 files changed

+265
-78
lines changed

7 files changed

+265
-78
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ TextAgent has undergone significant evolution since its inception. What started
544544

545545
| Date | Commits | Feature / Update |
546546
|------|---------|-----------------:|
547+
| **2026-04-01** | | 🎨 **AI Panel UI Redesign** — centered initial chat state (Claude-like welcome with input + model selector clustered in middle, transitions to bottom-pinned on first message via CSS `:has(.ai-welcome-message)`); merged separate Attach File (paperclip) and Screenshot (camera) buttons into single `+` button with unified dropdown menu (Attach File / Capture Page / Capture Screen / Upload Image, `+` rotates to `×` when open); merged header bar and status bar into single compact header (status text inline below title, download progress bar still standalone); fixed download progress bar stuck at 96% after model load |
547548
| **2026-03-31** | | 📷 **Screenshot to AI** — new 📷 camera button in the AI chat input bar with three capture modes: Capture Page (`html2canvas` full-page snapshot, AI panel hidden during capture), Capture Screen (`getDisplayMedia` screen-share with frame extraction from a hidden DOM-attached video element), and Upload Image (file picker); captured image auto-injected into `pendingAttachments` and sent to AI for analysis; fixed black-screen capture bug (video must be in DOM for GPU decoder, wait for `timeupdate` event not just `requestAnimationFrame`); self-healing button injection via `injectButtonIfMissing()` with 2s fallback poll; CSS-independent dropdown via inline `style.display` toggling; vision model warning toast; `js/ai-screenshot.js` new module (~300 lines) + `css/ai-panel.css` styles + `js/modal-templates.js` template update + `src/main.js` registration |
548549
| **2026-03-31** | | 🦀 **OpenClaw Integration Blog Post** — published detailed technical post (`CHANGELOG-openclaw-textagent-integration.md`) documenting how OpenClaw runs natively inside TextAgent's Docker-based Agent Flow; covers `AGENT_CLI_MAP`, native CLI invocation (`openclaw agent --message ... --json`), API key forwarding, structured JSON response parsing, multi-step context chaining, cloud mode via GitHub Codespaces, and security boundaries |
549550
| **2026-03-31** | | 🤖 **Qwen 3.6 Plus Preview via OpenRouter** — added `qwen/qwen3.6-plus-preview:free` (Alibaba) as a new free cloud model; appears in the model selector as "Qwen 3.6 Plus Preview · Alibaba · Free · via OpenRouter"; reuses existing `ai-worker-openrouter.js` and shared OpenRouter API key |
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# AI Panel UI Redesign — Centered Chat, Unified Attach Button & Compact Header
2+
3+
- Welcome message + input box + model selector now centered together (Claude-like initial state)
4+
- Input transitions to bottom-pinned layout once a conversation begins
5+
- Quick actions hidden in initial welcome state for a cleaner first impression
6+
- Merged separate "Attach File" (paperclip) and "Screenshot" (camera) buttons into a single "+" button
7+
- Unified dropdown menu with 4 options: Attach File, Capture Page, Capture Screen, Upload Image
8+
- "+" icon rotates 45° to "×" when menu is open
9+
- Merged AI panel header and status bar into a single compact header
10+
- Status text (e.g. "● Qwen 3.5 4B · Local (WEBGPU)") now shown inline below the title within the header
11+
- Download progress bar still uses standalone bar during active downloads
12+
- Fixed: Download progress bar stuck at 96% after model loaded (was never removed in new inline status flow)
13+
- Fixed: Reverted accidental duplicate CSS rules in styles.css from browser subagent
14+
- Fixed: API key error link now targets inline header status instead of removed standalone bar
15+
16+
---
17+
18+
## Summary
19+
Comprehensive UI redesign of the AI Assistant panel to create a more modern, Claude-like experience. Three major changes: centered initial state, unified attachment button, and compact merged header — reducing visual clutter and reclaiming vertical space.
20+
21+
---
22+
23+
## 1. Centered Initial State (Claude-like Welcome)
24+
**Files:** `css/ai-panel.css`
25+
**What:** Added CSS rules using `.ai-panel:has(.ai-welcome-message)` to split flex space equally between `.ai-chat-area` (flex: 1 1 0, justify-content: flex-end) and `.ai-input-area` (flex: 1 1 0, justify-content: flex-start). Both push their content toward each other, creating a centered cluster. Quick actions are hidden via `display: none`. Model selector is shown below the input with `order: 1`. Added smooth `transition` on input area properties. Mobile overrides added for safe-area insets and full-width input.
26+
**Impact:** Users see a clean, modern welcome screen with the greeting, input box, and model selector centered together — similar to Claude and ChatGPT's initial states. Once a message is sent, the layout transitions smoothly to the traditional bottom-pinned chat layout.
27+
28+
## 2. Unified Attach + Screenshot Button
29+
**Files:** `js/modal-templates.js`, `js/ai-chat.js`, `js/ai-screenshot.js`, `css/ai-panel.css`
30+
**What:** Replaced two separate buttons (paperclip for files, camera for screenshots) with a single `+` button (`bi-plus-lg`) wrapped in `.ai-attach-wrapper`. The button toggles a unified `#ai-attach-menu` dropdown containing all four options: Attach File, Capture Page, Capture Screen, Upload Image. Updated `ai-chat.js` to toggle the menu on click and handle the "Attach File" item. Updated `ai-screenshot.js` to reference `#ai-attach-menu` instead of old `#ai-screenshot-menu`, and simplified the self-heal injection. Removed the old `-45deg` paperclip rotation from CSS and added a `45deg` rotation when menu is active.
31+
**Impact:** Cleaner input bar with one button instead of two. All attachment/capture options accessible from a single, intuitive menu. The "+" → "×" rotation provides clear visual feedback.
32+
33+
## 3. Merged Header + Status Bar
34+
**Files:** `js/modal-templates.js`, `js/ai-assistant.js`, `css/ai-panel.css`
35+
**What:** Added `.ai-panel-title-group` and `.ai-panel-title-row` containers inside the header to create a two-line layout. Added `#ai-header-status` div for inline status text. Modified `addAiStatusBar()` in `ai-assistant.js` to inject text into this inline container instead of creating a standalone `<div>`. The standalone `.ai-status-bar` is now `display: none` by default, with `.downloading` overriding to `display: flex` for active download progress. When status transitions to `ready` or `error`, all standalone bars are removed.
36+
**Impact:** The header and status bar are merged into a single compact bar, saving ~30px of vertical space. Model status is visible at a glance without a separate row.
37+
38+
## 4. Bug Fix: Download Progress Bar Stuck
39+
**Files:** `js/ai-assistant.js`
40+
**What:** The original fix only removed `.ai-status-bar:not(.downloading)`, so the download progress bar (which has `.downloading` class) was never cleaned up when the model finished loading. Updated to remove all standalone bars when status becomes `ready` or `error`, while preserving them during `loading` updates.
41+
**Impact:** Download progress bar now properly disappears when model loading completes.
42+
43+
---
44+
45+
## Files Changed (5 total)
46+
47+
| File | Lines Changed | Type |
48+
|------|:---:|------|
49+
| `css/ai-panel.css` | +136 −3 | Centered state, unified button, merged header CSS |
50+
| `js/ai-assistant.js` | +16 −14 | Inline status bar, download bar cleanup fix |
51+
| `js/ai-chat.js` | +29 −5 | Unified attach menu toggle + file picker handler |
52+
| `js/ai-screenshot.js` | +24 −42 | Updated for merged menu, simplified self-heal |
53+
| `js/modal-templates.js` | +18 −13 | Unified attach button HTML, header restructure |

css/ai-panel.css

Lines changed: 131 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,11 @@
122122
display: flex;
123123
align-items: center;
124124
justify-content: space-between;
125-
padding: 14px 18px;
125+
padding: 10px 18px;
126126
border-bottom: 1px solid var(--border-color);
127127
background: var(--header-bg);
128128
flex-shrink: 0;
129+
min-height: 0;
129130
}
130131

131132
.ai-panel-title {
@@ -135,14 +136,50 @@
135136
font-weight: 600;
136137
font-size: 15px;
137138
color: var(--text-color);
139+
min-width: 0;
140+
overflow: hidden;
138141
}
139142

140-
.ai-panel-title i {
143+
.ai-panel-title > i {
141144
font-size: 18px;
142145
background: linear-gradient(135deg, #667eea, #764ba2);
143146
-webkit-background-clip: text;
144147
-webkit-text-fill-color: transparent;
145148
background-clip: text;
149+
flex-shrink: 0;
150+
}
151+
152+
.ai-panel-title-group {
153+
display: flex;
154+
flex-direction: column;
155+
gap: 1px;
156+
min-width: 0;
157+
}
158+
159+
.ai-panel-title-row {
160+
display: flex;
161+
align-items: center;
162+
gap: 8px;
163+
}
164+
165+
/* Inline status (merged into header) */
166+
.ai-header-status {
167+
font-size: 11px;
168+
font-weight: 400;
169+
color: var(--text-color);
170+
opacity: 0.6;
171+
display: flex;
172+
align-items: center;
173+
gap: 5px;
174+
white-space: nowrap;
175+
overflow: hidden;
176+
text-overflow: ellipsis;
177+
min-height: 14px;
178+
transition: opacity 0.2s;
179+
}
180+
181+
.ai-header-status:empty {
182+
display: none;
146183
}
147184

148185
.ai-badge {
@@ -164,6 +201,7 @@
164201
display: flex;
165202
align-items: center;
166203
gap: 4px;
204+
flex-shrink: 0;
167205
}
168206

169207
/* Input options row (below textarea) */
@@ -317,6 +355,66 @@
317355
display: flex;
318356
flex-direction: column;
319357
gap: 12px;
358+
justify-content: center;
359+
align-items: center;
360+
min-height: 0;
361+
transition: justify-content 0.3s ease, align-items 0.3s ease;
362+
}
363+
364+
/* When messages exist, switch to top-aligned scrollable conversation */
365+
.ai-chat-area:has(.ai-message) {
366+
justify-content: flex-start;
367+
align-items: stretch;
368+
}
369+
370+
/* ========================================
371+
CENTERED INITIAL STATE (Claude-like)
372+
When welcome message is showing, center
373+
the greeting + input together in the panel
374+
======================================== */
375+
376+
/* Both areas split remaining flex space equally —
377+
chat pushes content down, input pushes content up,
378+
so they meet in the center */
379+
.ai-panel:has(.ai-welcome-message) .ai-chat-area {
380+
flex: 1 1 0;
381+
justify-content: flex-end;
382+
align-items: center;
383+
padding-bottom: 4px;
384+
overflow-y: hidden;
385+
}
386+
387+
.ai-panel:has(.ai-welcome-message) .ai-input-area {
388+
flex: 1 1 0;
389+
display: flex;
390+
flex-direction: column;
391+
justify-content: flex-start;
392+
align-items: center;
393+
border-top: none;
394+
background: transparent;
395+
padding-top: 8px;
396+
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
397+
}
398+
399+
/* Constrain input children width within centered area */
400+
.ai-panel:has(.ai-welcome-message) .ai-input-area > .ai-model-selector,
401+
.ai-panel:has(.ai-welcome-message) .ai-input-area > .ai-attachments-strip,
402+
.ai-panel:has(.ai-welcome-message) .ai-input-area > .ai-input-wrapper {
403+
max-width: 440px;
404+
width: 100%;
405+
}
406+
407+
/* Style the model selector in centered state */
408+
.ai-panel:has(.ai-welcome-message) .ai-input-area > .ai-model-selector {
409+
max-width: 440px;
410+
width: 100%;
411+
order: 1; /* Push below input wrapper */
412+
margin-top: 8px;
413+
}
414+
415+
/* Also hide quick actions in initial state for cleaner look */
416+
.ai-panel:has(.ai-welcome-message) .ai-quick-actions {
417+
display: none;
320418
}
321419

322420
.ai-chat-area::-webkit-scrollbar {
@@ -557,6 +655,7 @@
557655
border-top: 1px solid var(--border-color);
558656
background: var(--header-bg);
559657
flex-shrink: 0;
658+
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
560659
}
561660

562661
/* --- Model Selector --- */
@@ -865,7 +964,12 @@
865964

866965
.ai-attach-button i {
867966
font-size: 15px;
868-
transform: rotate(-45deg);
967+
transition: transform 0.2s ease;
968+
}
969+
970+
/* Rotate '+' to 'x' when menu is open */
971+
.ai-attach-wrapper:has(.ai-screenshot-menu.active) .ai-attach-button i {
972+
transform: rotate(45deg);
869973
}
870974

871975
/* --- Attachments Preview Strip --- */
@@ -1531,6 +1635,7 @@
15311635
/* Compact input area on mobile */
15321636
.ai-input-area {
15331637
padding: 8px 10px;
1638+
padding-bottom: max(8px, env(safe-area-inset-bottom));
15341639
}
15351640

15361641
/* --- Merge model selector into chat bar on mobile --- */
@@ -1641,15 +1746,35 @@
16411746
.ai-action-chip i {
16421747
font-size: 11px;
16431748
}
1749+
1750+
/* Mobile centered initial state overrides */
1751+
.ai-panel:has(.ai-welcome-message) .ai-input-area {
1752+
padding: 8px 10px;
1753+
padding-bottom: max(8px, env(safe-area-inset-bottom));
1754+
}
1755+
1756+
.ai-panel:has(.ai-welcome-message) .ai-input-area > .ai-input-wrapper {
1757+
max-width: 100%;
1758+
}
1759+
1760+
/* Reset model selector absolute positioning in centered state */
1761+
.ai-panel:has(.ai-welcome-message) .ai-model-selector {
1762+
position: relative;
1763+
left: auto;
1764+
bottom: auto;
1765+
right: auto;
1766+
}
16441767
}
16451768

1646-
/* --- AI Status Indicator (in-panel) --- */
1769+
/* --- AI Status Indicator (in-panel) ---
1770+
Regular status now shown inline in header.
1771+
Download progress bar still uses standalone bar. */
16471772
.ai-status-bar {
16481773
padding: 8px 16px;
16491774
font-size: 12px;
16501775
color: var(--text-color);
16511776
opacity: 0.6;
1652-
display: flex;
1777+
display: none; /* Hidden by default — only shown for downloads */
16531778
align-items: center;
16541779
gap: 6px;
16551780
border-bottom: 1px solid var(--border-color);
@@ -1658,6 +1783,7 @@
16581783
}
16591784

16601785
.ai-status-bar.downloading {
1786+
display: flex; /* Override default display:none for downloads */
16611787
opacity: 1;
16621788
flex-direction: column;
16631789
align-items: stretch;

js/ai-assistant.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -942,9 +942,9 @@
942942
if (onError) { onError(msg.message); onError = null; }
943943
else {
944944
addAiStatusBar('error', msg.message);
945-
// Show clickable "Change API Key" link in status bar for auth errors
945+
// Show clickable "Change API Key" link in header status for auth errors
946946
if (msg.message.includes('API key') || msg.message.includes('Invalid') || msg.message.includes('401')) {
947-
const bar = aiPanel.querySelector('.ai-status-bar');
947+
const bar = document.getElementById('ai-header-status');
948948
if (bar) {
949949
const link = document.createElement('a');
950950
link.href = '#';
@@ -986,19 +986,19 @@
986986

987987
// --- Send to AI (routes to active model's worker) ---
988988

989-
// --- Status Bar ---
989+
// --- Status Bar (inline in header) ---
990990
function addAiStatusBar(status, text) {
991-
// Remove existing status bar
992-
const existing = aiPanel.querySelector('.ai-status-bar');
993-
if (existing) existing.remove();
994-
995-
const bar = document.createElement('div');
996-
bar.className = 'ai-status-bar';
997-
bar.innerHTML = `<span class="ai-status-dot ${status}"></span> ${text}`;
998-
999-
// Insert after header
1000-
const header = aiPanel.querySelector('.ai-panel-header');
1001-
header.insertAdjacentElement('afterend', bar);
991+
// Update the inline status inside the header
992+
const headerStatus = document.getElementById('ai-header-status');
993+
if (headerStatus) {
994+
headerStatus.innerHTML = `<span class="ai-status-dot ${status}"></span> ${text}`;
995+
}
996+
// Remove the standalone download progress bar when model is ready or errored
997+
// (keep it alive during 'loading' so the progress bar continues updating)
998+
if (status === 'ready' || status === 'error') {
999+
const allBars = aiPanel.querySelectorAll('.ai-status-bar');
1000+
allBars.forEach(bar => bar.remove());
1001+
}
10021002
}
10031003

10041004
// Show inline consent bar for Qwen download (falls through to popup)

js/ai-chat.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,11 +1086,35 @@
10861086
});
10871087
}
10881088

1089-
// --- Attach Button ---
1090-
if (aiAttachBtn && aiFileInput) {
1091-
aiAttachBtn.addEventListener('click', function () {
1092-
aiFileInput.click();
1089+
// --- Unified Attach Button (merged attach + screenshot) ---
1090+
if (aiAttachBtn) {
1091+
aiAttachBtn.addEventListener('click', function (e) {
1092+
e.stopPropagation();
1093+
var menu = document.getElementById('ai-attach-menu');
1094+
if (menu) {
1095+
var isOpen = menu.classList.contains('active');
1096+
menu.style.display = isOpen ? 'none' : 'flex';
1097+
menu.classList.toggle('active', !isOpen);
1098+
}
10931099
});
1100+
}
1101+
1102+
// "Attach File" menu item opens file picker
1103+
document.addEventListener('click', function (e) {
1104+
if (e.target.closest('#ai-attach-file-item')) {
1105+
var menu = document.getElementById('ai-attach-menu');
1106+
if (menu) { menu.style.display = 'none'; menu.classList.remove('active'); }
1107+
if (aiFileInput) aiFileInput.click();
1108+
return;
1109+
}
1110+
// Close menu on outside click (but not if clicking the toggle button itself)
1111+
if (!e.target.closest('#ai-attach-menu') && !e.target.closest('#ai-attach-btn')) {
1112+
var menu2 = document.getElementById('ai-attach-menu');
1113+
if (menu2) { menu2.style.display = 'none'; menu2.classList.remove('active'); }
1114+
}
1115+
});
1116+
1117+
if (aiFileInput) {
10941118
aiFileInput.addEventListener('change', function () {
10951119
if (aiFileInput.files && aiFileInput.files.length > 0) {
10961120
addFilesToPending(aiFileInput.files);

0 commit comments

Comments
 (0)