```
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:
@@ -12,6 +12,10 @@ struct TrendsView: View {
|
||||
|
||||
private var profile: UserProfile? { profiles.first }
|
||||
|
||||
/// 顶部搜索:点放大镜展开搜索框,按指标名(bucket.title)实时过滤两段列表。
|
||||
@State private var searching = false
|
||||
@State private var query = ""
|
||||
|
||||
private var seriesBuckets: [SeriesBucket] {
|
||||
SeriesBucket.build(from: indicators,
|
||||
profile: profile,
|
||||
@@ -25,6 +29,14 @@ struct TrendsView: View {
|
||||
seriesBuckets.filter { $0.kind == .lab }
|
||||
}
|
||||
|
||||
private func filtered(_ buckets: [SeriesBucket]) -> [SeriesBucket] {
|
||||
let q = query.trimmingCharacters(in: .whitespaces)
|
||||
guard !q.isEmpty else { return buckets }
|
||||
return buckets.filter { $0.title.localizedCaseInsensitiveContains(q) }
|
||||
}
|
||||
private var filteredMonitor: [SeriesBucket] { filtered(monitorBuckets) }
|
||||
private var filteredLab: [SeriesBucket] { filtered(labBuckets) }
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView(showsIndicators: false) {
|
||||
@@ -32,12 +44,14 @@ struct TrendsView: View {
|
||||
header.padding(.top, 4)
|
||||
if seriesBuckets.isEmpty {
|
||||
emptyState
|
||||
} else if filteredMonitor.isEmpty && filteredLab.isEmpty {
|
||||
noMatchState
|
||||
} else {
|
||||
if !monitorBuckets.isEmpty {
|
||||
section(title: String(appLoc: "长期监测"), buckets: monitorBuckets)
|
||||
if !filteredMonitor.isEmpty {
|
||||
section(title: String(appLoc: "长期监测"), buckets: filteredMonitor)
|
||||
}
|
||||
if !labBuckets.isEmpty {
|
||||
section(title: String(appLoc: "化验指标趋势"), buckets: labBuckets)
|
||||
if !filteredLab.isEmpty {
|
||||
section(title: String(appLoc: "化验指标趋势"), buckets: filteredLab)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,9 +65,73 @@ struct TrendsView: View {
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
Text("趋势")
|
||||
.font(.tjTitle(26))
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(alignment: .lastTextBaseline) {
|
||||
Text("趋势")
|
||||
.font(.tjTitle(26))
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
Spacer()
|
||||
searchToggle
|
||||
}
|
||||
if searching { searchField }
|
||||
}
|
||||
}
|
||||
|
||||
private var searchToggle: some View {
|
||||
Button {
|
||||
withAnimation(.easeInOut(duration: 0.18)) {
|
||||
searching.toggle()
|
||||
if !searching { query = "" }
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: searching ? "xmark" : "magnifyingglass")
|
||||
.font(.tjScaled( 15, weight: .semibold))
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
.frame(width: 36, height: 36)
|
||||
.background(Circle().fill(Tj.Palette.sand2))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(searching ? String(appLoc: "关闭搜索") : String(appLoc: "搜索指标"))
|
||||
}
|
||||
|
||||
private var searchField: some View {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.font(.tjScaled( 13))
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
TextField(String(appLoc: "搜索指标名"), text: $query)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
.tint(Tj.Palette.ink)
|
||||
if !query.isEmpty {
|
||||
Button { query = "" } label: {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 10)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
|
||||
.fill(Tj.Palette.paper)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
|
||||
.strokeBorder(Tj.Palette.line, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
|
||||
private var noMatchState: some View {
|
||||
VStack(spacing: 12) {
|
||||
TjPlaceholder(label: String(appLoc: "没有匹配「\(query)」的指标"))
|
||||
.frame(height: 120)
|
||||
.frame(maxWidth: 260)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 60)
|
||||
}
|
||||
|
||||
private func section(title: String, buckets: [SeriesBucket]) -> some View {
|
||||
|
||||
Reference in New Issue
Block a user