Skip to content

Commit 218d9f3

Browse files
committed
feat: Screenshot to AI — capture page, screen, or upload image for AI analysis
- New js/ai-screenshot.js module (~300 lines) with full capture pipeline - Camera button (📷) injected into AI panel input bar via self-healing logic - Three capture modes: Capture Page (html2canvas), Capture Screen (getDisplayMedia), Upload Image - Fixed black-screen bug: video must be in DOM, wait for timeupdate event before frame grab - CSS-independent dropdown using inline style.display (cache-proof) - AI panel hidden during page capture for clean shot, restored after - Auto-send after 900ms with 150ms inner gap to ensure attachment is ready - Module registered in src/main.js with try/catch for resilient loading - Styles added to css/ai-panel.css; button HTML added to js/modal-templates.js - README release notes updated; CHANGELOG-screenshot-to-ai.md added
1 parent 16d53bf commit 218d9f3

6 files changed

Lines changed: 569 additions & 0 deletions

File tree

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-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 |
547548
| **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 |
548549
| **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 |
549550
| **2026-03-31** | | 🎥 **RecStudio Teleprompter & Light Mode Fix** — fixed teleprompter textarea not expanding to fill panel height (changed container `overflow: auto``hidden`, set textarea `height: 100%` with `min-height: 0`); reduced text padding for maximum usable area; fixed "Share screen" button rendering as solid black block in light/day mode (added `background: #fff` override) |
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Screenshot to AI — Capture Page or Screen and Chat with AI
2+
3+
**Date**: 2026-03-31
4+
5+
- New `📷` camera button injected into the AI chat input bar for screen/page capture
6+
- Dropdown menu with three capture modes: Capture Page, Capture Screen, Upload Image
7+
- Capture Page uses `html2canvas` to snapshot the current document; AI panel is hidden during capture for a clean shot
8+
- Capture Screen uses `getDisplayMedia` (browser screen-share picker) with live frame extraction from a hidden `<video>` element
9+
- Fixed black-screen capture bug: video element must be in DOM for GPU decoder to render real frames
10+
- Fixed black-screen capture bug: wait for `timeupdate` event instead of single `requestAnimationFrame` before grabbing frame
11+
- Fixed black-screen capture bug: canvas dimensions read from both `track.getSettings()` and `video.videoWidth/videoHeight` with explicit `> 0` guard
12+
- Captured image injected into `pendingAttachments` via `_ai.addFilesToPending()` and AI panel opened automatically
13+
- Auto-send fires after 900ms (covers panel open animation + async attachment processing) with a nested 150ms inner gap
14+
- Auto-send only pre-fills input if it is currently empty (preserves existing user text)
15+
- Self-healing button injection: `injectButtonIfMissing()` runs at module load, on AI-panel toggle clicks, and via 2-second fallback poll
16+
- Dropdown toggle uses inline `style.display` (not CSS classes) so it works even with stale cached `ai-panel.css`
17+
- All dropdown menu items use inline styles so the UI is fully CSS-cache independent
18+
- Flash overlay shown during capture via `showFlash()` / `hideFlash()` with `ai-screenshot-flash` element
19+
- Vision model warning toast if selected model does not support image input (non-blocking)
20+
- Module registered in `src/main.js` inside a `try/catch` wrapper to prevent initialization failures
21+
- Styles for wrapper, dropdown, and flash overlay added to `css/ai-panel.css`
22+
- Camera button HTML added to AI panel template in `js/modal-templates.js`
23+
24+
---
25+
26+
## Summary
27+
28+
Adds a **Screenshot to AI** feature: users click the 📷 camera button in the AI chat input bar, choose to capture the current page, share a specific screen/window, or upload an image, and the captured frame is automatically attached to the AI chat and sent for analysis.
29+
30+
---
31+
32+
## 1. Self-Healing Camera Button Injection
33+
**Files:** `js/ai-screenshot.js`, `js/modal-templates.js`
34+
**What:** The camera button is included in the AI panel HTML template. In addition, `injectButtonIfMissing()` dynamically injects the button at runtime whenever `#ai-file-input` is found but `#ai-screenshot-btn` is missing — handling stale browser caches. It runs at module load, on AI-toggle click events (capture phase listener), and via a 2-second `setTimeout` fallback.
35+
**Impact:** The button appears reliably regardless of caching behaviour.
36+
37+
## 2. CSS-Independent Dropdown
38+
**Files:** `js/ai-screenshot.js`
39+
**What:** The dropdown menu and all its items use inline `style` attributes instead of relying on `.active` CSS class toggling. The `show`/`hide` is managed by directly setting `menu.style.display`.
40+
**Impact:** The dropdown works correctly even when `ai-panel.css` is served from a stale browser cache that predates this feature.
41+
42+
## 3. Capture Page (html2canvas)
43+
**Files:** `js/ai-screenshot.js`
44+
**What:** `capturePageScreenshot()` temporarily removes the AI panel from the DOM, waits 300ms for the close animation, runs `html2canvas` on `document.body` with `useCORS: true` and `scale: min(devicePixelRatio, 2)`, then restores the panel. Excluded elements: `#ai-panel`, `#ai-panel-overlay`, `.toast-container`, `#ai-screenshot-flash`.
45+
**Impact:** Produces a clean, full-page screenshot without the AI panel visible in the image.
46+
47+
## 4. Capture Screen — Black Screen Fix
48+
**Files:** `js/ai-screenshot.js`
49+
**What:** `captureScreenScreenshot()` calls `getDisplayMedia`, appends the `<video>` element to DOM (hidden, `1×1px`, fixed off-screen), waits for `onloadedmetadata` + `video.play()`, then waits for the `timeupdate` event (fires only when real pixel data flows), then waits 2 additional `requestAnimationFrame` ticks, captures the frame to a canvas, stops tracks, and removes the video element.
50+
**Impact:** Eliminates completely black screenshots caused by capturing before the GPU video decoder had rendered any frames. `timeupdate` is the browser-guaranteed signal that real frame data is available.
51+
52+
## 5. Auto-Send Timing Fix
53+
**Files:** `js/ai-screenshot.js`
54+
**What:** Changed auto-send `setTimeout` from 400ms to 900ms, with an inner 150ms gap before clicking the send button. Added a guard that skips pre-filling the input if the user has already typed something.
55+
**Impact:** Ensures the attachment is fully processed by `addFilesToPending` before the send button fires; prevents clobbering existing chat input.
56+
57+
## 6. Module Registration
58+
**Files:** `src/main.js`
59+
**What:** `await import('../js/ai-screenshot.js')` wrapped in `try/catch` in the Phase 3d module loading block.
60+
**Impact:** A failure in `ai-screenshot.js` (e.g., missing vendor dependency) does not crash the rest of the application startup.
61+
62+
## 7. Styles
63+
**Files:** `css/ai-panel.css`
64+
**What:** Added styles for `.ai-screenshot-wrapper`, `.ai-screenshot-btn`, `.ai-screenshot-menu`, `.ai-screenshot-item`, and `.ai-screenshot-flash` (full-screen flash overlay with `bi-camera` icon and message text).
65+
**Impact:** Provides the visual design for the camera button, dropdown, and capture feedback overlay.
66+
67+
---
68+
69+
## Files Changed (4 total)
70+
71+
| File | Lines Changed | Type |
72+
|------|:---:|------|
73+
| `js/ai-screenshot.js` | +300 | New module — full screenshot capture pipeline |
74+
| `css/ai-panel.css` | +148 | New styles for button, dropdown, and flash overlay |
75+
| `js/modal-templates.js` | +24 | Camera button HTML in AI panel input template |
76+
| `src/main.js` | +6 | Module registration with try/catch |

css/ai-panel.css

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,4 +2344,147 @@
23442344

23452345
.ai-source-link i {
23462346
font-size: 9px;
2347+
}
2348+
2349+
/* ========================================
2350+
SCREENSHOT → AI (ai-screenshot.js)
2351+
======================================== */
2352+
2353+
/* Camera button — same size as attach button, no rotation on icon */
2354+
.ai-screenshot-btn i {
2355+
transform: none !important;
2356+
font-size: 15px;
2357+
}
2358+
2359+
/* Active / capturing state */
2360+
.ai-screenshot-btn.capturing {
2361+
opacity: 1;
2362+
color: #667eea;
2363+
border-color: #667eea;
2364+
background: rgba(102, 126, 234, 0.1);
2365+
animation: screenshotPulse 0.8s ease infinite;
2366+
}
2367+
2368+
@keyframes screenshotPulse {
2369+
0%, 100% { box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.4); }
2370+
50% { box-shadow: 0 0 0 6px rgba(102, 126, 234, 0); }
2371+
}
2372+
2373+
/* Dropdown menu */
2374+
.ai-screenshot-menu {
2375+
position: absolute;
2376+
bottom: calc(100% + 8px);
2377+
left: 50%;
2378+
transform: translateX(-50%);
2379+
background: var(--bg-color);
2380+
border: 1px solid var(--border-color);
2381+
border-radius: 12px;
2382+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
2383+
padding: 6px;
2384+
min-width: 180px;
2385+
z-index: 2000;
2386+
display: none;
2387+
flex-direction: column;
2388+
gap: 2px;
2389+
animation: screenshotMenuIn 0.15s ease;
2390+
}
2391+
2392+
.ai-screenshot-menu.active {
2393+
display: flex;
2394+
}
2395+
2396+
@keyframes screenshotMenuIn {
2397+
from { opacity: 0; transform: translateX(-50%) translateY(6px); }
2398+
to { opacity: 1; transform: translateX(-50%) translateY(0); }
2399+
}
2400+
2401+
.ai-screenshot-item {
2402+
display: flex;
2403+
align-items: center;
2404+
gap: 10px;
2405+
width: 100%;
2406+
padding: 9px 12px;
2407+
border: none;
2408+
background: none;
2409+
color: var(--text-color);
2410+
font-size: 13px;
2411+
cursor: pointer;
2412+
border-radius: 8px;
2413+
transition: all 0.15s;
2414+
text-align: left;
2415+
}
2416+
2417+
.ai-screenshot-item:hover {
2418+
background: rgba(102, 126, 234, 0.08);
2419+
color: #667eea;
2420+
}
2421+
2422+
[data-theme="dark"] .ai-screenshot-item:hover {
2423+
color: #a5b4fc;
2424+
background: rgba(165, 180, 252, 0.1);
2425+
}
2426+
2427+
.ai-screenshot-item i {
2428+
font-size: 15px;
2429+
width: 20px;
2430+
text-align: center;
2431+
flex-shrink: 0;
2432+
opacity: 0.7;
2433+
}
2434+
2435+
.ai-screenshot-item:hover i {
2436+
opacity: 1;
2437+
}
2438+
2439+
/* Screenshot image bubble in chat */
2440+
.ai-msg-screenshot-thumb {
2441+
max-width: 100%;
2442+
max-height: 200px;
2443+
border-radius: 8px;
2444+
border: 1px solid var(--border-color);
2445+
object-fit: contain;
2446+
margin-bottom: 6px;
2447+
display: block;
2448+
}
2449+
2450+
/* Full-screen flash / feedback overlay */
2451+
.ai-screenshot-flash {
2452+
position: fixed;
2453+
inset: 0;
2454+
background: rgba(0, 0, 0, 0.6);
2455+
backdrop-filter: blur(4px);
2456+
z-index: 99999;
2457+
display: flex;
2458+
align-items: center;
2459+
justify-content: center;
2460+
opacity: 0;
2461+
pointer-events: none;
2462+
transition: opacity 0.25s ease;
2463+
}
2464+
2465+
.ai-screenshot-flash.active {
2466+
opacity: 1;
2467+
pointer-events: auto;
2468+
}
2469+
2470+
.ai-screenshot-flash-inner {
2471+
display: flex;
2472+
flex-direction: column;
2473+
align-items: center;
2474+
gap: 14px;
2475+
color: #fff;
2476+
font-size: 15px;
2477+
font-weight: 500;
2478+
text-align: center;
2479+
padding: 28px 40px;
2480+
background: rgba(255, 255, 255, 0.08);
2481+
border: 1px solid rgba(255, 255, 255, 0.15);
2482+
border-radius: 18px;
2483+
backdrop-filter: blur(8px);
2484+
}
2485+
2486+
.ai-screenshot-flash-inner i {
2487+
font-size: 36px;
2488+
color: #a5b4fc;
2489+
animation: screenshotPulse 1.2s ease infinite;
23472490
}

0 commit comments

Comments
 (0)