192 lines
7.8 KiB
Swift
192 lines
7.8 KiB
Swift
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()
|
|
}
|