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:
link2026
2026-06-15 09:24:59 +08:00
parent 6c6a950140
commit 9d856fcfc4
37 changed files with 2605 additions and 430 deletions

View File

@@ -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 / 315
/// ()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
}
}