From 9fbd31458c81a4c5ba2be5174f0b59abbfe44370 Mon Sep 17 00:00:00 2001 From: link2026 Date: Mon, 25 May 2026 16:50:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(debug):=20DebugAIRunner=20DEBUG=20?= =?UTF-8?q?=E8=87=AA=E6=A3=80=E5=85=A5=E5=8F=A3=E6=8C=82=E5=88=B0=20MeView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 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) --- 体己/Debug/DebugAIRunner.swift | 130 +++++++++++++++++++++++++++++++++ 体己/Features/Me/MeView.swift | 19 ++--- 2 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 体己/Debug/DebugAIRunner.swift diff --git a/体己/Debug/DebugAIRunner.swift b/体己/Debug/DebugAIRunner.swift new file mode 100644 index 0000000..186775c --- /dev/null +++ b/体己/Debug/DebugAIRunner.swift @@ -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 diff --git a/体己/Features/Me/MeView.swift b/体己/Features/Me/MeView.swift index 8622ccd..af71dab 100644 --- a/体己/Features/Me/MeView.swift +++ b/体己/Features/Me/MeView.swift @@ -2,16 +2,17 @@ import SwiftUI struct MeView: View { var body: some View { - VStack(spacing: 12) { - Spacer() - TjPlaceholder(label: "me · 设置 / 模型 / 档案管理\n(尚未实现)") - .frame(width: 280, height: 180) - Text("我的") - .font(.tjH2()) - .foregroundStyle(Tj.Palette.text2) - Spacer() + ScrollView { + VStack(spacing: 16) { + TjPlaceholder(label: "我的 · 模型管理 / Face ID / 关于\n(W6 实现)") + .frame(width: 280, height: 180) + + #if DEBUG + DebugAIRunner() + #endif + } + .padding(.vertical, 24) } - .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Tj.Palette.sand.ignoresSafeArea()) } }