Commit Graph
63 Commits
Author SHA1 Message Date
Liang Jiaqing 277c4859a4 feat(desktop): optional quick-access helper hook + trim factor
- Probe optional helper availability; show button only if present

- Bridge route for optional helper; i18n + cache bump

- llmcore: maxlen_multiplier factor 0.85->0.75
2026-07-21 11:15:21 +08:00
dd3xp 1080f9ee0a revert desktop strict empty-turn handling 2026-07-17 03:02:28 -04:00
dd3xp 58cb4cdc7c fix desktop empty turn handling 2026-07-13 23:50:14 -04:00
yiqi-017 8ea039724f feat(desktop): thin-shell over external core via GA_ROOT (方案三)
Run the bundle's own bridge + frontend but drive an external GenericAgent
核 injected via GA_ROOT, instead of swapping to the external bridge or
copying files into 本体. New UI + external核/data, no mutation, logs
naturally unified with the核's other frontends.

- desktop_bridge.py: find_default_ga_root() honors --ga-root / GA_ROOT
  (validated), else derives as before; conductor now spawned from the
  BUNDLE (APP_DIR/conductor.py) with GA_ROOT in env; scheduler stays核-side
- conductor.py: ROOT prefers GA_ROOT env, else own-path derivation
- lib.rs: override no longer changes project_dir (always bundle bridge);
  sanitize_bundle_env injects GA_ROOT; takeover compares effective ga_root;
  set_ga_source drops file-copy sync and instead runs a contract probe
  before committing; removed copy_dir_all/sync_desktop_files
- ga_contract_probe.py (new): subprocess, introspect-only (no GenericAgent
  instantiation) symbol/signature check of the核's bridge/conductor/
  cost_tracker contract; doubles as a dependency check
- GA桌面壳-参考方案.md: plan + feasibility review + probe checklist
2026-07-10 09:53:21 +08:00
dd3xp 8325595ad7 merge main into RoundSquisheen 2026-07-06 11:14:32 -04:00
dd3xp c727ca34ca fix(desktop): refine chat ui and persistence 2026-07-06 10:53:40 -04:00
yiqi-017 bdd5309607 perf(desktop): store sessions one file each, not one monolithic JSON
Every message called _persist(), which rewrote the ENTIRE
desktop_sessions.json (all sessions, full messages + llm_history) under
the lock. Write cost was O(all sessions), so performance degraded badly
as sessions piled up.

Now each session is its own temp/desktop_sessions/<id>.json:
- _persist_session(s): atomic per-session write, O(one session)
- _delete_session_file(sid): remove on delete
- add_message / create / plan-bind / set_model / patch now write only
  the touched session; write cost no longer scales with session count
- _load_sessions reads the dir; one-time migration from the legacy
  monolithic file, which is then renamed to .json.migrated
- import_sessions reads both source formats (per-file dir + legacy),
  persists only newly added sessions
2026-07-06 14:05:31 +08:00
yiqi-017 aff6560440 feat(desktop): merge desktop_sessions.json on memory import
Import now also merges the source's temp/desktop_sessions.json into the
current session list (dedup by id), updating both memory and the on-disk
file so past desktop chats reappear in the sidebar immediately.

- manager.import_sessions(): id-dedup merge, in-memory + persist
- import handler returns sessionsAdded/sessionsSkipped
- frontend shows session count in toast and reloads the sidebar
2026-07-06 00:48:15 +08:00
yiqi-017 74275f8980 feat(desktop): add import-memory feature in settings
Lets users import memory/ and temp/model_responses/ from an existing
GenericAgent install into the desktop app's ga_root, so past sessions
and memory are not lost when running the standalone desktop bundle.

- bridge: POST /memory/import copies memory (backup-then-overwrite) and
  model_responses (fill-missing), returns counts + backup dir
- tauri: pick_directory command (rfd native folder picker)
- frontend: "Import memory" button in Settings > Features + zh/en i18n
2026-07-05 22:28:17 +08:00
dd3xp 158aeabfa4 Align desktop plan mode activation with TUI 2026-07-05 05:14:52 -04:00
dd3xp d205cd023e merge: bring test desktop packaging and model fixes into main 2026-07-02 18:40:02 +08:00
yanghang 1e6f370cfd fix(desktop): restart conductor/scheduler only when actually broken after mykey import
conductor and scheduler are separate subprocesses that read mykey once at
startup. After a mykey rewrite (import keys / edit channel config), a healthy
running conductor already hot-reloads mykey on its next task via its own mtime
check, so it needs no restart — restarting it would only interrupt an in-flight
task.

So restart an extra only when it is genuinely broken: the process exited
unexpectedly (covers scheduler dying on a bad config), or the process is alive
but its "Exception in thread conductor-agent" crash signature is in the captured
log (uvicorn still up but the agent thread is dead and can no longer process
tasks). A fresh log buffer per start means the signature only reflects the
current process. Bridge-only; conductor core and agentmain untouched.
2026-06-26 16:18:21 +08:00
yanghang 1c3c14a5f8 fix(desktop): bind conductor model to its own session 2026-06-24 22:23:25 +08:00
yiqi-017 5e7e5f608e refactor(desktop): model switching only via /session/{sid}/model
Remove the implicit model-write that rode along with each prompt. The
frontend no longer sends llmNo on session/prompt, and the bridge no longer
reads/forwards it there — sending a message now uses only the session's
bound sess.llm_no (falling back to the global default). Model switching
goes through the single POST /session/{sid}/model entry point.

selectModel now rolls back the chip + shows a toast when the switch request
fails, so the frontend display never diverges from the backend.
2026-06-24 18:29:13 +08:00
yiqi-017 e0117c40de feat(desktop): per-session model binding with backend as source of truth
Session.llm_no is now the authoritative per-session model selection
(persisted to desktop_sessions.json). Frontend state.llmNo becomes a
display cache; switching a session re-displays its bound model, and the
chip follows the backend's live model for native models too (not just
mixin failover).

- bridge: Session.llm_no field + persistence; submit_prompt/run_agent_turn
  read sess.llm_no with global ui.llmNo as fallback; make_agent/restore
  seed from it; new POST /session/{sid}/model; _live_model reports llmNo
- app.js: applyLiveModel supports native; setActiveSession re-displays
  bound model; selectModel posts to session route + updates global default;
  remove ga_llm_no localStorage
- conductor unchanged: keeps reading global default ui.llmNo
2026-06-24 16:44:58 +08:00
dd3xp 788c534762 restore: bring back lsdefine's 70792af fix lost in -X theirs merge
The prior merge resolved conflicts to the desktop/main (graft) side,
which silently reverted lsdefine's recent upstream fix (inMixin check,
ask_user schema, turn-25 prompt). Restore the 4 files to origin/main.
2026-06-24 11:23:46 +08:00
yanghang de00ede0cf feat(desktop): merge tested desktop updates without release packaging 2026-06-22 19:06:11 +08:00
yanghang 0f5f99f928 fix(desktop): 用 build-id 判别 bridge 归属 + 强制接管,杜绝连到旧 bridge
- build.rs 注入 GA_BUILD_ID(commit 短哈希+构建戳),每次构建必不同,
  即使 tauri.conf 版本号不变也能区分(原 identity 只比 ga_root 路径,
  原地升级/同版本重发时旧 bridge 被误判'匹配'而不接管)
- bridge /services/identity 增报 build_id;tauri 经 sanitize_bundle_env 用
  GA_BUILD_ID 环境变量给所spawn 的 bridge 打标
- bridge_identity_matches: build_id 不一致(或缺失/旧 bridge)即判不匹配 → 接管
- takeover_stale_bridge: POST /exit 后端口仍占(旧 bridge 无该端点),
  force_free_bridge_port() 按端口强杀监听进程(Win netstat+taskkill / Unix lsof+kill)
- 本机 cargo check 通过
2026-06-22 03:11:19 +08:00
yanghang 71828c9103 feat(desktop): 渠道组(自动故障转移)模型管理 + 流式开关 + 运行态模型显示
- 模型管理分为「渠道组(自动故障转移)」与独立模型两层:渠道组可展开、
  拖拽调整成员优先级,独立模型用 ➕ 加入(已在渠道组则提示)
- 后端 list/add/remove/reorder mixin 成员均走 mykey 结构解析与校验
  (Native 一致性、reorder 仅重排同组、删除级联清理 llm_nos)
- 默认配置:空渠道组 + 无独立模型(新装不再出现不可用模板模型)
- 添加/编辑模型表单新增「响应方式」流式开关;编辑保留高级字段
- chip/菜单显示渠道组当前实际模型(随故障转移更新),修复语言切换滞后
- 字体/选中态统一(sans 标题、等宽补中文兜底、选中不变淡)
- llmcore: MixinSession 增加 current_name 暴露当前子会话名
2026-06-22 01:24:53 +08:00
lyx 11dc64a7c2 fix(desktop): invalidate mykey cache on import save
Import mykey via POST /services/mykey now uses _save_mykey_text so stale
LLM profiles are dropped from the model list after replacing mykey.py.
2026-06-19 22:55:59 +08:00
LLYYXX 333476c4e5 1. 设置页模型配置下面新增“后台服务管理”按钮,点击后关闭设置弹窗并跳到 后台服务 -> 状态面板
2. 状态面板里 Bridge / Conductor / Scheduler 支持启动和退出
2026-06-19 21:46:12 +08:00
yanghang ea28559f8f fix(desktop): take over stale/foreign bridge on 14168 (identity handshake + shutdown), so a new version never loads the old install's UI 2026-06-19 05:00:44 +08:00
nianyucatfish 90078ea68d Merge pull request #16 from dd3xp/fix/service-process-lifecycle
端口占用拒绝启动与退出清理
2026-06-19 04:56:27 +08:00
dd3xp 8fd05cf8c4 refactor(bridge): compact list_panel_state extras-first ordering into sort key
Same behavior (extras above IM in panel), one line instead of four.
2026-06-19 02:24:57 +08:00
dd3xp caefc54cae feat(desktop): add conductor to status panel; group system services above IM
list_panel_state now emits bridge → extras (conductor + scheduler) → IM,
giving conductor a friendly 指挥家 / Conductor label and grouping system
services together at the top of the panel.
2026-06-19 02:11:20 +08:00
lyx a149f2045c feat(desktop): auto start/stop conductor and scheduler
Keep test #13 bridge behavior; add start-extras/stop-extras endpoints and
wire desktop open/close to them. Show a native alert when conductor or
scheduler ports are already in use.
2026-06-19 00:45:47 +08:00
lyx 74e709d99e fix(desktop): 单实例服务生命周期 — 端口占用拒绝启动与退出清理
- bridge 退出时 stop_all_extras(conductor/scheduler),支持 POST /shutdown
- 启动前检查 14168/8900/45762,占用则失败;不复用已有 bridge
- Tauri:端口占用显示 port_busy.html;关窗时优雅关停本次 spawn 的 bridge
- 合并 test 分支打包/首启逻辑,去除 bridge 复用路径
2026-06-18 22:03:14 +08:00
dd3xp 500fe92d95 feat(desktop): bridge auto-starts conductor on boot (--no-browser flag)
Conductor used to be a separately-launched service that opens its own UI
in the user's browser. With the desktop app, users access conductor via
the built-in 「指挥家」 page connecting to port 8900, so the standalone UI
window is redundant and confusing.

- conductor.py: gain a --no-browser flag that skips the webbrowser.open
  call. Default behavior (no flag) unchanged for backward compat with
  start_conductor.ps1 / GAConductor scheduled task / manual python runs.
- desktop_bridge.py discover_extra_services: also enumerate conductor.py
  alongside scheduler.py, with --no-browser baked into the spawn cmd.
  autostart_extras then picks it up automatically on bridge boot.
2026-06-18 11:02:33 +08:00
yiqi-017 e256e9b982 feat: turn_segs 结构化渲染,支持按轮独立渲染与模型切换 2026-06-08 18:42:38 +08:00
yiqi-017 554586fa82 fix: 持久化 llm_history 修复重启后模型幻觉工具调用 2026-06-06 16:09:22 +08:00
dd3xp 5ade9841d2 feat(desktop): bridge auto-starts non-IM services on boot (L4 cron)
The reflect/scheduler.py service drives the L4 archive cron every 12h,

but was only registered in the catalog and never auto-started. The UI

panel also wasn't surfacing it (WS snapshot filters to IM only), so

there was effectively no user-facing way to turn L4 archiving on.

Net effect: every GA deployment (local Tauri, Squishy, kw, etc.) had

L4_raw_sessions/ permanently empty. compress_session.py never ran.

Fix: on bridge startup, autostart everything in _catalog that isn't an

IM service. Currently that's just reflect/scheduler.py. IM services stay

manual because they need mykey.py config + explicit user opt-in.

After this lands:

  - bridge boots -> scheduler subprocess spawned

  - ~2 min later -> first L4 archive runs (since _l4_t starts at 0)

  - every 12h thereafter -> next L4 archive cycle

  - users get L4 with zero config
2026-06-05 22:37:43 +08:00
yiqi-017 8e37028370 [update]:修改文件上传大小至500MB 2026-06-05 18:30:29 +08:00
lyx 8f88506e9a 优化plan card视觉 2026-06-05 01:46:27 +08:00
lyx 1b88a2eac3 fix(desktop): Plan 按会话绑定 plan_path,修复完成态与跨会话串扰 2026-06-05 00:59:56 +08:00
lyx 6942c8bdbd fix(desktop): Plan 条稳定显示,对齐 TUI grace 与按会话 cwd 解析 2026-06-05 00:48:34 +08:00
lyx 6b77d8a5e0 fix(desktop): Plan 与 TUI 对齐,按会话绑定且不因预设文案误激活 2026-06-05 00:22:18 +08:00
lyx 8aab1fa49f 1. 聊天框和Conductor对齐
2. 尝试修复聊天渲染卡顿问题
2026-06-05 00:01:31 +08:00
lyx c62bcc5f9d feat(desktop): Plan 模式 todo 卡片初版
poll 响应附带 plan 事实数据(plan_state + bridge 两行接入),composer 上方渲染进度与任务行;完成/读空 grace 在前端处理。plan_state 原 API 与 TUI 完全兼容,Desktop 仅新增 *_for_desktop 辅助函数。
2026-06-04 15:14:00 +08:00
dd3xp 492caa610e fix(desktop-bridge): apply mykey edits to live agents without restart
Editing a model key / adding a profile wrote mykey.py but the running session
agents kept using the LLM sessions built at startup (old key), so the change
only took effect after a restart.

Root cause: llmcore tracks "changed since last load" via a single global mtime
flag, and load_llm_sessions() skips rebuilding when changed=False. Whichever
agent reloads first consumes that flag — e.g. the throwaway GenericAgent the UI
spins up to render the model list — so the real persistent session agent later
sees changed=False and keeps its stale sessions.

After saving a profile, force every live session agent to rebuild: reset
llmcore._mykey_mtime to None before each agent.load_llm_sessions() so
reload_mykeys() reports changed=True. Bridge-only (same trick as the existing
_invalidate_mykey_cache); no core changes to agentmain/llmcore; the rebuild
preserves conversation history via agentmain's oldhistory hand-off.
2026-05-28 01:22:07 +08:00
dd3xp bab5dec66f feat(desktop): group uploads by session id; purge on session delete
Uploads now land in temp/desktop_uploads/<sid>/ instead of a flat dir, so a
session's attachments can be cleaned up when the session is deleted.

- upload_handler reads `sid` from the body and saves under desktop_uploads/<sid>/
  (sanitized; missing sid -> _misc bucket).
- delete_session purges the session's upload subdir.
- _sweep_stale_uploads recurses into sid subdirs (rglob) and drops empty dirs.
- Frontend: uploadOne sends the active session's bridgeSessionId; addFiles
  ensures a session exists (reusing newSession/ensureBridgeSession) before
  uploading, so a sid is available — the empty-app case creates one first.

Whitelist checks (upload_raw / upload_delete) already validate "under root",
so the deeper subdir paths pass unchanged.
2026-05-26 00:41:40 +08:00
dd3xp 4cb073ee05 refactor(desktop): drop dead image-upload path; images & files share one dir
The 05-20 image mechanism (_save_image_data -> desktop_image_uploads, images
sent as dataUrl in the prompt) was superseded on 05-21 by the placeholder-path
flow: the frontend uploads every attachment via /upload to desktop_uploads/ and
inlines the file path into the prompt text, sending only path metadata
(files/imageMetas) — never dataUrl. So _save_image_data was never called and
desktop_image_uploads stayed empty (dead code).

- Remove _save_image_data and the _UPLOAD_DIR (desktop_image_uploads) dir.
- normalize_prompt now only flattens prompt text; image handling dropped
  (images arg kept for backward compat, ignored).
- _sweep_stale_uploads now sweeps only desktop_uploads.

Net: images and files share one dir (desktop_uploads), no duplicate storage.
2026-05-26 00:16:14 +08:00
dd3xp 36424089e9 fix(desktop): persist uploads in GA temp dir with 30-day retention
Uploaded files were stored in the OS temp dir, and the attachment dir was
wiped wholesale on every bridge startup (shutil.rmtree) — so attachments in
past sessions became dead links after any restart (e.g. "File not found:
...\ga_web_uploads\...__thesis.pdf").

- Move both _UPLOAD_DIR (images) and _WEB_UPLOAD_DIR (attachments) under
  ga_root/temp/ (gitignored): desktop_image_uploads/ and desktop_uploads/.
- Drop the rmtree-on-startup; uploads now survive bridge restarts.
- Add _sweep_stale_uploads(): on startup, delete only files older than
  UPLOAD_RETENTION_DAYS (30) by mtime, so temp storage can't grow unbounded.
- Remove now-unused tempfile/shutil imports.
2026-05-25 23:24:32 +08:00
dd3xp 56b688cc28 feat(desktop): file attachments survive refresh + remote download + fix Windows open
Three fixes for the chat file-attachment flow:

1. Attachments vanished on refresh — sendPrompt now sends files/imageMetas
   (name+path) to the bridge, submit_prompt persists them on the user message,
   and msgNode renders images via GET /upload/raw URL instead of the in-memory
   dataUrl. So after a page refresh (bridge still running) file cards and image
   thumbnails come back. (Hard limit: restarting the bridge rmtree's
   ga_web_uploads, so a restart still drops the files.)

2. Remote can't open files — opening a file runs os.startfile/xdg-open on the
   BRIDGE machine, useless when the browser is on another box. openUploadFile
   now detects non-localhost and downloads the file to the client instead
   (GET /upload/raw?download=1). Remote image preview works too (lightbox uses
   the inline /upload/raw URL).

3. Windows opened pdf/docx in Notepad — PR #12's open path used
   _open_path_in_editor ('edit' verb), which falls back to Notepad for types
   with no edit verb (pdf, etc.). Uploads now use _open_path_default (default
   'open' verb → registered default app). mykey still uses the editor path.

New bridge endpoint: GET /upload/raw?path=&download= — whitelisted to
_WEB_UPLOAD_DIR, inline preview or attachment download, restores original
filename. Serves both the refresh-preview and remote-download cases.
2026-05-25 01:18:38 +08:00
顾祎炜andYiwei Gu 4c8ec489e2 feat(chat): file preview routing — open vs reveal-in-finder (#12)
Click on a file attachment now picks behavior by extension: previewable
types (PDF, images, text, code, Office docs, audio/video) open in the
system default app; everything else (zip, binaries, unknown) reveals the
file in Finder/Explorer instead of opening it directly — avoids garbled
output for non-textual formats.

- frontend: PREVIEWABLE_EXTS whitelist + isPreviewableByName()
- bridge: /path/open accepts mode='reveal', uses 'open -R' (macOS),
  'explorer /select,' (Windows), parent-dir xdg-open (Linux fallback)
- pending thumb-strip chips also pass data-name for the same routing

Co-authored-by: Yiwei Gu <yiwei.gu@sap.com>
2026-05-24 17:09:54 +08:00
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
顾祎炜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
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
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 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