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

@@ -18,21 +18,19 @@ struct HomeView: View {
/// sheet( C1 )
@State private var selectedEntry: TimelineEntry?
/// ( + , C1 )
@State private var selectedGroup: IndicatorGroup?
@MainActor
private var recentEntries: [TimelineEntry] {
let all =
TimelineEntry.from(indicators: indicators) +
TimelineEntry.aggregatedIndicators(indicators) +
reports.map(TimelineEntry.from(report:)) +
diaries.map(TimelineEntry.from(diary:)) +
symptoms.map(TimelineEntry.from(symptom:))
return all.sorted { $0.date > $1.date }.prefix(6).map { $0 }
}
private var recentGrouped: [(section: DateSection, items: [TimelineEntry])] {
TimelineGrouping.group(recentEntries)
}
var body: some View {
ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) {
@@ -65,6 +63,9 @@ struct HomeView: View {
TimelineEntryDetailView(detail: d)
}
}
.sheet(item: $selectedGroup) { group in
IndicatorSeriesDetailView(group: group)
}
}
private var greeting: some View {
@@ -100,7 +101,10 @@ struct HomeView: View {
}
private var recentSection: some View {
VStack(alignment: .leading, spacing: 10) {
// ( O(m²)) body ,, .isEmpty
let entries = recentEntries
let groups = TimelineGrouping.group(entries)
return VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .lastTextBaseline) {
Text("最近记录").font(.tjH2()).foregroundStyle(Tj.Palette.text)
Spacer()
@@ -112,11 +116,11 @@ struct HomeView: View {
.buttonStyle(.plain)
}
if recentEntries.isEmpty {
if entries.isEmpty {
emptyRecent
} else {
VStack(alignment: .leading, spacing: 14) {
ForEach(recentGrouped, id: \.section) { group in
ForEach(groups, id: \.section) { group in
VStack(alignment: .leading, spacing: 8) {
Text(group.section.label)
.font(.tjScaled( 11, weight: .semibold))
@@ -125,12 +129,16 @@ struct HomeView: View {
VStack(spacing: 10) {
ForEach(group.items) { entry in
Button {
if TimelineDetail.resolve(
// ( + ); C1
guard let d = TimelineDetail.resolve(
for: entry,
indicators: indicators, reports: reports,
diaries: diaries, symptoms: symptoms
) != nil {
selectedEntry = entry
) else { return }
switch d {
case .indicator(let i): selectedGroup = IndicatorGroup.of(i)
case .bloodPressure(let sys, _): selectedGroup = IndicatorGroup.of(sys)
default: selectedEntry = entry
}
} label: {
TimelineRow(entry: entry)