Commit Graph
33 Commits
Author SHA1 Message Date
yiqi-017 913ec672a5 @
feat(desktop): persist token usage history to local file instead of localStorage

Token stats (history + snapshot) now stored in temp/desktop_token_history.json
via GET/POST /token-history endpoints, ensuring cross-browser consistency.
@
2026-05-24 15:38:59 +08:00
yiqi-017 a045524f84 feat(desktop):
>>   move session persistence from localStorage to backend API
>>
>>   Sessions are now loaded from GET /sessions and metadata updates (pin,
>>   title) go through PATCH /session/{sid}, eliminating cross-browser
>>   isolation issues between Chrome and Safari.
2026-05-24 15:12:38 +08:00
wjl2023anddd3xp 63f8018be0 feat(desktop): frontend enhancements v2 — IME, fold-by-turn, copy buttons, smart scroll (#11)
Redo of #8 on top of latest main (97f613d), keeping all merged work intact:
- F4 IME: Enter ignores composition (isComposing / keyCode 229) so CJK input
  confirmation no longer fires a send
- F1 copy buttons unified to SVG clipboard/check icons
- F5 assistant bubble width fills the conversation column
- F3 fold history by turn (last turn flat); tool-call/thinking regex aligned
  with agent_loop streaming
- F2 per-bubble "copy this turn" (last turn only, strips <summary>)
- F6 smart scroll: only follow when isNearBottom(80px); typewriter rerender
  preserves user-expanded <details>
- F7 fold header gets the turn's <summary> as subtitle

Verified to keep #5 interrupt-and-resend, the 97f613d stop-button behavior,
#6 image bubble, and #10 file cards untouched. No hardcoded colors/strings.

DEV_NOTEBOOK.md from the PR is intentionally dropped — it's wjl2023's personal
dev journal (local paths, fork notes), not project documentation.

Closes #11

Co-Authored-By: dd3xp <zhangjingming131@gmail.com>
2026-05-24 02:57:05 +08:00
dd3xp 57cf4d6632 feat(desktop): run-state becomes display-only; send key turns into a stop button while running
The top-left run-state is now a pure status indicator (green=ready, red=running)
and no longer handles clicks. Stopping the current turn moves to the send key:
while the agent is running the send button turns into a red stop square — clicking
it cancels the current turn (ChatGPT/Claude style). The Enter key still submits, so
"type while running + Enter" keeps lyx's interrupt-and-resend behavior (#5) intact.

- setBusy toggles .is-stop on the send button + aria-label (stop/send)
- send click: cancelPrompt() when busy, submitInput() otherwise
- remove run-toggle click handler + .stopped state; .run-state.busy goes red
- .send.is-stop: red bg + white rounded square (hide arrow); i18n act.send
- cache bust styles v19->20, app v25->26
2026-05-23 23:38:34 +08:00
顾祎炜andYiwei Gu 8783914077 feat(chat): file attachment cards in user message + click-to-open (#10)
Non-image attachments now render as Gemini/ChatGPT-style file cards above
the user text bubble (icon disc + bold filename + kind subtitle). Click on
a file card opens it via the system default app — bridge `/path/open` gains
a `kind=upload` mode that whitelists \`_WEB_UPLOAD_DIR\` to prevent the
frontend from opening arbitrary local paths.

Composer thumb-strip uses the same card style for non-image pending files,
also click-to-open. Image thumbs (48×48) unchanged.

Co-authored-by: Yiwei Gu <yiwei.gu@sap.com>
2026-05-23 19:42:58 +08:00
dd3xp 26872bd39a style(desktop): dark md-link mix anchor uses --txt-strong, not hardcoded #fff
PR #9 left the dark-mode markdown link color as
color-mix(... var(--blue) 42%, #ffffff). Route the white anchor through
var(--txt-strong) (which is #ffffff in dark) so no hex literal sits in a
selector. Bump styles.css cache bust v17 -> v18.
2026-05-23 19:05:08 +08:00
lyxanddd3xp 1a54f1788a feat(desktop): chat font-size control + dark-mode Markdown readability (#9)
Settings gains a 10–20px chat font stepper (default 14, slider with aria),
applied only to chat bubbles + composer via --chat-font; pre/code/fold switch
to em-relative sizing so they scale with it. Dark mode swaps highlight.js to
the github-dark theme and adds link/code/pre/katex/table/blockquote styling so
markdown is legible on dark backgrounds.

Persistence rides the same ui-prefs mechanism as #7 (ui.fontSize in
~/.ga_desktop_settings.json via /config), so this PR supersedes #7 — it
contains the full server-first persistence layer (bootUiFromDom / persistUiPrefs
/ syncBootCache) plus fontSize. Keeps #5's interrupt-on-resend behavior intact.

Closes #9

Co-Authored-By: dd3xp <zhangjingming131@gmail.com>
2026-05-23 19:03:52 +08:00
顾祎炜andYiwei Gu e31528f58c style(chat): Gemini-style image bubble (#6)
* 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.

* style(chat): Gemini-style image bubble — image floats free above text bubble

Image attachments now render as a fixed-size 120x120 rounded thumbnail above
the text bubble (separate elements, both right-aligned), instead of being
embedded inside the bubble. Lightbox click-to-preview rebound to .user-imgs.

---------

Co-authored-by: Yiwei Gu <yiwei.gu@sap.com>
2026-05-23 17:42:35 +08:00
dd3xp 914e632fa2 style(desktop): send-button busy spinner uses --on-accent, not hardcoded white
PR #5 left the .send.is-busy::after spinner at rgba(255,255,255,.35) / #fff.
Route it through var(--on-accent) so it follows the accent foreground color
(white by default, dark on theme 7's yellow accent) instead of always white.
Bump styles.css cache bust v8 -> v9.
2026-05-23 17:25:57 +08:00
lyxanddd3xp 4bbfbd11ad fix(desktop): interrupt running turn on resend, lock composer + loading hint (#5)
Sending while a reply is still streaming now cancels the in-flight turn,
waits for the session to go idle, then submits the new prompt — instead of
silently dropping the new message. During the interrupt the composer is
locked, a sticky loading hint shows in the chat area, and the typed text +
pending attachments are preserved. Reuses the bridge cancel -> stopped-message
path so the interrupted turn's partial output is kept.

- interruptBeforeSend(): cancel + waitSessionIdle + min loading duration
- removeUsedPendingFiles(): only clear attachments actually consumed by send
- showChanToast gains an 'info' kind
- submitInput async + _submitInFlight guard; setComposerLocked / setMsgLoading

Closes #5

Co-Authored-By: dd3xp <zhangjingming131@gmail.com>
2026-05-23 17:25:01 +08:00
dd3xp e2a67f0803 fix(upload): surface payload-too-large errors, reject empty files, use toast not chat-log
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.
2026-05-21 23:39:39 +08:00
dd3xp 22d4654e85 fix(chat): post-merge cleanup for PR #4 rendering — i18n + theme vars + typewriter race
- 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
2026-05-21 19:52:07 +08:00
HYC-hsyanddd3xp 1534a93cd8 feat(chat): frontend rendering enhancements — markdown + LaTeX + code highlight + typewriter (#4)
Adapted from wjl2023's PR by HYC-hsy on top of latest main. Adds:
- Markdown rendering with LaTeX protect/restore + KaTeX
- Code blocks: highlight.js syntax coloring + per-block copy button
- Fold-block class differentiation (thinking/tool/result/turn)
- postRenderEnhance: hook applied after markdown render for both assistant messages and streaming drafts
- Typewriter streaming: time-based character reveal, ~400 chars/sec

Closes #4

Co-Authored-By: dd3xp <zhangjingming131@gmail.com>
2026-05-21 19:35:30 +08:00
dd3xp 5fe5401ec6 style(desktop): drop hardcoded colors/strings, remove placeholder page-subs
- 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
2026-05-21 17:46:47 +08:00
dd3xp e970ec5172 fix(desktop): persist stopped output, decouple user display from LLM prompt, anti-flicker boot
- 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)
2026-05-21 17:12:42 +08:00
顾祎炜andYiwei Gu 7b83ec0ee2 feat(chat): chat UI — Plan/Auto, presets, image/file upload, model dropdown (#3)
* 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>
2026-05-21 15:59:46 +08:00
yiqi-017 46fc1faaa7 feat(desktop): session persistence + token stats fixes + model pricing tooltip
- Sessions persist across refresh (localStorage metadata + bridge disk storage)
- Bridge restores LLM context on restart via /session/{sid}/restore endpoint
- Sort sessions: pinned first, then by most recent activity
- Fix token stats: remove double .json() parse, handle negative deltas after restart
- Add model pricing tooltip on hover in token statistics page
- localStorage only stores session metadata (not messages) to prevent quota overflow
- Remove duplicate tokPollBridge() call in pollSession
2026-05-21 15:05:37 +08:00
weaselkkkandCursor ea69be0c73 Merge branch 'feat/message_channel' into main
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 14:42:31 +08:00
weaselkkk 7ce4778903 fix: update setting board in message turnnel page 2026-05-21 14:32:49 +08:00
dd3xp 4f18d6e9e7 fix(bridge): protocol now required for add-model (no apibase auto-guess); UX: segmented control + flag SVGs
- _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.
2026-05-21 12:59:19 +08:00
weaselkkk 3fcf12e707 feat: complete status panel page 2026-05-21 12:29:07 +08:00
China Nikocado Avocado 05661574af Merge pull request #2 from LLYYXX/feat/settings-page
实现设置页能力:用户可在 UI 内管理 LLM 模型配置、切换语言与外观
2026-05-21 11:50:24 +08:00
lyx 1a83c28ccf refactor: 压缩 bridge 与主题 CSS 行数
合并 mykey 块改写 helper,--blue 改由 JS 设置,去掉重复 CSS 映射规则。
2026-05-21 03:29:37 +08:00
lyx 562b47f3b6 feat(desktop): 复杂主题色与外观调整
浅/深外观、素色模式、8 色色盘、语言列表 UI 与设置表单样式优化。
2026-05-21 03:29:37 +08:00
lyx 852ab63d43 feat(desktop): 设置页 — 弹窗与模型 CRUD
设置弹窗、模型列表增删改、表单必填/选填提示;bridge 提供 model-profiles CRUD 与 llmNo 传递。
2026-05-21 03:29:37 +08:00
dd3xp 59c4efb89c feat: collapsible/resizable left sidebar + pin marker + preserve partial on stop
- 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).
2026-05-21 02:51:45 +08:00
weaselkkk e6379b7f0c feat: complete message channel page 2026-05-21 00:35:50 +08:00
yiqi-017 c2a7707d1d feat: token stats page with per-prompt tracking, time filter & cost estimation
- 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
2026-05-20 18:38:57 +08:00
yiqi-017 94b83d4226 Revert "feat: token stats with persistent history, time filter, pagination & model-based cost"
This reverts commit 011824860d.
2026-05-20 15:40:42 +08:00
yiqi-017 5180eca7d6 feat: token stats with persistent history, time filter, pagination & model-based cost
- 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)
2026-05-20 15:14:50 +08:00
yiqi-017 3ae3b065d4 feat: session search, collapsible & resizable right panel
- 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)
2026-05-20 03:08:42 +08:00
dd3xp 275856dcc3 i18n: externalize all UI text (zh/en, empty defaults); separate dev annotation layer into annot.css/annot.js
- 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.
2026-05-20 02:10:14 +08:00
dd3xp 5032fe7091 GenericAgent Desktop: mockup-based client wired to bridge
Initial snapshot. Desktop frontend (frontends/desktop/static/) rebuilt as the served client: 3-card UI, sidebar nav, preset functions, sessions, streaming via HTTP poll + WS notify, markdown, modals (preset/add-model/settings), theme-variable styling. mykey.py excluded (gitignored).
2026-05-20 00:29:22 +08:00