feat(translation-polish): dual-engine translation UX + provider picker filter + topbar cleanup

- Add LLMProvider.isUserSelectable (default true) and filter
  ProviderPickerSection on it; next pass can hide non-user presets
  (e.g. a future DeepSeek key-preset) without changing call sites.
- KeyboardRootView: hide TranslationChip when off (matches user's
  mental model of an opt-in feature), drop the 'warming' branch
  (Qwen3 download UX was removed with the backend in v0.2.0), unify
  chip pill height to minHeight 28 + vertical 6 for visual rhythm
  across all topbar chips.
- TranslationChip: drop isLocal warning path — both engines now
  run the translate-and-polish step (local routes through DeepSeek
  via ProviderConfig.localModeProviderId).
- OnboardingView: cloud engine branch now wraps the translation
  row in the same surface card chrome as the local branch.
- Strings: drop keyboard.models.warming (no longer referenced).

DeepSeek key pre-fill deferred to a follow-up.
This commit is contained in:
2026-06-25 15:16:04 +08:00
parent 0956bb8534
commit 93b6aa6c02
7 changed files with 46 additions and 43 deletions
+5
View File
@@ -742,6 +742,11 @@ private struct APISetupPage: View {
// step through DeepSeek, so the constraint is gone).
// Wrapped in the same surface card chrome as the
// APISettingsCard above for visual symmetry.
//
// v0.2.1 final review (topbar cleanup pass): the
// surface card chrome is owned by `translationSection`
// itself, so both branches get it for free no
// per-branch duplication.
if config.isTranslationRowVisible {
translationSection
.padding(.horizontal, Spacing.lg)
@@ -10,15 +10,20 @@ struct ProviderPickerSection: View {
@ObservedObject var config: ProviderConfig
var body: some View {
// v0.2.1 follow-up: filter out presets marked as
// `isUserSelectable == false` so a future "DeepSeek key
// pre-fill" preset (or similar) can ship in `presets` without
// showing up in the picker.
let visiblePresets = LLMProvider.presets.filter { $0.isUserSelectable }
VStack(spacing: 0) {
ForEach(Array(LLMProvider.presets.enumerated()), id: \.element.id) { index, provider in
ForEach(Array(visiblePresets.enumerated()), id: \.element.id) { index, provider in
Button {
select(provider)
} label: {
row(provider, selected: provider.id == config.providerId)
}
.buttonStyle(.plain)
if index < LLMProvider.presets.count - 1 {
if index < visiblePresets.count - 1 {
Divider().background(palette.divider)
}
}