Root cause of "uploads silently land as 0-byte __file": aiohttp default body
limit is 1 MiB; PDFs almost always exceed it, request.json() raises
HTTPRequestEntityTooLarge, the generic read_json() swallows it as bare
Exception, upload_handler then sees an empty dict and happily writes an
empty file with name='file', returning ok=true. Front-end thinks the
upload worked.
Bridge:
- Set web.Application(client_max_size=1MB) explicitly so the limit is
visible at the call site (kept at 1MB intentionally for now; bump later
if larger uploads are needed)
- upload_handler reads body itself instead of read_json so it can catch
HTTPRequestEntityTooLarge and HTTPBadRequest and return a real error
to the client instead of silently writing 0 bytes
- Reject blob == b'' as a second line of defence
Frontend:
- addFiles skips files with size === 0 (handles drag-folder ghost File
and edge-case clipboard items) with a dedicated 'upload.empty' toast
- 4x showSystem(...) in addFiles -> showChanToast(title, detail, 'err').
Upload failures should be transient toasts, not permanent system
messages glued into the chat transcript.
- Replace hardcoded 'Copy'/'Copied!'/'TeX' in postRenderEnhance with t('act.copy/copied/copyTex'); add zh/en keys
- Fix var(--accent) reference (undefined) in .cursor-blink -> var(--blue)
- Add dark-mode overrides for --fold-thinking/tool/result border+bg so fold blocks don't glow pastel on dark backgrounds
- Typewriter: timer now reads r.draftText each tick (was a stale closure on full -> never picked up streamed extensions); drop the visible || full fallback that immediately revealed the entire content on first paint
- Move pricing-tooltip text in modelPriceTip to i18n keys (tok.priceInput/Output/CacheW/CacheR/pricingUnknown), drop lang === 'zh' ? : ternaries
- Replace remaining hex literals in selectors with CSS vars: chan-log-pre and mykey-editor backgrounds now respect --field-bg / --card (dark mode no longer shows white panels)
- Introduce --sample-light-* / --sample-dark-* vars for appearance preview cards (intentionally static, do not follow current theme)
- Remove placeholder page-sub on Channels / Status / Token pages (annotation copy, never meant to ship); keep on Collab page since that page is still unimplemented
- Bridge cancel() saves partial assistant content as a stopped message so the truncated output survives a refresh
- submit_prompt accepts a separate display field; UI now shows the original user input while the bridge stores the composed Plan/Auto SOP + expanded file paths for LLM/restore_context
- Boot-time chat-start is hidden via early data-boot-has-sessions attribute when localStorage has sessions, removing the welcome-page flash on refresh
- Conv-item click on non-chat pages now jumps back to the chat page with that session selected
- Remove dead refresh button from topbar (no handler, no id)
* i18n: add empty keys for chat-page button text (chip/upload/model)
* html: bind chat composer chips to i18n (plan/auto/upload/model)
* feat(chat): add Plan/Auto sticky toggle state with localStorage hydration
* style(chat): chip.on toggle, dropdown menu, image thumb strip
* style(chat): anchor .composer for .ga-menu dropdown
* feat(chat): image upload via hidden file input + thumb strip
* feat(chat): sendPrompt prepends Plan/Auto SOP, passes pending images
* fix(chat): drop hardcoded '[image]' session-title fallback
* feat(chat): model chip dropdown menu (replaces click-to-cycle)
* i18n: fill chat composer + Plan/Auto preset prompt values
* feat(chat): drag-drop + paste image upload (chat panel overlay)
* feat(chat): image lightbox + custom/built-in preset management
- Image lightbox: click composer thumbnails or sent message images to preview full-screen
- Custom presets: add via modal, delete via hover ×, persisted in localStorage (ga_custom_presets)
- Built-in preset hide: × on builtin cards hides them (ga_hidden_builtins); "restore defaults" button in preset-modal head brings them back
- Plan/Auto mutex: clicking one turns off the other
- Unified preset rendering: makeCardEl + renderAllPresets covers both grids (chat-start and preset-modal)
* feat(upload): tuiapp_v2-style file upload (any file, path placeholders)
Bridge:
- POST /upload: save dataUrl → <tempdir>/ga_web_uploads/<uuid>__<name>, return abs path
- DELETE /upload: remove uploaded file (path-traversal guarded)
- Wipe ga_web_uploads/ on bridge startup (no orphan accumulation)
Frontend:
- Pick / drag / paste any file → POST /upload → insert [Image #N] or [File #N] placeholder in textarea
- Attachment strip shows image thumbnail or filename chip with × to remove
- × also fires DELETE /upload + strips placeholder from textarea
- sendPrompt expands placeholders to bare paths in-place; no longer sends images field
- Limits: 50 MB/file, 10 files/batch
- i18n: upload.* keys reworded for "file" (was "image"); added upload.failed
* fix(chat): drop dead langSel block, hook renderAllPresets into selectLang
Post-merge cleanup: feature/chat-ui's old langSel <select> handler was
left behind after origin/main switched to the lang-list radio control,
causing 'langSel is not defined' at top level which aborted app.js
before applyI18n() could run (entire UI rendered blank).
Also wire renderAllPresets() into selectLang() so dynamically-built
preset cards refresh on language change.
* fix(chat): drop fake 'auto' model menu item, align llmNo with profile id
The model dropdown injected a synthetic 'auto-select' row at data-llmno=0
and offset real profiles by +1 (no = i+1). Backend list_llms() / next_llm
both index from 0, so the labels were off by one — clicking 'auto'
selected the first real profile, clicking the first real label selected
the second, and clicking the last wrapped via modulo back to the first.
Render real profiles only, using each profile's id directly as data-llmno.
Menu click now routes through selectModel() so the choice persists to
localStorage and bridge config.
---------
Co-authored-by: Yiwei Gu <yiwei.gu@sap.com>
- _next_native_var now requires explicit protocol ('oai' or 'claude'); raises if missing. Removes the silent 'native_claude_config' default that wrongly routed all OAI-compatible relays through Anthropic protocol.
- Add-model form: replace native <select> with a segmented control (2 radio buttons styled as pills) since browsers can't style native dropdown panels. Required validation kept.
- Language list: inline SVG flags before labels (China / USA, per request), uses official flag colors. Reliable on Windows where flag emojis render inconsistently.
- i18n: model.protocol/protocolPick/protocolOai/protocolClaude in zh+en.
- Edit mode now backfills protocol radio from varName.
- Add sb-toggle/sb-resize mirroring rp-toggle/rp-resize; refactor into shared bindResize().
- Tuck both toggles into their panel's inner edge via transform; lighter color + opacity:.6 (uniform with collapsed state).
- Narrow body.gap 12→6 so cards sit closer, main effectively wider.
- Pin marker: tilted pin SVG (rotate 45deg, --blue) before title when sess.pinned; toggle pin/unpin via ⋯ menu with dynamic i18n label (ctx.pin/ctx.unpin). Unpin places session at top of the non-pinned area.
- Preserve streamed partial on stop: pollSession exit no longer drops draft text; finalizes it as an assistant message with [stopped] marker (i18n).
- Add /token-stats bridge endpoint exposing cost_tracker data + model name
- Frontend localStorage persistence for per-prompt token records (WS-triggered)
- Time range filter (datetime-local), pagination, expandable session details
- Model-based cost estimation (Claude/GPT/DeepSeek price table with cache rates)
- All UI text via i18n, all colors via CSS variables
- Persist per-prompt token usage to ~/.GenericAgent/token_history.json
- Add GET /token-stats API endpoint with since/until filtering
- Token page: real data, expandable per-prompt details with model & cost
- Model price table for accurate cost estimation (GPT/Claude/DeepSeek/etc)
- Time range filter (datetime-local) with reset button
- Pagination (15 sessions per page)
- Stat cards (total/today/cost) follow time filter, today always absolute
- Migrate Rust settings path to ~/.GenericAgent/ga_desktop_settings.json
- Fix cost_tracker Python 3.9 compat (from __future__ import annotations)
- Wire up search input to filter sessions by title/message content
- Add toggle button to collapse/expand the session panel
- Add drag handle for resizing panel width (160–400px)
- All static UI text via data-i18n + I18N{zh,en}; runtime applyI18n; language switch in settings, persisted.
- Empty defaults in HTML (no source language baked in).
- Annotation/dev scaffolding fully extracted to annot.css + annot.js (runtime-injected); index.html/styles.css/app.js carry zero annotation footprint; deletable via 2 include lines.
- Preset cards decoupled via data-preset; model chip default emptied.