Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ Every item below is one more layer sedimented into a default — a capability yo
- **Toggle and push-to-talk** recording modes, plus a **MediaPlayPause trigger** so wired-earbud inline controls can start and stop recording. `Esc` cancels at any phase, including polish and insertion.
- **Cloud ASR**: Volcengine streaming ASR (bigasr), iFlytek realtime ASR (RTASR), Alibaba Cloud Bailian (classic realtime / Qwen3 realtime / Fun-ASR-Flash file transcription), StepFun StepAudio (batch + realtime), Zhipu GLM-ASR, Xiaomi MiMo ASR, ElevenLabs Scribe, OpenAI-compatible batch transcription (OpenAI Whisper / Groq / SiliconFlow SenseVoice / OpenRouter / ZenMux), and Apple Speech (macOS).
- **Local ASR**: bundled Qwen3-ASR (0.6B / 1.7B) via vendored `Open-Less/qwen-asr` (macOS); Windows Foundry Local Whisper and sherpa-onnx (experimental) variants.
- **Polish providers**: Ark (Volcengine), DeepSeek, OpenAI, Google Gemini, Codex OAuth, SiliconFlow, Atlas Cloud, Xiaomi MiMo, CometAPI, OpenRouter, Alibaba Cloud Coding Plan, CodingPlanX, MiniMax, and StepFun — plus any OpenAI-compatible endpoint you bring.
- **Polish providers**: Ark (Volcengine), DeepSeek, OpenAI, Google Gemini, Codex OAuth, SiliconFlow, Atlas Cloud, Xiaomi MiMo, CometAPI, OpenRouter, Alibaba Cloud Coding Plan, CodingPlanX, MiniMax, StepFun, and OpenCode Zen — plus any OpenAI-compatible endpoint you bring.
- **Four output modes**: raw, light polish, structured (**AI-prompt mode**), and formal. Plus a **translation hotkey** that converts speech directly into the configured target language ([#43](../../issues/43)).
- **Selection-ask QA panel** — a separate hotkey opens a floating panel that runs voice Q&A against the highlighted text in any app ([#118](../../issues/118)).
- **Main window**: Overview / History / Vocab / Style / Marketplace / Settings. Persistent tray icon, plus a mini status capsule that floats on screen and follows the display you are typing on (multi-monitor).
Expand Down
2 changes: 1 addition & 1 deletion README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ OpenLess 只做一件事:**把语音变成可用的书面文字(尤其是 AI 提
- **切换式与按住说话(push-to-talk)** 两种录音模式,外加 **MediaPlayPause 触发**,让有线耳机的线控也能开始 / 停止录音。`Esc` 可在任意阶段取消,包括润色与插入。
- **云端 ASR**:Volcengine 流式 ASR(bigasr)、讯飞实时语音转写(RTASR)、阿里云百炼(经典实时 / Qwen3 实时 / Fun-ASR-Flash 录音文件)、阶跃星辰 StepAudio(批式 + 实时)、智谱 GLM-ASR、小米 MiMo ASR、ElevenLabs Scribe、OpenAI 兼容批量转写(OpenAI Whisper / Groq / 硅基流动 SenseVoice / OpenRouter / ZenMux),以及 Apple Speech(macOS)。
- **本地 ASR**:通过 vendored 的 `Open-Less/qwen-asr` 内置 Qwen3-ASR(0.6B / 1.7B)(macOS);Windows 上的 Foundry Local Whisper 与 sherpa-onnx(实验性)变体。
- **润色提供方**:Ark(火山方舟)、DeepSeek、OpenAI、Google Gemini、Codex OAuth、硅基流动、Atlas Cloud、小米 MiMo、CometAPI、OpenRouter、阿里云 Coding Plan、CodingPlanX、MiniMax、StepFun,以及你自带的任意 OpenAI 兼容端点。
- **润色提供方**:Ark(火山方舟)、DeepSeek、OpenAI、Google Gemini、Codex OAuth、硅基流动、Atlas Cloud、小米 MiMo、CometAPI、OpenRouter、阿里云 Coding Plan、CodingPlanX、MiniMax、StepFun、OpenCode Zen,以及你自带的任意 OpenAI 兼容端点。
- **四种输出模式**:原文、轻度润色、结构化(**AI 提示词模式**)、正式。另有一个**翻译快捷键**,将语音直接转换为所配置的目标语言([#43](../../issues/43))。
- **选区问答面板**——一个独立快捷键打开浮动面板,针对任意应用中被高亮选中的文本进行语音问答([#118](../../issues/118))。
- **主窗口**:概览 / 历史 / 词典 / 风格 / 市场 / 设置。常驻托盘图标,以及一个浮于屏幕、并跟随你正在输入的显示器的迷你状态胶囊(多显示器)。
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/src-tauri/src/commands/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ fn llm_provider_default_endpoint(provider: &str) -> Option<&'static str> {
"alibabaCoding" => Some("https://coding-intl.dashscope.aliyuncs.com/v1"),
"codingPlanX" => Some("https://api.codingplanx.ai/v1"),
"stepfun" => Some("https://api.stepfun.com/v1"),
"opencode" => Some("https://opencode.ai/zen/v1"),
_ => None,
}
}
Expand Down
38 changes: 36 additions & 2 deletions openless-all/app/src-tauri/src/polish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ fn is_builtin_llm_provider(provider_id: &str) -> bool {
| "codingPlanX"
| "minimax"
| "stepfun"
| "opencode"
)
}

Expand Down Expand Up @@ -1875,7 +1876,7 @@ pub(crate) enum ThinkingControl {

pub(crate) fn openai_compatible_thinking_control(provider_id: &str) -> Option<ThinkingControl> {
match provider_id.trim() {
"deepseek" => Some(ThinkingControl::DeepSeekThinking),
"deepseek" | "opencode" => Some(ThinkingControl::DeepSeekThinking),
// provider_id 预设(见 ProvidersSection.tsx::LLM_PRESETS)。
"minimax" => Some(ThinkingControl::MiniMaxThinking),
"openrouterFree" => Some(ThinkingControl::OpenRouterReasoning),
Expand Down Expand Up @@ -1911,7 +1912,7 @@ pub(crate) fn openai_compatible_thinking_control_for_base_url(
if host.contains("minimax") {
return Some(ThinkingControl::MiniMaxThinking);
}
if host.contains("deepseek") {
if host.contains("deepseek") || host.contains("opencode.ai") {
return Some(ThinkingControl::DeepSeekThinking);
}
if host.contains("openrouter") {
Expand Down Expand Up @@ -3619,6 +3620,39 @@ mod tests {
assert_eq!(body["thinking"]["type"], "disabled");
}

#[test]
fn openai_chat_body_adds_deepseek_thinking_toggle_for_opencode() {
let provider = OpenAICompatibleLLMProvider::new(OpenAICompatibleConfig::new(
"opencode",
"OpenCode Zen",
"https://opencode.ai/zen/v1",
"k",
"deepseek-v4-flash",
));

let body = provider.chat_body(false, vec![json!({ "role": "user", "content": "hi" })]);

assert_eq!(body["thinking"]["type"], "disabled");
}

#[test]
fn openai_chat_body_falls_back_to_base_url_for_custom_opencode_endpoint() {
let provider = OpenAICompatibleLLMProvider::new(
OpenAICompatibleConfig::new(
"custom",
"Custom",
"https://opencode.ai/zen/go/v1",
"k",
"deepseek-v4-flash",
)
.with_thinking_enabled(false),
);

let body = provider.chat_body(false, vec![json!({ "role": "user", "content": "hi" })]);

assert_eq!(body["thinking"]["type"], "disabled");
}

#[test]
fn openai_chat_body_disables_minimax_thinking_by_preset() {
// provider_id 预设命中 "minimax" → 走 MiniMaxThinking 分支,关闭时下发
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,7 @@ export const en: typeof zhCN = {
codingPlanX: 'CodingPlanX',
minimax: 'MiniMax (M3)',
stepfun: 'StepFun',
opencode: 'OpenCode Zen',
custom: 'Custom',
asrVolcengine: 'Volcengine bigasr',
asrBailian: 'Alibaba Bailian realtime ASR',
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@ export const ja: typeof zhCN = {
codingPlanX: 'CodingPlanX',
minimax: 'MiniMax(M3)',
stepfun: 'StepFun(階躍星辰)',
opencode: 'OpenCode Zen',
custom: 'カスタム',
asrVolcengine: 'Volcengine bigasr',
asrBailian: 'Alibaba Bailian リアルタイム ASR',
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@ export const ko: typeof zhCN = {
codingPlanX: 'CodingPlanX',
minimax: 'MiniMax (M3)',
stepfun: 'StepFun',
opencode: 'OpenCode Zen',
custom: '사용자 정의',
asrVolcengine: 'Volcengine bigasr',
asrBailian: 'Alibaba Bailian 실시간 ASR',
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,7 @@ export const zhCN = {
codingPlanX: 'CodingPlanX',
minimax: 'MiniMax(M3)',
stepfun: 'StepFun(阶跃星辰)',
opencode: 'OpenCode Zen',
custom: '自定义',
asrVolcengine: '火山引擎 bigasr',
asrBailian: '阿里云百炼实时 ASR',
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/src/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,7 @@ export const zhTW: typeof zhCN = {
codingPlanX: 'CodingPlanX',
minimax: 'MiniMax(M3)',
stepfun: 'StepFun(階躍星辰)',
opencode: 'OpenCode Zen',
custom: '自定義',
asrVolcengine: '火山引擎 bigasr',
asrBailian: '阿里雲百煉即時 ASR',
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/src/pages/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const LLM_NAME_KEY_BY_ID: Record<string, string> = {
openrouterFree: 'openrouterFree',
alibabaCoding: 'alibabaCoding',
codingPlanX: 'codingPlanX',
opencode: 'opencode',
custom: 'custom',
};

Expand Down
20 changes: 20 additions & 0 deletions openless-all/app/src/pages/settings/ProvidersSection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ if (atlascloudPreset.modelPlaceholder !== 'qwen/qwen3.5-flash') {
throw new Error(`unexpected Atlas Cloud default model: ${atlascloudPreset.modelPlaceholder}`);
}

const opencodePreset = LLM_PRESETS.find(p => p.id === 'opencode');

if (!opencodePreset) {
throw new Error('OpenCode LLM preset is missing');
}

if (opencodePreset.baseUrl !== 'https://opencode.ai/zen/v1') {
throw new Error(`unexpected OpenCode base URL: ${opencodePreset.baseUrl}`);
}

if (opencodePreset.modelPlaceholder !== 'deepseek-v4-flash') {
throw new Error(`unexpected OpenCode default model: ${opencodePreset.modelPlaceholder}`);
}

const customPresetIndex = LLM_PRESETS.findIndex(p => p.id === 'custom');
const opencodePresetIndex = LLM_PRESETS.findIndex(p => p.id === 'opencode');
if (customPresetIndex < 0 || opencodePresetIndex < 0 || opencodePresetIndex >= customPresetIndex) {
throw new Error('OpenCode preset must sit before the custom fallback preset');
}

const openAiCompatiblePreset = ASR_PRESETS.find(p => p.id === 'openai-compatible');

if (!openAiCompatiblePreset) {
Expand Down
10 changes: 10 additions & 0 deletions openless-all/app/src/pages/settings/ProvidersSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,16 @@ export const LLM_PRESETS = [
baseUrl: 'https://api.stepfun.com/v1',
modelPlaceholder: 'step-1o-turbo-vision',
},
{
// OpenCode Zen:OpenAI 兼容 /zen/v1/chat/completions。
// 默认模型 deepseek-v4-flash(官方 model id,见 https://opencode.ai/docs/zen)。
// OpenCode Go 订阅把 Base URL 改成 https://opencode.ai/zen/go/v1,Key 用 Go 控制台那把。
// provider_id 在后端 polish.rs 命中 "opencode" → DeepSeekThinking(默认模型是 Flash)。
id: 'opencode',
nameKey: 'opencode',
baseUrl: 'https://opencode.ai/zen/v1',
modelPlaceholder: 'deepseek-v4-flash',
},
{
id: 'custom',
nameKey: 'custom',
Expand Down
Loading