缺少代码差异信息,无法生成具体的commit message。请提供code differences内容以便分析并生成符合Angular规范的提交信息。

当您提供代码差异后,我将按照以下格式生成:

```
<type>(<scope>): <subject>

<body>
```

其中type会根据更改类型选择(feat、fix、docs、style、refactor等),scope表示影响范围,subject简要描述变更内容,body详细说明修改内容。
This commit is contained in:
link2026
2026-06-07 14:17:18 +08:00
parent 074d99715d
commit 77a4ee1c37
66 changed files with 2676 additions and 548 deletions

View File

@@ -21,7 +21,8 @@ struct QuickRegionCaptureFlow: View {
@State private var analyzeTask: Task<Void, Never>? = nil
/// VL (); cancel ,UI
private let analyzeTimeoutSeconds: Int = 30
/// token ,30s , 60s
private let analyzeTimeoutSeconds: Int = 60
enum Phase {
case idle
@@ -86,23 +87,42 @@ struct QuickRegionCaptureFlow: View {
}
}
// MARK: - :()/ ()
// MARK: - :()/ ()
// RegionCameraView ( 1-2 ); ·
// , ,VL VisionKit :
// + ,VL / 退
@ViewBuilder
private var captureEntry: some View {
#if targetEnvironment(simulator)
PhotoPickerSheet(
onFinish: { imgs in if let first = imgs.first { startAnalyze(image: first) } },
onFinish: { imgs in handleScanned(imgs) },
onCancel: onClose
)
#else
RegionCameraView(
onCapture: { startAnalyze(image: $0) },
onCancel: onClose
)
if DocumentScannerView.isSupported {
DocumentScannerView(
onFinish: { imgs in handleScanned(imgs) },
onCancel: onClose
)
} else {
PhotoPickerSheet(
onFinish: { imgs in handleScanned(imgs) },
onCancel: onClose
)
}
#endif
}
/// /:();
private func handleScanned(_ images: [UIImage]) {
if let first = images.first {
startAnalyze(image: first)
} else {
onClose()
}
}
// MARK: -
private func startAnalyze(image: UIImage) {
@@ -110,12 +130,9 @@ struct QuickRegionCaptureFlow: View {
phase = .analyzing(image: image)
let timeout = analyzeTimeoutSeconds
// MainActor ,Task{} , phase 线,
// :Vision OCR Qwen3-1.7B LLM ( 3B VL )
analyzeTask = Task {
guard let data = image.jpegData(compressionQuality: 0.9) else {
phase = .confirm(image: image, items: [],
warning: String(appLoc: "图片编码失败,手动补充或重拍"))
return
}
let timeoutWarn = String(appLoc: "识别超时(>\(timeout)s),手动补充或重拍")
let watchdog = Task {
try? await Task.sleep(for: .seconds(timeout))
@@ -124,12 +141,25 @@ struct QuickRegionCaptureFlow: View {
defer { watchdog.cancel() }
do {
let parsed = try await CaptureService.shared.recognizeRegion(imageData: data)
// 1. OCR
let text = try await OCRService.recognizeText(in: image)
if Task.isCancelled {
phase = .confirm(image: image, items: [], warning: timeoutWarn); return
}
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
#if DEBUG
print("🔤 [OCR] recognized text:\n\(trimmed)\n--- end OCR ---")
#endif
if trimmed.isEmpty {
phase = .confirm(image: image, items: [],
warning: String(appLoc: "识别超时(>\(timeout)s),手动补充或重拍"))
warning: String(appLoc: "识别到文字,手动补充或重拍"))
return
}
// 2. LLM
let parsed = try await CaptureService.shared.recognizeIndicators(fromOCRText: trimmed)
if Task.isCancelled {
phase = .confirm(image: image, items: [], warning: timeoutWarn); return
}
let items = Self.buildItems(from: parsed)
phase = .confirm(
image: image,
@@ -138,23 +168,23 @@ struct QuickRegionCaptureFlow: View {
)
} catch CaptureError.modelNotReady {
phase = .confirm(image: image, items: [],
warning: String(appLoc: "VL 模型未就绪,手动补充"))
warning: String(appLoc: "AI 模型未就绪,手动补充"))
} catch let CaptureError.parseFailed(msg) {
phase = .confirm(image: image, items: [],
warning: String(appLoc: "VL 输出无法解析:\(msg)"))
warning: String(appLoc: "解析失败:\(msg)"))
} catch let CaptureError.inferenceFailed(msg) {
phase = .confirm(image: image, items: [],
warning: Task.isCancelled
? String(appLoc: "识别超时(>\(timeout)s),手动补充或重拍")
: String(appLoc: "推理失败:\(msg)"))
warning: Task.isCancelled ? timeoutWarn
: String(appLoc: "识别失败:\(msg)"))
} catch {
phase = .confirm(image: image, items: [],
warning: String(appLoc: "未知错误:\(error.localizedDescription)"))
warning: Task.isCancelled ? timeoutWarn
: String(appLoc: "未知错误:\(error.localizedDescription)"))
}
}
}
/// VL ,(high/low)
/// LLM ,(high/low)
private static func buildItems(from parsed: [ParsedReport.ParsedIndicator]) -> [QuickRegionItem] {
let mapped = parsed.map {
QuickRegionItem(name: $0.name, value: $0.value, unit: $0.unit,
@@ -233,16 +263,16 @@ private struct AnalyzingRegionView: View {
.font(.tjH2())
.foregroundStyle(Tj.Palette.text)
Text("100% 本地推理 · 已用 \(elapsed)s")
.font(.system(size: 12))
.font(.tjScaled( 12))
.foregroundStyle(Tj.Palette.text3)
if elapsed >= timeoutSeconds - 5 {
Text("快超时了,>\(timeoutSeconds)s 会自动转手动录入")
.font(.system(size: 11))
.font(.tjScaled( 11))
.foregroundStyle(Tj.Palette.amber)
}
}
Button("取消识别 · 改为手动录入", action: onCancel)
.font(.system(size: 13, weight: .medium))
.font(.tjScaled( 13, weight: .medium))
.foregroundStyle(Tj.Palette.text3)
.padding(.top, 4)
Spacer()

View File

@@ -55,7 +55,7 @@ struct QuickRegionConfirmView: View {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(Tj.Palette.amber)
Text(text)
.font(.system(size: 13))
.font(.tjScaled( 13))
.foregroundStyle(Tj.Palette.text2)
Spacer()
}
@@ -70,11 +70,11 @@ struct QuickRegionConfirmView: View {
VStack(alignment: .leading, spacing: 10) {
HStack {
Text("拍到的局部")
.font(.system(size: 13, weight: .semibold))
.font(.tjScaled( 13, weight: .semibold))
.foregroundStyle(Tj.Palette.text2)
Spacer()
Text("仅核对用 · 不保存照片")
.font(.system(size: 11))
.font(.tjScaled( 11))
.foregroundStyle(Tj.Palette.text3)
}
Image(uiImage: image)
@@ -91,7 +91,7 @@ struct QuickRegionConfirmView: View {
onRetake()
} label: {
Label("重拍", systemImage: "camera.rotate")
.font(.system(size: 13, weight: .medium))
.font(.tjScaled( 13, weight: .medium))
.foregroundStyle(Tj.Palette.ink)
}
}
@@ -102,7 +102,7 @@ struct QuickRegionConfirmView: View {
private var timeCard: some View {
VStack(alignment: .leading, spacing: 10) {
Text("测量时间")
.font(.system(size: 13, weight: .semibold))
.font(.tjScaled( 13, weight: .semibold))
.foregroundStyle(Tj.Palette.text2)
DatePicker("", selection: $capturedAt, in: ...Date.now)
.datePickerStyle(.compact)
@@ -116,7 +116,7 @@ struct QuickRegionConfirmView: View {
VStack(alignment: .leading, spacing: 14) {
HStack {
Text("识别到的指标 (\(items.count))")
.font(.system(size: 13, weight: .semibold))
.font(.tjScaled( 13, weight: .semibold))
.foregroundStyle(Tj.Palette.text2)
Spacer()
Button {
@@ -124,14 +124,14 @@ struct QuickRegionConfirmView: View {
status: .high, include: true))
} label: {
Label("加一项", systemImage: "plus.circle.fill")
.font(.system(size: 13, weight: .medium))
.font(.tjScaled( 13, weight: .medium))
.foregroundStyle(Tj.Palette.ink)
}
}
if items.isEmpty {
Text("没有识别到指标,点「加一项」手动补充,或返回重拍")
.font(.system(size: 13))
.font(.tjScaled( 13))
.foregroundStyle(Tj.Palette.text3)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.vertical, 20)
@@ -153,17 +153,17 @@ struct QuickRegionConfirmView: View {
item.wrappedValue.include.toggle()
} label: {
Image(systemName: item.wrappedValue.include ? "checkmark.circle.fill" : "circle")
.font(.system(size: 20))
.font(.tjScaled( 20))
.foregroundStyle(item.wrappedValue.include ? Tj.Palette.ink : Tj.Palette.text3)
}
.buttonStyle(.plain)
TextField(String(appLoc: "指标名"), text: item.name)
.font(.system(size: 15, weight: .medium))
.font(.tjScaled( 15, weight: .medium))
if abnormal {
Text(statusLabel(item.wrappedValue.status))
.font(.system(size: 10, weight: .semibold))
.font(.tjScaled( 10, weight: .semibold))
.foregroundStyle(statusColor(item.wrappedValue.status))
.padding(.horizontal, 7).padding(.vertical, 3)
.background(Capsule().fill(statusColor(item.wrappedValue.status).opacity(0.16)))
@@ -175,7 +175,7 @@ struct QuickRegionConfirmView: View {
}
} label: {
Image(systemName: "trash")
.font(.system(size: 14))
.font(.tjScaled( 14))
.foregroundStyle(Tj.Palette.brick)
}
}
@@ -203,10 +203,10 @@ struct QuickRegionConfirmView: View {
mono: Bool = false) -> some View {
VStack(alignment: .leading, spacing: 4) {
Text(label)
.font(.system(size: 11))
.font(.tjScaled( 11))
.foregroundStyle(Tj.Palette.text3)
TextField("", text: text)
.font(.system(size: 14, weight: mono ? .semibold : .regular,
.font(.tjScaled( 14, weight: mono ? .semibold : .regular,
design: mono ? .monospaced : .default))
.keyboardType(mono ? .decimalPad : .default)
.textInputAutocapitalization(.never)
@@ -234,7 +234,7 @@ struct QuickRegionConfirmView: View {
item.wrappedValue.status = st
} label: {
Text(statusLabel(st))
.font(.system(size: 12, weight: selected ? .semibold : .regular))
.font(.tjScaled( 12, weight: selected ? .semibold : .regular))
.foregroundStyle(selected ? Tj.Palette.paper : Tj.Palette.text2)
.padding(.horizontal, 12)
.padding(.vertical, 6)

View File

@@ -69,7 +69,7 @@ struct RegionCameraView: View {
//
Text("把异常项放进框里 · 对准一两行")
.font(.system(size: 13, weight: .medium))
.font(.tjScaled( 13, weight: .medium))
.foregroundStyle(.white)
.padding(.horizontal, 12)
.padding(.vertical, 6)
@@ -89,7 +89,7 @@ struct RegionCameraView: View {
onCancel()
} label: {
Text("取消")
.font(.system(size: 16, weight: .medium))
.font(.tjScaled( 16, weight: .medium))
.foregroundStyle(.white)
.padding(.horizontal, 14)
.padding(.vertical, 8)
@@ -126,19 +126,19 @@ struct RegionCameraView: View {
private var deniedView: some View {
VStack(spacing: 16) {
Image(systemName: "camera.fill")
.font(.system(size: 40))
.font(.tjScaled( 40))
.foregroundStyle(.white.opacity(0.8))
Text("相机权限未开启")
.font(.tjH2())
.foregroundStyle(.white)
Text("异常项快拍需要相机。去「设置 → 康康 → 相机」打开后再回来。")
.font(.system(size: 13))
.font(.tjScaled( 13))
.foregroundStyle(.white.opacity(0.7))
.multilineTextAlignment(.center)
.padding(.horizontal, 36)
HStack(spacing: 12) {
Button("取消") { onCancel() }
.font(.system(size: 15))
.font(.tjScaled( 15))
.foregroundStyle(.white)
.padding(.horizontal, 18).padding(.vertical, 10)
.background(Capsule().strokeBorder(.white.opacity(0.5), lineWidth: 1))
@@ -147,7 +147,7 @@ struct RegionCameraView: View {
UIApplication.shared.open(url)
}
}
.font(.system(size: 15, weight: .semibold))
.font(.tjScaled( 15, weight: .semibold))
.foregroundStyle(.black)
.padding(.horizontal, 18).padding(.vertical, 10)
.background(Capsule().fill(.white))