feat(debug): DebugAIRunner DEBUG 自检入口挂到 MeView
按 W2 plan Task 7 落地,实现胜过 plan 原稿(强化 UX 减少 Xcode console 依赖): - 卡片显示 Application Support 路径 + 模型预期完整路径 - 一键复制路径到剪贴板,方便 `cp -R` 拷模型 - 模型就绪状态徽章(✓ 就绪 / ⚠ 未就绪),依赖 ModelStore.isReady - 跑一段 prompt 流式输出,顶部 tok/s 速率显示 - 全文件 #if DEBUG 包裹,Release 不打包 MeView 在 DEBUG 时挂 DebugAIRunner 在 placeholder 下方。 下一步用户手动:把 ~/tiji-models/Qwen3-1.7B-4bit 拷到模拟器沙盒 Application Support/Models/ 下,然后跑 App → Me 页点按钮验收。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
130
体己/Debug/DebugAIRunner.swift
Normal file
130
体己/Debug/DebugAIRunner.swift
Normal file
@@ -0,0 +1,130 @@
|
||||
#if DEBUG
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
/// DEBUG 自检:加载 LLM 并跑一段 prompt,流式显示 token + 速率。
|
||||
/// 同时显示沙盒 Application Support 路径,方便把模型拷进去。
|
||||
struct DebugAIRunner: View {
|
||||
@State private var output: String = ""
|
||||
@State private var status: String = "未开始"
|
||||
@State private var rate: Double = 0
|
||||
@State private var running = false
|
||||
@State private var modelReady: Bool = false
|
||||
|
||||
private var appSupportPath: String {
|
||||
(try? FileManager.default.url(
|
||||
for: .applicationSupportDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: nil,
|
||||
create: false
|
||||
).path) ?? "(无法获取)"
|
||||
}
|
||||
|
||||
private var modelExpectedPath: String {
|
||||
appSupportPath + "/Models/Qwen3-1.7B-4bit"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
Text("DEBUG · AI 自检")
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
|
||||
// 沙盒路径与模型状态卡
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("模型预期路径")
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
Text(modelExpectedPath)
|
||||
.font(.system(size: 10, design: .monospaced))
|
||||
.foregroundStyle(Tj.Palette.text2)
|
||||
.textSelection(.enabled)
|
||||
.lineLimit(3)
|
||||
HStack(spacing: 8) {
|
||||
Button("复制路径") {
|
||||
UIPasteboard.general.string = modelExpectedPath
|
||||
}
|
||||
.font(.system(size: 11))
|
||||
.buttonStyle(.borderless)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(modelReady ? "✓ 模型就绪" : "⚠ 模型未就绪")
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundStyle(modelReady ? Tj.Palette.leaf : Tj.Palette.brick)
|
||||
}
|
||||
}
|
||||
.padding(10)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(Color.black.opacity(0.03))
|
||||
)
|
||||
|
||||
// 推理状态
|
||||
HStack {
|
||||
Text("状态:\(status)")
|
||||
Spacer()
|
||||
Text(String(format: "%.1f tok/s", rate))
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
.monospaced()
|
||||
}
|
||||
.font(.system(size: 12))
|
||||
|
||||
Button(running ? "推理中..." : "跑一段 prompt") {
|
||||
Task { await run() }
|
||||
}
|
||||
.buttonStyle(TjPrimaryButton())
|
||||
.disabled(running)
|
||||
|
||||
ScrollView {
|
||||
Text(output.isEmpty ? "(暂无输出)" : output)
|
||||
.font(.system(.footnote, design: .monospaced))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(10)
|
||||
}
|
||||
.frame(maxHeight: 240)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(Color.black.opacity(0.04))
|
||||
)
|
||||
}
|
||||
.padding(16)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Tj.Radius.md)
|
||||
.fill(Color.yellow.opacity(0.08))
|
||||
)
|
||||
.padding(.horizontal, 16)
|
||||
.onAppear { refreshModelStatus() }
|
||||
}
|
||||
|
||||
private func refreshModelStatus() {
|
||||
modelReady = ModelStore.shared.isReady(.llm)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func run() async {
|
||||
running = true
|
||||
output = ""
|
||||
rate = 0
|
||||
status = "加载模型..."
|
||||
do {
|
||||
try await AIRuntime.shared.prepare()
|
||||
status = "推理中..."
|
||||
|
||||
let prompt = "用中文一句话介绍肝功能里 ALT 这个指标。"
|
||||
for try await chunk in await AIRuntime.shared.generate(
|
||||
prompt: prompt,
|
||||
maxTokens: 200
|
||||
) {
|
||||
output += chunk.text
|
||||
rate = chunk.decodeRate
|
||||
}
|
||||
status = "完成 ✓"
|
||||
} catch {
|
||||
status = "失败:\(error.localizedDescription)"
|
||||
}
|
||||
running = false
|
||||
refreshModelStatus()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user