// dudu 设置窗口 — 四分组(输入 / 通用 / 账号与时长 / 帮助),规格见 doc/frontend-design.html 4.2
import { useEffect, useState } from 'react';
import { Button } from '@dudu/design/components/core/Button';
import { Badge } from '@dudu/design/components/core/Badge';
import { Card } from '@dudu/design/components/core/Card';
import { HotkeyCombo } from '@dudu/design/components/core/Kbd';
import { ProgressBar } from '@dudu/design/components/core/ProgressBar';
import { Switch } from '@dudu/design/components/forms/Switch';
import { SettingRow } from '@dudu/design/components/forms/SettingRow';
import { Select } from '@dudu/design/components/forms/Select';
import { invoke } from '../shared/tauri';
import { setThemePref } from '../shared/theme';
const THEME_OPTIONS = [['system', '跟随系统'], ['light', '浅色'], ['dark', '深色']];
// 分组卡片(对齐原型 SettingsTray 2026-07-11 改版):组标题在卡外、
// radius-lg 卡片体块、载入分组轻微上浮淡入(40ms 级差)。
const groupCss = `
@keyframes dd-set-in { from { opacity: 0; transform: translateY(4px); } }
.dd-set-group { animation: dd-set-in var(--dur-base) var(--ease-out) both; }
`;
function Group({ title, order = 0, children }) {
return (
);
}
export function SettingsApp() {
const [settings, setSettings] = useState({
hotkey: 'CmdOrCtrl+Shift+Space', mic: '', sound: false, autostart: true, theme: 'system',
});
const [mics, setMics] = useState([]);
const [me, setMe] = useState(null); // null = 未登录
useEffect(() => {
invoke('get_settings').then((s) => { if (s) { setSettings(s); setThemePref(s.theme); } });
invoke('list_mic_devices').then((d) => d && setMics(d));
invoke('fetch_me').then((m) => m && setMe(m));
}, []);
// 仅这些字段属于「用户可改的设置」,回写时只动这几项,
// 其余字段(token / device_id / onboarding_done / server_url)保持后端最新值。
const USER_FIELDS = ['hotkey', 'mic', 'sound', 'autostart', 'theme'];
const update = (patch) => {
const next = { ...settings, ...patch };
setSettings(next);
if (patch.theme) setThemePref(patch.theme);
// 保存前重新读最新 settings 再合并,避免用陈旧(可能空)token 覆盖登录流程写入的 token,
// 导致先开设置窗再扫码登录、回设置页改开关时静默登出(丢失更新)。
invoke('get_settings').then((latest) => {
const base = latest || settings;
const merged = { ...base };
for (const k of USER_FIELDS) merged[k] = next[k];
invoke('set_settings', { settings: merged });
});
};
const minutes = me ? Math.floor(me.balance_seconds / 60) : 0;
const trialUsedMin = me ? Math.floor(me.trial_used_today / 60) : 0;
return (
// 透明窗口自绘 MacWindow 框(对齐原型):radius-md + border-1,占满窗口,
// 阴影用 macOS 原生(贴合内容圆角,窗口即卡片实际尺寸)。
// 原生 NSWindow 底色在深色内容外露白边,故窗口 transparent、框由页面画。
{/* 自绘顶条:系统红绿灯悬浮于左上,标题绝对居中(mac 原生习惯,永不重叠);整条可拖拽 */}
dudu 设置
update({ sound: v })} />
update({ autostart: v })} />
{THEME_OPTIONS.map(([val, lab]) => (
))}
{me ? (
<>
{me.nickname_masked}
{me.account_state === 'ok' ? '余额充足' : me.account_state === 'trial' ? '试用中' : '余额不足'}
>
) : (
)}
{minutes}
分钟
今日免费试用
{trialUsedMin} / 3 分钟
);
}