```
feat(AI): 集成MNN推理引擎替换MLX作为主AI运行时 - 引入MNN(alibaba) + Arm SME2 + CPU作为主AI运行时,支持A19/iPhone17的 SME2和A17的NEON加速 - 添加MLX Swift作为兜底GPU推理方案,实现双后端切换机制 - 使用单一Qwen3.5-2B多模态模型(1.2GB),替代原有的LLM+VL分离架构 - 实现InferenceEngine.current引擎选择逻辑,真机默认MNN,模拟器回退MLX - 更新AIAgent架构,通过MNNLLMBridge(ObjC++) → MNNBackend进行推理 - 修改队列机制防止并发推理导致OOM,使用信号量闸门控制显存占用 - 更新文档中的技术栈说明、模块边界和周次交付计划 ```
This commit is contained in:
@@ -171,6 +171,12 @@ final class DiaryEntry {
|
||||
var createdAt: Date
|
||||
var tags: [String]
|
||||
|
||||
/// 拍药盒入档时关联的原图(最多 5 张:正面/背面/说明书…)。
|
||||
/// 默认空数组 → 旧数据轻量迁移安全(见 swiftdata-rebuild-data-loss)。
|
||||
/// cascade:删日记同删 Asset 记录;Vault 里的 JPEG 仍需在删除入口手动 unlink。
|
||||
@Relationship(deleteRule: .cascade)
|
||||
var assets: [Asset] = []
|
||||
|
||||
init(content: String, createdAt: Date = .now, tags: [String] = []) {
|
||||
self.content = content
|
||||
self.createdAt = createdAt
|
||||
@@ -204,6 +210,45 @@ final class Asset {
|
||||
}
|
||||
}
|
||||
|
||||
/// 药品库:用户「我有哪些药」的 master 档案(拍药盒识别或手输入库)。
|
||||
/// 与「用药使用记录」(带 `DiaryEntry.medicationTag` 的日记,记某次服用的剂量 + 时间)分层:
|
||||
/// 这里只放清单 / 规格 / 用法 / 原图,不带服用时间。
|
||||
/// 新增 @Model 表 → SwiftData 轻量迁移安全(见 KangkangApp 兜底注释)。
|
||||
@Model
|
||||
final class Medication {
|
||||
var name: String // 药名(通用名,可含商品名),与 ParsedMedication.name 同义
|
||||
var strength: String // 规格,如 "80mg×7粒";无则 ""
|
||||
var usage: String // 用法,如 "一日一次,一次一粒";无则 ""
|
||||
var note: String? // 备注(可选)
|
||||
var createdAt: Date
|
||||
var updatedAt: Date
|
||||
|
||||
/// 入库时关联的原图(药盒正面 / 背面 / 说明书,最多 5 张)。默认空数组 → 旧数据轻量迁移安全。
|
||||
/// cascade:删药品同删 Asset 记录;Vault 里的 JPEG 仍需在删除入口手动 unlink(同 DiaryEntry.assets 约定)。
|
||||
@Relationship(deleteRule: .cascade)
|
||||
var assets: [Asset] = []
|
||||
|
||||
init(name: String,
|
||||
strength: String = "",
|
||||
usage: String = "",
|
||||
note: String? = nil,
|
||||
createdAt: Date = .now) {
|
||||
self.name = name
|
||||
self.strength = strength
|
||||
self.usage = usage
|
||||
self.note = note
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = createdAt
|
||||
}
|
||||
|
||||
/// 列表副标题 / 写日记选药时的展示行:"80mg×7粒 · 一日一次"(缺项自动省略)。
|
||||
var detailLine: String {
|
||||
[strength, usage]
|
||||
.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
||||
.joined(separator: " · ")
|
||||
}
|
||||
}
|
||||
|
||||
@Model
|
||||
final class Symptom {
|
||||
var name: String
|
||||
@@ -353,9 +398,13 @@ final class CustomReminder {
|
||||
var hour: Int // 0...23
|
||||
var minute: Int // 0...59
|
||||
var weekdays: [Int] // iOS Calendar 约定:1=日, 2=一, ..., 7=六。全 7 个 = 每天
|
||||
var frequencyRaw: String = "daily" // CustomReminder.Frequency 原始值
|
||||
var dayOfMonth: Int = 1 // monthly / yearly 用,1...31
|
||||
var frequencyRaw: String = "daily" // 旧:单选频率代表值;多选见 frequenciesRaw
|
||||
var dayOfMonth: Int = 1 // yearly 用 + 旧 monthly 单选兜底,1...31
|
||||
var month: Int = 1 // yearly 用,1...12
|
||||
/// 多选频率原始值(["daily","weekly",...])。空 = 旧数据,回退到单选 frequency。
|
||||
var frequenciesRaw: [String] = []
|
||||
/// 每月多选日期(1...31)。空 = 旧数据,回退到单选 dayOfMonth。
|
||||
var monthDays: [Int] = []
|
||||
var enabled: Bool
|
||||
var createdAt: Date
|
||||
var updatedAt: Date
|
||||
@@ -392,10 +441,41 @@ final class CustomReminder {
|
||||
set { frequencyRaw = newValue.rawValue }
|
||||
}
|
||||
|
||||
/// 列表行副标题:按频率展示「每天 / 每周 一三五 / 每月15日 / 每年3月15日」。
|
||||
/// 生效的频率集合(多选)。frequenciesRaw 为空时回退到单选 frequency(兼容旧数据 / 旧 init)。
|
||||
var frequencies: Set<Frequency> {
|
||||
get {
|
||||
let parsed = Set(frequenciesRaw.compactMap { Frequency(rawValue: $0) })
|
||||
return parsed.isEmpty ? [frequency] : parsed
|
||||
}
|
||||
set {
|
||||
frequenciesRaw = newValue.map(\.rawValue).sorted()
|
||||
// 同步单选代表值,旧读者读 frequency 仍合理。
|
||||
if let rep = newValue.map(\.rawValue).sorted().first { frequencyRaw = rep }
|
||||
}
|
||||
}
|
||||
|
||||
/// 每月生效日期(多选,1...31)。monthDays 为空时回退到单选 dayOfMonth(兼容旧数据)。
|
||||
/// 注意:不回写 dayOfMonth —— 后者仍归 yearly 独占,避免「同时选每月+每年」时互相覆盖。
|
||||
var monthlyDays: [Int] {
|
||||
get { monthDays.isEmpty ? [dayOfMonth] : monthDays.sorted() }
|
||||
set { monthDays = Set(newValue.map { max(1, min(31, $0)) }).sorted() }
|
||||
}
|
||||
|
||||
/// 列表行副标题:多选频率用「 · 」拼接,如「每周一三五 · 每月1·15日」。
|
||||
/// 含「每日」时直接显示「每天」(已覆盖其余)。
|
||||
var frequencyLabel: String {
|
||||
if !enabled { return String(appLoc: "已关闭") }
|
||||
switch frequency {
|
||||
let active = frequencies
|
||||
if active.contains(.daily) { return String(appLoc: "每天") }
|
||||
// weekly 选满 7 天等价每天。
|
||||
if active == [.weekly] && isEveryDay { return String(appLoc: "每天") }
|
||||
let order: [Frequency] = [.weekly, .monthly, .yearly]
|
||||
let parts = order.filter { active.contains($0) }.map { freqPartLabel($0) }
|
||||
return parts.isEmpty ? String(appLoc: "未选日") : parts.joined(separator: " · ")
|
||||
}
|
||||
|
||||
private func freqPartLabel(_ f: Frequency) -> String {
|
||||
switch f {
|
||||
case .daily:
|
||||
return String(appLoc: "每天")
|
||||
case .weekly:
|
||||
@@ -404,7 +484,9 @@ final class CustomReminder {
|
||||
let names = [String(appLoc: "日"), String(appLoc: "一"), String(appLoc: "二"), String(appLoc: "三"), String(appLoc: "四"), String(appLoc: "五"), String(appLoc: "六")]
|
||||
return String(appLoc: "每周 ") + weekdays.sorted().map { names[$0 - 1] }.joined()
|
||||
case .monthly:
|
||||
return String(appLoc: "每月\(dayOfMonth)日")
|
||||
let days = monthlyDays
|
||||
if days.isEmpty { return String(appLoc: "未选日") }
|
||||
return String(appLoc: "每月") + days.map { String($0) }.joined(separator: "·") + String(appLoc: "日")
|
||||
case .yearly:
|
||||
return String(appLoc: "每年\(month)月\(dayOfMonth)日")
|
||||
}
|
||||
@@ -420,12 +502,17 @@ final class CustomReminder {
|
||||
func occurs(on date: Date, calendar: Calendar = .current) -> Bool {
|
||||
guard enabled else { return false }
|
||||
let c = calendar.dateComponents([.weekday, .day, .month], from: date)
|
||||
switch frequency {
|
||||
case .daily: return true
|
||||
case .weekly: return weekdays.contains(c.weekday ?? -1)
|
||||
case .monthly: return dayOfMonth == (c.day ?? -1)
|
||||
case .yearly: return month == (c.month ?? -1) && dayOfMonth == (c.day ?? -1)
|
||||
let wd = c.weekday ?? -1, day = c.day ?? -1, mo = c.month ?? -1
|
||||
// 多选频率:任一命中即触发。
|
||||
for f in frequencies {
|
||||
switch f {
|
||||
case .daily: return true
|
||||
case .weekly: if weekdays.contains(wd) { return true }
|
||||
case .monthly: if monthlyDays.contains(day) { return true }
|
||||
case .yearly: if month == mo && dayOfMonth == day { return true }
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user