feat(translation-polish-2): local-engine dedicated section + PreconfiguredKeys for DeepSeek

- Add PreconfiguredKeys.swift: placeholder constant for the DeepSeek
  API key the local engine uses. DEBUG build asserts at launch when
  the placeholder is still in place so nobody ships an always-401
  build by accident. Replace TODO_FILL_LATER_DEEPSEEK_KEY before
  distributing.

- PolishingService.polishRemote:
  * Adopted (A) path: when effective provider is DeepSeek, take the
    apiKey from PreconfiguredKeys.deepseek instead of store.apiKey.
    Refuse the round-trip with PolishError.missingAPIKey when the
    placeholder is still in place.
  * Adopted fix for pre-existing typo: store.model.isEmpty ?
    preset.defaultModel : store.model (the right-hand side was
    defaultModel on both branches, silently ignoring the user's
    custom model field).

- LocalEngineSettingsRows.LocalModelsGroup:
  * Dropped the inline 'uses DeepSeek' caption — it added visual
    weight without telling the user anything they couldn't infer.
  * Translation row now lives inside the group so the local engine
    reads as one cohesive card.
  * Group owns its surface card chrome (palette.surface + rounded
    border) — callers no longer need to wrap it externally.

- SettingsView:
  * languageAndModelsSection: dropped the trailing TranslationPickerRow
    and the inner LocalModelsGroup (now lifted to its own section).
  * New localEngineSettingsSection: renders only when
    engineMode == 'local', wrapping LocalModelsGroup with a
    'settings.localEngine.title' header.
  * body: added the new section between languageAndModelsSection and
    the cloud-only systemPromptLinkSection.

- Localizable.strings (en + zh-Hans):
  * Removed settings.localModels.cloudPolish.caption (no references).
  * Added settings.localEngine.title = 'Local engine' / '本地引擎'.

DeepSeek key pre-fill tripwire + local-engine UI cohesion. No main
merge, no PR.
This commit is contained in:
2026-06-25 17:34:02 +08:00
parent 93b6aa6c02
commit 4fec0da7f0
6 changed files with 118 additions and 35 deletions
@@ -0,0 +1,50 @@
// PreconfiguredKeys.swift
// OSGKeyboard · Shared
//
// v0.2.1 follow-up: preconfigured API keys for built-in cloud providers
// the keyboard ships with out of the box. Today the only one is DeepSeek
// the local engine's default polish vendor (see
// `ProviderConfig.localModeProviderId`). Future builds may pre-fill
// additional providers as we harden them.
//
// These constants live in source so a developer building from the repo
// can swap in their own key once and have every Debug / TestFlight build
// "just work" without round-tripping the Keychain settings UI.
//
// IMPORTANT: Replace the placeholder string with a real key before
// shipping a build. The DEBUG assert below catches the placeholder at
// launch so nobody accidentally publishes an "always 401" build.
import Foundation
public enum PreconfiguredKeys {
/// Placeholder string we ship in the repo. Any value other than
/// this is treated as "configured".
private static let placeholder = "TODO_FILL_LATER_DEEPSEEK_KEY"
/// Preconfigured DeepSeek API key. Replace `placeholder` with a
/// real key in `Sources/.../PreconfiguredKeys.swift` before
/// distributing a build.
public static let deepseek: String = placeholder
#if DEBUG
/// Forces a lazy init at app launch in DEBUG builds so the assert
/// below fires immediately when somebody forgets to swap the
/// placeholder. The boolean is intentionally unused at runtime
/// it's a tripwire.
public static let isDeepseekConfigured: Bool = {
assert(
deepseek != placeholder,
"DeepSeek preconfigured key not filled — replace TODO_FILL_LATER_DEEPSEEK_KEY in PreconfiguredKeys.swift before building"
)
return deepseek != placeholder
}()
/// Touch the tripwire so the assert fires at launch rather than
/// only the first time the local engine actually tries to polish.
/// Called from app startup; safe to invoke multiple times.
public static func assertProductionReadinessAtLaunch() {
_ = isDeepseekConfigured
}
#endif
}