import SwiftUI /// 推理引擎设置:本项目主模型 Gemma 4(端侧 4bit),**端侧统一走 MLX/GPU**。 /// MNN/SME2 LLM 引擎已移除(Gemma 4 无 MNN 转换模型),不再提供引擎选择—— /// 端侧只此一种,页面改为展示当前端侧后端 + 性能自检 + 云端 AI 开关。 struct InferenceSettingsView: View { // 云端 AI(Gemini)开关与 key —— 键名与 CloudAI 对齐,@AppStorage 写入即被后端读到。 @AppStorage("cloud_ai_gemini_enabled") private var cloudEnabled = false @AppStorage("cloud_ai_gemini_key") private var cloudKey = "" @State private var modelService = ModelDownloadService.shared /// 性能自检改为当前页就地展开,不再 push 新页面。 @State private var showSelfTest = false /// 性能自检需要主模型(Gemma 4,MLX)就绪。 private var modelReady: Bool { modelService.states[.llm]?.phase == .ready } var body: some View { ScrollView { VStack(spacing: 12) { HStack { Text("推理引擎") .font(.tjTitle()) .foregroundStyle(Tj.Palette.text) Spacer() } .padding(.top, 4) .padding(.bottom, 6) localEngineCard selfTestSection cloudSection noteCard } .padding(.horizontal, 16) .padding(.vertical, 20) } .background(Tj.Palette.sand.ignoresSafeArea()) .onAppear { modelService.refreshStates() } } /// 端侧后端信息卡(只此一种,不可切换):MLX · Metal GPU,端侧 Gemma 4 E2B。 private var localEngineCard: some View { HStack(spacing: 12) { ZStack { Circle().fill(Tj.Palette.amber.opacity(0.25)) Image(systemName: "bolt.fill") .font(.tjScaled(18)) .foregroundStyle(Tj.Palette.ink) } .frame(width: 44, height: 44) VStack(alignment: .leading, spacing: 2) { Text("MLX · GPU") .font(.tjScaled(15, weight: .semibold)) .foregroundStyle(Tj.Palette.text) Text("Metal GPU · 端侧推理 Gemma 4 E2B") .font(.tjScaled(12)) .foregroundStyle(Tj.Palette.text3) .lineLimit(2) } Spacer() Image(systemName: "checkmark.circle.fill") .font(.tjScaled(18)) .foregroundStyle(Tj.Palette.leaf) } .padding(14) .tjCard() } /// 性能自检入口:它是「动作/工具」而非引擎选择,所以做成描边动作按钮(TjGhostButton), /// 从视觉类别上和上方引擎/状态卡区分开。模型未就绪时禁用并给出下载提示而非死链。 @ViewBuilder private var selfTestSection: some View { if modelReady { VStack(spacing: 12) { Button { withAnimation(.easeInOut(duration: 0.22)) { showSelfTest.toggle() } } label: { HStack(spacing: 8) { Image(systemName: "gauge.with.needle") .font(.tjScaled(15, weight: .semibold)) Text("性能自检") Image(systemName: "chevron.down") .font(.tjScaled(13, weight: .semibold)) .rotationEffect(.degrees(showSelfTest ? 180 : 0)) } .frame(maxWidth: .infinity) } .buttonStyle(TjGhostButton()) if showSelfTest { ModelSelfTestView(embedded: true) .transition(.opacity.combined(with: .move(edge: .top))) } } .padding(.top, 4) } else { VStack(spacing: 8) { HStack(spacing: 8) { Image(systemName: "gauge.with.needle") .font(.tjScaled(15, weight: .semibold)) Text("性能自检") } .font(.tjScaled(15, weight: .semibold)) .foregroundStyle(Tj.Palette.text3) .frame(maxWidth: .infinity) .frame(height: 48) .background(Capsule().strokeBorder(Tj.Palette.line, lineWidth: 1)) .opacity(0.6) Text("模型未就绪,前往「模型管理」下载后可用") .font(.tjScaled(12)) .foregroundStyle(Tj.Palette.text3) } .padding(.top, 4) } } private var cloudConfigured: Bool { cloudEnabled && !cloudKey.trimmingCharacters(in: .whitespaces).isEmpty } /// 云端 AI(Gemini)开关区。hybrid 的「云端」一侧:默认关(隐私优先), /// 开启并填入 AI Studio 的 key 后,「读报告原图 / 深度解读 / 多语言」走 Google Gemini。 private var cloudSection: some View { VStack(alignment: .leading, spacing: 12) { HStack { Text("云端 AI · Gemini") .font(.tjTitle()) .foregroundStyle(Tj.Palette.text) Spacer() } .padding(.top, 10) VStack(alignment: .leading, spacing: 12) { Toggle(isOn: $cloudEnabled) { VStack(alignment: .leading, spacing: 2) { Text("启用云端增强") .font(.tjScaled(15, weight: .semibold)) .foregroundStyle(Tj.Palette.text) Text("默认端侧 Gemma 4;开启后「读报告原图 / 深度解读 / 多语言」走 Google Gemini。") .font(.tjScaled(12)) .foregroundStyle(Tj.Palette.text3) } } .tint(Tj.Palette.leaf) if cloudEnabled { SecureField(String(appLoc: "粘贴 Google AI Studio 的 API Key"), text: $cloudKey) .font(.tjScaled(14)) .textInputAutocapitalization(.never) .autocorrectionDisabled() .padding(12) .background(RoundedRectangle(cornerRadius: Tj.Radius.md).fill(Tj.Palette.sand2)) HStack(spacing: 6) { Image(systemName: cloudConfigured ? "checkmark.seal.fill" : "exclamationmark.circle") .font(.tjScaled(13)) .foregroundStyle(cloudConfigured ? Tj.Palette.leaf : Tj.Palette.text3) Text(cloudConfigured ? String(appLoc: "已就绪 · \(AIRuntime.cloudLabel)") : String(appLoc: "请填入 API Key(aistudio.google.com 免费获取)")) .font(.tjScaled(12)) .foregroundStyle(Tj.Palette.text3) } Text("Key 仅存本机;断网或额度用尽时自动回退端侧 Gemma 4,功能不中断。") .font(.tjScaled(11)) .foregroundStyle(Tj.Palette.text3) } } .padding(14) .tjCard() } } private var noteCard: some View { Text("隐私优先:默认端侧 Gemma 4(MLX · Metal GPU)推理,数据不出设备;仅在你开启「云端 AI」后,深度任务才走 Google Gemini。切换后下一次 AI 调用生效。") .font(.tjScaled(12)) .foregroundStyle(Tj.Palette.text3) .frame(maxWidth: .infinity, alignment: .leading) .padding(14) .tjCard() } } #Preview { InferenceSettingsView() }