diff --git a/README.md b/README.md index 818dce7..210b612 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ SayIt/ | 语音识别 | Qwen3-ASR + vLLM / 豆包 ASR / 千问 ASR / sherpa-onnx | | AI 润色 | DeepSeek / 通义千问 / Azure OpenAI / Ollama | | 部署 | Docker Compose、NVIDIA Container Toolkit | -| 开发 | 整个项目使用 Claude Opus 4.6 开发 | +| 开发 | 整个项目使用 Claude Opus 开发 | ## 开发 @@ -195,6 +195,18 @@ cd backend && uvicorn app.main:app --port 8000 前置要求:Python 3.10+、NVIDIA GPU + CUDA +## 交流反馈 + +有任何问题、建议或想法,欢迎扫码加入用户反馈微信群,一起交流: + +
+ +SayIt 用户反馈微信群 + +*微信扫码加入 SayIt 用户反馈群* + +
+ ## 贡献 欢迎提交 Bug 报告和功能建议!请阅读 [贡献指南](./CONTRIBUTING.md)。 diff --git a/client/package.json b/client/package.json index f31ce0d..bc12fb3 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "sayit-tauri", - "version": "0.0.5", + "version": "0.0.7", "description": "SayIt - Voice to Text Desktop App (Tauri)", "type": "module", "scripts": { diff --git a/client/src-tauri/Cargo.lock b/client/src-tauri/Cargo.lock index 9610a19..a0cb3fa 100644 --- a/client/src-tauri/Cargo.lock +++ b/client/src-tauri/Cargo.lock @@ -3763,7 +3763,7 @@ dependencies = [ [[package]] name = "sayit" -version = "0.0.5" +version = "0.0.7" dependencies = [ "base64 0.22.1", "bzip2", diff --git a/client/src-tauri/Cargo.toml b/client/src-tauri/Cargo.toml index ab3879a..72f67ba 100644 --- a/client/src-tauri/Cargo.toml +++ b/client/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sayit" -version = "0.0.5" +version = "0.0.7" authors = ["Liu Qianglong", "Claude"] edition = "2021" diff --git a/client/src-tauri/src/commands/system.rs b/client/src-tauri/src/commands/system.rs index 8fa435b..6339e1b 100644 --- a/client/src-tauri/src/commands/system.rs +++ b/client/src-tauri/src/commands/system.rs @@ -246,7 +246,11 @@ pub fn check_for_updates() -> Result { /// 下载更新安装包到临时目录 #[tauri::command] pub async fn download_update(url: String) -> Result { - let client = reqwest::Client::new(); + // 必须带 User-Agent:生产环境 AWS WAF 的 NoUserAgent_HEADER 规则会拦截无 UA 的请求(403) + let client = reqwest::Client::builder() + .user_agent(concat!("SayIt/", env!("CARGO_PKG_VERSION"))) + .build() + .map_err(|e| format!("初始化下载客户端失败: {}", e))?; let resp = client .get(&url) .timeout(std::time::Duration::from_secs(300)) diff --git a/client/src-tauri/src/main.rs b/client/src-tauri/src/main.rs index f242419..93248d3 100644 --- a/client/src-tauri/src/main.rs +++ b/client/src-tauri/src/main.rs @@ -178,11 +178,21 @@ fn main() { hook.start(app.handle(), &ptt_str, &hf_str); // 设置窗口图标(用 ICO 文件,包含多尺寸帧,Windows 自动选最合适的) + // 并根据启动方式决定是否显示主窗口: + // 开机自启会带 --minimized 参数 → 保持隐藏,静默停留在托盘 + // 用户手动打开则正常显示(窗口配置为 visible:false,需显式 show) + let launched_minimized = std::env::args().any(|arg| arg == "--minimized"); if let Some(main_window) = app.get_webview_window("main") { let ico_bytes = include_bytes!("../icons/icon.ico"); if let Ok(icon) = tauri::image::Image::from_bytes(ico_bytes) { let _ = main_window.set_icon(icon); } + if !launched_minimized { + let _ = main_window.show(); + let _ = main_window.set_focus(); + } else { + log::info!("Launched with --minimized, staying hidden in tray"); + } } // 系统托盘图标 @@ -261,14 +271,26 @@ fn main() { { use tauri_plugin_autostart::ManagerExt; let storage: tauri::State = app.state(); + let autostart = app.autolaunch(); let already_set = storage.get("autoLaunchInitialized", None); if already_set.is_null() || already_set.as_str() == Some("") { - // 首次运行,注册开机自启并标记 - let autostart = app.autolaunch(); + // 首次运行,注册开机自启并标记(新注册已带 --minimized 参数) let _ = autostart.enable(); let flag = serde_json::json!("true"); let _ = storage.set("autoLaunchInitialized", &flag); + let _ = storage.set("autoLaunchArgsMigrated", &flag); log::info!("Auto-launch enabled on first run"); + } else { + // 老用户迁移:旧自启项不带 --minimized 参数,重新注册一次以写入新参数 + let migrated = storage.get("autoLaunchArgsMigrated", None); + if migrated.as_str() != Some("true") { + if autostart.is_enabled().unwrap_or(false) { + let _ = autostart.disable(); + let _ = autostart.enable(); + log::info!("Auto-launch re-registered with --minimized arg"); + } + let _ = storage.set("autoLaunchArgsMigrated", &serde_json::json!("true")); + } } } @@ -284,7 +306,7 @@ fn main() { // .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_autostart::init( tauri_plugin_autostart::MacosLauncher::LaunchAgent, - None, + Some(vec!["--minimized"]), )) .invoke_handler(tauri::generate_handler![ // Store diff --git a/client/src-tauri/tauri.conf.json b/client/src-tauri/tauri.conf.json index 9e5120d..b4fe411 100644 --- a/client/src-tauri/tauri.conf.json +++ b/client/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "productName": "SayIt", - "version": "0.0.5", + "version": "0.0.7", "identifier": "com.sayit.app", "build": { "frontendDist": "../dist", @@ -20,7 +20,7 @@ "minHeight": 600, "decorations": false, "center": true, - "visible": true + "visible": false } ], "security": { diff --git a/client/src/services/defaults.ts b/client/src/services/defaults.ts index 3a4c504..a462a87 100644 --- a/client/src/services/defaults.ts +++ b/client/src/services/defaults.ts @@ -22,7 +22,7 @@ export const DEFAULTS: Record = { selectedMic: '', // 设备 ID,空字符串 = 系统默认 // ── AI 校对 ── - aiEnabled: false, // 是否开启 AI 校对。可选: true | false + aiEnabled: true, // 是否开启 AI 校对。可选: true | false aiPromptAppend: '', // 全局附加 prompt // ── AI 供应商 ── diff --git a/client/src/services/personalization/defaults.ts b/client/src/services/personalization/defaults.ts index 36b2291..cfd2b8a 100644 --- a/client/src/services/personalization/defaults.ts +++ b/client/src/services/personalization/defaults.ts @@ -6,7 +6,7 @@ export const BUILTIN_APP_RULES: AppPromptRule[] = [ appId: 'teams', name: 'Teams', builtin: true, - enabled: true, + enabled: false, priority: 100, presetId: 'intent', promptAppend: '适合即时协作聊天。优先输出可以直接发送的短消息,语气自然、清晰、简洁,避免邮件腔。', @@ -20,7 +20,7 @@ export const BUILTIN_APP_RULES: AppPromptRule[] = [ appId: 'outlook', name: 'Outlook', builtin: true, - enabled: true, + enabled: false, priority: 95, presetId: 'intent', promptAppend: '适合工作邮件草稿。语气正式、完整,必要时自然分段,但不要编造收件人、称呼或任何事实。', @@ -34,7 +34,7 @@ export const BUILTIN_APP_RULES: AppPromptRule[] = [ appId: 'kiro', name: 'Kiro', builtin: true, - enabled: true, + enabled: false, priority: 90, presetId: 'faithful', promptAppend: '面向开发工具输入。保留代码、命令、文件名、路径、英文标识符和 Markdown 结构,不要把技术词改写成普通中文。', @@ -48,7 +48,7 @@ export const BUILTIN_APP_RULES: AppPromptRule[] = [ appId: 'vscode', name: 'VSCode', builtin: true, - enabled: true, + enabled: false, priority: 85, presetId: 'faithful', promptAppend: '面向 VSCode 编辑区。保留代码、命令、文件名、API、英文术语和 Markdown 结构,不要过度润色技术内容。', @@ -62,7 +62,7 @@ export const BUILTIN_APP_RULES: AppPromptRule[] = [ appId: 'cursor', name: 'Cursor', builtin: true, - enabled: true, + enabled: false, priority: 80, presetId: 'faithful', promptAppend: '面向 Cursor 编辑区。保留代码、命令、路径、技术名词和英文标识符,不要擅自解释或补全技术内容。', @@ -76,7 +76,7 @@ export const BUILTIN_APP_RULES: AppPromptRule[] = [ appId: 'notepad', name: '记事本', builtin: true, - enabled: true, + enabled: false, priority: 70, presetId: 'intent', promptAppend: '面向 Windows 记事本,适合随手记录的纯文本。输出纯文本,不要使用 Markdown 标记或特殊格式符号。', diff --git a/client/src/services/personalization/store.ts b/client/src/services/personalization/store.ts index 2722e26..63372bd 100644 --- a/client/src/services/personalization/store.ts +++ b/client/src/services/personalization/store.ts @@ -39,7 +39,7 @@ function normalizeAppPromptRule(raw: unknown, fallback?: AppPromptRule): AppProm appId, name, builtin: value.builtin === true || fallback?.builtin === true, - enabled: typeof value.enabled === 'boolean' ? value.enabled : fallback?.enabled ?? true, + enabled: typeof value.enabled === 'boolean' ? value.enabled : fallback?.enabled ?? false, priority, presetId: String(value.presetId ?? fallback?.presetId ?? '').trim() || undefined, promptAppend: String(value.promptAppend ?? fallback?.promptAppend ?? '').trim(), diff --git a/docs/SayIt AI 服务提供商.md b/docs/SayIt AI 服务提供商.md deleted file mode 100644 index 235880d..0000000 --- a/docs/SayIt AI 服务提供商.md +++ /dev/null @@ -1,83 +0,0 @@ -# SayIt AI 润色供应商 - -SayIt 有两个核心功能:语音转文本(ASR)和 AI 文本润色。ASR 部分可以参考 [豆包语音识别凭证获取](豆包ASR凭据获取.md) 和 [阿里云千问语音识别](aliyun.md)。 - -AI 润色功能可以去除口癖词(嗯、啊、那个)、修正语音识别错误、自动分段排版等,都可以通过 Prompt 自定义控制。 - -SayIt 已对接国内常用的 AI 服务商(通义千问、DeepSeek、豆包等),同时支持 OpenAI 兼容格式,可以接入其他供应商或第三方中转。 - -## 1. 最佳实践 - -**推荐 DeepSeek deepseek-v4-flash 作为默认选择。** 质量最好、价格极低、速度可接受。 - -DeepSeek V4 Flash(2026 年 4 月发布)是 284B 参数的 MoE 模型,13B 激活参数,支持 1M 上下文。关闭思考模式后速度接近旧版 deepseek-chat,但质量明显更好,价格还更便宜(输入 1 元/百万 Token,输出 2 元/百万 Token)。 - -如果对延迟极度敏感(需要 <500ms),可以用通义千问 qwen3.6-flash,速度最快但质量略逊。 - -| 供应商    | 模型       | 短文本  | 长文本   | 每月成本 | -| --------------| -------------------| -----------| ------------| ----------| -| **DeepSeek** | deepseek-v4-flash | **803ms** | **2089ms** | ~1.5 元 | -| 通义千问   | qwen3.6-flash   | 393ms   | 1308ms   | ~3.8 元 | - -> 月成本按每天 100 次、每次约 100 字估算。V4 Flash 默认开启 thinking,会导致延迟飙升到 2~7 秒。SayIt 已在代码中自动关闭思考模式,无需用户手动配置。 - -## 2. 如何选择AI模型 - -- **速度**:总耗时由 TTFT(Time To First Token,首个字生成时间)和 TPS(每秒 Token 数)决定。语音转写场景下,总耗时超过 1~2 秒会有明显等待感。 -- **质量**:能否准确执行 Prompt 指令——去口癖、识别自我修正、格式化列表、纠正专有名词大小写等。 -- **价格**:V4 Flash 的自动 prompt 缓存机制让重复 system prompt 的场景成本极低。 - -## 3. 实测数据 - -以下数据是 2026 年 5 月实测的,使用非流式请求(和 SayIt 实际调用方式一致),每条用例跑了 3 次取中位数。 - -### 速度 - -| 供应商  | 模型             | 短文本 | 中等文本 | 长文本(~200字) | -| ----------| -------------------------------| --------| ----------| ------------------| -| DeepSeek | deepseek-v4-flash(关闭思考) | 803ms | 971ms  | 2089ms      | -| 通义千问 | qwen3.6-flash         | 393ms | 489ms  | 1308ms      | - - -### 质量 - -两家都能正确去口癖、识别列表结构、纠正专有名词大小写(DeepSeek、ChatGPT、OpenAI 等)。V4 Flash 在专有名词纠正和格式化方面更稳定,输出更简洁。 - - -## 4. 关于豆包 - -豆包的语音识别(ASR)是目前中文场景下最好的,SayIt 推荐使用豆包 ASR。但豆包的 AI 文本模型(lite 和 mini 都测了)延迟极不稳定,中长文本经常超过 10 秒,不适合语音转写的润色场景。建议 ASR 用豆包,AI 润色用 DeepSeek 或通义千问。 - -## 5. 价格参考 - -| 供应商  | 模型         | 输入(百万Token) | 输出(百万Token) | -| ----------| ----------------------| -------------------| -------------------| -| DeepSeek | deepseek-v4-flash  | 1 元       | 2 元       | -| 通义千问 | qwen3.6-flash    | 1.8 元      | 10.8 元      | -| 豆包   | doubao-seed-2-0-mini | 0.2 元      | 2 元       | - - - -## 6. 配置指南 - -> 建议直接使用官方 API。第三方中转站延迟普遍更高。 - -### DeepSeek(推荐) - -- API 地址:`https://api.deepseek.com` -- 模型:`deepseek-v4-flash` -- 获取 API Key:[platform.deepseek.com](https://platform.deepseek.com/api_keys) -- **注意**:SayIt 会自动在请求中添加 `thinking: {type: "disabled"}` 关闭思考模式以降低延迟 -![alt text](DeepSeek获取API.png) - -### 通义千问 - -- API 地址:`https://dashscope.aliyuncs.com/compatible-mode` -- 模型:`qwen3.6-flash` -- 获取 API Key:[百炼平台](https://bailian.console.aliyun.com/?spm=a2c4g.11186623.0.0.60905ec6iyaRqr&tab=model#/api-key) -![alt text](百炼获取API.png) - -### 豆包(火山方舟) -- API 地址:`https://ark.cn-beijing.volces.com/api/v3` -- 模型:`doubao-seed-2-0-mini-260215` -![alt text](豆包获取API.png) \ No newline at end of file diff --git a/docs/SayIt ASR 服务提供商.md b/docs/SayIt ASR 服务提供商.md deleted file mode 100644 index e5ea29f..0000000 --- a/docs/SayIt ASR 服务提供商.md +++ /dev/null @@ -1,95 +0,0 @@ -# SayIt ASR 语音识别配置 - -SayIt 支持三种语音识别模式: - -- **云端 API**:调用云服务商的语音识别接口,准确率最高,适合大多数个人用户 -- **本地推理**:使用本地模型离线识别,不需要网络,但准确率和速度不如云端 -- **服务器模式**:自己部署后端服务,适合团队或有定制需求的场景 - -对个人用户来说,**云端 API 是最推荐的方式**,下面主要介绍这个。 - -## 1. 最佳实践 - -中文语音识别准确率最高的是**豆包流式语音识别 2.0**(Seed-ASR-2.0),其次是阿里的**千问 ASR**(qwen3-asr-flash)。 - -SayIt 对豆包和千问 ASR 都做了流式优化——按住说话时,音频会实时发送给服务端,松手后只需要等待最后一小段音频的处理时间,通常几百毫秒就能拿到结果。 - -另外 SayIt 还支持**千问 3.5 Omni** 多模态模型,这类模型原生支持音频输入、文本输出,可以在转录的同时执行 Prompt 指令(比如去口癖、翻译等),相当于 ASR + AI 润色一步到位。 - -| 供应商    | 模型           | 特点           | 价格          | -| --------------| --------------------------| --------------------------| -------------------------| -| **豆包**   | Seed-ASR-2.0       | 中文准确率最高,推荐首选 | 1.00 元/小时      | -| **阿里千问** | qwen3-asr-flash-realtime | 流式识别,速度极快    | 1.19 元/小时      | -| **阿里千问** | qwen3.5-omni-flash    | ASR + AI 一步到位    | 按 Token 计费(见下方) | - - -## 2. 使用豆包语音识别 - -SayIt 支持使用字节跳动「豆包流式语音识别模型 2.0」作为语音识别引擎。豆包 ASR 是目前中文语音识别准确率最高的模型,推荐使用。 - -1. 注册 / 登录火山引擎 - -打开[火山引擎语音服务控制台](https://console.volcengine.com/speech/service/10038),用手机号注册或登录即可。已有账号的直接登录,没有账号会引导你先注册。 -[图片] - - -2. 完成实名认证 - -火山引擎的 API 服务需要先做实名认证,没认证的话后面创建应用会卡住。进入[实名认证页面](https://console.volcengine.com/user/authentication/detail/),按页面提示走完就行。 -[图片] - - -3. 创建应用并获取 APP ID - -进入[应用管理页面](https://console.volcengine.com/speech/app1),点击「创建应用」。填写应用名称和简介(随便写就行),关键一步:一定要勾选「豆包流式语音识别模型 2.0 小时版」,不然后面用不了。 -[图片] - - -4. 获取APP ID 和 Access Token - -进入 [API 服务中心页面](https://console.volcengine.com/speech/service/10038),点击「豆包流式语音识别模型2.0」(配图是旧版界面)。页面上会显示你的 APP ID,鼠标放到 Access Token 上面,点击小眼睛图标,可以看到 Access Token 信息。 -[图片] - -记录下来这两个信息填到 SayIt 软件中。选择 语音引擎 - 云 API 模式 - 选择ASR供应商为豆包 ASR,填写凭证信息后可以保存测试。 -[图片] - - -豆包开通试用后,半年内有 20 小时的[免费额度](https://www.volcengine.com/docs/6561/1359369?lang=zh)。超出免费额度后,按量计费:豆包流式语音识别模型 2.0 — 1 元/小时。[后付费价格详情](https://www.volcengine.com/docs/6561/1359370?lang=zh) - - -## 3. 阿里千问 ASR - -阿里百炼平台提供了两个语音识别模型,都可以在 SayIt 中使用。 - -**qwen3-asr-flash / qwen3-asr-flash-realtime** - -纯语音识别模型,中文准确率仅次于豆包。有两个版本: -- **qwen3-asr-flash**:非流式,录完再发,速度也很快 -- **qwen3-asr-flash-realtime**:流式,边录边发,松手后几乎立刻出结果,体验极佳 - -两者准确率一样,推荐使用流式版本。适合搭配独立的 AI 润色供应商使用(比如通义千问或 DeepSeek 做文本校对)。 - -**qwen3.5-omni-flash** - -多模态模型,可以直接接收音频输入并输出文本。和普通 ASR + AI 润色的两步流程不同,Omni 模型一步完成,可以在 System Prompt 中直接控制输出格式(比如去口癖、翻译、列表排版等)。 - -适合不想分别配置 ASR 和 AI 润色的用户,配置更简单。 - -**配置方式** - -在 SayIt 设置中选择千问 ASR 系列,需要填写 API Key。 - -- 获取 API Key:[阿里百炼平台](https://bailian.console.aliyun.com/cn-beijing?tab=model#/api-key) - -## 4. 价格 - -下面是各个模型按需使用的价格参考。 - -| 模型                 | 价格      | 免费额度        | -| ---------------------------------------| -----------------| ------------------------| -| 豆包 Seed-ASR-2.0           | 1.00 元/小时  | 20 小时(半年内)   | -| 千问 qwen3-asr-flash(非流式)    | 0.79 元/小时  | 10 小时(90天内)   | -| 千问 qwen3-asr-flash-realtime(流式) | 1.19 元/小时  | 10 小时(90天内)   | -| 千问 qwen3.5-omni-flash        | 约 2.3 元/小时* | 100 万 Token(90天内) | - -*Omni 按 Token 计费(每秒音频 ≈ 25 token),2.3 元为粗略估算。详见[千问价格文档](https://help.aliyun.com/zh/model-studio/model-pricing#95f0464a10q5c)。 diff --git a/docs/images/readme/SayIt 用户反馈微信群.jpg b/docs/images/readme/SayIt 用户反馈微信群.jpg new file mode 100644 index 0000000..0006eb9 Binary files /dev/null and b/docs/images/readme/SayIt 用户反馈微信群.jpg differ diff --git a/docs/images/readme/screenshot-home.png b/docs/images/readme/screenshot-home.png index 2970d93..6c64d46 100644 Binary files a/docs/images/readme/screenshot-home.png and b/docs/images/readme/screenshot-home.png differ