Belt-and-suspenders: inline onkeydown on textarea ensures Enter
works regardless of JS event listener registration order, plus
a visible Send button as clickable fallback.
- Audio starts playing as soon as ~1KB arrives instead of waiting for full download
- Browser-aware format detection (webm+opus for Firefox, mp3 for Chrome)
- Falls back to buffered download if MediaSource not supported
- Updated speakText() and speakTextQueued() to use streaming
- Cache blobs instead of Audio objects for cleaner replay
Correct the artifact implementation to match Unsandbox API:
- Request parameter: return_artifact (boolean, singular)
- Response field: artifacts (array, plural)
- Data field: content_base64 (not data/content)
Changes:
- Backend: Changed artifacts to return_artifact in request body
- Frontend: Use return_artifact in execute request
- Frontend: Look for artifacts array in response (not artifact singleton)
- Frontend: Decode content_base64 field (not data/content)
- Frontend: Use filename field (primary) over name
- Remove debug console.log statements
- Update CLAUDE.md with correct API contract
The API parameter is singular but response is plural - this is intentional
design by Unsandbox API. Request enables artifact collection, response
returns array of generated artifacts.
Add console.log statements to see actual artifact object structure:
- Log artifacts array when received
- Log each artifact object and its keys
- Log download attempt with artifact keys and data presence
This will help diagnose why artifacts show metadata but fail on download/view.
Artifacts are returned as base64-encoded data directly in the job response
payload, not as URLs. Previous implementation incorrectly tried to fetch
artifacts from URLs via a proxy endpoint.
Changes:
- Remove /api/code/artifacts proxy endpoint (not needed)
- Update downloadArtifact() to decode base64 and trigger download
- Update viewArtifact() to decode base64 and display inline:
* Images: rendered as data URLs (data:image/png;base64,...)
* Videos: decoded to blob URLs with controls
* Text: decoded and displayed in <pre> blocks
- Update CLAUDE.md with correct artifact format and implementation details
- Add artifact response format example showing base64 data structure
Artifact format in response:
{
"artifacts": [{
"name": "output.png",
"type": "image/png",
"data": "base64string...",
"size": 12345
}]
}
- Add helper functions to extract first image and generate description from chat messages
- Update base.html and index.html with og:image, og:description, twitter:card meta tags
- Chat rooms now use first image from messages for unfurling instead of site logo
- Description shows first 500 chars of room content for link previews
- Add default OG image (black with OC) as fallback when no image found
- is_base64_image() now detects any base64 image (jpeg, png, gif, webp, etc.)
not just the hardcoded jpeg/png patterns
- Added [Vision] debug logging to trace image fetch/save flow
URLs like eBay images contain % and _ which have special meaning in SQL
LIKE patterns. Added escape_like_pattern() helper to escape these chars.
This fixes saved base64 images not being found on refresh.
- Find the most recent image message
- Include full text conversation history for context
- Only send base64 for the most recent image
- Skip older images entirely (no useful text in them)
Multiple large base64 images can overwhelm the vision model. Now we:
1. First pass: identify the 2 most recent image messages
2. Second pass: build chat history, skipping older images
Remove race condition where syncDropdownsAndQueryString() was called before
dropdowns were populated, causing empty values to overwrite localStorage.
Model and voice restoration now happens only in populateModelDropdown() and
populateVoiceDropdown() after async fetch completes.
- Add find_saved_base64_for_url() to lookup existing saved base64 in DB
- Check for existing saved version before fetching external images
- Prevent duplicate saves in save_fetched_image_as_message()
- Use in-memory cache for faster repeated lookups within same session
- Fix Message.query.filter_by() chain mocking in test_bedrock_streaming_protocol
and test_streaming_protocol_backwards_compatibility
- Add is_gevent_patched() helper to detect monkey patching
- Skip bedrock test at runtime when gevent has already patched (avoids RecursionError)
- save_fetched_image_as_message() creates a new message with base64 content
- build_message_content() now accepts room_id to persist fetched images
- Fetched images saved with username "system" (added to SYSTEM_USERS)
- Prevents re-fetching: once fetched, base64 version is in chat history
- extract_external_image_url() to find http/https URLs in img tags
- fetch_external_image_as_base64() fetches via CORS proxy, converts to base64
- build_message_content() now handles both base64 and external image URLs
- Caches fetched images to avoid re-fetching
- Uses cors-proxy.uncloseai.com for robots.txt compliance
- Fetch external images through proxy.unturf.com (respects robots.txt)
- Convert fetched images to base64 for vision API
- Cache both fetched images and descriptions
- Shows "Fetching image..." for external URLs, "Generating description..." for base64
- Handles 403 responses when blocked by robots.txt
Backend:
- Track VISION_MODELS list at startup from available endpoints
- Add is_vision_model() to detect vision-capable models (*-vl*, *vision*, gpt-4o)
- Add extract_base64_from_img_tag() and build_message_content() helpers
- Modify chat_gpt() to include base64 images for vision models
- Add GET /vision endpoint for vision availability status
- Add POST /vision/describe endpoint for image alt-text generation
Frontend:
- Check vision availability on page load via /vision
- Add hover event delegation on chat images
- On hover: call vision model, cache result, set img.title and img.alt
- Shows cursor:wait while loading description
Update code execution error detection to use stderr exclusively
instead of checking both stdout and stderr. This aligns with the
Unsandbox API's proper stream separation where:
- stdout is for program output
- stderr is for errors/warnings
Changes:
- Remove stdout from error detection logic
- Only trigger auto-fix when exit_code != 0 AND stderr is non-empty
- Send only stderr to /api/fix-code endpoint
This prevents false positives where stdout contains normal output
that was previously being treated as error content.
- Use codeBlock.dataset.autoExecAttempt to pass attempt number
- executeCodeBlock transfers attempt to results container
- No longer pre-creates empty results container
- Clean up data attribute after use
- Add null check for classList before calling contains()
- Handles case where nextSibling might be a text node
- Prevents 'classList is undefined' error