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

@@ -10,13 +10,20 @@ struct CustomReminderEditSheet: View {
/// nil =
let reminder: CustomReminder?
/// (,:+ )
/// (reminder != nil)
private let prefillTitle: String
private let prefillNote: String
@State private var title = ""
@State private var note = ""
@State private var pickedTime: Date = .now
@State private var frequency: CustomReminder.Frequency = .daily
/// (/// )
@State private var frequencies: Set<CustomReminder.Frequency> = [.daily]
@State private var weekdays: Set<Int> = Set(1...7)
@State private var dayOfMonth = 1
/// (1...31)
@State private var monthDays: Set<Int> = [1]
@State private var dayOfMonth = 1 //
@State private var month = 1
@State private var hydrated = false
@State private var showAuthDeniedAlert = false
@@ -24,8 +31,10 @@ struct CustomReminderEditSheet: View {
/// (, ): / / /
private let timePresets: [(h: Int, m: Int)] = [(8, 0), (12, 0), (18, 0), (22, 0)]
init(reminder: CustomReminder? = nil) {
init(reminder: CustomReminder? = nil, prefillTitle: String = "", prefillNote: String = "") {
self.reminder = reminder
self.prefillTitle = prefillTitle
self.prefillNote = prefillNote
}
private var isEditing: Bool { reminder != nil }
@@ -33,8 +42,9 @@ struct CustomReminderEditSheet: View {
title.trimmingCharacters(in: .whitespacesAndNewlines)
}
private var canSave: Bool {
guard !trimmedTitle.isEmpty else { return false }
if frequency == .weekly { return !weekdays.isEmpty }
guard !trimmedTitle.isEmpty, !frequencies.isEmpty else { return false }
if frequencies.contains(.weekly) && weekdays.isEmpty { return false }
if frequencies.contains(.monthly) && monthDays.isEmpty { return false }
return true
}
@@ -51,18 +61,12 @@ struct CustomReminderEditSheet: View {
}
Section {
Picker(String(appLoc: "重复"), selection: $frequency) {
Text(String(appLoc: "每日")).tag(CustomReminder.Frequency.daily)
Text(String(appLoc: "每周")).tag(CustomReminder.Frequency.weekly)
Text(String(appLoc: "每月")).tag(CustomReminder.Frequency.monthly)
Text(String(appLoc: "每年")).tag(CustomReminder.Frequency.yearly)
}
.pickerStyle(.segmented)
.listRowBackground(Color.clear)
frequencyChips
frequencyDetail
} header: {
Text("重复")
} footer: {
Text("可多选:如同时勾选「每周一三五」+「每月1日」,两种节奏都会提醒。")
}
Section {
@@ -109,23 +113,60 @@ struct CustomReminderEditSheet: View {
}
}
// MARK: -
// MARK: - chip
private static let freqOrder: [CustomReminder.Frequency] = [.daily, .weekly, .monthly, .yearly]
private func freqLabel(_ f: CustomReminder.Frequency) -> String {
switch f {
case .daily: return String(appLoc: "每日")
case .weekly: return String(appLoc: "每周")
case .monthly: return String(appLoc: "每月")
case .yearly: return String(appLoc: "每年")
}
}
private var frequencyChips: some View {
HStack(spacing: 8) {
ForEach(Self.freqOrder, id: \.self) { f in
let on = frequencies.contains(f)
Button {
if on { frequencies.remove(f) } else { frequencies.insert(f) }
} label: {
Text(freqLabel(f))
.font(.tjScaled( 13, weight: on ? .semibold : .regular))
.foregroundStyle(on ? Tj.Palette.paper : Tj.Palette.text)
.frame(maxWidth: .infinity, minHeight: 32)
.background(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(on ? Tj.Palette.ink : Tj.Palette.paper)
)
.overlay(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.strokeBorder(Tj.Palette.line, lineWidth: on ? 0 : 1)
)
}
.buttonStyle(.plain)
}
}
.listRowBackground(Color.clear)
}
// MARK: - (,)
@ViewBuilder
private var frequencyDetail: some View {
switch frequency {
case .daily:
EmptyView()
case .weekly:
if frequencies.contains(.weekly) {
subCaption(String(appLoc: "每周 · 选星期几"))
weekdayRow
case .monthly:
Picker(String(appLoc: "日期"), selection: $dayOfMonth) {
ForEach(1...31, id: \.self) { d in
Text(String(appLoc: "\(d)")).tag(d)
}
}
if dayOfMonth >= 29 { skipHint }
case .yearly:
}
if frequencies.contains(.monthly) {
subCaption(String(appLoc: "每月 · 选日期(可多选)"))
monthDayGrid
if monthDays.contains(where: { $0 >= 29 }) { skipHint }
}
if frequencies.contains(.yearly) {
subCaption(String(appLoc: "每年 · 选月/日"))
Picker(String(appLoc: "月份"), selection: $month) {
ForEach(1...12, id: \.self) { mo in
Text(String(appLoc: "\(mo)")).tag(mo)
@@ -140,6 +181,41 @@ struct CustomReminderEditSheet: View {
}
}
private func subCaption(_ text: String) -> some View {
Text(text)
.font(.tjScaled( 11, weight: .semibold))
.foregroundStyle(Tj.Palette.text3)
.frame(maxWidth: .infinity, alignment: .leading)
.listRowBackground(Color.clear)
}
/// (1...31,7 )
private var monthDayGrid: some View {
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 6), count: 7), spacing: 6) {
ForEach(1...31, id: \.self) { d in
let on = monthDays.contains(d)
Button {
if on { monthDays.remove(d) } else { monthDays.insert(d) }
} label: {
Text("\(d)")
.font(.tjScaled( 12, weight: on ? .semibold : .regular))
.foregroundStyle(on ? Tj.Palette.paper : Tj.Palette.text)
.frame(maxWidth: .infinity, minHeight: 30)
.background(
RoundedRectangle(cornerRadius: 6, style: .continuous)
.fill(on ? Tj.Palette.ink : Tj.Palette.paper)
)
.overlay(
RoundedRectangle(cornerRadius: 6, style: .continuous)
.strokeBorder(Tj.Palette.line, lineWidth: on ? 0 : 1)
)
}
.buttonStyle(.plain)
}
}
.listRowBackground(Color.clear)
}
private var skipHint: some View {
Text(String(appLoc: "部分月份无此日,该月将跳过"))
.font(.tjScaled( 11))
@@ -229,13 +305,18 @@ struct CustomReminderEditSheet: View {
if let r = reminder {
title = r.title
note = r.note
frequency = r.frequency
frequencies = r.frequencies
weekdays = Set(r.weekdays)
monthDays = Set(r.monthlyDays)
dayOfMonth = r.dayOfMonth
month = r.month
pickedTime = Calendar.current.date(
bySettingHour: r.hour, minute: r.minute, second: 0, of: .now
) ?? .now
} else {
// :( / )
title = prefillTitle
note = prefillNote
}
}
@@ -245,6 +326,7 @@ struct CustomReminderEditSheet: View {
let hour = cal.component(.hour, from: pickedTime)
let minute = cal.component(.minute, from: pickedTime)
let sortedDays = weekdays.sorted()
let sortedMonthDays = monthDays.sorted()
let target: CustomReminder
if let r = reminder {
@@ -253,8 +335,9 @@ struct CustomReminderEditSheet: View {
r.hour = hour
r.minute = minute
r.weekdays = sortedDays
r.frequency = frequency
r.dayOfMonth = dayOfMonth
r.frequencies = frequencies // frequenciesRaw(+ frequencyRaw)
r.monthlyDays = sortedMonthDays // monthDays
r.dayOfMonth = dayOfMonth //
r.month = month
r.updatedAt = .now
target = r
@@ -265,10 +348,11 @@ struct CustomReminderEditSheet: View {
hour: hour,
minute: minute,
weekdays: sortedDays,
frequency: frequency,
dayOfMonth: dayOfMonth,
month: month
)
new.frequencies = frequencies
new.monthlyDays = sortedMonthDays
ctx.insert(new)
target = new
}