feat(capture): 统一报告捕获流程并集成视觉语言模型识别

- 替换 QuickCaptureFlow 和 ArchiveFlow 为 UnifiedCaptureFlow 统一流程
- 新增 VLSession 封装 Qwen2.5-VL 模型进行图像文本推理
- 实现 AIRuntime 中 VL 模型的准备和分析功能
- 添加 VLPrompts 定义体检化验单识别的 JSON 输出模板
- 创建 CaptureReviewForm 提供 VL 解析结果的可编辑表单界面
- 集成 VisionKit 文档扫描器支持真机多页文档扫描
- 为模拟器实现 PhotosPicker 回退方案选择已有照片
- 在 RootView 中统一使用 UnifiedCaptureFlow 处理快速和归档流程
- 添加 CustomMetricEditor 支持自定义监测指标的创建编辑删除
- 扩展 KangkangApp 模型配置以支持新数据类型
- 实现档案列表中症状结束功能通过时间线行点击触发
This commit is contained in:
link2026
2026-05-26 11:18:00 +08:00
parent 39edc25dc1
commit 1b01923c8e
27 changed files with 3128 additions and 29 deletions

View File

@@ -171,6 +171,98 @@ final class Symptom {
}
}
///
/// hardcoded `MonitorMetric` IndicatorQuickSheet grid ;
/// `seriesKey` `"custom.<uuid>"`, Indicator
@Model
final class CustomMonitorMetric {
@Attribute(.unique) var seriesKey: String
var name: String
var unit: String
var lowerBound: Double?
var upperBound: Double?
var icon: String
var createdAt: Date
init(name: String,
unit: String,
lowerBound: Double? = nil,
upperBound: Double? = nil,
icon: String = "circle.fill",
createdAt: Date = .now) {
self.seriesKey = "custom.\(UUID().uuidString)"
self.name = name
self.unit = unit
self.lowerBound = lowerBound
self.upperBound = upperBound
self.icon = icon
self.createdAt = createdAt
}
var referenceRange: ClosedRange<Double>? {
guard let lo = lowerBound, let hi = upperBound, lo <= hi else { return nil }
return lo...hi
}
var rangeText: String {
guard let r = referenceRange else { return "" }
return "\(Self.format(r.lowerBound)) - \(Self.format(r.upperBound))"
}
private static func format(_ v: Double) -> String {
v.truncatingRemainder(dividingBy: 1) == 0
? String(format: "%.0f", v)
: String(format: "%.1f", v)
}
}
///
/// metric (`metricId` = `MonitorMetric.rawValue`)
/// `enabled=false`(), `ctx.delete`
@Model
final class MetricReminder {
@Attribute(.unique) var metricId: String
var displayName: String
var enabled: Bool
var hour: Int // 0...23
var minute: Int // 0...59
var weekdays: [Int] // iOS Calendar :1=, 2=, ..., 7= 7 =
var createdAt: Date
var updatedAt: Date
init(metricId: String,
displayName: String,
hour: Int = 8,
minute: Int = 0,
weekdays: [Int] = [1, 2, 3, 4, 5, 6, 7],
enabled: Bool = true,
createdAt: Date = .now) {
self.metricId = metricId
self.displayName = displayName
self.enabled = enabled
self.hour = max(0, min(23, hour))
self.minute = max(0, min(59, minute))
self.weekdays = weekdays
self.createdAt = createdAt
self.updatedAt = createdAt
}
var isEveryDay: Bool { Set(weekdays) == Set(1...7) }
var frequencyLabel: String {
if !enabled { return "已关闭" }
if isEveryDay { return "每天" }
if weekdays.isEmpty { return "未选日" }
let names = ["", "", "", "", "", "", ""]
let sorted = weekdays.sorted()
return "每周 " + sorted.map { names[$0 - 1] }.joined()
}
var timeLabel: String {
String(format: "%02d:%02d", hour, minute)
}
}
@Model
final class ChatTurn {
var question: String