缺少代码差异信息,无法生成具体的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,6 +21,11 @@ struct ParsedReport: Sendable {
var unit: String
var range: String
var status: IndicatorStatus
var sourcePageIndex: Int?
var sourceBoxX: Double?
var sourceBoxY: Double?
var sourceBoxWidth: Double?
var sourceBoxHeight: Double?
}
/// = ,UI 退
@@ -100,11 +105,16 @@ actor CaptureService {
do {
raw = try await AIRuntime.shared.analyzeReport(
imageURLs: [tmpURL],
prompt: VLPrompts.regionExtraction()
prompt: VLPrompts.regionExtraction(),
// ,512 token
maxTokens: 2048
)
} catch {
throw CaptureError.inferenceFailed("\(error)")
}
#if DEBUG
print("🔎 [recognizeRegion] image bytes=\(imageData.count), VL raw output:\n\(raw)\n--- end VL raw ---")
#endif
do {
return try CaptureService.parseIndicatorsJSON(raw)
} catch let CaptureError.parseFailed(msg) {
@@ -114,6 +124,56 @@ actor CaptureService {
}
}
/// OCR : Vision OCR LLM(Qwen3-1.7B)
/// Report; `CaptureError`,UI 退(§3.2)
/// (MainActor) OCR,OCR actor, UIImage actor
func recognizeIndicators(fromOCRText text: String) async throws -> [ParsedReport.ParsedIndicator] {
do {
try await AIRuntime.shared.prepare() // LLM( VL,OOM )
} catch {
throw CaptureError.modelNotReady
}
let prompt = VLPrompts.indicatorsFromText(text)
var collected = ""
do {
// , token;LLM VL AIRuntime
let stream = await AIRuntime.shared.generate(prompt: prompt, maxTokens: 2048)
for try await chunk in stream {
collected += chunk.text
}
} catch {
throw CaptureError.inferenceFailed("\(error)")
}
// Qwen3 <think></think>, JSON
let cleaned = CaptureService.stripThink(collected)
#if DEBUG
print("🧠 [recognizeIndicators] LLM cleaned output:\n\(cleaned)\n--- end LLM ---")
#endif
do {
return try CaptureService.parseIndicatorsJSON(cleaned)
} catch let CaptureError.parseFailed(msg) {
throw CaptureError.parseFailed(msg)
} catch {
throw CaptureError.parseFailed("\(error)")
}
}
/// Qwen3 <think></think>( / / ), trim
/// HealthExportService.stripThinkBlocks , MainActor actor, nonisolated
nonisolated static func stripThink(_ raw: String) -> String {
var s = raw
while let openR = s.range(of: "<think>"),
let closeR = s.range(of: "</think>", range: openR.upperBound..<s.endIndex) {
s.removeSubrange(openR.lowerBound..<closeR.upperBound)
}
if let openR = s.range(of: "<think>") { s = String(s[..<openR.lowerBound]) }
if let closeR = s.range(of: "</think>") { s = String(s[closeR.upperBound...]) }
while let first = s.first, first.isWhitespace { s.removeFirst() }
return s
}
/// VL + JSON assets Vault
private func runVL(on assets: [FileVault.SavedAsset]) async throws -> ParsedReport {
do {
@@ -344,7 +404,36 @@ actor CaptureService {
let range = stringValue(d, keys: ["range", "reference", "reference_range", "ref", "参考", "参考值", "参考范围", "正常范围"]) ?? ""
let statusRaw = stringValue(d, keys: ["status", "flag", "abnormal", "异常", "提示", "标记"])
let status = parseIndicatorStatus(raw: statusRaw, value: value, range: range)
return .init(name: name, value: value, unit: unit, range: range, status: status)
let evidence = parseEvidenceLocation(d)
return .init(
name: name,
value: value,
unit: unit,
range: range,
status: status,
sourcePageIndex: evidence?.pageIndex,
sourceBoxX: evidence?.box.x,
sourceBoxY: evidence?.box.y,
sourceBoxWidth: evidence?.box.width,
sourceBoxHeight: evidence?.box.height
)
}
private static func parseEvidenceLocation(_ d: [String: Any]) -> (pageIndex: Int, box: (x: Double, y: Double, width: Double, height: Double))? {
guard let page = intValue(d, keys: ["source_page", "sourcePage", "page", "页码", "来源页码"]),
page >= 1,
let box = numberArrayValue(d, keys: ["source_box", "sourceBox", "box", "bbox", "位置", "来源位置"]),
box.count == 4 else {
return nil
}
let x = box[0]
let y = box[1]
let width = box[2]
let height = box[3]
guard x >= 0, y >= 0, width > 0, height > 0, x + width <= 1, y + height <= 1 else {
return nil
}
return (page - 1, (x, y, width, height))
}
private static func stringValue(_ d: [String: Any], keys: [String]) -> String? {
@@ -359,6 +448,44 @@ actor CaptureService {
return nil
}
private static func intValue(_ d: [String: Any], keys: [String]) -> Int? {
for key in keys {
if let i = d[key] as? Int {
return i
}
if let n = d[key] as? NSNumber {
return n.intValue
}
if let s = d[key] as? String, let i = Int(s.trimmingCharacters(in: .whitespacesAndNewlines)) {
return i
}
}
return nil
}
private static func numberArrayValue(_ d: [String: Any], keys: [String]) -> [Double]? {
for key in keys {
if let arr = d[key] as? [Double] {
return arr
}
if let arr = d[key] as? [NSNumber] {
return arr.map(\.doubleValue)
}
if let arr = d[key] as? [Any] {
let values = arr.compactMap { item -> Double? in
if let d = item as? Double { return d }
if let n = item as? NSNumber { return n.doubleValue }
if let s = item as? String { return Double(s.trimmingCharacters(in: .whitespacesAndNewlines)) }
return nil
}
if values.count == arr.count {
return values
}
}
}
return nil
}
private static func arrayValue(_ d: [String: Any], keys: [String]) -> [[String: Any]] {
for key in keys {
if let arr = d[key] as? [[String: Any]] {
@@ -480,7 +607,12 @@ extension Report {
status: p.status,
capturedAt: reportDate,
report: self,
source: .report
source: .report,
sourcePageIndex: p.sourcePageIndex,
sourceBoxX: p.sourceBoxX,
sourceBoxY: p.sourceBoxY,
sourceBoxWidth: p.sourceBoxWidth,
sourceBoxHeight: p.sourceBoxHeight
)
ctx.insert(i)
}