From 6a733bb77988d0b600ddf47a532b2b98a4e84506 Mon Sep 17 00:00:00 2001
From: esengine <359807859@qq.com>
Date: Thu, 5 Nov 2026 04:55:48 +0000
Subject: [PATCH] feat(desktop): the Data settings page is the board's three
cards of single rows
Set-data draws memory and usage, session storage and backup with one short
row per setting: memory (with its count), usage, upload volume, storage and the
two backups sit behind a pill, while currency, auto-archive, session location
and 1.x sessions act on the row. The board's upload switch is not drawn: the
kernel has no upload setting, only a report of request sizes, so that row opens
the report. Opening settings at 'memory' still lands on the memory page.
---
desktop/frontend-next/src/design/settings.css | 2 +
desktop/frontend-next/src/i18n/msg/data.ts | 6 ---
desktop/frontend-next/src/i18n/msg/session.ts | 1 -
.../frontend-next/src/i18n/msg/settings.ts | 14 ++++++
desktop/frontend-next/src/i18n/msg/usage.ts | 3 --
desktop/frontend-next/src/ui/AutoArchive.tsx | 46 +++++--------------
.../src/ui/DataSettings.test.tsx | 45 ++++++++++++++++++
desktop/frontend-next/src/ui/DataSettings.tsx | 13 +++---
.../ui/DisplayCurrency.interaction.test.tsx | 18 ++++----
.../frontend-next/src/ui/DisplayCurrency.tsx | 43 ++++++-----------
.../src/ui/LegacySessionsSection.tsx | 12 ++---
.../src/ui/SessionDirSettings.tsx | 34 +++++++-------
desktop/frontend-next/src/ui/Settings.tsx | 5 +-
desktop/frontend-next/src/ui/UploadUsage.tsx | 5 +-
.../frontend-next/src/ui/UsageSettings.tsx | 6 +--
.../legacy-sessions-hide.interaction.test.tsx | 6 +--
desktop/frontend-next/src/ui/prefsnav.tsx | 6 +--
.../settings-host-owner.interaction.test.tsx | 3 ++
18 files changed, 141 insertions(+), 127 deletions(-)
create mode 100644 desktop/frontend-next/src/ui/DataSettings.test.tsx
diff --git a/desktop/frontend-next/src/design/settings.css b/desktop/frontend-next/src/design/settings.css
index 4f25c24ff0..e50690d83c 100644
--- a/desktop/frontend-next/src/design/settings.css
+++ b/desktop/frontend-next/src/design/settings.css
@@ -328,3 +328,5 @@
.prefs { padding: 24px; }
.prefs-sheet:not([data-wide]) { width: 100%; height: 100%; }
.srow-ctl .unit-field input { width: 72px; }
+.srow-val { color: var(--muted); font: var(--w-reg) var(--fs-meta) var(--ui); max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.srow-val > * { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
diff --git a/desktop/frontend-next/src/i18n/msg/data.ts b/desktop/frontend-next/src/i18n/msg/data.ts
index 84163f62fa..ae391684f1 100644
--- a/desktop/frontend-next/src/i18n/msg/data.ts
+++ b/desktop/frontend-next/src/i18n/msg/data.ts
@@ -5,7 +5,6 @@ export const DATA: Messages = {
"data.a_save_applies_to_sessions": { zh: "保存后只对之后新开的会话生效,已开的会话沿用旧设置。这些设置写在用户配置文件里:", en: "A save applies to sessions opened afterwards; open ones keep the old setting. These settings live in your user config file:" },
"data.api_keys": { zh: "API 密钥", en: "API keys" },
"data.archive_conversations_left_idle": { zh: "自动归档长时间未活动的对话", en: "Archive conversations left idle" },
- "data.archived_only_once_it_has": { zh: "距上次活动超过这个天数才会归档。默认 {n} 天。", en: "Archived only once it has been idle longer than this. Default {n} days." },
"data.archiving_only_moves_a_conversation": { zh: "归档只是把对话移出常用列表,内容不动,随时可在“已归档”里恢复。置顶的、正在运行的、等待你回答或批准的对话不会被归档。只处理已打开会话窗格的文件夹;导入的旧会话按原有的最后活动时间计算,开启后可能一次归档较多。", en: "Archiving only moves a conversation out of the everyday list; nothing is deleted and you can restore it from Archived at any time. Pinned conversations, running ones and ones waiting for your answer or approval are never archived. Only folders with an open session pane are swept, and imported older conversations count from their original last activity, so enabling this can archive many at once." },
"data.assistant": { zh: "助手", en: "Assistant" },
"data.auth_header_name": { zh: "认证请求头名称", en: "Auth header name" },
@@ -35,7 +34,6 @@ export const DATA: Messages = {
"data.differs_from_this_machine": { zh: "与本机不同", en: "Differs from this machine" },
"data.drive_free_free_of_total": { zh: "{drive} 剩余 {free} / {total}", en: "{drive} — {free} free of {total}" },
"data.e_g_work_laptop": { zh: "例如:公司笔记本", en: "e.g. work laptop" },
- "data.encrypt_this_machines_models_extensions": { zh: "把本机的模型、扩展、记忆等配置加密保存到账号,换机器或重装后按项恢复。", en: "Encrypt this machine's models, extensions, memory and other setup into your account, and restore it item by item on a new machine or after a reinstall." },
"data.encrypting": { zh: "正在加密…", en: "Encrypting…" },
"data.encrypting_and_uploading": { zh: "正在加密上传…", en: "Encrypting and uploading…" },
"data.enter_the_passphrase_for_name": { zh: "输入备份「{name}」的加密口令,先预览它和本机的差异,不会立即写入。", en: "Enter the passphrase for “{name}” to preview how it differs from this machine. Nothing is written yet." },
@@ -88,13 +86,11 @@ export const DATA: Messages = {
"data.n_of_max_used": { zh: "已用 {n}/{max} 份", en: "{n} of {max} used" },
"data.needs_install": { zh: "需安装", en: "Needs install" },
"data.no_abnormal_exit_record_has": { zh: "没有留下异常退出的记录。", en: "No abnormal-exit record has been left." },
- "data.no_account_needed_export_this": { zh: "不用账号:把本机的模型、扩展、记忆等配置加密导出成一个文件,换机器时再导入。", en: "No account needed: export this machine's models, extensions, memory and other setup as one encrypted file, and import it on another machine." },
"data.no_backups_in_this_account": { zh: "账号里还没有备份。", en: "No backups in this account yet." },
"data.no_older_sessions_were_found": { zh: "这个文件夹里没有找到旧版会话。请选择 Reasonix 的数据文件夹(里面有 sessions、projects 或 desktop-sessions-v5)。", en: "No older sessions were found in this folder. Pick the Reasonix data folder (the one holding sessions, projects or desktop-sessions-v5)." },
"data.not_on_this_machine": { zh: "本机没有", en: "Not on this machine" },
"data.nothing_new_to_import": { zh: "没有发现尚未导入的旧会话。", en: "Nothing new to import." },
"data.off_by_default_if_ticked": { zh: "默认不导出。勾选后密钥会随文件一起加密,拿到文件和口令的人就能使用它们", en: "Off by default. If ticked, keys are encrypted into the file, and anyone with the file and the passphrase can use them" },
- "data.off_by_default_when_on": { zh: "默认关闭。开启后,超过下面天数没有活动的对话会在后台被归档。", en: "Off by default. When on, a conversation with no activity for longer than the number of days below is archived in the background." },
"data.off_by_default_when_ticked": { zh: "默认不备份。勾选后密钥会随备份一起加密上传", en: "Off by default. When ticked, keys are encrypted and uploaded with the backup" },
"data.off_now_nothing_is_sent": { zh: "现在是关闭的:开启并填写收集器地址后才会发送。", en: "Off now: nothing is sent until it is on and has a collector address." },
"data.older_data_folder_path": { zh: "旧版数据目录路径", en: "Older data folder path" },
@@ -158,10 +154,8 @@ export const DATA: Messages = {
"data.user_level_instruction_files_and": { zh: "用户级指令文件与全局记忆", en: "User-level instruction files and global memory" },
"data.verifying": { zh: "正在校验", en: "Verifying" },
"data.what_the_agent_remembers_and": { zh: "agent 记住了什么、花了多少", en: "What the agent remembers and what it costs" },
- "data.what_the_agent_wrote_down": { zh: "agent 自动记录的内容:未经配置,但会据此执行。可以按类型和星系查找,刚记住的在「新的」里等你确认。", en: "What the agent wrote down on its own: nobody configured it, yet it acts on it. Find facts by type or galaxy; ones it just learned wait under New for you to confirm." },
"data.what_to_back_up": { zh: "备份内容", en: "What to back up" },
"data.what_to_export": { zh: "导出内容", en: "What to export" },
"data.where_sessions_and_indexes_live": { zh: "会话和索引放在哪里、占多少空间", en: "Where sessions and indexes live and how much space they take" },
- "data.where_the_data_is_written": { zh: "数据的存储位置与占用空间。会话和索引会持续增长,配置和凭据不会,因此只有前者可以迁移。迁移在重启后生效。", en: "Where the data is written and how much space it uses. Sessions and indexes keep growing while configuration and credentials do not, so only the former can be moved. A move takes effect after a restart." },
"data.whether_sessions_migrated_from_reasonix": { zh: "从 Reasonix 1.x 迁移过来的会话是否出现在侧栏列表里。隐藏后不会删除它们,搜索时仍能找到,正在打开的旧会话也照常显示。默认显示。", en: "Whether sessions migrated from Reasonix 1.x appear in the sidebar lists. Hiding does not delete them; search still finds them and an open one stays visible. Shown by default." },
};
diff --git a/desktop/frontend-next/src/i18n/msg/session.ts b/desktop/frontend-next/src/i18n/msg/session.ts
index b6275769e6..a39e597cab 100644
--- a/desktop/frontend-next/src/i18n/msg/session.ts
+++ b/desktop/frontend-next/src/i18n/msg/session.ts
@@ -218,7 +218,6 @@ export const SESSION: Messages = {
"session.work_in_a_separate_git": { zh: "在 Git worktree 中创建独立副本,修改不会影响当前分支", en: "Work in a separate Git worktree so changes do not land on the current branch" },
"session.work_on_an_isolated_copy": { zh: "创建隔离副本", en: "Work on an isolated copy" },
"session.work_time": { zh: "工作时长", en: "Work time" },
- "session.working_directory": { zh: "工作目录", en: "Working directory" },
"session.workspace_bound": { zh: "工作目录已绑定", en: "Workspace bound" },
"session.workspace_path": { zh: "工作区路径", en: "Workspace path" },
"session.workspace_ready": { zh: "工作目录已就绪", en: "Workspace ready" },
diff --git a/desktop/frontend-next/src/i18n/msg/settings.ts b/desktop/frontend-next/src/i18n/msg/settings.ts
index 086173886d..8cb028aa6a 100644
--- a/desktop/frontend-next/src/i18n/msg/settings.ts
+++ b/desktop/frontend-next/src/i18n/msg/settings.ts
@@ -227,4 +227,18 @@ export const SETTINGS: Messages = {
"settings.sessions_send_each_other": { zh: "让一个会话给另一个会话发消息(实验)", en: "Let one session send another a message (experimental)" },
"settings.persona_and_presets": { zh: "人设与预设", en: "Persona and presets" },
"settings.manage_presets": { zh: "管理预设", en: "Manage presets" },
+ "settings.which_currency_costs_show": { zh: "费用用什么币种显示", en: "Which currency costs are shown in" },
+ "settings.days_before_archiving": { zh: "多少天没动的会话自动归档", en: "How many idle days before a conversation is archived" },
+ "settings.migrated_sessions_in_the_sidebar": { zh: "旧版迁移过来的会话是否出现在侧栏", en: "Whether sessions migrated from 1.x show in the sidebar" },
+ "settings.where_session_records_go": { zh: "会话记录写到哪里", en: "Where session records are written" },
+ "settings.open_memory": { zh: "打开记忆", en: "Open memory" },
+ "settings.what_it_remembers": { zh: "它记住的东西:星系、遗忘、整理", en: "What it remembers: constellations, forgetting, tidying" },
+ "settings.view_usage": { zh: "查看用量", en: "View usage" },
+ "settings.usage_hint": { zh: "输入、输出、费用,按天和按模型看", en: "Input, output and cost, by day and by model" },
+ "settings.manage_storage": { zh: "管理", en: "Manage" },
+ "settings.storage_hint": { zh: "会话、索引、缓存占了多少空间", en: "How much space sessions, indexes and caches take" },
+ "settings.export_and_import": { zh: "导出与导入…", en: "Export and import…" },
+ "settings.config_in_one_file": { zh: "把模型、扩展、记忆等配置加密导出成一个文件", en: "Export models, extensions and memory to one encrypted file" },
+ "settings.back_up_to_the_cloud": { zh: "登录后把配置备份到云端", en: "Back up your configuration to the cloud once signed in" },
+ "settings.size_of_requests_sent": { zh: "发给模型服务商的请求有多大", en: "How large the requests sent to model providers are" },
};
diff --git a/desktop/frontend-next/src/i18n/msg/usage.ts b/desktop/frontend-next/src/i18n/msg/usage.ts
index d0301bc6f3..10ba8d2c44 100644
--- a/desktop/frontend-next/src/i18n/msg/usage.ts
+++ b/desktop/frontend-next/src/i18n/msg/usage.ts
@@ -90,7 +90,6 @@ export const USAGE: Messages = {
"usage.not_applicable": { zh: "不适用", en: "Not applicable" },
"usage.nothing_recorded_yet_counting_starts": { zh: "还没有记录:从这个版本起开始统计。", en: "Nothing recorded yet: counting starts with this version." },
"usage.of_the_tok_input_tokens": { zh: "该时间段送入的 {tok} 输入 tokens 中,{rate}% 命中前缀缓存 —— 命中部分按缓存价计费,较未命中低一个数量级。", en: "Of the {tok} input tokens sent in this span, {rate}% hit the prefix cache — a hit bills at the cache rate, an order of magnitude under a miss." },
- "usage.only_decides_which_currency_costs": { zh: "只决定费用以哪种货币显示,不改动各来源的价目表。「自动」跟随账户余额的币种,没有余额信息时按价目表的币种显示。", en: "Only decides which currency costs are shown in; each source's price list is untouched. Auto follows the account balance's currency, and falls back to the price list's currency when there is no balance." },
"usage.open_the_price_source_of": { zh: "{model} 打开价格来源", en: "Open the price source of {model}" },
"usage.openrouter_format": { zh: "OpenRouter 格式", en: "OpenRouter format" },
"usage.paid": { zh: "实付", en: "Paid" },
@@ -127,7 +126,6 @@ export const USAGE: Messages = {
"usage.the_search_models_connection_has": { zh: "搜索模型所在的连接已被移出", en: "The search model's connection has been removed" },
"usage.the_search_models_connection_has_2": { zh: "搜索模型所在的连接没有凭据", en: "The search model's connection has no credentials" },
"usage.the_search_models_protocol_has": { zh: "搜索模型的协议不支持原生搜索,或已关闭搜索", en: "The search model's protocol has no native search, or search is turned off for it" },
- "usage.the_sessions_cost_the_balance": { zh: "当前会话的费用、余额与用量页立即按所选币种显示,无需重启。", en: "The session's cost, the balance and the usage page switch to the chosen currency at once, no restart." },
"usage.the_size_of_the_request": { zh: "发给模型服务商的请求内容大小(不含连接本身的开销,也不含下载)。每次请求都会带上完整的对话,所以它取决于上下文有多长、请求有多少次,缓存命中只省费用、不省流量。", en: "The size of the request bodies sent to model providers (without the connection's own overhead, and without downloads). Every request carries the whole conversation, so it depends on how long the context is and how many requests are made; cache hits save cost, not bandwidth." },
"usage.the_source_must_be_an": { zh: "来源必须是 http 或 https 链接,不能带用户名密码。", en: "The source must be an http or https link without a username or password." },
"usage.these_models_prices_are_not": { zh: "这些模型的价格不在官方价目表里,无法换算成所选币种,费用只能按原币种显示:{models}。到「服务商」编辑该模型,点「恢复官方价」即可。", en: "These models' prices are not in the vendor's price list, so they cannot be converted to the chosen currency and cost shows in the original one: {models}. Edit the model under Providers and choose Restore official price." },
@@ -137,7 +135,6 @@ export const USAGE: Messages = {
"usage.time": { zh: "时间", en: "Time" },
"usage.time_range": { zh: "时间范围", en: "Time range" },
"usage.today": { zh: "今日", en: "Today" },
- "usage.token_usage_and_cost_recorded": { zh: "本机记录的 token 用量与花费,仅保存在这台机器上,不会上传。命中缓存的输入按缓存价计费,因此命中率直接影响费用。", en: "Token usage and cost recorded on this machine only; nothing is uploaded. Input that hits the cache is billed at the cache rate, so the hit rate drives the bill." },
"usage.tokens_per_round": { zh: "每回合用量", en: "Tokens per round" },
"usage.two_currencies_not_summed": { zh: "两种币种不合计", en: "Two currencies, not summed" },
"usage.two_settlement_currencies_do_not": { zh: "两种结算币种不予合并 —— 合成单一总额需要虚构一个汇率。", en: "Two settlement currencies do not add up — one total would mean inventing a rate." },
diff --git a/desktop/frontend-next/src/ui/AutoArchive.tsx b/desktop/frontend-next/src/ui/AutoArchive.tsx
index 222436d750..3dee463e92 100644
--- a/desktop/frontend-next/src/ui/AutoArchive.tsx
+++ b/desktop/frontend-next/src/ui/AutoArchive.tsx
@@ -3,7 +3,7 @@ import { t } from "../i18n";
import { reason } from "../i18n/kernel";
import type { AgentPort } from "../port/port";
import type { AutoArchiveSettings } from "../port/boundary";
-import { Group } from "./Group";
+import { SettingRow } from "./SettingRow";
import { Switch } from "./Switch";
const LIMIT = 3650;
@@ -44,30 +44,12 @@ export function AutoArchive({ port }: { port: AgentPort }) {
if (n !== state.days) void save({ enabled: state.enabled, days: n });
};
- const body = !state ? (
-