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
@@ -109,8 +109,8 @@ public actor PolishingService {
// v0.2.1 follow-up: when the caller pins a provider id (the
// local engine pins DeepSeek) we still want to honor the
// injected test client, but we have to re-derive the
// preset/baseURL/model triplet from the *override* so the
// injected client gets the right values when it's nil.
// preset/baseURL/model/apiKey quartet from the *override* so
// the injected client gets the right values when it's nil.
let effectiveProviderId = providerIdOverride ?? store.providerId
let client: LLMClient
if let injectedClient {
@@ -118,8 +118,25 @@ public actor PolishingService {
} else {
let preset = LLMProvider.provider(id: effectiveProviderId)
let baseURL = store.baseURL.isEmpty ? preset.defaultBaseURL : store.baseURL
let model = store.model.isEmpty ? preset.defaultModel : preset.defaultModel
client = OpenAICompatibleClient(baseURL: baseURL, apiKey: store.apiKey, model: model)
// Pre-existing typo fix: the user-overridden `store.model`
// path was returning `preset.defaultModel` on both branches,
// silently ignoring the user's custom model field. Restore
// the asymmetry so the user override actually wins.
let model = store.model.isEmpty ? preset.defaultModel : store.model
let apiKey: String
if effectiveProviderId == "deepseek" {
let preconfigured = PreconfiguredKeys.deepseek
if preconfigured == "TODO_FILL_LATER_DEEPSEEK_KEY" {
// Placeholder still in place refuse the round-
// trip so the UI can surface a "build not
// configured" hint instead of a 401.
throw PolishError.missingAPIKey
}
apiKey = preconfigured
} else {
apiKey = store.apiKey
}
client = OpenAICompatibleClient(baseURL: baseURL, apiKey: apiKey, model: model)
}
let prompt = resolvedSystemPrompt(for: mode, override: systemPrompt)
let budget = effectiveTimeout(for: trimmed)