Files
openless/Sources/OpenLessApp/Settings/SettingsView.swift
T
baiqing 2517df5d2f feat(settings): LLM Providers tab + rewire DictationCoordinator
新增「LLM Provider」侧边栏 Tab:
- 顶部 picker 切换 active provider(含已配置条目 + 一键添加)
- 下方表单编辑 displayName / API Key / Base URL / Model / Temperature
  - 字段编辑后 0.5s debounce 自动写盘并 post `.openLessCredentialsChanged`
  - Base URL 与预设默认值相同时显示「默认」徽标
  - 预设条目展开「怎么获取 API Key?」disclosure,附 docsURL 跳转
- 「+ 添加」sheet 列出 5 家预设 + 自定义 OpenAI 兼容;自定义需填 slug+displayName
- 删除按钮在 active provider 上禁用,必须先切走再删

DictationCoordinator 不再构造 `DoubaoPolishClient`,改成:

    let active = vault.activeLLMProviderId
    guard let cfg = vault.llmProviderConfig(for: active),
          !cfg.apiKey.isEmpty else { return raw }   // 静默插入原文
    let provider = OpenAICompatibleLLMProvider(config: cfg, ...)
    let polished = try await provider.polish(rawText:..., mode:..., hotwords:...)

`SettingsHubTab` 移除硬编码的 Ark 三字段表单,改为只显示当前 active provider
状态并把详细设置指向新 Tab。
2026-04-29 11:54:24 +08:00

92 lines
2.7 KiB
Swift

import AppKit
import SwiftUI
import OpenLessCore
import OpenLessHotkey
import OpenLessPersistence
import OpenLessRecorder
import OpenLessASR
import OpenLessPolish
// 设置窗口的根入口:导航枚举 + 模型 + 主分发。
// 共享组件、侧边栏、各 Tab、遗留 Tab、通知名都在同目录其他文件里。
enum OpenLessMainTab: String, CaseIterable, Identifiable {
case home
case history
case dictionary
case polish
case llm
case help
case settings
var id: String { rawValue }
var title: String {
switch self {
case .home: return "首页"
case .history: return "历史记录"
case .dictionary: return "词汇表"
case .polish: return "风格"
case .llm: return "LLM Provider"
case .help: return "帮助中心"
case .settings: return "设置"
}
}
var symbol: String {
switch self {
case .home: return "chart.line.uptrend.xyaxis"
case .history: return "clock"
case .dictionary: return "text.book.closed"
case .polish: return "paintpalette"
case .llm: return "wand.and.stars"
case .help: return "questionmark.circle"
case .settings: return "gearshape"
}
}
}
@MainActor
final class SettingsNavigationModel: ObservableObject {
@Published var selection: OpenLessMainTab
init(selection: OpenLessMainTab = .home) {
self.selection = selection
}
}
struct SettingsView: View {
@ObservedObject private var navigation: SettingsNavigationModel
init(navigation: SettingsNavigationModel) {
self.navigation = navigation
}
var body: some View {
HStack(spacing: 14) {
FixedSidebar(selection: $navigation.selection)
Group {
switch navigation.selection {
case .home: HomeTab()
case .history: HistoryTab()
case .dictionary: DictionaryTab()
case .polish: StyleTab()
case .llm: LLMProvidersTab()
case .help: HelpTab()
case .settings: SettingsHubTab()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
// 右栏顶部留点空白,避免内容贴到 title bar 区域。
.padding(.top, 12)
}
// 左/下/右用相等 12pt;上方为 0,让侧边栏顶边贴窗口顶,
// 红绿灯(系统画在 title bar 层)就自然落在侧边栏的圆角矩形内部。
.padding(.leading, 12)
.padding(.trailing, 12)
.padding(.bottom, 12)
.background(WindowCanvasBackground())
.frame(minWidth: 1040, minHeight: 700)
}
}