- 架构层:ViewModel/ObservableObject、Service/Repository、网络层 APIClient/APIEndpoint/APIError - 设计系统:Color(light:dark:) 自适应 28 色 Token、ColorSchemeManager 深浅色切换 - 全页面:AI 对话/反馈/回忆/薄弱点、知识库 CRUD、学习工作台、复习计划、学习分析、个人中心/设置 - 登录与引导:Sign in with Apple、AppSession 状态管理、引导流程、演示模式 - 本地持久化:FileCache + PersistenceController(学习任务/复习任务/学习记录) - 本地化:zh-Hans Localizable.strings ~120 条、ZXStrings 程序化引用、LanguageManager - 组件库:ZXTabBar/ZXBackHeader/ZXSTaskRow/ZXChartView/ZXTypingIndicator 等 22 个共享组件 - 等待名单:WaitlistView 邮箱收集表单 - 动效:ZXTypingIndicator AI 打字动画、ZXShimmerModifier 骨架屏 - 测试:StudyHomeViewModel/AIChatViewModel/ReviewPlanViewModel/FileCache 共 28 条 - Dynamic Type 支持 + 范围限制 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
37 lines
2.1 KiB
Swift
37 lines
2.1 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - AI Chat Page
|
|
|
|
struct AIChatPage: View {
|
|
@StateObject private var vm = AIChatViewModel()
|
|
|
|
var body: some View {
|
|
ZStack { Color.zxBg0.ignoresSafeArea()
|
|
VStack(spacing: 0) {
|
|
ZXBackHeader(title: "AI 对话", subtitle: "学习助手") {}
|
|
ScrollViewReader { proxy in ScrollView { VStack(spacing: 16) {
|
|
ForEach(vm.messages) { m in
|
|
HStack(alignment: .top, spacing: 8) {
|
|
if m.role == .ai {
|
|
Image(systemName: "brain.head.profile").foregroundColor(Color.zxPurple).frame(width: 28, height: 28).background(Color(hex: "#7C6EFA", opacity: 0.15)).clipShape(Circle())
|
|
}
|
|
Text(m.content).font(.system(size: 14)).foregroundColor(m.role == .user ? .white : Color.zxF007).padding(12).background(m.role == .user ? AnyView(ZXGradient.brandPurple) : AnyView(Color.zxFill004)).clipShape(RoundedRectangle(cornerRadius: 16))
|
|
if m.role == .user { Circle().frame(width: 28, height: 28).foregroundColor(Color.zxPurpleBG(0.2)).overlay(Text("我").font(.system(size: 10, weight: .bold)).foregroundColor(Color.zxPurple)) }
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: m.role == .user ? .trailing : .leading)
|
|
}
|
|
if vm.isSending {
|
|
HStack {
|
|
Image(systemName: "brain.head.profile").foregroundColor(Color.zxPurple).frame(width: 28, height: 28).background(Color(hex: "#7C6EFA", opacity: 0.15)).clipShape(Circle())
|
|
ZXTypingIndicator()
|
|
Spacer()
|
|
}
|
|
}
|
|
}.padding(.horizontal, 20).padding(.bottom, 100).id("bottom") }.scrollIndicators(.hidden)
|
|
.onChange(of: vm.messages.count) { withAnimation { proxy.scrollTo("bottom") } } }
|
|
ZXAIInputBar(text: $vm.inputText, onSend: { vm.send() })
|
|
}
|
|
}.navigationBarHidden(true)
|
|
}
|
|
}
|