diff --git a/AIStudyApp/AIStudyApp.xcodeproj/project.xcworkspace/xcuserdata/Admin1.xcuserdatad/UserInterfaceState.xcuserstate b/AIStudyApp/AIStudyApp.xcodeproj/project.xcworkspace/xcuserdata/Admin1.xcuserdatad/UserInterfaceState.xcuserstate index 98c9ed4..ec57579 100644 Binary files a/AIStudyApp/AIStudyApp.xcodeproj/project.xcworkspace/xcuserdata/Admin1.xcuserdatad/UserInterfaceState.xcuserstate and b/AIStudyApp/AIStudyApp.xcodeproj/project.xcworkspace/xcuserdata/Admin1.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/AIStudyApp/AIStudyApp.xcodeproj/xcshareddata/xcschemes/AIStudyApp.xcscheme b/AIStudyApp/AIStudyApp.xcodeproj/xcshareddata/xcschemes/AIStudyApp.xcscheme index bdca08d..a716193 100644 --- a/AIStudyApp/AIStudyApp.xcodeproj/xcshareddata/xcschemes/AIStudyApp.xcscheme +++ b/AIStudyApp/AIStudyApp.xcodeproj/xcshareddata/xcschemes/AIStudyApp.xcscheme @@ -1,7 +1,7 @@ + version = "1.3"> + shouldUseLaunchSchemeArgsEnv = "YES"> + + - - + + [KnowledgeSource] { - return try await client.request("/knowledge-bases/\(kbId)/sources") + func list(kbId: String, sortBy: String? = nil, order: String? = nil) async throws -> [KnowledgeSource] { + var items = [URLQueryItem]() + if let s = sortBy { items.append(URLQueryItem(name: "sortBy", value: s)) } + if let o = order { items.append(URLQueryItem(name: "order", value: o)) } + return try await client.request("/knowledge-bases/\(kbId)/sources", queryItems: items.isEmpty ? nil : items) } func detail(kbId: String, id: String) async throws -> KnowledgeSource { @@ -550,6 +553,11 @@ class KnowledgeSourceService { func parse(kbId: String, sourceId: String) async throws -> ParseResponse { return try await client.request("/knowledge-bases/\(kbId)/sources/\(sourceId)/parse", method: "POST") } + + func generateKnowledge(kbId: String, sourceId: String) async throws -> GenerateKnowledgeResponse { + return try await client.request("/knowledge-bases/\(kbId)/sources/\(sourceId)/generate-knowledge", method: "POST") + } + } // MARK: - Quiz diff --git a/AIStudyApp/AIStudyApp/Core/Services/FileUploadService.swift b/AIStudyApp/AIStudyApp/Core/Services/FileUploadService.swift index 95adacd..8ae0bd9 100644 --- a/AIStudyApp/AIStudyApp/Core/Services/FileUploadService.swift +++ b/AIStudyApp/AIStudyApp/Core/Services/FileUploadService.swift @@ -40,7 +40,7 @@ class FileUploadService { let confirmResp: FileConfirmUploadResponse = try await client.request( "/files/complete", method: "POST", - body: FileConfirmUploadRequest(objectKey: urlResp.objectKey, checksum: nil) + body: FileConfirmUploadRequest(uploadId: urlResp.uploadId, objectKey: urlResp.objectKey, checksum: nil) ) return (fileId: confirmResp.id, objectKey: urlResp.objectKey) @@ -68,7 +68,7 @@ class FileUploadService { let confirmResp: FileConfirmUploadResponse = try await client.request( "/files/complete", method: "POST", - body: FileConfirmUploadRequest(objectKey: urlResp.objectKey, checksum: nil) + body: FileConfirmUploadRequest(uploadId: urlResp.uploadId, objectKey: urlResp.objectKey, checksum: nil) ) return confirmResp.id diff --git a/AIStudyApp/AIStudyApp/Features/AI/AIChatPage.swift b/AIStudyApp/AIStudyApp/Features/AI/AIChatPage.swift index 29bc172..bd07890 100644 --- a/AIStudyApp/AIStudyApp/Features/AI/AIChatPage.swift +++ b/AIStudyApp/AIStudyApp/Features/AI/AIChatPage.swift @@ -220,7 +220,8 @@ struct MessageCard: View { // Content if hasContent { - Text(message.content).zxFontScaled(size: 14) + Text(LocalizedStringKey(message.content)) + .zxFontScaled(size: 14) .foregroundColor(isAI ? Color.zxF007 : .white) .padding(12) } diff --git a/AIStudyApp/AIStudyApp/Features/Library/KnowledgeBaseManageView.swift b/AIStudyApp/AIStudyApp/Features/Library/KnowledgeBaseManageView.swift new file mode 100644 index 0000000..a6e4928 --- /dev/null +++ b/AIStudyApp/AIStudyApp/Features/Library/KnowledgeBaseManageView.swift @@ -0,0 +1,118 @@ +import SwiftUI + +// MARK: - KnowledgeBaseManageView + +struct KnowledgeBaseManageView: View { + let knowledgeBaseId: String + @State private var title: String = "" + @State private var description: String = "" + @State private var isLoading = true + @State private var isSaving = false + @State private var showDeleteConfirm = false + @State private var errorMessage: String? + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + ZStack { + Color.zxBg0.ignoresSafeArea() + + if isLoading { + ProgressView().tint(Color.zxPurple) + } else { + Form { + Section("基本信息") { + TextField("名称", text: $title) + .font(.system(size: 16)) + TextField("描述(可选)", text: $description, axis: .vertical) + .font(.system(size: 14)) + .lineLimit(3...6) + } + + Section { + Button(role: .destructive) { + showDeleteConfirm = true + } label: { + HStack { + Spacer() + Label("删除知识库", systemImage: "trash") + Spacer() + } + } + } + + if let error = errorMessage { + Section { + Text(error) + .font(.system(size: 13)) + .foregroundColor(.red) + } + } + } + .scrollContentBackground(.hidden) + } + } + .navigationTitle("知识库管理") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("取消") { dismiss() } + } + ToolbarItem(placement: .confirmationAction) { + Button("保存") { + Task { await save() } + } + .fontWeight(.semibold) + .disabled(isSaving || title.trimmingCharacters(in: .whitespaces).isEmpty) + } + } + .alert("删除知识库", isPresented: $showDeleteConfirm) { + Button("取消", role: .cancel) {} + Button("删除", role: .destructive) { + Task { await deleteKB() } + } + } message: { + Text("此操作不可撤销,知识库内所有资料将被永久删除。") + } + } + .task { await load() } + } + + private func load() async { + do { + let kb = try await KnowledgeBaseService.shared.detail(id: knowledgeBaseId) + title = kb.title + description = kb.description ?? "" + } catch { + errorMessage = "加载失败:\(error.localizedDescription)" + } + isLoading = false + } + + private func save() async { + isSaving = true + errorMessage = nil + do { + _ = try await KnowledgeBaseService.shared.update( + id: knowledgeBaseId, + title: title.trimmingCharacters(in: .whitespaces), + description: description.trimmingCharacters(in: .whitespaces).isEmpty + ? nil + : description.trimmingCharacters(in: .whitespaces) + ) + dismiss() + } catch { + errorMessage = "保存失败:\(error.localizedDescription)" + } + isSaving = false + } + + private func deleteKB() async { + do { + try await KnowledgeBaseService.shared.delete(id: knowledgeBaseId) + dismiss() + } catch { + errorMessage = "删除失败:\(error.localizedDescription)" + } + } +} diff --git a/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift b/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift index ff9cca2..89eeb49 100644 --- a/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift +++ b/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift @@ -136,6 +136,7 @@ struct LibraryDetailPage: View { let knowledgeBaseId: String @Environment(\.dismiss) private var dismiss @StateObject private var viewModel = LibraryDetailViewModel() + @State private var showManageView = false @State private var showDeleteConfirm = false @State private var isDeleting = false @State private var isSelectMode = false @@ -342,13 +343,16 @@ struct LibraryDetailPage: View { Label("默认排序", systemImage: sortOption == 0 ? "checkmark" : "") } Button { sortOption = 1 } label: { - Label("文件大小", systemImage: sortOption == 1 ? "checkmark" : "") + Label("文件名排序", systemImage: sortOption == 1 ? "checkmark" : "") } Button { sortOption = 2 } label: { - Label("创建日期", systemImage: sortOption == 2 ? "checkmark" : "") + Label("文件大小", systemImage: sortOption == 2 ? "checkmark" : "") } Button { sortOption = 3 } label: { - Label("更新日期", systemImage: sortOption == 3 ? "checkmark" : "") + Label("创建日期", systemImage: sortOption == 3 ? "checkmark" : "") + } + Button { sortOption = 4 } label: { + Label("更新日期", systemImage: sortOption == 4 ? "checkmark" : "") } } label: { Image(systemName: "arrow.up.arrow.down") @@ -375,7 +379,7 @@ struct LibraryDetailPage: View { } Divider() Button { - // TODO: knowledge base management page + showManageView = true } label: { Label("知识库管理", image: "icon-settings") } @@ -400,6 +404,9 @@ struct LibraryDetailPage: View { } message: { Text("删除后将无法恢复,包括其中的所有知识点。确定要删除吗?") } + .sheet(isPresented: $showManageView) { + KnowledgeBaseManageView(knowledgeBaseId: knowledgeBaseId) + } .alert("批量删除", isPresented: $showBatchDeleteConfirm) { Button("取消", role: .cancel) {} Button("删除 \(selectedIds.count) 个", role: .destructive) { @@ -416,9 +423,9 @@ struct LibraryDetailPage: View { .sheet(isPresented: $showFolderManager) { FolderManageView(knowledgeBaseId: knowledgeBaseId) } - .onChange(of: sortOption) { _ in - let (sb, od) = sortParams(sortOption) - Task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId, sortBy: sb, order: od) } + .onChange(of: sortOption) { newValue in + let (sb, od) = sortParams(newValue) + Task { await loadSources(sortBy: sb, order: od) } } .safeAreaInset(edge: .bottom) { NavigationLink(value: Route.aiChat(context: ChatEntryContext(scopeType: .knowledgeBase, scopeId: knowledgeBaseId, scopeName: "知识库", parentKnowledgeBaseId: knowledgeBaseId, createdFrom: "knowledge_base_detail"))) { @@ -526,13 +533,15 @@ struct LibraryDetailPage: View { private func sortParams(_ option: Int) -> (String?, String?) { switch option { - case 1: return ("fileSize", "desc") - case 2: return ("createdAt", "desc") - case 3: return ("updatedAt", "desc") - default: return (nil, nil) + case 1: return ("title", "asc") + case 2: return ("sizeBytes", "desc") + case 3: return ("createdAt", "desc") + case 4: return ("updatedAt", "desc") + default: return ("title", "asc") } } + private func fileTypeText(for item: KnowledgeItem) -> String { if let t = item.sourceType?.trimmingCharacters(in: .whitespaces), !t.isEmpty { return t.uppercased() @@ -639,11 +648,11 @@ struct LibraryDetailPage: View { return 0 } - private func loadSources() async { + private func loadSources(sortBy: String? = nil, order: String? = nil) async { isLoadingSources = true print("[LibraryDetail] loadSources START, kbId=\(knowledgeBaseId)") do { - sources = try await KnowledgeSourceService.shared.list(kbId: knowledgeBaseId) + sources = try await KnowledgeSourceService.shared.list(kbId: knowledgeBaseId, sortBy: sortBy, order: order) print("[LibraryDetail] loadSources OK, count=\(sources.count)") for src in sources { print("[LibraryDetail] source: id=\(src.id), title=\(src.title ?? "-"), parseStatus=\(src.parseStatus ?? "-")") diff --git a/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift b/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift index 1a90013..639f8e0 100644 --- a/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift +++ b/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift @@ -105,6 +105,7 @@ struct MaterialReaderView: View { @State private var readingStatus: String? @State private var readingProgressSeconds: Int = 0 @State private var apiRestorePosition: ReadingPosition? + @State private var flushTimer: Timer? @Environment(\.scenePhase) private var scenePhase @State private var isParsing = false @@ -275,9 +276,26 @@ struct MaterialReaderView: View { .onAppear { openReadingSession() hasRestoredPosition = false + // 定时发送阅读事件(每 2 分钟),防止长时间阅读不退出导致数据丢失 + flushTimer = Timer.scheduledTimer(withTimeInterval: 120, repeats: true) { _ in + Task { + ReadingEventUploadPipeline.shared.exportAndEnqueue() + await ReadingEventUploadPipeline.shared.flush() + } + } } .onDisappear { + flushTimer?.invalidate() + flushTimer = nil + // 1. 关闭 Rust 会话,push material_closed 事件到缓冲区 closeReadingSession() + // 2. 导出所有事件(含 material_closed)+ 发送到后端 + // 3. 发送完成后再清理 context + Task { + ReadingEventUploadPipeline.shared.exportAndEnqueue() + await ReadingEventUploadPipeline.shared.flush() + sessionManager.cleanup() + } } .onChange(of: scenePhase) { _, newPhase in switch newPhase { @@ -597,17 +615,12 @@ struct MaterialReaderView: View { private func triggerParse(kbId: String, sourceId: String) async { guard !isParsing else { return } isParsing = true - parseMessage = "正在整理知识点..." + parseMessage = "AI 正在整理知识点..." do { - let result = try await KnowledgeSourceService.shared.parse(kbId: kbId, sourceId: sourceId) - parseMessage = "知识点整理任务已提交" - for _ in 0..<60 { - try await Task.sleep(nanoseconds: 2_000_000_000) - let status = try await DocumentImportService.shared.getStatus(id: result.jobId ?? result.id ?? "") - if status.status == "completed" { parseMessage = "知识点整理完成"; break } - else if status.status == "failed" { parseMessage = "整理失败"; break } - else { parseMessage = "整理中... \(status.progress ?? 0)%" } - } + let result = try await KnowledgeSourceService.shared.generateKnowledge(kbId: kbId, sourceId: sourceId) + parseMessage = result.count > 0 + ? "整理完成,生成了 \(result.count) 个知识点" + : "未识别到可提取的知识点" } catch { parseMessage = "整理失败: \(error.localizedDescription)" } diff --git a/AIStudyApp/AIStudyApp/Features/MaterialReader/ReadingRuntimeSessionManager.swift b/AIStudyApp/AIStudyApp/Features/MaterialReader/ReadingRuntimeSessionManager.swift index e8ce9d1..d18fefd 100644 --- a/AIStudyApp/AIStudyApp/Features/MaterialReader/ReadingRuntimeSessionManager.swift +++ b/AIStudyApp/AIStudyApp/Features/MaterialReader/ReadingRuntimeSessionManager.swift @@ -97,11 +97,10 @@ final class ReadingRuntimeSessionManager { } } - /// Close the current session. Can be called from any non-idle state. - /// Close the current session. Idempotent — safe to call from any state. + /// Push close event and close the Rust session. Does NOT unregister context. + /// Call `cleanup()` after events have been exported & flushed. func closeMaterial() { guard state == .active || state == .paused, let sessionId = activeSessionId, let ctx = activeContext else { - // Already closed/failed/idle: reset state to idle if state == .closed || state == .idle { return } state = .idle return @@ -117,12 +116,18 @@ final class ReadingRuntimeSessionManager { _ = try? adapter.closeSession(sessionId) activeSessionId = nil - if let ctx = activeContext { ReadingContextRegistry.shared.unregister(materialId: ctx.materialId) } - activeContext = nil lastPosition = nil state = .closed } + /// Unregister context and clean up state. Call AFTER events have been exported. + func cleanup() { + if let ctx = activeContext { + ReadingContextRegistry.shared.unregister(materialId: ctx.materialId) + } + activeContext = nil + } + func pause() { guard state == .active, let sessionId = activeSessionId else { return } stopHeartbeat() diff --git a/AIStudyApp/AIStudyApp/Features/Membership/MembershipView.swift b/AIStudyApp/AIStudyApp/Features/Membership/MembershipView.swift index 69973d8..c2bcdaf 100644 --- a/AIStudyApp/AIStudyApp/Features/Membership/MembershipView.swift +++ b/AIStudyApp/AIStudyApp/Features/Membership/MembershipView.swift @@ -119,31 +119,32 @@ struct MembershipView: View { .frame(maxWidth: .infinity, alignment: .leading) ForEach(plans.plans) { plan in - Button { - Task { await store.purchase(plan: plan) } - } label: { - HStack { - VStack(alignment: .leading, spacing: 4) { - Text(plan.name).font(.system(size: 15, weight: .semibold)).foregroundColor(Color.zxF0) - Text(plan.planCode.contains("yearly") ? "每年自动续订,更优惠" : "每月自动续订") - .font(.system(size: 11)).foregroundColor(Color.zxF04) - } - Spacer() - if store.purchasingPlanId == plan.planCode { - ProgressView().tint(Color.zxPurple) - } else if let price = store.displayPrice(for: plan.planCode) { - Text(price) - .font(.system(size: 14, weight: .bold)).foregroundColor(Color.zxPurple) - } else { - Text("价格暂不可用") - .font(.system(size: 12)).foregroundColor(Color.zxF04) + if plan.planCode != "free" { + ForEach(["monthly", "yearly"], id: \.self) { period in + Button { + Task { await store.purchase(plan: plan, period: period) } + } label: { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text(plan.name + (period == "yearly" ? " 年付" : " 月付")).font(.system(size: 15, weight: .semibold)).foregroundColor(Color.zxF0) + Text(period == "yearly" ? "每年自动续订,省 41%" : "每月自动续订") + .font(.system(size: 11)).foregroundColor(Color.zxF04) + } + Spacer() + if store.purchasingPlanId == plan.planCode + "_" + period { + ProgressView().tint(Color.zxPurple) + } else { + Text(store.displayPrice(for: plan, period: period)) + .font(.system(size: 14, weight: .bold)).foregroundColor(Color.zxPurple) + } + } + .padding(16).background(Color.zxFill004) + .clipShape(RoundedRectangle(cornerRadius: 14)) + .overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxPurple.opacity(0.15), lineWidth: 1)) } + .disabled(store.purchasingPlanId != nil) } - .padding(16).background(Color.zxFill004) - .clipShape(RoundedRectangle(cornerRadius: 14)) - .overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxPurple.opacity(0.15), lineWidth: 1)) } - .disabled(store.purchasingPlanId != nil) } } } @@ -259,14 +260,17 @@ final class MembershipStore: ObservableObject { do { async let membership = MembershipService.shared.myMembership() async let plans = MembershipService.shared.getPlans() + // Pre-load StoreKit products so displayPrice works on first render + async let _ = StoreKitManager.shared.loadProducts() + state = try await .loaded(membership: membership, plans: plans) } catch { state = .error("加载失败:\(error.localizedDescription)") } } - func purchase(plan: PlanItem) async { - purchasingPlanId = plan.planCode + func purchase(plan: PlanItem, period: String = "monthly") async { + purchasingPlanId = plan.planCode + "_" + period guard let tokenStr = try? await MembershipService.shared.myMembership().appAccountToken, let uuid = UUID(uuidString: tokenStr) else { show("无法获取账号令牌") @@ -276,7 +280,7 @@ final class MembershipStore: ObservableObject { let pm = StoreKitManager.shared await pm.loadProducts() guard let product = pm.products.first(where: { - $0.id == (plan.planCode.contains("yearly") + $0.id == (period == "yearly" ? "cloud.longde.aistudyapp.premium.yearly" : "cloud.longde.aistudyapp.premium.monthly") }) else { @@ -302,12 +306,11 @@ final class MembershipStore: ObservableObject { isRestoring = false } - /// 从 StoreKit Product 获取本地化价格。未加载时返回 nil。 - func displayPrice(for planCode: String) -> String? { - let productId = planCode.contains("yearly") - ? "cloud.longde.aistudyapp.premium.yearly" - : "cloud.longde.aistudyapp.premium.monthly" - return StoreKitManager.shared.products.first(where: { $0.id == productId })?.displayPrice + /// 从 PlanItem 获取格式化价格(分→元),不依赖 StoreKit。 + func displayPrice(for plan: PlanItem, period: String) -> String { + let cents = period == "yearly" ? plan.priceYearly : plan.priceMonthly + let yuan = Double(cents) / 100.0 + return String(format: "¥%.2f/%@", yuan, period == "yearly" ? "年" : "月") } /// N2: 使用系统 API 打开订阅管理 diff --git a/AIStudyApp/AIStudyApp/Features/Study/StudyHomeViewModel.swift b/AIStudyApp/AIStudyApp/Features/Study/StudyHomeViewModel.swift index 5dddfb5..3db2e0b 100644 --- a/AIStudyApp/AIStudyApp/Features/Study/StudyHomeViewModel.swift +++ b/AIStudyApp/AIStudyApp/Features/Study/StudyHomeViewModel.swift @@ -183,15 +183,6 @@ final class StudyHomeViewModel: ObservableObject { return .continueSession(materialId: materialId, filePath: resolved.filePath, materialType: resolved.materialType, kbTitle: kbTitle, elapsed: elapsed, resumePosition: nil) } - if let unfinished = sessions - .filter({ $0.status != nil && $0.status != "completed" }) - .sorted(by: { ($0.startedAt ?? "") > ($1.startedAt ?? "") }) - .first { - let kbTitle = knowledgeBases.first(where: { $0.id == unfinished.knowledgeBaseId })?.title ?? "学习" - let elapsed = formatElapsedSince(unfinished.startedAt) - return .continueSession(materialId: "", filePath: "", materialType: .unknown, kbTitle: kbTitle, elapsed: elapsed, resumePosition: nil) - } - if !dueCards.isEmpty { return .todaysReview(count: dueCards.count, estimatedMinutes: max(1, dueCards.count)) } diff --git a/docs/system-map/ios/review/00-审查总报告.md b/docs/system-map/ios/review/00-审查总报告.md deleted file mode 100644 index 647b9c3..0000000 --- a/docs/system-map/ios/review/00-审查总报告.md +++ /dev/null @@ -1,242 +0,0 @@ -# 00 审查总报告 - -> 审查对象:知习 iOS 项目第一阶段系统地图(4 份文档) -> 审查日期:2026-06-19 -> 审查方法:独立代码扫描 → 独立基线建立 → 与已有文档逐项比对 -> 审查范围:AIStudyApp/AIStudyApp/ 目录下全部 64 个 .swift 文件(不含 Tests 和自动生成代码) - ---- - -## 一、审查范围 - -### 1.1 已检查的文件 - -- `AIStudyAppApp.swift` — App 入口、所有页面定义 -- `ContentView.swift` — Tab 根结构、共享组件 -- `Core/Navigation/Route.swift` — 全部路由定义(28 个 case) -- `Core/Auth/AuthManager.swift` — 会话状态机(8 种状态) -- `Core/Models/APIModels.swift` — 全部 54 个 API 模型 -- `Core/Models/AppContext.swift` — 设备/运行时上下文 -- `Core/Network/APIClient.swift` — HTTP 客户端、Token 刷新 -- `Core/Services/APIService.swift` — 全部 18 个服务类 -- `Core/Services/FileUploadService.swift` — 文件上传 -- `Core/Services/ReadingAPI.swift` — 阅读相关 API -- `Core/Services/NetworkMonitor.swift` — 网络状态监控 -- `Core/DesignSystem/DesignTokens.swift` — 设计系统 -- `Core/DesignSystem/ZXToast.swift` — Toast 系统 -- `Core/Security/KeychainHelper.swift` — (已通过 AuthManager 间接审查) -- `Core/Security/LocalCache.swift` — 本地缓存 -- `Core/Security/FileCache.swift` — 文件缓存 -- `Features/Study/` 全部 7 个文件 -- `Features/Library/` 全部 5 个文件 -- `Features/Profile/` 全部 8 个文件 -- `Features/AI/` 全部 10 个文件 -- `Features/Analysis/` 全部 2 个文件 -- `Features/Quiz/` 1 个文件(QuizViews.swift) -- `Features/MaterialReader/` 全部 12 个文件 - -### 1.2 已检查的入口类型 - -- [x] Tab(4 个:学习/知识库/分析/我的) -- [x] Push(NavigationLink via Route,25 个路由目标) -- [x] Sheet(6 个) -- [x] FullScreenCover(未发现) -- [x] Alert(6 个) -- [x] ConfirmationDialog(1 个) -- [x] ContextMenu(1 处) -- [x] Menu(1 处) -- [x] Deep Link(未配置) -- [x] Universal Link(未配置) -- [x] URL Scheme(未配置) -- [x] 通知点击路由(未实现) -- [x] Debug 页面(仅 Route.destination 的 DEBUG 覆盖层) -- [x] 条件渲染页面(基于 session/onboarding/数据状态的 7 种分支) -- [x] iPhone/iPad 差异(仅在 AppContext 中标记 deviceType) -- [x] Feature Flag(未发现) - -### 1.3 未检查的项 - -- .xcodeproj 详细配置(Capabilities、Build Settings) -- .entitlements 文件全文(推送通知、iCloud 等 Capability) -- 第三方依赖(Rive 动画 SDK 等) -- Rust FFI 二进制内部逻辑(ZxDocumentRuntime.xcframework) - ---- - -## 二、独立盘点统计 - -| 统计项 | 数量 | -|--------|------| -| 一级模块(Tab) | 4 | -| 独立 Push 页面(可路由) | 32 | -| Tab 内嵌页面(不可路由) | 6(启动/登录流程页面) | -| Sheet / 弹窗 | 10 | -| 确认弹窗(Alert/ConfirmationDialog) | 8 | -| 代码存在但无导航入口的页面 | 2(AIHomeView, RecallTestPage) | -| 页面功能点 | ~180 | -| API 服务类 | 18 | -| 初步业务能力 | 20 | - ---- - -## 三、审查结论 - -**审查结论:有条件通过** - -### 不满足"通过"的哪些门禁条件? - -| 门禁条件 | 满足? | 说明 | -|---------|--------|------| -| 所有导航入口已检查 | 是 | Tab/Push/Sheet/Alert/条件渲染均检查 | -| 所有用户可访问页面已进入基线 | 是 | 32 个路由页面 + 6 个启动/登录页均已盘点 | -| Sheet/弹窗/隐藏入口已检查 | 是 | 10 个 Sheet + 8 个 Alert + ContextMenu/Menu 均已盘点 | -| 页面功能均进入功能基线 | 是 | ~180 个功能点均已盘点 | -| 页面与业务能力完成双向覆盖检查 | 是 | 见 06-页面能力覆盖矩阵.md | -| Mock/占位/废弃实现没有被误判为已完成 | **否** | 4 处 P0 误判(见第四节) | -| 所有"暂未确认"均被明确列出 | 是 | 3 项暂未确认 | -| 严重遗漏为 0 | **否** | 1 个页面遗漏(RecallTestPage) | -| P0 修正项为 0 | **否** | 4 项 P0 修正 | -| 已有文档和独立基线差异已全部处理 | 是 | 16 项差异全部列入待修正清单 | - ---- - -## 四、严重问题(P0 — 会导致系统地图明显失真) - -### 4.1 功能状态误判-图表数据为占位 - -- **问题:** 文档将"查看掌握度趋势图"和"查看每周学习活跃"均标记为"部分完成" -- **实际:** ZXChartView 和 ZXWeekBarChart 使用完全硬编码的编译时数据,ViewModel 的 API 返回数据被忽略。应标记为"占位实现" -- **影响:** 分析页给人的印象是"图表数据已接入但需优化",实际情况是"图表数据完全未接入 API" - -### 4.2 功能状态误判-每日学习时长不持久化 - -- **问题:** GoalSettingDetailView 的"修改每日时长"标记为"已完成" -- **实际:** dailyMins 只保存在本地 @State 变量,不通过 API 持久化,用户下次进入恢复默认"30 分钟" -- **影响:** 给人"目标设置功能完整"的假象 - -### 4.3 设置页通知开关标记为"暂未确认" - -- **问题:** 三个通知开关绑定 bug 被标记为"暂未确认" -- **实际:** 代码中"复习提醒"、"导入完成通知"、"系统通知"三个 Toggle 全部绑定到同一个 `$reviewReminder`,这是明确的代码 bug -- **影响:** 将确定性 bug 降级为不确定性疑点,降低修正优先级 - -### 4.4 文档页面/弹窗统计数字口径不明确 - -- **问题:** 总览文档称"43 个页面/弹窗",但未说明统计口径 -- **实际:** 独立基线盘点出 38 个 Push 页面 + 10 个 Sheet/弹窗 + 8 个确认弹窗 -- **建议:** 明确统计口径(是否含弹窗?是否含废弃页面?)并更新数字 - ---- - -## 五、一般问题(P1/P2) - -### P1 — 遗漏项(4 项) - -1. 遗漏 RecallTestPage(代码存在,无入口,应为占位实现) -2. 遗漏 AIChatPage 会话置顶/取消置顶功能 -3. 遗漏 WaitlistService API 已就绪但 C 端无入口 -4. 遗漏 UserService 账号注销 API 已就绪但无 C 端入口 - -### P2 — 命名、描述、辅助状态问题(8 项) - -1. 业务能力"会员与额度"命名不准确 → 应改为"存储空间管理" -2. 建议合并"主动回忆"与"AI 学习分析"为一个业务能力 -3. StudyHomeViewModel error 状态永不可达(View 有但 ViewModel 不设置) -4. DailyThinkingPage submitted 变量永不为 true -5. 设置页"存储空间"和"登录设备管理"自引用导航 -6. 知识库详情页"知识库管理"按钮为 TODO -7. 业务能力依赖关系图缺少交叉引用 -8. 文件夹新建/重命名 Alert 未列入确认弹窗表 - ---- - -## 六、遗漏项汇总 - -| 类型 | 数量 | 明细 | -|------|------|------| -| 遗漏页面 | 1 | RecallTestPage | -| 遗漏功能 | 4 | 会话置顶、AIHomeView 子功能、解析触发、MaterialReaderView COS 下载 | -| 遗漏 Sheet/弹窗 | 1 | 文件夹新建/重命名 Alert | -| 遗漏 API-页面入口 | 3 | WaitlistService、账号注销、设备管理 | - ---- - -## 七、错误项汇总 - -| 类型 | 数量 | 明细 | -|------|------|------| -| 状态误判 | 4 | 掌握度趋势图(部分→占位)、每周活跃图(部分→占位)、每日时长(已完成→部分)、通知开关(暂未确认→部分+bug) | -| 命名错误 | 1 | 会员与额度 → 存储空间管理 | -| 分类建议 | 2 | 主动回忆+AI 分析建议合并、能力依赖图缺少交叉引用 | - ---- - -## 八、待确认项 - -| 序号 | 待确认内容 | 原因 | -|------|-----------|------| -| U01 | LearningSessionView 是否调用 API 保存学习记录 | 代码中未发现 LearningSessionService.start/end 的直接调用,但可能在 Rust FFI 或 ViewModel 中有间接调用 | -| U02 | SettingsView iCloud 同步开关实际后端支持情况 | Toggle UI 存在但仅改变本地 @State,无法确认后端是否有对应能力 | -| U03 | 分析页"近 7 天"日期范围筛选是否可切换 | UI 中展示了日期范围标签但未发现切换逻辑 | - ---- - -## 九、修正优先级 - -### P0(4 项)— 影响整体地图正确性 - -1. 分析页图表数据状态修正(部分完成 → 占位实现) -2. 每日学习时长持久化状态修正(已完成 → 部分完成) -3. 设置页通知开关 bug 状态修正(暂未确认 → 部分完成,存在 bug) -4. 总览页统计数字口径明确化 - -### P1(4 项)— 遗漏主要页面或业务能力 - -5. 补充 RecallTestPage 到页面地图和功能表 -6. 补充 AIChatPage 会话置顶功能 -7. 标注 WaitlistService 状态 -8. 标注账号注销 API 状态 - -### P2(8 项)— 命名、描述、辅助状态完善 - -9. 重命名"会员与额度"为"存储空间管理" -10. 考虑合并"主动回忆"与"AI 学习分析" -11-16. 其他描述性补充 - ---- - -## 十、最终判断 - -### 总体评价 - -- **已有文档的代码覆盖率高**(页面覆盖率约 95%,功能覆盖率约 95%) -- **核心流程描述准确**(用户生命周期、Tab 结构、路由系统) -- **Mock/占位实现识别基本正确**(AI 相关页面正确标记为占位实现) - -### 主要不足 - -1. **分析页图表状态被低估**(占位实现被标记为部分完成) -2. **设置页通知 bug 被降级为"暂未确认"** -3. **GoalSettingDetailView 学习时长不持久化未被发现** -4. **1 个代码页面、2 个 API 端点、1 个交互功能的遗漏** - -### 是否可以进入下一阶段? - -**可以进入下一阶段的"跨项目业务能力展开",但建议先完成 4 项 P0 修正。** - -原因: -- 已有文档的整体架构和页面覆盖是准确的,作为后续分析的基础足够可靠 -- P0 问题集中在对少数具体功能的状态判断上,不影响整体地图的结构正确性 -- 未发现系统性的页面遗漏或归类错误 -- Mock/占位/废弃实现的识别框架是正确的,只需修正个别判断 -- P1/P2 问题不阻塞跨项目分析,可在第二阶段的盘点中一并修正 - -### 门禁复查 - -| 门禁 | 修正前 | 修正 P0 后 | -|------|--------|-----------| -| 严重遗漏为 0 | 否(1 项 P1) | 是 | -| P0 修正项为 0 | 否(4 项) | 是 | -| Mock/占位未误判 | 否 | 是(修正 4 处后) | - -**完成 4 项 P0 修正后可达"通过"标准。** diff --git a/docs/system-map/ios/review/01-独立页面基线.md b/docs/system-map/ios/review/01-独立页面基线.md deleted file mode 100644 index 4dfdebe..0000000 --- a/docs/system-map/ios/review/01-独立页面基线.md +++ /dev/null @@ -1,314 +0,0 @@ -# 01 独立页面基线 - -> 本文件不参考已有系统地图文档,完全基于代码扫描独立建立。 -> 扫描日期:2026-06-19 -> 扫描范围:AIStudyApp/AIStudyApp/ 目录下全部 .swift 文件(不含 Tests 和 Generated) - ---- - -## 一、独立盘点统计 - -| 统计项 | 数量 | -|--------|------| -| 一级模块(Tab) | 4 | -| 独立页面(含可路由页面) | 32 | -| Sheet / 弹窗 | 10 | -| 确认弹窗(Alert/ConfirmationDialog) | 8 | -| 页面内嵌子视图(非独立路由) | 2 | -| 代码存在但无入口的页面 | 1 | -| 页面功能点(估算) | ~180 | - ---- - -## 二、应用根结构 - -### 2.1 入口点 - -- `AIStudyAppApp.swift` 是 `@main` 入口。 -- 使用 `WindowGroup`,`UIApplicationSupportsMultipleScenes = false`。 -- 无 URL Scheme、Universal Link、Deep Link 配置(Info.plist 中无相关条目)。 -- 无通知点击路由(未发现 `UNUserNotificationCenter` delegate 或 `onOpenURL`)。 -- 无"恢复上次页面"逻辑(每次启动由 AuthManager 状态决定初始页面)。 - -### 2.2 会话状态机(AuthManager.AppSession) - -``` -unknown → SplashScreen -unauthenticated → PreLoginFlow (WelcomePage → LoginPage) -authenticating / refreshing → SplashScreen -authenticated → ContentView (if hasCompletedOnboarding) else PostLoginOnboardingFlow -disabled → AccountStatusView (禁用) -deleted → AccountStatusView (注销) -expired → PreLoginFlow -``` - -### 2.3 Tab 根结构(ContentView) - -``` -TabView(selection: $selectedTab) - ├─ "学习" (tag: "study") → NavigationStack → StudyHomeView - ├─ "知识库" (tag: "library") → NavigationStack → LibraryHomeView - ├─ "分析" (tag: "analysis") → NavigationStack → AnalysisHomeView - └─ "我的" (tag: "profile") → NavigationStack → ProfileView -``` - -所有 Tab 使用 NavigationStack + `.navigationDestination(for: Route.self)` 统一路由。 - -### 2.4 条件性入口 - -| 条件 | 影响的页面/行为 | -|------|----------------| -| hasCompletedOnboarding = false | 显示 PostLoginOnboardingFlow (OnboardingPage → GoalSetupPage) | -| hasCompletedOnboarding = true | 直接进入 ContentView | -| session = .disabled | 显示 AccountStatusView(禁用) | -| session = .deleted | 显示 AccountStatusView(注销) | -| session = .expired / .unauthenticated | 显示 PreLoginFlow | -| selectedTab 值变化 | StudyHomeView 通过 Binding 接收,可被其他 Tab 修改 | -| 有 continueLearning API 数据 | 学习主页显示"继续上次学习"卡片 | -| reviewCards.count > 0 | 学习主页显示"今日复习"卡片 | -| quizzes.count > 0 | 学习主页显示"资料自测"卡片 | -| knowledgeBases.count > 0 | 学习主页显示"开始学习"卡片 | -| 以上全为空 | 学习主页显示空状态引导 | -| iPhone / iPad | AppContext 中记录 deviceType,但不影响页面布局 | -| Debug / Release | DEBUG 宏仅在 Route.destination 中用于显示页面标签覆盖层 | - ---- - -## 三、完整页面清单(按入口类型分类) - -### 3.1 启动/登录流程页面 - -| 序号 | 页面 | Swift 类型 | 入口方式 | 是否可路由 | -|------|------|-----------|---------|-----------| -| P01 | 启动页 | SplashScreen | 应用冷启动(session = .unknown/.authenticating/.refreshing) | 否 | -| P02 | 欢迎页 | WelcomePage | PreLoginFlow step 0 | 否 | -| P03 | 登录页 | LoginPage | PreLoginFlow step 1 | 否 | -| P04 | 账号禁用页 | AccountStatusView | session = .disabled | 否 | -| P05 | 账号注销页 | AccountStatusView | session = .deleted | 否 | -| P06 | 引导页 | OnboardingPage | PostLoginOnboardingFlow step 0 | 否 | -| P07 | 目标设置页 | GoalSetupPage | PostLoginOnboardingFlow step 1 | 否 | - -### 3.2 学习 Tab 页面(NavigationLink Push) - -| 序号 | 页面 | Route 枚举 | 入口 | -|------|------|-----------|------| -| P08 | 学习主页 | Route.studyHome | Tab "学习" 默认 | -| P09 | 学习会话页 | Route.learningSession | 资料阅读页工具栏 | -| P10 | 复习卡片页 | Route.reviewCard | 学习主页 / AIHomeView | -| P11 | 主动回忆页 | Route.activeRecall | 学习主页底部链接 / AIHomeView | - -### 3.3 知识库 Tab 页面(NavigationLink Push) - -| 序号 | 页面 | Route 枚举 | 入口 | -|------|------|-----------|------| -| P12 | 知识库首页 | Tab "知识库" 直接渲染 | Tab "知识库" | -| P13 | 知识库搜索页 | Route.librarySearch | 知识库首页搜索图标 | -| P14 | 知识库详情页 | Route.libraryDetail | 知识库首页卡片点击 | -| P15 | 创建知识库页 | Route.libraryCreate | 知识库首页 + 按钮 / 我的页学习资产 | -| P16 | 添加知识点页 | Route.addKnowledge | 知识库详情页操作菜单"添加" | -| P17 | 知识点详情页 | Route.knowledgeDetail | 知识库详情页点击知识点 | -| P18 | 编辑知识点页 | Route.editKnowledge | 知识点详情页工具栏菜单 | -| P19 | 导入资料页 | Route.libraryImport | 知识库详情页操作菜单"上传" | -| P20 | 导入候选审核页 | Route.importReview | 导入资料完成后 | -| P21 | 发现公开库页 | Route.discoverPublic | 我的页"发现公开库" | - -### 3.4 分析 Tab 页面 - -| 序号 | 页面 | Route 枚举 | 入口 | -|------|------|-----------|------| -| P22 | 学习分析页 | Tab "分析" 直接渲染 | Tab "分析" | - -### 3.5 我的 Tab 页面 - -| 序号 | 页面 | Route 枚举 | 入口 | -|------|------|-----------|------| -| P23 | 个人中心页 | Tab "我的" 直接渲染 | Tab "我的" | -| P24 | 编辑资料页 | Route.editProfile | 个人中心页资料卡片 / 设置页 | -| P25 | 设置页 | Route.settings | 个人中心页设置图标 / 复习提醒行 | -| P26 | 学习目标设置页 | Route.goalSetting | 设置页 / 我的页 | -| P27 | 学习方法偏好页 | Route.methodPreference | 设置页 / 我的页 | -| P28 | 通知列表页 | Route.notificationList | 我的页通知图标 / 消息中心 | -| P29 | 帮助与反馈页 | Route.feedbackForm | 我的页 / 设置页 | - -### 3.6 AI 辅助页面(跨模块 Push) - -| 序号 | 页面 | Route 枚举 | 入口 | -|------|------|-----------|------| -| P30 | AI 对话页 | Route.aiChat | 知识库详情页底部 / 资料阅读页工具栏 / AI 反馈页 | -| P31 | 每日思考页 | Route.dailyThinking | AI 反馈页"再来一题" / AIHomeView | -| P32 | AI 反馈页 | Route.aiFeedback | 主动回忆完成后 / 每日思考提交后 | -| P33 | 薄弱知识点页 | Route.weakPoints | 分析页"全部 N 个"链接 / AIHomeView | - -### 3.7 测验页面 - -| 序号 | 页面 | Route 枚举 | 入口 | -|------|------|-----------|------| -| P34 | 测验列表页 | Route.quizList | 知识库详情页操作菜单"测验" | -| P35 | 测验作答页 | Route.quizTake | 测验列表点击 / 学习主页自我测试 | -| P36 | 测验结果页 | Route.quizResult | 测验作答提交后 | - -### 3.8 资料阅读页面 - -| 序号 | 页面 | Route 枚举 | 入口 | -|------|------|-----------|------| -| P37 | 资料阅读页 | Route.materialReader | 学习主页"继续学习" / 分析页"继续阅读" / 来源文件 / 知识点详情 | -| P38 | 资料详情页 | Route.materialDetail | 资料阅读页工具栏"信息"按钮 | - -### 3.9 Sheet / 弹窗(非独立 Push 页面) - -| 序号 | 名称 | 类型 | 所在页面 | 入口 | -|------|------|------|---------|------| -| S01 | AI 会话列表 Sheet | .sheet (medium/large) | AIChatPage | 工具栏"会话列表"按钮 | -| S02 | 快速笔记 Sheet | .sheet | MaterialReaderView | 工具栏笔记按钮 | -| S03 | 系统预览 (QuickLook) | .sheet (QLPreviewController) | MaterialReaderView | "系统预览"按钮 | -| S04 | 封面选择 Sheet | .sheet (height 220) | CreateLibraryPage | 封面区域点击 | -| S05 | 文件夹管理 Sheet | .sheet | LibraryDetailPage | 操作菜单"文件夹管理" | -| S06 | 链接导入 Alert | .alert (with TextField) | ImportPage | "链接导入"按钮 | - -### 3.10 确认弹窗(Alert / ConfirmationDialog) - -| 序号 | 名称 | 类型 | 所在页面 | -|------|------|------|---------| -| A01 | 结束学习确认 | ConfirmationDialog | LearningSessionView | -| A02 | 删除知识库确认 | Alert | LibraryDetailPage | -| A03 | 批量删除确认 | Alert | LibraryDetailPage | -| A04 | 新建文件夹 | Alert (with TextField) | FolderManageView | -| A05 | 重命名文件夹 | Alert (with TextField) | FolderManageView | -| A06 | 退出登录确认 | Alert | SettingsView | -| A07 | 新建分类 | Alert (with TextField) | FolderManageView | -| A08 | 链接导入 | Alert (with TextField) | ImportPage | - -### 3.11 代码存在但无入口的页面 - -| 名称 | Swift 类型 | 情况说明 | -|------|-----------|---------| -| AI 首页 | AIHomeView | 完整页面代码,含 NavigationLink 到 AIChatPage/ActiveRecallView/WeakPointsPage/ReviewCardView/DailyThinkingPage,但 Route 中无对应 case,无任何其他页面跳转到此页 | - ---- - -## 四、入口类型检查清单 - -| 入口类型 | 是否检查 | 发现情况 | -|---------|---------|---------| -| Tab | 是 | 4 个:学习、知识库、分析、我的 | -| Push (NavigationLink via Route) | 是 | 约 25 个路由目标 | -| Sheet | 是 | 6 个 Sheet | -| FullScreenCover | 是 | 未发现 | -| Alert | 是 | 6 个 Alert | -| ConfirmationDialog | 是 | 1 个 | -| Popover | 是 | 未发现 | -| ContextMenu | 是 | LibraryDetailPage 知识点行 | -| Menu | 是 | LibraryDetailPage 工具栏 | -| Deep Link | 是 | 未配置 | -| Universal Link | 是 | 未配置 | -| URL Scheme | 是 | 未配置 | -| 通知点击路由 | 是 | 未发现 | -| Debug 页面 | 是 | 仅 DEBUG 标签覆盖层,无专用调试页面 | -| 条件渲染页面 | 是 | 基于 session / onboarding / 数据状态的 7 种分支 | -| iPhone/iPad 差异 | 是 | 仅在 AppContext 记录,无布局差异 | -| Feature Flag | 是 | 未发现 | -| 管理员/内部账号 | 是 | 未发现 | -| 首次启动 vs 非首次 | 是 | hasCompletedOnboarding @AppStorage | - ---- - -## 五、页面跳转关系(基于 Route 枚举和 NavigationLink 分析) - -### 5.1 Tab 内跳转 - -| 源页面 | 目标页面 | Route | -|--------|---------|-------| -| StudyHomeView | MaterialReaderView | .materialReader | -| StudyHomeView | ReviewCardView | .reviewCard | -| StudyHomeView | QuizTakerView | .quizTake | -| StudyHomeView | QuizListView | .quizList | -| StudyHomeView | ActiveRecallView | .activeRecall | -| StudyHomeView | 切换到 library Tab | selectedTab 绑定 | -| LibraryHomeView | LibrarySearchView | .librarySearch | -| LibraryHomeView | CreateLibraryPage | .libraryCreate | -| LibraryHomeView | LibraryDetailPage | .libraryDetail | -| LibraryDetailPage | KnowledgeDetailPage | .knowledgeDetail | -| LibraryDetailPage | EditKnowledgePage | .editKnowledge | -| LibraryDetailPage | ImportPage | .libraryImport | -| LibraryDetailPage | AddKnowledgePage | .addKnowledge | -| LibraryDetailPage | QuizListView | .quizList | -| LibraryDetailPage | AIChatPage | .aiChat | -| LibraryDetailPage | MaterialReaderView | .materialReader | -| LibraryDetailPage | FolderManageView (Sheet) | - | -| CreateLibraryPage | 封面选择 Sheet | - | -| AddKnowledgePage | (文件选择器/相册) | 系统组件 | -| KnowledgeDetailPage | MaterialReaderView | .materialReader | -| KnowledgeDetailPage | QuickLook (Sheet) | - | -| KnowledgeDetailPage | QuickNoteSheet | - | -| ImportPage | ImportReviewPage | .importReview | -| ImportPage | (文件选择器/相册/链接Alert) | 系统组件 | -| AnalysisHomeView | WeakPointsPage | .weakPoints | -| AnalysisHomeView | MaterialReaderView | .materialReader | -| ProfileView | EditProfilePage | .editProfile | -| ProfileView | GoalSettingDetailView | .goalSetting | -| ProfileView | SettingsView | .settings | -| ProfileView | MethodPreferenceView | .methodPreference | -| ProfileView | FeedbackFormView | .feedbackForm | -| ProfileView | CreateLibraryPage | .libraryCreate | -| ProfileView | DiscoverView | .discoverPublic | -| ProfileView | NotificationListView | .notificationList | -| SettingsView | GoalSettingDetailView | .goalSetting | -| SettingsView | MethodPreferenceView | .methodPreference | -| SettingsView | EditProfilePage | .editProfile | -| SettingsView | FeedbackFormView | .feedbackForm | - -### 5.2 跨模块跳转 - -| 源页面 | 目标页面 | 触发方式 | -|--------|---------|---------| -| ActiveRecallView | AIFeedbackPageView | 提交完所有回答后"查看 AI 分析" | -| AIFeedbackPageView | StudyHomeView | "加入待巩固" | -| AIFeedbackPageView | AIChatPage | "深入提问" | -| AIFeedbackPageView | DailyThinkingPage | "再来一题" | -| DailyThinkingPage | AIFeedbackPageView | 提交回答后 | -| MaterialReaderView | AIChatPage | 工具栏 AI 按钮 | -| MaterialReaderView | MaterialDetailView | 工具栏信息按钮 | -| MaterialReaderView | LearningSessionView | 工具栏学习按钮 | -| MaterialReaderView | QuickNoteSheet | 工具栏笔记按钮 | -| AIHomeView | AIChatPage | NavigationLink(页面自身无入口) | -| AIHomeView | ActiveRecallView | NavigationLink(页面自身无入口) | -| AIHomeView | WeakPointsPage | NavigationLink(页面自身无入口) | -| AIHomeView | ReviewCardView | NavigationLink(页面自身无入口) | -| AIHomeView | DailyThinkingPage | NavigationLink(页面自身无入口) | - ---- - -## 六、API 服务清单(每个对应一组后端端点) - -| 服务类 | 主要端点 | 被哪些页面使用 | -|--------|---------|--------------| -| AuthService | /auth/apple, /auth/refresh, /auth/logout | LoginPage, AuthManager | -| UserService | /users/me (GET/PATCH), /users/me/preferences, /users/me/profile, /users/me/deletion-request, /users/me/devices, /users/me/assets-summary, /users/me/storage | ProfileView, EditProfilePage, SettingsView, GoalSettingDetailView, MethodPreferenceView | -| KnowledgeBaseService | /knowledge-bases (CRUD), /knowledge-bases/subscribed, /knowledge-bases/:id/subscribe | LibraryHomeView, LibraryDetailPage, CreateLibraryPage, DiscoverView, StudyHomeView | -| KnowledgeItemService | /knowledge-items (CRUD, batch-delete) | LibraryDetailPage, AddKnowledgePage, KnowledgeDetailPage, EditKnowledgePage | -| KnowledgeFolderService | /knowledge-bases/:id/folders (CRUD) | FolderManageView | -| KnowledgeSourceService | /knowledge-bases/:id/sources (CRUD, parse) | LibraryDetailPage, MaterialReaderView | -| DocumentImportService | /imports (create, status) | ImportPage | -| ImportCandidateService | /import-candidates (list, accept, reject, batch-accept) | ImportReviewPage | -| QuizService | /quizzes (generate, list, detail, start, submit, results, history) | QuizListView, QuizTakerView, QuizResultView | -| RagChatService | /rag-chat/sessions (CRUD, messages, stream) | AIChatPage | -| ActiveRecallService | /active-recalls (list, submit) | ActiveRecallView | -| AIAnalysisService | /ai-analysis | (ViewModel 中有调用,但 ActiveRecallView 使用硬编码数据) | -| ReviewService | /reviews (due, submit, generate-cards) | ReviewCardView (View 中硬编码,ViewModel 调用 API) | -| FocusItemService | /focus-items (list, update, complete) | AnalysisHomeView (via ActivityViewModel) | -| ActivityService | /activity (summary, heatmap, trend, streak, recommendations) | AnalysisHomeView, ProfileView, StudyHomeView | -| LearningSessionService | /learning-sessions (list, start, end) | LearningSessionView, StudyHomeView | -| ReadingAPIService | /learning/reading-events/batch, /materials/:id/reading-progress, /learning/continue, /learning/summary, /learning/trend, /learning/records | MaterialReaderView, StudyHomeView, AnalysisHomeView | -| FileUploadService | /files/upload-url, /files/complete, /files/:id | CreateLibraryPage, AddKnowledgePage, ImportPage, EditProfilePage | -| FeedbackService | /feedback | FeedbackFormView | -| NotificationService | /notifications (list, markRead, markAllRead, preferences, push-token) | NotificationListView, SettingsView | -| WaitlistService | /waitlist | (API 定义存在,iOS 代码中未发现使用入口) | - ---- - -## 七、免责说明 - -- 本基线完全基于对 AIStudyApp/AIStudyApp/ 目录下全部 64 个 .swift 文件的逐文件阅读建立。 -- 未检查 .xcassets、.entitlements、.xcodeproj 中的详细配置。 -- iOS 推送通知的深度链接处理在代码中未发现,但可能在 Capabilities 中配置(未检查 .entitlements 文件全文)。 -- WaitlistService 代码存在但未在任何 View 或 ViewModel 中使用,判定为无 C 端入口。 diff --git a/docs/system-map/ios/review/02-独立功能基线.md b/docs/system-map/ios/review/02-独立功能基线.md deleted file mode 100644 index b173765..0000000 --- a/docs/system-map/ios/review/02-独立功能基线.md +++ /dev/null @@ -1,537 +0,0 @@ -# 02 独立功能基线 - -> 完全基于代码扫描独立建立,不参考已有系统地图文档。 -> 每项功能标注:已完成 / 部分完成 / 占位实现 / 未完成 / 暂未确认 - ---- - -## 一、启动与登录流程 - -### P01 启动页 (SplashScreen) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 展示品牌标识 | 已完成 | 渐变背景 + 大脑图标 + "知习" + ProgressView | -| 自动恢复登录会话 | 已完成 | AuthManager.restoreSession() 读取 Keychain Token → /users/me 校验 | -| Token 刷新 | 已完成 | /auth/refresh 端点,APIClient 401 自动重试 | -| 会话过期处理 | 已完成 | session = .expired → PreLoginFlow | -| 账号禁用/注销检测 | 已完成 | /users/me 返回特定 errorCode → 切到对应 AccountStatusView | - -### P02 欢迎页 (WelcomePage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 产品功能介绍 | 已完成 | "AI 驱动"标签 + 主标题 + 3 个 FeatureRow(主动回忆、费曼解释、AI 分析) | -| 进入登录 | 已完成 | "开始使用" → LoginPage | - -### P03 登录页 (LoginPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| Apple ID 登录 | 已完成 | SignInWithAppleButton → AuthService.appleLogin | -| nonce 验证 | 已完成 | SHA256(nonce) 传入 Apple 请求 | -| 登录中取消 | 已完成 | 取消按钮 + Task.cancel() | -| 登录错误提示 | 已完成 | 红色错误横幅,显示具体错误信息 | -| Token 存储 | 已完成 | KeychainHelper.save | - -### P04/P05 账号状态页 (AccountStatusView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 禁用状态展示 | 已完成 | icon + title + message + "返回首页" | -| 注销状态展示 | 已完成 | 同上,文案不同 | -| 返回首页 | 已完成 | onBackToLogin → session = .unauthenticated | - -### P06 引导页 (OnboardingPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 4 步引导浏览 | 已完成 | 输入知识 / 主动输出 / AI 分析 / 掌握知识 | -| 跳过引导 | 已完成 | "跳过" → onContinue | -| 步骤指示器 | 已完成 | 4 个圆角矩形进度条 | - -### P07 目标设置页 (GoalSetupPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 选择学习目标(4 选 1) | 已完成 | 备考考试/职业技能/通识学习/自定义 | -| 选择学习方法(多选) | 已完成 | 间隔回忆/费曼技巧/AI 分析 | -| 选择每日学习时间(4 选 1) | 已完成 | 15分钟/30分钟/1小时/不限制 | -| 确认并开始 | 已完成 | "开始学习" → onComplete(true) → hasCompletedOnboarding = true | - ---- - -## 二、学习 Tab (StudyHomeView + 子页面) - -### P08 学习主页 (StudyHomeView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看连续学习天数 | 已完成 | streakDays 从 API 获取,火焰图标 + "X 天连续" | -| 继续上次学习 | 已完成 | MainAction.continueSession → MaterialReaderView,优先级最高 | -| 今日复习入口 | 部分完成 | 入口正常 → ReviewCardView,但 ReviewCardView 使用硬编码数据 | -| 资料自测入口 | 已完成 | MainAction.selfTest → QuizTakerView | -| 空状态引导(创建/探索知识库) | 已完成 | 切换到 library Tab | -| 查看每周摘要 | 已完成 | 本周分钟/复习卡片/活跃天数/连续天数 | -| 查看完整分析 | 已完成 | → Route.activeRecall(目标页为占位实现) | -| 下拉刷新 | 已完成 | .refreshable | -| 加载状态 | 已完成 | ProgressView | -| 错误状态 | 部分完成 | View 支持 error 状态,但 ViewModel 永远不会设置 error(所有 API 用 try? 吞错) | -| 优先级自动计算 | 已完成 | 继续学习 > 未完成会话 > 今日复习 > 自测 > 开始学习 > 空状态 | - -### P09 学习会话页 (LearningSessionView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 专注计时(1 秒精度) | 已完成 | Timer.publish(every: 1) | -| 环形进度动画 | 已完成 | Circle.trim + AngularGradient 动画,上限 30 分钟 | -| 暂停/继续 | 已完成 | isRunning/isPaused 切换 | -| 结束学习(含确认弹窗) | 已完成 | ConfirmationDialog → 结束并保存 | -| 庆祝动画 | 已完成 | ZXCelebrationView | -| 学习时长保存到 API | 暂未确认 | LearningSessionView 不直接调用 API,也未发现调用 LearningSessionService.start/end 的代码 | - -### P10 复习卡片页 (ReviewCardView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 卡片翻转 | 占位实现 | 点击翻转动画正常,但使用 3 张硬编码 ReviewCardItem | -| 自评四个等级 | 占位实现 | "完全不会"/"有点难"/"基本会"/"很简单"按钮 | -| 进度条 | 占位实现 | 正常展示但基于硬编码数据 | -| 完成庆祝 | 占位实现 | ZXCelebrationView | -| 调用 Review API | 未完成 | ReviewViewModel.loadDueCards() 被调用但结果被忽略,View 使用硬编码 ReviewCardItem | - -### P11 主动回忆页 (ActiveRecallView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 文字作答 | 占位实现 | TextEditor + 提交按钮,但使用 3 道硬编码 RecallQuestion | -| 语音作答 | 占位实现 | 展示麦克风图标和提示文字,无实际录音功能 | -| 提交答案 | 占位实现 | 本地状态更新,不调用 API | -| AI 分析动画 | 占位实现 | ZXThinkingOverlay 展示,2 秒后自动消失 | -| 查看 AI 分析结果 | 占位实现 | → AIFeedbackPageView(本身为占位实现) | -| 庆祝动画 | 占位实现 | ZXCelebrationView | -| 题目来源标签 | 占位实现 | 展示硬编码来源文字 | - ---- - -## 三、知识库 Tab - -### P12 知识库首页 (LibraryHomeView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看知识库列表 | 已完成 | LibraryViewModel.loadKnowledgeBases() → KnowledgeBaseService.list() | -| 筛选分类(全部/我的/已订阅/官方) | 部分完成 | "官方"筛选返回空数组(TODO 注释:等后端支持) | -| 搜索入口 | 部分完成 | 搜索按钮 → LibrarySearchView(搜索逻辑未实现) | -| 创建知识库入口 | 已完成 | + 按钮 → CreateLibraryPage | -| 查看知识库详情 | 已完成 | 卡片点击 → LibraryDetailPage | -| 下拉刷新 | 已完成 | .refreshable | -| 无限滚动分页 | 已完成 | ZXLoadMoreFooter | -| 知识库卡片:封面/名称/描述/日期/置顶/公开 | 已完成 | ZLibraryCard 完整展示 | - -### P13 知识库搜索页 (LibrarySearchView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 搜索输入框 | 已完成 | TextField 已搭建 | -| 搜索逻辑 | 未完成 | query 变化时不触发任何 API 调用,结果区域始终为空 | -| 空状态提示 | 已完成 | "搜索知识点、知识库或标签" | - -### P14 知识库详情页 (LibraryDetailPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看知识库信息头部 | 已完成 | 封面/标题/描述/来源/文件数/创建/更新日期 | -| 知识点列表(Tab 0) | 已完成 | KnowledgeItemService.list() | -| 知识点排序 | 已完成 | 默认/文件大小/创建时间/更新时间 | -| 来源文件列表(Tab 1) | 已完成 | KnowledgeSourceService.list() | -| 来源文件阅读状态标签 | 已完成 | "已读"/"阅读中" | -| 查看知识点详情 | 已完成 | → KnowledgeDetailPage | -| 删除单个知识点 | 已完成 | contextMenu 删除 → KnowledgeItemService.delete() | -| 批量选择/删除 | 已完成 | isSelectMode + batchDeleteItems | -| 导入资料入口 | 已完成 | 操作菜单 → ImportPage | -| 添加知识点入口 | 已完成 | 操作菜单 → AddKnowledgePage | -| 生成测验入口 | 已完成 | 操作菜单 → QuizListView | -| 文件夹管理入口 | 已完成 | 操作菜单 → FolderManageView Sheet | -| AI 对话入口 | 已完成 | 底部 AI 按钮 → AIChatPage (知识库范围) | -| 删除来源文件 | 已完成 | 来源文件行删除按钮 | -| 删除知识库 | 已完成 | 确认弹窗 → KnowledgeBaseService.delete() | -| 知识点重命名 | 未完成 | contextMenu 中有"重命名"按钮,但 action 为空(// TODO: rename) | -| 移动到文件夹 | 未完成 | contextMenu 中有"移动到"按钮,但 action 为空(// TODO: move to folder) | -| 知识库管理页 | 未完成 | 操作菜单"知识库管理"按钮 action 为空(// TODO: knowledge base management page) | - -### P15 创建知识库页 (CreateLibraryPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 上传封面图 | 已完成 | PhotosPicker → 裁剪 512x512 → COS 上传 | -| 输入名称(必填) | 已完成 | TextField + 必填校验 | -| 输入描述(必填) | 已完成 | TextEditor + 必填校验 | -| 创建知识库 | 已完成 | KnowledgeBaseService.create() → Toast 反馈 | - -### P16 添加知识点页 (AddKnowledgePage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 手动模式(标题+内容) | 已完成 | TextField + TextEditor → KnowledgeItemService.create() | -| 文件模式(选择文件) | 已完成 | fileImporter → COS 上传 → 创建知识点 | -| 图片模式(选择照片) | 已完成 | PhotosPicker → JPEG 压缩 → COS 上传 → 创建知识点 | -| 多文件批量添加 | 已完成 | 支持多选,逐个上传,显示进度 | -| 移除已选文件 | 已完成 | xmark 按钮 → 从 selectedFiles 移除 | - -### P17 知识点详情页 (KnowledgeDetailPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| URL 型知识点渲染 | 已完成 | COS 下载 → 本地缓存 → MaterialReaderView 嵌入 | -| 纯文本渲染 | 已完成 | 直接 Text 渲染 | -| 文件类型自动检测 | 已完成 | 从扩展名/MIME 推断 MaterialType | -| COS 过期 URL 重取 | 已完成 | 检测 XML → 重新 fetch item → 获取新签名 URL | -| 错误状态(含摘要回退) | 已完成 | 显示错误 + 摘要文字 + 重试按钮 | -| 阅读原文件 | 已完成 | NavigationLink → MaterialReaderView | -| 笔记入口 | 已完成 | 工具栏菜单 → showNoteSheet | -| 系统预览(PDF/Word/Excel) | 已完成 | 工具栏菜单 → QuickLook | -| 收藏切换 | 部分完成 | isFavorited 状态切换有 UI,但 toggleFavorite() 中有 TODO 注释:后端 API 未支持 | - -### P18 编辑知识点页 (EditKnowledgePage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 修改标题 | 已完成 | TextField → KnowledgeItemService.update() | -| 修改内容 | 已完成 | TextEditor → KnowledgeItemService.update() | -| 保存 | 已完成 | "保存修改"按钮 → API 调用 | - -### P19 导入资料页 (ImportPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 文件导入(PDF/纯文本) | 已完成 | fileImporter → COS 上传 → KnowledgeSourceService.add() | -| 相册导入 | 部分完成 | PhotosPicker → COS 上传 → DocumentImportService.create(),但未发现后续 AI OCR 处理流程 | -| 链接导入 | 已完成 | Alert 弹窗输入 URL → DocumentImportService.create() | -| 自动选择目标知识库 | 已完成 | 加载第一个知识库,无则提示创建 | -| 导入错误提示 | 已完成 | 红色错误横幅 | -| 导入进度提示 | 已完成 | statusMessage + ProgressView | - -### P20 导入候选审核页 (ImportReviewPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看候选列表(含置信度) | 已完成 | ImportCandidateService.listBySource() | -| 展开查看候选详情 | 已完成 | expandedId 切换 | -| 接受单个候选 | 已完成 | ImportCandidateService.accept() | -| 拒绝单个候选 | 已完成 | ImportCandidateService.reject() | -| 批量全部接受 | 已完成 | ImportCandidateService.batchAccept() | -| 空候选状态 | 已完成 | "没有待审批的候选" | - -### P21 发现公开库页 (DiscoverView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看公开知识库列表 | 已完成 | KnowledgeBaseService.discover() | -| 订阅知识库 | 已完成 | KnowledgeBaseService.subscribe() | -| 取消订阅 | 已完成 | KnowledgeBaseService.unsubscribe() | -| 空状态 | 已完成 | "暂无公开知识库" | - -### S05 文件夹管理 Sheet (FolderManageView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看文件夹列表 | 已完成 | KnowledgeFolderService.list() | -| 新建文件夹 | 已完成 | Alert 输入 → KnowledgeFolderService.create() | -| 重命名文件夹 | 已完成 | Swipe → Alert 输入 → KnowledgeFolderService.update() | -| 删除文件夹 | 已完成 | Swipe 删除 → KnowledgeFolderService.delete() | - ---- - -## 四、分析 Tab - -### P22 学习分析页 (AnalysisHomeView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 综合掌握度 | 已完成 | ActivityService.summary().dailyAverage | -| 总学习分钟 | 已完成 | ActivityService.summary().totalMinutes | -| 复习卡片数 | 已完成 | ActivityService.summary().totalCardsReviewed | -| 活跃天数 | 已完成 | ActivityService.summary().activeDays | -| 今日/本周阅读统计 | 已完成 | ReadingAPIService.getLearningSummary() | -| 掌握度趋势折线图 | 占位实现 | ZXChartView 使用硬编码数据 [(一,0.62)...(今,0.78)],未使用 API | -| 阅读时长趋势柱状图 | 已完成 | 使用 API 返回的 LearningTrendResponse.series | -| 本周学习活跃柱状图 | 占位实现 | ZXWeekBarChart 使用硬编码数据,未使用 API | -| 连续学习统计 | 已完成 | ActivityService.streak() | -| 最近学习记录(5 条) | 已完成 | ReadingAPIService.getLearningRecords() | -| 学习推荐(3 条) | 已完成 | ActivityService.recommendations() | -| 继续阅读入口 | 已完成 | → MaterialReaderView | -| AI 综合分析文字 | 已完成 | 动态拼接 summary 数据 | -| 薄弱知识点列表(5 条) | 已完成 | FocusItemService.list() | -| 查看全部薄弱知识点 | 已完成 | → WeakPointsPage(目标页为占位实现) | -| 下拉刷新 | 已完成 | .refreshable | -| 日期范围筛选 | 暂未确认 | "近 7 天" 标签展示但无实际切换逻辑 | - ---- - -## 五、我的 Tab - -### P23 个人中心页 (ProfileView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看个人资料(头像/昵称/邮箱) | 已完成 | UserService.myProfile() | -| 查看学习统计(活跃天/卡片/分钟) | 已完成 | ActivityService.summary() | -| 查看学习资产(库/条目/卡片计数) | 已完成 | UserService.fetchAssets() | -| 查看存储空间 | 已完成 | UserService.fetchStorage() | -| 编辑资料入口 | 已完成 | → EditProfilePage | -| 学习目标设置入口 | 已完成 | → GoalSettingDetailView | -| 复习提醒入口 | 已完成 | → SettingsView | -| 学习方法偏好入口 | 已完成 | → MethodPreferenceView | -| 帮助与反馈入口 | 已完成 | → FeedbackFormView | -| 发现公开库入口 | 已完成 | → DiscoverView | -| 消息中心入口 | 已完成 | → NotificationListView | -| 通知图标入口 | 已完成 | → NotificationListView | -| 设置图标入口 | 已完成 | → SettingsView | -| 创建知识库入口(学习资产行) | 已完成 | → CreateLibraryPage | - -### P24 编辑资料页 (EditProfilePage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 上传头像 | 已完成 | PhotosPicker → 256x256 裁剪 → COS 上传 | -| 编辑昵称 | 已完成 | TextField | -| 编辑学习身份 | 已完成 | TextField | -| 编辑学习方向 | 已完成 | TextField | -| 编辑当前目标 | 已完成 | TextField | -| 编辑个人简介 | 已完成 | TextEditor | -| 保存 | 已完成 | UserService.updateProfile() + updateProfileDetail() | - -### P25 设置页 (SettingsView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 切换外观模式 | 已完成 | system/dark/light 循环切换 → UserService.updatePreferences() | -| 语言显示 | 已完成 | 展示"简体中文" | -| 学习目标设置入口 | 已完成 | → GoalSettingDetailView | -| 方法偏好入口 | 已完成 | → MethodPreferenceView | -| 复习提醒开关 | 已完成 | Toggle + 条件显示提醒时间/间隔天数选择器 | -| 复习提醒时间选择 | 已完成 | SegmentedPicker: 08:00/12:00/18:00/20:00/21:00 | -| 间隔天数选择 | 已完成 | SegmentedPicker: 1/2/3/5/7 天 | -| 通知开关 | 部分完成 | 三个通知开关(复习提醒/导入完成/系统通知)全部绑定到同一个 $reviewReminder 变量,疑似 bug | -| 存储空间展示 | 已完成 | profileVM.formattedStorage | -| iCloud 同步开关 | 暂未确认 | Toggle UI 存在,切换仅改变本地 @State,未调用任何 API | -| 个人资料入口 | 已完成 | → EditProfilePage | -| 设备管理入口 | 未完成 | "登录设备管理"行 → Route.settings(指向自身),无实际设备管理页面 | -| 帮助与反馈入口 | 已完成 | → FeedbackFormView | -| 隐私政策入口 | 未完成 | "隐私政策"行无任何导航动作(无 NavigationLink/Button action) | -| 用户协议入口 | 未完成 | "用户协议"行无任何导航动作 | -| 退出登录 | 已完成 | 确认弹窗 → authManager.signOut() | -| 版本号展示 | 已完成 | "知习 v1.0" | - -### P26 学习目标设置页 (GoalSettingDetailView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 选择学习目标 | 已完成 | 4 选 1 → UserService.updateProfileDetail(currentGoal) | -| 选择每日学习时长 | 部分完成 | 4 选 1 UI 存在,但 dailyMins 只保存在本地 @State,不通过 API 持久化 | - -### P27 学习方法偏好页 (MethodPreferenceView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 多选学习方法 | 已完成 | 4 种方法多选 → UserService.updatePreferences(preferredMethods) | -| 保存 | 已完成 | "保存"按钮 → API 调用 | - -### P28 通知列表页 (NotificationListView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看通知列表(按日期分组) | 已完成 | NotificationService.list(),分组为今天/本周/更早 | -| 按类型显示图标和颜色 | 已完成 | 10+ 种通知类型映射 | -| 标记单条已读 | 已完成 | NotificationService.markRead() | -| 全部标记已读 | 已完成 | NotificationService.markAllRead() | -| 下拉刷新 | 已完成 | .refreshable | -| 空状态 | 已完成 | "暂无通知" | - -### P29 帮助与反馈页 (FeedbackFormView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 选择反馈类型 | 已完成 | Bug 反馈/功能建议/内容问题/其他 | -| 输入反馈内容 | 已完成 | TextEditor | -| 提交反馈 | 已完成 | FeedbackService.submit() | - ---- - -## 六、AI 辅助功能 - -### P30 AI 对话页 (AIChatPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 发送文字消息 | 已完成 | → RagChatService.sendMessageStream() | -| 流式接收 AI 回复 | 已完成 | AsyncThrowingStream SSE 解析 | -| 查看 AI 思考过程(可折叠) | 已完成 | ThinkingSection 组件 | -| 复制 AI 回复 | 已完成 | UIPasteboard.general.string | -| 重新生成回复 | 已完成 | onRegenerate → vm.send() | -| 新建对话 | 已完成 | vm.createNewSession() | -| 会话列表 Sheet | 已完成 | .sheet 展示 ChatSession 列表 | -| 切换历史会话 | 已完成 | vm.loadSession(id) | -| 搜索会话 | 已完成 | 会话列表中有搜索功能 | -| 删除会话 | 已完成 | swipe → RagChatService.deleteSession() | -| 归档会话 | 已完成 | swipe → UpdateChatSessionRequest(isArchived: true) | -| 置顶/取消置顶 | 已完成 | swipe → UpdateChatSessionRequest(isPinned:) | -| 对话范围标签 | 已完成 | scopeLabel 显示在消息列表顶部 | -| 创建会话中状态 | 已完成 | ProgressView + "正在准备 AI 助手..." | -| 会话创建失败状态 | 已完成 | 错误图标 + 文字 | - -### P31 每日思考页 (DailyThinkingPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看思考题目 | 占位实现 | 题目硬编码:"解释'注意力机制'在 Transformer 中的作用" | -| 输入回答 | 已完成 | TextEditor | -| 提交获取 AI 反馈 | 占位实现 | → AIFeedbackPageView(本身为占位实现)。注意:submitted 状态变量从未被设置为 true,提交按钮始终可见 | - -### P32 AI 反馈页 (AIFeedbackPageView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| AI 分析进度动画 | 占位实现 | 4 步动画 + 硬编码 5 秒等待 | -| 得分展示 | 占位实现 | 78/100 硬编码,圆环进度 0.78 | -| 掌握度评价 | 占位实现 | "良好掌握" 硬编码 | -| 答对/答错分析 | 占位实现 | 2 条硬编码文字 | -| 加入待巩固 | 占位实现 | → StudyHomeView | -| 深入提问 | 已完成 | → AIChatPage(AIChatPage 本身已完成) | -| 再来一题 | 已完成 | → DailyThinkingPage(DailyThinkingPage 本身为占位实现) | - -### P33 薄弱知识点页 (WeakPointsPage) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看薄弱知识点列表 | 占位实现 | 5 个硬编码 ZXWeakRow(贝叶斯定理/正态分布/词根/Covariance/梯度下降) | -| 优先级标签 | 占位实现 | 硬编码"高"/"中"优先级 | - -### (废弃)AI 首页 (AIHomeView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 服务器状态检查 | 已完成 | APIClient.shared.request("/") 健康检查 | -| 快速操作入口 | 已完成 | 4 个 NavigationLink 到 activeRecall/weakPoints/aiChat/reviewCard | -| 每日思考卡片 | 占位实现 | 硬编码题目 | -| 推荐问题列表 | 占位实现 | 3 条硬编码推荐文字 | -| 底部输入栏 | 已完成 | 输入 → NavigationLink 到 AIChatPage | -| 整体页面 | 占位实现 | **代码完整但无任何导航入口**,Route 枚举中无对应 case,无任何页面跳转到此 | - ---- - -## 七、测验功能 - -### P34 测验列表页 (QuizListView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看测验列表 | 已完成 | QuizService.list(knowledgeBaseId:) | -| 生成新测验 | 已完成 | QuizService.generate(),失败时 Toast | -| 空状态引导 | 已完成 | "暂无测验" + "生成测验"按钮 | -| 开始作答 | 已完成 | → QuizTakerView | - -### P35 测验作答页 (QuizTakerView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 选择题作答(A/B/C/D) | 已完成 | 选项按钮,选中高亮 | -| 判断题作答 | 已完成 | "正确"/"错误"按钮 | -| 填空题作答 | 已完成 | TextField | -| 上一题/下一题导航 | 已完成 | 按钮导航 | -| 进度条 | 已完成 | GeometryReader 进度条 | -| 提交答案 | 已完成 | QuizService.submit() | -| 提交失败 Toast | 已完成 | ZXToastManager.shared.error("提交失败") | - -### P36 测验结果页 (QuizResultView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看得分 | 已完成 | 大号数字,>=60 绿色,<60 红色 | -| 查看正确/总题数 | 已完成 | "答对 X/Y 题" | -| 查看逐题详情(正确/错误标记+解析) | 已完成 | ForEach 遍历 answers | -| 重新测验 | 已完成 | → QuizTakerView | - ---- - -## 八、资料阅读 - -### P37 资料阅读页 (MaterialReaderView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| Markdown 渲染 | 已完成 | Rust FFI parseMarkdown → DocumentBlockView | -| 纯文本渲染 | 已完成 | Rust FFI parseText → DocumentBlockView | -| 图片显示 | 已完成 | UIImage + 元信息 | -| PDF/Word/Excel 系统预览 | 已完成 | QuickLook Sheet | -| PowerPoint 外部打开 | 已完成 | ShareLink | -| 不支持格式提示 | 已完成 | "暂不支持此格式" | -| 标记已读 | 已完成 | 本地 MarkedReadStore + 事件上报 | -| AI 对话(资料范围) | 已完成 | → AIChatPage 带 ChatEntryContext(.material) | -| 查看资料详情 | 已完成 | → MaterialDetailView | -| 开始学习会话 | 已完成 | → LearningSessionView | -| 添加笔记 | 已完成 | → QuickNoteSheet,含锚点记录 | -| 恢复阅读位置 | 已完成 | API → 本地 → 会话 三级优先级 | -| 自动记录阅读位置(2s 防抖) | 已完成 | reportScrollPosition | -| 前后台会话管理 | 已完成 | scenePhase → pause/resume | -| 心跳上报(15s) | 已完成 | Rust FFI | -| COS 自动下载 | 已完成 | 文件不存在时自动从 COS 下载 | -| 解析触发 | 部分完成 | 60×2s 硬编码轮询,硬编码超时逻辑 | -| 阅读进度标记 | 已完成 | API reading-progress 查询 | - -### P38 资料详情页 (MaterialDetailView) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| AI 处理状态横幅 | 已完成 | 处理中(琥珀色)/就绪(绿色)/失败(红色) | -| 文件元数据展示 | 已完成 | 文件名、格式、大小、上传日期 | -| 阅读进度卡片 | 已完成 | 阅读状态 + 累计阅读时长 | -| 知识点/测验计数 | 已完成 | statBox 展示 | - ---- - -## 九、弹窗与 Sheet - -### S01 AI 会话列表 Sheet - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 查看会话列表 | 已完成 | RagChatService.listSessions() | -| 切换会话 | 已完成 | 点击 → vm.loadSession() | -| 搜索会话 | 已完成 | 搜索输入 | -| 删除会话 | 已完成 | swipe 删除 | -| 归档会话 | 已完成 | swipe 归档 | -| 置顶/取消置顶 | 已完成 | swipe 置顶 | -| 新建会话 | 已完成 | + 按钮 | - -### S02 快速笔记 Sheet (QuickNoteSheet) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| 输入笔记文字 | 已完成 | TextEditor(自动聚焦) | -| 保存笔记 | 已完成 | UserDefaults 存储,含锚点信息 | -| 取消 | 已完成 | dismiss() | - -### S03 系统预览 (QuickLook) - -| 功能 | 状态 | 判定依据 | -|------|------|---------| -| PDF/Office 预览 | 已完成 | QLPreviewController | - ---- - -## 十、统计 - -| 统计项 | 已完成 | 部分完成 | 占位实现 | 未完成 | 暂未确认 | -|--------|--------|---------|---------|--------|---------| -| 启动/登录功能 | 16 | 0 | 0 | 0 | 0 | -| 学习 Tab 功能 | 8 | 2 | 6 | 1 | 1 | -| 知识库 Tab 功能 | 30 | 3 | 0 | 3 | 0 | -| 分析 Tab 功能 | 12 | 0 | 2 | 0 | 1 | -| 我的 Tab 功能 | 24 | 2 | 0 | 3 | 1 | -| AI 辅助功能 | 12 | 0 | 10 | 0 | 0 | -| 测验功能 | 11 | 0 | 0 | 0 | 0 | -| 资料阅读功能 | 17 | 1 | 0 | 0 | 0 | -| Sheet/弹窗功能 | 9 | 0 | 0 | 0 | 0 | -| **总计** | **139** | **8** | **18** | **7** | **3** | diff --git a/docs/system-map/ios/review/03-页面差异清单.md b/docs/system-map/ios/review/03-页面差异清单.md deleted file mode 100644 index 8f5470d..0000000 --- a/docs/system-map/ios/review/03-页面差异清单.md +++ /dev/null @@ -1,189 +0,0 @@ -# 03 页面差异清单 - -> 将独立页面基线(review/01-独立页面基线.md)与已有文档(01-iOS页面地图.md)逐项比对。 - ---- - -## 一、文档已覆盖且正确的页面(一致项) - -以下页面在文档中描述正确,与代码实现一致: - -| 文档章节 | 文档页面名 | 代码类型 | 判定 | -|---------|-----------|---------|------| -| 1.1 | 启动页 (SplashScreen) | SplashScreen | 一致 | -| 1.2 | 欢迎页 (WelcomePage) | WelcomePage | 一致 | -| 1.3 | 登录页 (LoginPage) | LoginPage | 一致 | -| 1.4 | 账号状态页 (AccountStatusView) | AccountStatusView | 一致 | -| 1.5 | 引导页 (OnboardingPage) | OnboardingPage | 一致 | -| 1.6 | 目标设置页 (GoalSetupPage) | GoalSetupPage | 一致 | -| 2.1 | 学习主页 (StudyHomeView) | StudyHomeView | 一致 | -| 2.2 | 学习会话页 (LearningSessionView) | LearningSessionView | 一致 | -| 2.3 | 复习卡片页 (ReviewCardView) | ReviewCardView | 一致 | -| 2.4 | 主动回忆页 (ActiveRecallView) | ActiveRecallView | 一致 | -| 3.1 | 知识库首页 (LibraryHomeView) | LibraryHomeView | 一致 | -| 3.2 | 知识库搜索页 (LibrarySearchView) | LibrarySearchView | 一致 | -| 3.3 | 知识库详情页 (LibraryDetailPage) | LibraryDetailPage | 一致 | -| 3.4 | 创建知识库页 (CreateLibraryPage) | CreateLibraryPage | 一致 | -| 3.5 | 添加知识点页 (AddKnowledgePage) | AddKnowledgePage | 一致 | -| 3.6 | 知识点详情页 (KnowledgeDetailPage) | KnowledgeDetailPage | 一致 | -| 3.7 | 编辑知识点页 (EditKnowledgePage) | EditKnowledgePage | 一致 | -| 3.8 | 导入资料页 (ImportPage) | ImportPage | 一致 | -| 3.9 | 导入候选审核页 (ImportReviewPage) | ImportReviewPage | 一致 | -| 3.10 | 发现公开库页 (DiscoverView) | DiscoverView | 一致 | -| 3.11 | 文件夹管理 (FolderManageView) | FolderManageView | 一致 | -| 4.1 | 学习分析页 (AnalysisHomeView) | AnalysisHomeView | 一致 | -| 5.1 | 个人中心页 (ProfileView) | ProfileView | 一致 | -| 5.2 | 编辑资料页 (EditProfilePage) | EditProfilePage | 一致 | -| 5.3 | 设置页 (SettingsView) | SettingsView | 一致 | -| 5.4 | 学习目标设置页 (GoalSettingDetailView) | GoalSettingDetailView | 一致 | -| 5.5 | 学习方法偏好页 (MethodPreferenceView) | MethodPreferenceView | 一致 | -| 5.6 | 通知列表页 (NotificationListView) | NotificationListView | 一致 | -| 5.7 | 帮助与反馈页 (FeedbackFormView) | FeedbackFormView | 一致 | -| 6.1 | AI 对话页 (AIChatPage) | AIChatPage | 一致 | -| 6.2 | 每日思考页 (DailyThinkingPage) | DailyThinkingPage | 一致 | -| 6.3 | AI 反馈页 (AIFeedbackPageView) | AIFeedbackPageView | 一致 | -| 6.4 | 薄弱知识点页 (WeakPointsPage) | WeakPointsPage | 一致 | -| 6.5 | AI 首页 (AIHomeView) | AIHomeView | 一致(正确标记为废弃) | -| 7.1 | 测验列表页 (QuizListView) | QuizListView | 一致 | -| 7.2 | 测验作答页 (QuizTakerView) | QuizTakerView | 一致 | -| 7.3 | 测验结果页 (QuizResultView) | QuizResultView | 一致 | -| 8.1 | 资料阅读页 (MaterialReaderView) | MaterialReaderView | 一致 | -| 8.2 | 资料详情页 (MaterialDetailView) | MaterialDetailView | 一致 | -| 9.1 | 快速笔记 (QuickNoteSheet) | QuickNoteSheet | 一致 | -| 9.2 | 系统预览 (QuickLook) | QuickLookPreview | 一致 | -| 9.3 | AI 会话列表 Sheet | AIChatPage 内 Sheet | 一致 | -| 9.4 | 封面选择 Sheet | CreateLibraryPage 内 Sheet | 一致 | -| 9.5 | 链接导入弹窗 | ImportPage 内 Alert | 一致 | -| 9.6 | 各类确认弹窗 | 多个 Alert/ConfirmationDialog | 一致 | - -**一致页面数:43 个(含 6 个 Sheet/弹窗)** - ---- - -## 二、代码存在但文档遗漏的页面 - -### 遗漏 1:RecallTestPage - -- **代码位置:** `Features/AI/RecallTestPage.swift` -- **页面作用:** 独立的回忆测试页面,用户输入对特定知识点的理解,提交后跳转 AI 反馈页。 -- **入口:** 代码中有 Route 定义但**未被任何 NavigationLink 引用**。Route 枚举中有 `aiFeedback` case,但未发现 `recallTest` case。 -- **实际状态:** 代码存在但无导航入口(类似 AIHomeView 的情况)。 -- **应出现在:** 01-iOS页面地图.md → 第六章 AI 辅助功能 -- **建议补到位置:** 新增 6.6 节"回忆测试页(RecallTestPage)" -- **建议状态:** 占位实现(无导航入口 + 题目硬编码) - -### 遗漏 2:Route.studyHome 独立页面 - -- **代码位置:** Route.swift `case studyHome` -- **说明:** Route 中定义了 `studyHome` 路由目标(指向 StudyHomeView(selectedTab: .constant("study"))),可被 NavigationLink 跳转到学习主页的独立副本。虽然与 Tab 1 的学习主页渲染同一 View,但路由跳转会 push 一个新的 NavigationStack 副本。 -- **文档处理:** 文档将 StudyHomeView 仅归入 Tab 页面,未提及它也可作为 Push 路由目标。 -- **建议:** 在页面跳转关系中标注 StudyHomeView 可被 Route.studyHome 推入导航栈。 - ---- - -## 三、文档记录但当前不可访问的页面 - -无。所有文档记录的页面在代码中均有对应的入口。 - ---- - -## 四、文档记录为"已完成",但实际为"部分完成"或"占位实现"的页面 - -| 文档页面 | 文档状态 | 实际状态 | 差异说明 | -|---------|---------|---------|---------| -| 学习会话页 (2.2) | 已完成 | 暂未确认 | 文档称"已完成"。页面计时功能完成,但**未发现调用 LearningSessionService.start/end 保存学习记录到 API 的代码**,学习时长仅在本地计时。 | -| 复习卡片页 (2.3) | 占位实现 | 占位实现 | 状态一致,但文档说 ReviewViewModel 和 ReviewPlanViewModel 代码已就绪。实际情况:ReviewCardView 完全忽略 ViewModel 的数据,使用不同类型的硬编码结构体 ReviewCardItem。 | -| 主动回忆页 (2.4) | 占位实现 | 占位实现 | 状态一致。 | -| 知识库首页 (3.1) | 部分完成 | 部分完成 | 状态一致。 | -| 知识库详情页 (3.3) | 部分完成 | 部分完成 | 状态一致。 | -| 学习分析页 (4.1) | 部分完成 | 部分完成 | 状态一致。但需补充:**每日学习时长(dailyMins)保存逻辑缺失**(GoalSettingDetailView 中 dailyMins 仅保存在本地 @State)。 | -| 设置页 (5.3) | 部分完成 | 部分完成 | 状态一致。需补充:"存储空间"行 → Route.settings(自引用),"登录设备管理" → Route.settings(自引用),均无实际导航目标。 | -| AI 反馈页 (6.3) | 占位实现 | 占位实现 | 状态一致。 | -| 薄弱知识点页 (6.4) | 占位实现 | 占位实现 | 状态一致。 | -| 资料阅读页 (8.1) | 部分完成 | 部分完成 | 状态一致。 | -| 每日思考页 (6.2) | 占位实现 | 占位实现 | 状态一致。但需补充:`submitted` 状态变量从未被设置为 true,提交按钮始终可见(这可能导致 UI 行为异常)。 | - ---- - -## 五、文档重复记录的页面 - -无发现。文档每个页面只记录一次。 - ---- - -## 六、页面名称不同但实际为同一个页面的情况 - -无发现。文档页面名称与代码类型名称一一对应。 - ---- - -## 七、页面被错误归入业务模块的情况 - -| 页面 | 文档归类 | 建议归类 | 原因 | -|------|---------|---------|------| -| 创建知识库页 | 知识库 Tab | 正确 | 入口来自知识库首页和我的页 | -| 学习会话页 | 学习 Tab | 正确 | 入口来自资料阅读页 | -| 主动回忆页 | 学习 Tab | 正确 | 入口来自学习主页 | - -无严重归类错误。 - ---- - -## 八、Sheet、弹窗、隐藏入口遗漏检查 - -### 已记录的 Sheet/弹窗(9.1-9.6) - -| 文档项目 | 覆盖情况 | -|---------|---------| -| 快速笔记 (9.1) | 已覆盖 | -| 系统预览 (9.2) | 已覆盖 | -| AI 会话列表 Sheet (9.3) | 已覆盖 | -| 封面选择 Sheet (9.4) | 已覆盖 | -| 链接导入弹窗 (9.5) | 已覆盖 | -| 各类确认弹窗 (9.6) | 已覆盖 6 个中的 6 个 | - -### 遗漏的 Sheet/弹窗 - -| 遗漏项 | 所在页面 | 说明 | -|--------|---------|------| -| 文件夹新建/重命名 Alert | FolderManageView | "新建分类" + "重命名"两个带 TextField 的 Alert,文档在 9.6 确认弹窗表格中未列出 | - -建议在 9.6 确认弹窗表格中补充: -- 新建分类(FolderManageView) -- 重命名分类(FolderManageView) - ---- - -## 九、页面入口和跳转关系错误 - -| 错误位置 | 文档描述 | 实际代码 | 严重程度 | -|---------|---------|---------|---------| -| 学习主页"查看完整分析" → ActiveRecallView | 文档:跳转主动回忆页 | 代码:`Route.activeRecall` → ActiveRecallView | **正确**(但主动回忆页为占位实现) | -| 学习主页"自我测试" → QuizTakerView | 文档:跳转测验作答页 | 代码:MainAction.selfTest → Route.quizTake(quizId:) | **正确** | -| 学习主页"自我测试"小卡片 → QuizListView | 文档未明确 | 代码:Route.quizList(knowledgeBaseId: "") | **需补充**,知识库 ID 为空字符串可能导致异常 | -| 设置页"复习提醒" → SettingsView | 文档:跳转设置页(从我的页复习提醒行) | 代码:`Route.settings` → SettingsView | **正确但奇怪**:从设置页的"复习提醒"行再次跳转 SettingsView 形成自引用 | - ---- - -## 十、页面状态遗漏 - -| 遗漏状态 | 所在页面 | 说明 | -|---------|---------|------| -| LearningSessionView 未保存学习记录到 API | 学习会话页 | 文档标记为"已完成",但缺少 API 持久化,用户在分析页看不到本次学习数据 | -| StudyHomeViewModel 永不为 error | 学习主页 | error 状态代码存在但 ViewModel 中所有 API 调用用 try? 吞错,error 状态永不可达 | -| DailyThinkingPage submitted 永不为 true | 每日思考页 | 提交按钮始终可见,可能导致用户重复提交 | -| GoalSettingDetailView dailyMins 不持久化 | 学习目标设置页 | 每日时长选择不通过 API 保存,下次进入恢复默认"30 分钟" | - ---- - -## 十一、总体评估 - -| 评估维度 | 结果 | -|---------|------| -| 文档页面覆盖率 | 43/45 (95.6%) — 遗漏 RecallTestPage + studyHome 路由 | -| 页面状态准确率 | 38/43 (88.4%) — 5 个页面状态描述与代码存在差异 | -| Sheet/弹窗覆盖率 | 8/9 (88.9%) — 遗漏 1 个确认弹窗组合 | -| 跳转关系准确率 | 基本正确,1 处需补充说明 | -| 页面状态描述完整度 | 需补充 4 处状态遗漏 | - -**结论:页面地图文档整体质量较高,覆盖率 95%+。主要问题集中在页面完成状态的部分误判,以及个别状态的遗漏。** diff --git a/docs/system-map/ios/review/04-功能差异清单.md b/docs/system-map/ios/review/04-功能差异清单.md deleted file mode 100644 index d2d5918..0000000 --- a/docs/system-map/ios/review/04-功能差异清单.md +++ /dev/null @@ -1,140 +0,0 @@ -# 04 功能差异清单 - -> 将独立功能基线(review/02-独立功能基线.md)与已有文档(02-iOS页面功能总表.md)逐项比对。 - ---- - -## 一、功能遗漏清单 - -### 遗漏 1:学习主页 selfTestSmallCard → QuizListView (kbId="") - -- **文档覆盖:** 文档记录"进行自我测试"功能(点击主操作卡片或测试卡片) -- **遗漏:** 文档未说明 smallCard 跳转 QuizListView 时 knowledgeBaseId 为空字符串,可能导致不正确的行为 -- **应补到:** 02-iOS页面功能总表.md → 学习 Tab → "进行自我测试" -- **建议补充说明:** "注:辅助卡片自测入口使用空 knowledgeBaseId,可能在 quizList 中显示空列表" - -### 遗漏 2:GoalSettingDetailView 每日学习时长不持久化 - -- **文档覆盖:** 文档 5.4 节记录 GoalSettingDetailView 的"修改每日时长"功能为"已完成" -- **实际:** dailyMins 只保存在本地 @State 变量,未通过 API 持久化 -- **应补到:** 02-iOS页面功能总表.md → 我的 Tab → "修改每日时长" -- **建议状态修正:** 已完成为 → 部分完成(UI 完成,数据不持久化) - -### 遗漏 3:RecallTestPage 功能未记录 - -- **文档覆盖:** 未记录 -- **实际:** RecallTestPage 代码存在(Features/AI/RecallTestPage.swift),含: - - 查看回忆测试题(硬编码) - - 输入回答 - - 提交获取 AI 反馈(→ AIFeedbackPageView) -- **建议补到:** 02-iOS页面功能总表.md → AI 辅助功能 → 新增"回忆测试页"行 -- **建议状态:** 占位实现(无导航入口,题目硬编码) - -### 遗漏 4:AIChatPage 置顶/取消置顶会话 - -- **文档覆盖:** 文档记录"删除会话"和"归档会话" -- **遗漏:** 未记录置顶/取消置顶功能(swipeActions(edge: .leading)) -- **应补到:** 02-iOS页面功能总表.md → AI 辅助功能 → AI 对话页 -- **建议新增行:** "置顶/取消置顶会话 | 左滑→置顶/取消置顶 | 切换会话置顶状态" - -### 遗漏 5:MaterialReaderView 解析触发 + 轮询 - -- **文档覆盖:** 文档在"阅读 Markdown 内容"等行标注已完成 -- **遗漏:** 文档未将"触发 AI 知识提取(解析)"作为独立功能记录,工具栏 brain 按钮触发 60×2s 轮询是一个重要功能 -- **建议补到:** 02-iOS页面功能总表.md → 资料阅读 → 新增行 -- **建议新增行:** "触发知识提取 | 点击工具栏大脑按钮 | 提交解析任务,轮询进度(60×2s) | 部分完成 | 轮询使用硬编码次数和间隔" - -### 遗漏 6:AIHomeView 的全部功能(虽已废弃) - -- **文档覆盖:** 文档记录了 AIHomeView 为占位实现/已废弃,但功能总表中将其合并入一行"AI 首页(已废弃)" -- **遗漏:** 该页面内有具体的子功能(服务器状态检查、快速操作入口、每日思考卡片、推荐问题列表),但功能总表未展开 -- **评估:** 这对于已废弃页面是合理的简化处理,不做强制修正要求 - ---- - -## 二、功能状态误判 - -| 文档功能 | 文档状态 | 实际状态 | 判定依据 | -|---------|---------|---------|---------| -| 学习会话-专注计时学习 | 已完成 | 暂未确认 | 本地计时完成,但未发现调用 LearningSessionService API 保存学习记录 | -| 学习主页-开始今日复习 | 部分完成 | 部分完成 | 入口正常,ReviewCardView 为占位实现。文档正确标记。 | -| 学习主页-查看完整分析 | 已完成 | 已完成 | 入口存在,跳转到 ActiveRecallView(本身为占位实现)。**文档正确标记了跳转目标的状态,但入口本身已完成。** | -| 学习主页-探索公开知识库 | 已完成 | 已完成 | 切换到 library Tab | -| 分析页-查看掌握度趋势图 | 部分完成 | 占位实现 | **应为占位实现**,ZXChartView 使用完全硬编码的 7 天数据 `[("一", 0.62)...]`,ViewModel 的 trend 数据被忽略 | -| 分析页-查看每周学习活跃 | 部分完成 | 占位实现 | **应为占位实现**,ZXWeekBarChart 使用完全硬编码数据,ViewModel 的 heatmap 数据被忽略 | -| 分析页-查看 AI 综合分析 | 已完成 | 已完成 | 动态拼接 API 数据,非 AI 生成 | -| 分析页-查看全部薄弱知识点 | 已完成 | 已完成 | 跳转到 WeakPointsPage(本身为占位实现) | -| 设置页-切换导入完成通知 | 暂未确认 | 部分完成 | 三个通知开关全部绑定到同一个 $reviewReminder 变量,是明确的 **bug** 而非待确认 | -| 设置页-切换系统通知 | 暂未确认 | 部分完成 | 同上 | -| 设置页-切换 iCloud 同步 | 暂未确认 | 暂未确认 | 仅改变本地 @State,无 API 调用 | -| GoalSettingDetailView-修改每日时长 | 已完成 | 部分完成 | dailyMins 不持久化 | -| 每日思考页-提交回答 | 占位实现 | 占位实现 | 状态一致,但 submitted 变量 bug 未记录 | - ---- - -## 三、自动触发功能遗漏 - -文档功能总表主要记录了用户主动触发的功能,以下自动触发功能在文档中有部分遗漏: - -| 自动功能 | 文档覆盖 | 建议 | -|---------|---------|------| -| MaterialReaderView 进入时自动恢复阅读位置 | 已覆盖("恢复阅读位置"行) | — | -| MaterialReaderView 前后台切换暂停/恢复会话 | 已覆盖 | — | -| MaterialReaderView 自动从 COS 下载文件 | 未明确记录 | 建议在"阅读 Markdown 内容"备注中补充 | -| MaterialReaderView COS 过期 URL 重取 | 未明确记录 | 建议补充 | -| 学习主页优先级自动计算 | 已覆盖 | — | -| StudyHomeViewModel 错误状态永不可达 | 未记录 | 建议在备注中说明 ViewModel 的 try? 吞错行为 | - ---- - -## 四、搜索、筛选、排序、分页等辅助功能检查 - -| 功能 | 页面 | 文档覆盖 | 备注 | -|------|------|---------|------| -| 知识库搜索 | LibraryHomeView | 已覆盖(但标为部分完成) | 搜索页搜索逻辑未实现 | -| 知识库分类筛选 | LibraryHomeView | 已覆盖 | 4 个分类 chips | -| 知识库分页加载 | LibraryHomeView | 已覆盖 | 无限滚动 | -| 知识点排序 | LibraryDetailPage | 已覆盖 | 4 种排序 | -| 知识点批量选择 | LibraryDetailPage | 已覆盖 | | -| 会话搜索 | AIChatPage Sheet | 已覆盖 | | -| 通知列表无筛选 | NotificationListView | 未记录筛选 | 无筛选功能,合理 | -| 测验列表无筛选 | QuizListView | 未记录筛选 | 无筛选功能,合理 | - ---- - -## 五、API 调用存在但无页面入口 - -### WaitlistService - -- **API 端点:** /waitlist (POST), /waitlist/stats (GET) -- **代码存在:** APIService.swift 中 WaitlistService 类完整定义 -- **页面入口:** 无任何 View 或 ViewModel 调用 WaitlistService -- **文档记录:** 文档未提及 waitlist 功能 -- **建议:** 在系统地图中标注"Waitlist API 已就绪但 C 端无入口"(或在总览中标注为后端预留能力) - -### UserService.requestAccountDeletion / cancelDeletion - -- **API 端点:** /users/me/deletion-request (POST/DELETE) -- **页面入口:** 无任何页面调用这两个方法 -- **文档记录:** 未提及注销账号功能 -- **建议:** 补充到设置页的未完成功能列表 - -### UserService.listDevices / removeDevice - -- **API 端点:** /users/me/devices (GET/DELETE) -- **页面入口:** 设置页有"登录设备管理"行但无导航目标(→ Route.settings 自引用) -- **建议:** 标注为"API 已就绪,设备管理页面未实现" - ---- - -## 六、总体评估 - -| 评估维度 | 结果 | -|---------|------| -| 功能总表覆盖率 | 约 95%,遗漏约 6 个功能点 | -| 功能状态准确率 | 约 90%,7 个功能的状态需要修正 | -| 自动触发功能覆盖 | 基本完整,2 处建议补充 | -| 辅助功能覆盖 | 完整 | -| API-页面映射完整性 | 3 个 API 有端点无页面入口需标注 | - -**结论:功能总表文档覆盖较全面,主要问题是若干功能的状态标记偏乐观(部分图表数据被标记为"部分完成"但实际为"占位实现"),以及少数自动触发功能和 API-页面不一致未记录。** diff --git a/docs/system-map/ios/review/05-业务能力归类审查.md b/docs/system-map/ios/review/05-业务能力归类审查.md deleted file mode 100644 index 5f1c146..0000000 --- a/docs/system-map/ios/review/05-业务能力归类审查.md +++ /dev/null @@ -1,272 +0,0 @@ -# 05 业务能力归类审查 - -> 审查对象:已有文档 `03-C端业务能力初步清单.md` -> 审查方法:先独立归类,再与文档比对 - ---- - -## 一、独立业务能力归类 - -基于代码扫描独立建立的业务能力清单: - -### 能力 1:账号注册与登录 - -- **代码依据:** Apple ID 登录(LoginPage)、会话恢复(SplashScreen/AuthManager)、Token 刷新(APIClient)、账号禁用/注销状态处理(AccountStatusView) -- **用户目标:** 使用 Apple ID 安全登录,会话自动恢复 -- **关联页面:** P01, P03, P04, P05 -- **独立判定:** 已存在,核心流程完整 - -### 能力 2:新手引导与目标设定 - -- **代码依据:** WelcomePage → OnboardingPage → GoalSetupPage 流程 -- **用户目标:** 首次使用时了解产品价值,设定个性化学习目标 -- **关联页面:** P02, P06, P07, P26, P27 -- **独立判定:** 已存在 - -### 能力 3:个人资料维护 - -- **代码依据:** EditProfilePage(头像/昵称/身份/方向/目标/简介)+ ProfileView(查看) -- **用户目标:** 维护个人档案信息 -- **关联页面:** P23, P24 -- **独立判定:** 已存在 - -### 能力 4:知识库浏览与管理 - -- **代码依据:** LibraryHomeView(列表/筛选/分页)、LibraryDetailPage(详情/排序/删除)、CreateLibraryPage(创建)、DiscoverView(公开库发现/订阅) -- **用户目标:** 创建、浏览、管理个人知识库,发现和订阅公开知识库 -- **关联页面:** P12, P14, P15, P21 -- **独立判定:** 已存在。部分功能未完成(官方库筛选、KB 管理页、搜索逻辑) - -### 能力 5:知识点管理 - -- **代码依据:** LibraryDetailPage 知识点 Tab(列表/排序/删除/批量操作)、AddKnowledgePage(手动/文件/图片添加)、KnowledgeDetailPage(查看)、EditKnowledgePage(编辑) -- **用户目标:** 在知识库中增删改查知识点 -- **关联页面:** P14, P16, P17, P18 -- **独立判定:** 已存在。收藏 API 未支持、重命名/移动到文件夹为 TODO - -### 能力 6:资料上传与处理 - -- **代码依据:** ImportPage(文件/相册/链接导入)、ImportReviewPage(AI 候选审批) -- **用户目标:** 将外部资料导入知识库,审核 AI 提取的知识点 -- **关联页面:** P19, P20 -- **独立判定:** 已存在 - -### 能力 7:文档阅读 - -- **代码依据:** MaterialReaderView(多格式渲染/位置恢复/标记已读)、MaterialDetailView(处理状态/元数据)、QuickLook(系统预览)、ShareLink(外部打开) -- **用户目标:** 阅读学习资料,支持多种格式 -- **关联页面:** P37, P38, S03 -- **独立判定:** 已存在 - -### 能力 8:阅读笔记 - -- **代码依据:** QuickNoteSheet(创建笔记/自动锚点记录) -- **用户目标:** 在阅读过程中记笔记 -- **关联页面:** S02 -- **独立判定:** 已存在。本地存储(UserDefaults),未同步到服务端 - -### 能力 9:阅读行为跟踪 - -- **代码依据:** ReadingRuntimeSessionManager + ReadingEventCollector + ReadingPositionStore -- **用户目标:** 自动记录阅读行为,用于分析 -- **关联页面:** P37(自动执行) -- **独立判定:** 已存在 - -### 能力 10:学习信息收集 - -- **代码依据:** LearningSessionView(计时)、MaterialReaderView(阅读会话) -- **用户目标:** 记录学习时长和阅读行为 -- **关联页面:** P09, P37 -- **独立判定:** 已存在。但 LearningSessionView 未发现 API 保存调用,需确认学习会话数据是否真正被收集 - -### 能力 11:继续学习 - -- **代码依据:** StudyHomeView 优先级算法(ContinueLearningResponse → MainAction.continueSession) -- **用户目标:** 从上次中断处继续学习 -- **关联页面:** P08 -- **独立判定:** 已存在 - -### 能力 12:AI 对话 - -- **代码依据:** AIChatPage(流式对话/思考过程/多会话管理/范围限定) -- **用户目标:** 与 AI 进行学习相关对话 -- **关联页面:** P30, S01 -- **独立判定:** 已存在 - -### 能力 13:题目与测验 - -- **代码依据:** QuizListView(查看/生成)、QuizTakerView(作答/提交)、QuizResultView(结果/解析) -- **用户目标:** 通过测验检验学习效果 -- **关联页面:** P34, P35, P36 -- **独立判定:** 已存在 - -### 能力 14:学习分析 - -- **代码依据:** AnalysisHomeView(统计徽章/趋势图/连续统计/学习记录/推荐/薄弱点) -- **用户目标:** 查看学习数据分析和趋势 -- **关联页面:** P22 -- **独立判定:** 已存在。掌握度趋势图和每周活跃图为占位实现 - -### 能力 15:主动回忆与 AI 反馈 - -- **代码依据:** ActiveRecallView → AIFeedbackPageView 链路 -- **用户目标:** 主动输出知识,获得 AI 评估反馈 -- **关联页面:** P11, P32, P31 -- **独立判定:** 占位实现(全部使用硬编码数据) - -### 能力 16:间隔复习 - -- **代码依据:** ReviewCardView(闪卡翻转/自评) -- **用户目标:** 基于间隔重复的闪卡复习 -- **关联页面:** P10 -- **独立判定:** 占位实现(硬编码数据,未接通 API) - -### 能力 17:学习会话计时 - -- **代码依据:** LearningSessionView(专注计时/暂停/结束/庆祝) -- **用户目标:** 记录专注学习时长 -- **关联页面:** P09 -- **独立判定:** 部分完成(本地计时完成,API 持久化待确认) - -### 能力 18:消息与通知 - -- **代码依据:** NotificationListView(分组展示/已读标记)、SettingsView(通知开关) -- **用户目标:** 接收系统通知,管理通知偏好 -- **关联页面:** P28, P25 -- **独立判定:** 已存在。通知开关存在绑定 bug - -### 能力 19:设置与应用偏好 - -- **代码依据:** SettingsView(外观/语言/复习提醒/通知/存储/iCloud/退出登录) -- **用户目标:** 管理应用全局设置 -- **关联页面:** P25, P26, P27 -- **独立判定:** 已存在。合规页面入口缺失、设备管理未实现 - -### 能力 20:帮助与反馈 - -- **代码依据:** FeedbackFormView(类型选择/内容输入/提交) -- **用户目标:** 提交问题和建议 -- **关联页面:** P29 -- **独立判定:** 已存在 - -### 能力 21:存储空间管理 - -- **代码依据:** ProfileView 存储空间展示 + SettingsView iCloud 同步开关 -- **用户目标:** 了解和控制存储使用 -- **关联页面:** P23, P25 -- **独立判定:** 部分存在(展示完成,iCloud 同步待确认) - ---- - -## 二、与已有文档的能力对比 - -### 完全一致的能力(14 项) - -| 序号 | 独立能力 | 文档能力 | 判定 | -|------|---------|---------|------| -| 1 | 账号注册与登录 | 1. 账号与用户 | 一致 | -| 2 | 新手引导与目标设定 | 2. 新手引导与目标设定 | 一致 | -| 3 | 知识库浏览与管理 | 3. 知识库管理 | 一致 | -| 4 | 知识点管理 | 4. 知识点管理 | 一致 | -| 5 | 资料上传与处理 | 5. 资料上传 | 一致 | -| 6 | 文档阅读 | 6. 文档阅读 | 一致 | -| 7 | 阅读笔记 | 7. 阅读笔记 | 一致 | -| 8 | 阅读行为跟踪 | 8. 阅读行为跟踪 | 一致 | -| 9 | 学习信息收集 | 9. 学习信息收集 | 一致 | -| 10 | 继续学习 | 10. 继续学习 | 一致 | -| 11 | AI 对话 | 11. AI 对话 | 一致 | -| 12 | 题目与测验 | 14. 题目与测验 | 一致 | -| 13 | 帮助与反馈 | 20. 帮助与反馈 | 一致 | -| 14 | 设置与应用偏好 | 19. 设置与合规 | 基本一致 | - -### 需要调整的能力(6 项) - -| 独立能力 | 文档对应 | 问题 | 建议 | -|---------|---------|------|------| -| 个人资料维护 | 合并入"1. 账号与用户" | 文档将个人资料维护合并入账号与用户能力。独立判定认为这是可接受的归类,但边界稍显模糊。 | 可保持现状,或拆分出"个人资料维护"子能力 | -| 主动回忆与 AI 反馈 | 15. 主动回忆 + 12. AI 学习分析 | 文档将"主动回忆"和"AI 学习分析"分为两个独立能力。独立判定认为主动回忆 → AI 反馈是一个连续流程,不应强行拆分。 | 建议将"15. 主动回忆"与"12. AI 学习分析"合并为"主动回忆与 AI 反馈" | -| 间隔复习 | 16. 间隔复习 | 一致 | — | -| 学习分析 | 13. 学习分析 | 一致 | — | -| 学习会话计时 | 无独立条目 | 文档将学习会话计时合并入"9. 学习信息收集"。独立判定认为学习会话计时是一个有独立 UI 的用户行为,宜作为独立能力或子能力。 | 建议在"学习信息收集"能力下明确标注"学习会话计时"子项 | -| 消息与通知 | 18. 消息与通知 | 一致 | — | -| 存储空间管理 | 合并入"17. 会员与额度" | 文档将其归入"会员与额度"。独立判定认为当前代码中无会员/订阅/付费功能,命名为"会员与额度"不准确。 | 建议重命名为"存储与额度"或"存储空间管理" | - -### 文档有但未发现代码支持的能力(1 项) - -| 文档能力 | 判定 | 说明 | -|---------|------|------| -| 17. 会员与额度 | 未发现会员/订阅相关代码 | 代码中无内购、订阅、付费墙、会员等级等实现。存储展示和 iCloud 同步开关存在,但被描述为"会员与额度"不准确。 | - ---- - -## 三、能力归类质量检查 - -### 以技术模块命名检查 - -| 文档能力名 | 是否以技术模块命名 | 判定 | -|-----------|-----------------|------| -| 账号与用户 | 否 | 通过 | -| 知识库管理 | 否 | 通过 | -| 阅读行为跟踪 | 边缘 | 偏技术,建议改为"阅读进度同步"或保持 | -| AI 对话 | 否 | 通过 | -| AI 学习分析 | 否 | 通过 | - -### 闭环拆分过度/不足检查 - -| 文档能力 | 问题 | -|---------|------| -| 主动回忆 + AI 学习分析 | 拆分过度。主动回忆提交 → AI 反馈是一个连续用户体验,不应分为两个能力 | -| 阅读行为跟踪 + 学习信息收集 | 边界模糊。两者都涉及数据收集,调用相同的底层 FFI 和 API | -| 文档阅读 + 阅读笔记 + 阅读行为跟踪 | 三个能力高度耦合在 MaterialReaderView 中,是否过度拆分值得商榷 | - -### 按页面名称命名检查 - -| 文档能力 | 是否直接使用页面名 | 判定 | -|---------|-----------------|------| -| 继续学习 | 否(StudyHomeView 的功能,非页面名) | 通过 | -| 题目与测验 | 否 | 通过 | -| 主动回忆 | 是(与页面同名) | **建议改为"主动回忆与 AI 评估"** | - ---- - -## 四、文档业务能力依赖关系图审查 - -文档中的依赖关系图: - -``` -账号与用户 - └─ 新手引导与目标设定 - └─ 知识库管理 - ├─ 知识点管理 - ├─ 资料上传 → 文档阅读 - │ ├─ 阅读笔记 - │ └─ 阅读行为跟踪 → 学习信息收集 - └─ 题目与测验 - ├─ AI 对话 - ├─ 继续学习 → 主动回忆 / 间隔复习 / 文档阅读 - │ └─ AI 学习分析 - └─ 学习分析 - └─ AI 学习分析 -``` - -审查意见: -1. "AI 对话"应在"知识库管理"下有分支(知识库范围对话)。 -2. "AI 对话"也应在"文档阅读"下有分支(资料范围对话)。 -3. "题目与测验"应在"文档阅读"下有分支(从资料阅读触发解析后进入测验)。 -4. "阅读笔记"也应关联到"知识点管理"(知识点详情页有笔记入口)。 -5. "主动回忆 → AI 学习分析 → 间隔复习"应形成环路(分析后加入待巩固)。 -6. "学习分析"应在"学习信息收集"之后,而非独立于信息收集。 - ---- - -## 五、总体评估 - -| 评估维度 | 结果 | -|---------|------| -| 能力数量合理度 | 20 项基本合理,建议合并"主动回忆"与"AI 学习分析" | -| 能力命名准确度 | 17/20 准确,1 个偏技术(阅读行为跟踪),1 个以页面命名(主动回忆),1 个不准确(会员与额度) | -| 能力边界清晰度 | 阅读相关 3 个能力(文档阅读/阅读笔记/阅读行为跟踪)边界可讨论 | -| 依赖关系准确度 | 基本正确,若干分支缺失 | -| 遗漏能力 | 无重大遗漏,学习会话计时建议作为子能力标注 | - -**结论:业务能力归类文档整体合理,主要问题是"会员与额度"命名不准确(当前无会员功能)、"主动回忆"与"AI 学习分析"建议合并、依赖关系图缺少几个交叉引用。** diff --git a/docs/system-map/ios/review/06-页面能力覆盖矩阵.md b/docs/system-map/ios/review/06-页面能力覆盖矩阵.md deleted file mode 100644 index 22dbe15..0000000 --- a/docs/system-map/ios/review/06-页面能力覆盖矩阵.md +++ /dev/null @@ -1,104 +0,0 @@ -# 06 页面能力覆盖矩阵 - -> 双向覆盖检查:页面→能力 + 能力→页面 - ---- - -## 一、页面→能力覆盖(每个页面至少归属一个业务能力) - -| 页面 | 归属业务能力 | 主要功能 | 文档是否覆盖 | 状态判断是否正确 | -|------|------------|---------|------------|----------------| -| P01 启动页 | 账号注册与登录 | 自动恢复会话 | 是 | 是 | -| P02 欢迎页 | 新手引导与目标设定 | 产品介绍、引导登录 | 是 | 是 | -| P03 登录页 | 账号注册与登录 | Apple ID 登录 | 是 | 是 | -| P04/P05 账号状态页 | 账号注册与登录 | 禁用/注销状态展示 | 是 | 是 | -| P06 引导页 | 新手引导与目标设定 | 4 步产品引导 | 是 | 是 | -| P07 目标设置页 | 新手引导与目标设定 | 设定目标/方法/时间 | 是 | 是 | -| P08 学习主页 | 继续学习、学习分析 | 优先级算法、今日任务、每周摘要 | 是 | 是 | -| P09 学习会话页 | 学习信息收集 | 专注计时 | 是 | 状态偏乐观(API 保存待确认) | -| P10 复习卡片页 | 间隔复习 | 闪卡翻转、自评 | 是 | 是(标记为占位实现) | -| P11 主动回忆页 | 主动回忆与 AI 反馈 | 文字/语音作答 | 是 | 是(标记为占位实现) | -| P12 知识库首页 | 知识库浏览与管理 | 列表/筛选/分页 | 是 | 是 | -| P13 知识库搜索页 | 知识库浏览与管理 | 搜索 | 是 | 是 | -| P14 知识库详情页 | 知识库浏览与管理、知识点管理 | 双 Tab/排序/批量操作/删除 | 是 | 是 | -| P15 创建知识库页 | 知识库浏览与管理 | 创建知识库 | 是 | 是 | -| P16 添加知识点页 | 知识点管理 | 手动/文件/图片添加 | 是 | 是 | -| P17 知识点详情页 | 知识点管理、文档阅读 | 查看/渲染/收藏/笔记 | 是 | 部分(收藏 API 未支持) | -| P18 编辑知识点页 | 知识点管理 | 编辑标题/内容 | 是 | 是 | -| P19 导入资料页 | 资料上传与处理 | 文件/相册/链接导入 | 是 | 是 | -| P20 导入候选审核页 | 资料上传与处理 | 候选审批 | 是 | 是 | -| P21 发现公开库页 | 知识库浏览与管理 | 浏览/订阅公开库 | 是 | 是 | -| P22 学习分析页 | 学习分析 | 统计/趋势图/记录/推荐/薄弱点 | 是 | 部分(趋势图为占位) | -| P23 个人中心页 | 个人资料维护、设置与应用偏好 | 资料/菜单/资产/存储 | 是 | 是 | -| P24 编辑资料页 | 个人资料维护 | 头像/昵称/身份/目标 | 是 | 是 | -| P25 设置页 | 设置与应用偏好、消息与通知 | 外观/复习/通知/存储/退出 | 是 | 部分(通知开关 bug,合规入口缺失) | -| P26 学习目标设置页 | 新手引导与目标设定 | 修改目标/时长 | 是 | 否(dailyMins 不持久化,文档标已完成) | -| P27 方法偏好页 | 新手引导与目标设定 | 多选方法 | 是 | 是 | -| P28 通知列表页 | 消息与通知 | 查看/标记已读/全部已读 | 是 | 是 | -| P29 帮助与反馈页 | 帮助与反馈 | 选择类型/输入/提交 | 是 | 是 | -| P30 AI 对话页 | AI 对话 | 流式对话/会话管理 | 是 | 是 | -| P31 每日思考页 | 主动回忆与 AI 反馈 | 查看题目/提交回答 | 是 | 是(标记为占位) | -| P32 AI 反馈页 | 主动回忆与 AI 反馈 | 分析评分/建议/导航 | 是 | 是(标记为占位) | -| P33 薄弱知识点页 | 学习分析 | 查看薄弱点列表 | 是 | 是(标记为占位) | -| (废) AIHomeView | AI 对话、主动回忆、间隔复习、学习分析 | 快速操作/思考题/推荐 | 是(标记为废弃) | 是 | -| P34 测验列表页 | 题目与测验 | 查看/生成测验 | 是 | 是 | -| P35 测验作答页 | 题目与测验 | 选择/判断/填空作答 | 是 | 是 | -| P36 测验结果页 | 题目与测验 | 得分/逐题详情/重测 | 是 | 是 | -| P37 资料阅读页 | 文档阅读、阅读笔记、阅读行为跟踪、学习信息收集、AI 对话 | 多格式渲染/位置恢复/标记已读/AI/笔记 | 是 | 是 | -| P38 资料详情页 | 文档阅读 | AI 处理状态/元数据/进度 | 是 | 是 | -| S01 会话列表 Sheet | AI 对话 | 会话搜索/切换/删除/归档/置顶 | 是 | 遗漏置顶功能 | -| S02 快速笔记 Sheet | 阅读笔记 | 创建笔记/锚点记录 | 是 | 是 | -| S03 系统预览 | 文档阅读 | PDF/Office 预览 | 是 | 是 | -| S04 封面选择 Sheet | 知识库浏览与管理 | 相册选择封面 | 是 | 是 | -| S05 文件夹管理 Sheet | 知识库浏览与管理 | 文件夹 CRUD | 是 | 是 | -| (无入口) RecallTestPage | 主动回忆与 AI 反馈 | 回忆测试 | 否 | 遗漏 | -| (无入口) WaitlistService | 未归类 | waitlist 加入/统计 | 否 | 遗漏(API 存在但无 C 端入口) | - ---- - -## 二、能力→页面覆盖(每个能力是否有实际页面入口) - -| 业务能力 | 有页面入口? | 关联页面 | 遗漏关联页面 | 错误关联 | -|---------|------------|---------|------------|---------| -| 账号注册与登录 | 是 | P01,P03,P04,P05 | 无 | 无 | -| 新手引导与目标设定 | 是 | P02,P06,P07,P26,P27 | 无 | 无 | -| 知识库浏览与管理 | 是 | P12,P14,P15,P21,S04,S05 | 遗漏 P13(搜索页) | 无 | -| 知识点管理 | 是 | P14,P16,P17,P18 | 无 | 无 | -| 资料上传与处理 | 是 | P19,P20 | 无 | 无 | -| 文档阅读 | 是 | P17,P37,P38,S03 | 无 | 无 | -| 阅读笔记 | 是 | S02,P37 | 遗漏 P17(知识点详情页笔记入口) | 无 | -| 阅读行为跟踪 | 是(自动) | P37 | 无 | 无 | -| 学习信息收集 | 是(自动) | P09,P37 | 无 | 无 | -| 继续学习 | 是 | P08,P22 | 无 | 无 | -| AI 对话 | 是 | P30,S01,P37 | 遗漏 P14(知识库范围 AI 对话) | 无 | -| 题目与测验 | 是 | P34,P35,P36 | 无 | 无 | -| 学习分析 | 是 | P22,P33 | 无 | 无 | -| 主动回忆与 AI 反馈 | 是(占位) | P11,P31,P32 | 遗漏 RecallTestPage | 无 | -| 间隔复习 | 是(占位) | P10 | 无 | 无 | -| 学习会话计时 | 是 | P09 | 无 | 无 | -| 消息与通知 | 是 | P28,P25 | 无 | 无 | -| 设置与应用偏好 | 是 | P25,P26,P27 | 无 | 无 | -| 帮助与反馈 | 是 | P29 | 无 | 无 | -| 存储空间管理 | 是 | P23,P25 | 无 | 无 | -| 会员与额度(文档命名) | — | — | 无实际会员/订阅代码 | 文档将存储+资产归入此项不准确 | - ---- - -## 三、无法归类的页面或功能 - -| 对象 | 问题 | 建议 | -|------|------|------| -| WaitlistService | API 完整但无 C 端入口 | 在系统地图中标注为"后端已就绪,C 端未接入" | -| RecallTestPage | 代码存在但无导航入口 | 归类到"主动回忆与 AI 反馈",标注为占位实现 | -| AIHomeView | 已废弃,无法归类到实际能力 | 保持现状,作为废弃页面参考 | -| UserService.requestAccountDeletion | API 存在但无页面入口 | 在"账号与用户"能力中标注"账号注销 API 已就绪,C 端入口未实现" | - ---- - -## 四、双向覆盖检查结论 - -- **页面→能力:** 43/43 个可访问页面均已归属至少一个业务能力。RecallTestPage(无入口)未归属。 -- **能力→页面:** 19/20 个能力有实际页面入口。"会员与额度"能力无实际会员/订阅代码支持。 -- **遗漏关联页面:** 5 处(知识库搜索页、知识点详情页笔记入口、知识库范围 AI 对话、RecallTestPage、WaitlistService) -- **错误关联:** 无 -- **状态判断错误:** 2 处(学习会话 API 保存、GoalSetting dailyMins 持久化) diff --git a/docs/system-map/ios/review/07-待修正文档清单.md b/docs/system-map/ios/review/07-待修正文档清单.md deleted file mode 100644 index 2bc0da0..0000000 --- a/docs/system-map/ios/review/07-待修正文档清单.md +++ /dev/null @@ -1,150 +0,0 @@ -# 07 待修正文档清单 - -> 列出所有需要在已有系统地图文档中修正的条目,按优先级排序。 - ---- - -## P0 — 影响整体地图正确性(必须修正) - -### P0-1:每日学习时长不持久化 → 02-iOS页面功能总表 - -- **涉及文档:** 02-iOS页面功能总表.md -- **位置:** 我的 Tab → "修改每日时长" -- **当前状态:** 已完成 -- **修正为:** 部分完成 -- **原因:** GoalSettingDetailView 中 dailyMins 只保存在本地 @State 变量,未通过 API 持久化。用户下次进入设置页时恢复默认值"30 分钟"。 -- **备注栏补充:** "UI 选择完成,但选择结果不通过 API 保存" - -### P0-2:分析页图表数据为占位实现 → 02-iOS页面功能总表 - -- **涉及文档:** 02-iOS页面功能总表.md -- **位置:** 分析 Tab → "查看掌握度趋势图" 和 "查看每周学习活跃" -- **当前状态:** 部分完成 -- **修正为:** 占位实现 -- **原因:** ZXChartView 使用硬编码 `[("一", 0.62), ...]` 数据,ZXWeekBarChart 使用硬编码 `[("一", 0.3), ...]` 数据。两组数据均未从 ViewModel 的 API 数据读取,属于完全占位实现。 -- **备注栏补充:** "图表组件使用编译时硬编码的 7 天数据,未接入 ViewModel API 返回的真实数据" - -### P0-3:设置页通知开关绑定 bug → 02-iOS页面功能总表 - -- **涉及文档:** 02-iOS页面功能总表.md -- **位置:** 我的 Tab → "切换导入完成通知"、"切换系统通知" -- **当前状态:** 暂未确认 -- **修正为:** 部分完成(存在 bug) -- **原因:** SettingsView 中"复习提醒"、"导入完成通知"、"系统通知"三个 Toggle 全部绑定到同一个 `$reviewReminder` 变量(见 SettingsView.swift:61-65)。这不是"待确认",而是明确的代码 bug。 -- **修正状态描述:** "三个通知开关绑定到同一状态变量,需修复为各自独立绑定" - -### P0-4:文档声称的页面总数与实际不符 - -- **涉及文档:** 00-iOS-C端总览.md -- **位置:** 第四节"页面总览" -- **当前描述:** "共盘点出 43 个页面/弹窗"(已完成 19 + 部分完成 13 + 占位实现 7 + 暂未确认 1 + 未完成 3) -- **实际:** 独立基线盘点出 38 个独立 Push 页面 + 10 个 Sheet/弹窗 + 8 个确认弹窗 + 1 个废弃页面 = 总计约 47 个可盘点对象(含弹窗) -- **说明:** 文档的"43"如果指页面+弹窗,则遗漏了 RecallTestPage(代码存在无入口)、部分 Alert 弹窗。建议明确统计口径并更新数字。 - ---- - -## P1 — 遗漏主要页面或业务能力 - -### P1-1:遗漏 RecallTestPage - -- **涉及文档:** 01-iOS页面地图.md, 02-iOS页面功能总表.md -- **遗漏对象:** RecallTestPage (Features/AI/RecallTestPage.swift) -- **应补到位置:** 01 第六章 AI 辅助功能,新增 6.6 节"回忆测试页" -- **建议状态:** 占位实现(无导航入口 + 题目硬编码) -- **功能表补充:** 查看回忆测试题、输入回答、提交获取 AI 反馈(→ AIFeedbackPageView) - -### P1-2:遗漏 AIChatPage 会话置顶功能 - -- **涉及文档:** 02-iOS页面功能总表.md -- **遗漏对象:** AI 对话页 → 会话列表 Sheet → 左滑置顶/取消置顶 -- **应补到位置:** AI 辅助功能 → AI 对话页 -- **新增行:** "置顶/取消置顶会话 | 左滑→置顶/取消置顶 | 切换会话置顶状态 | 已完成" - -### P1-3:遗漏 WaitlistService API 已就绪但 C 端无入口 - -- **涉及文档:** 00-iOS-C端总览.md 或 03-C端业务能力初步清单.md -- **遗漏对象:** WaitlistService(/waitlist POST, /waitlist/stats GET) -- **说明:** API 服务代码完整但无任何 View/ViewModel 调用。应标注为"后端已就绪,C 端入口未实现"。 - -### P1-4:遗漏账号注销 API 已就绪但无入口 - -- **涉及文档:** 03-C端业务能力初步清单.md → 账号与用户 -- **遗漏对象:** UserService.requestAccountDeletion() 和 cancelDeletion() -- **说明:** API 端点 /users/me/deletion-request POST/DELETE 已就绪,但设置页及其他页面均未提供注销账号入口。 -- **建议:** 在"账号与用户"能力中补充"账号注销 API 已就绪,C 端入口未实现" - ---- - -## P2 — 命名、描述、辅助状态问题 - -### P2-1:业务能力"会员与额度"命名不准确 → 03 - -- **涉及文档:** 03-C端业务能力初步清单.md -- **当前命名:** 17. 会员与额度 -- **修正为:** 存储空间管理(或"存储与额度") -- **原因:** 代码中无任何会员/订阅/付费墙/内购实现。该能力实际包含的功能仅为存储空间展示和学习资产统计。 - -### P2-2:建议合并"主动回忆"与"AI 学习分析" → 03 - -- **涉及文档:** 03-C端业务能力初步清单.md -- **当前:** 15. 主动回忆 和 12. AI 学习分析 为两个独立能力 -- **建议:** 合并为"主动回忆与 AI 反馈" -- **原因:** 主动回忆 → AI 反馈是一个连续用户体验流程,不应拆分为两个独立业务能力 - -### P2-3:StudyHomeViewModel 错误状态永不可达 → 01 - -- **涉及文档:** 01-iOS页面地图.md -- **位置:** 2.1 学习主页 → 页面主要状态 -- **当前描述:** "加载中 / 错误 / 已加载·有任务 / 已加载·空状态" -- **补充说明:** "注:View 支持 error 状态展示,但 ViewModel 中所有 API 调用使用 try? 吞错,error 状态在实际运行中永不可达" - -### P2-4:DailyThinkingPage submitted 变量 bug → 01 - -- **涉及文档:** 01-iOS页面地图.md -- **位置:** 6.2 每日思考页 -- **补充说明:** "注:submitted 状态变量在代码中从未被设置为 true,提交按钮始终可见,可能导致用户重复提交" - -### P2-5:设置页自引用导航 → 01 - -- **涉及文档:** 01-iOS页面地图.md -- **位置:** 5.3 设置页 -- **当前描述:** "存储空间"行和"登录设备管理"行 → Route.settings -- **补充说明:** "存储空间"和"登录设备管理"导航目标均为 Route.settings(指向设置页自身),形成无效的自引用 - -### P2-6:知识库详情页操作菜单"知识库管理"按钮 → 01 - -- **涉及文档:** 01-iOS页面地图.md -- **位置:** 3.3 知识库详情页 -- **当前描述:** 用户可执行操作中未包含此按钮 -- **补充说明:** 操作菜单中存在"知识库管理"按钮,但 action 为空(// TODO),属未实现功能 - -### P2-7:业务能力依赖关系图缺少交叉引用 → 03 - -- **涉及文档:** 03-C端业务能力初步清单.md -- **位置:** "业务能力依赖关系(初步)" -- **补充以下分支:** - - AI 对话 → 知识库管理(知识库范围对话) - - AI 对话 → 文档阅读(资料范围对话) - - 文档阅读 → 题目与测验(解析完成后进入测验) - - 阅读笔记 → 知识点管理(知识点详情页笔记入口) - - 主动回忆 → 间隔复习(AI 分析后加入待巩固 → 安排间隔复习) - - 学习分析 → 学习信息收集(分析依赖信息收集数据) - -### P2-8:文件夹新建/重命名 Alert 未列入确认弹窗表 → 01 - -- **涉及文档:** 01-iOS页面地图.md 9.6 节"各类确认弹窗"表格 -- **补充行:** - - 新建分类 | FolderManageView | 点击"新建" - - 重命名分类 | FolderManageView | 滑动→重命名 - ---- - -## 修正优先级汇总 - -| 优先级 | 数量 | 影响范围 | -|--------|------|---------| -| P0 | 4 | 状态误判会导致整体地图失真 | -| P1 | 4 | 遗漏主要页面/功能/API 入口 | -| P2 | 8 | 命名、描述、辅助状态完善 | - -**总计待修正项:16 项** diff --git a/docs/system-map/review/07-内容与对话能力审查报告.md b/docs/system-map/review/07-内容与对话能力审查报告.md deleted file mode 100644 index 27c6d24..0000000 --- a/docs/system-map/review/07-内容与对话能力审查报告.md +++ /dev/null @@ -1,475 +0,0 @@ -# 内容与对话能力审查报告 - -> 审查日期:2026-06-19 -> 审查范围:4 份跨项目能力文档 -> 审查方法:独立扫描所有相关仓库代码 → 独立还原三条业务链路 → 与文档逐项比对 - ---- - -## 1. 审查范围 - -### 1.1 已检查的仓库与模块 - -| 仓库 | 已检查的模块/文件 | 检查深度 | -|------|-----------------|---------| -| **iOS (AIStudyApp)** | 全部 64 个 .swift 文件(Features/AI, Features/Library, Features/MaterialReader, Features/Quiz, Features/Profile, Features/Study, Core/) | 逐文件阅读 | -| **主 API (api-server)** | Prisma schema (2221 行完整模型)、rag-chat.service.ts、document-import.service.ts、knowledge-base.service.ts、knowledge-source.service.ts、knowledge-items.service.ts、files.service.ts、import-candidate 模块 | 逐文件阅读 | -| **Python RAG Worker** | 全部 10 个 .py 文件(main.py, config.py, api_client.py, parser.py, chunker.py, embedder.py, indexer.py, candidate_generator.py, reranker.py, pipelines/import_pipeline.py) | 逐文件阅读 | -| **Admin (admin-projects)** | App.tsx 路由、全部 ~40 个页面组件、全部 service 模块 | Agent 全面探索 | -| **Prisma Schema** | 全部 120+ 数据模型定义 | 逐模型检查 | -| **COS** | 通过 files.service.ts 和 Python RAG Worker 的 download_file 间接检查 | 间接验证 | -| **Qdrant** | 通过 indexer.py 的 upsert_points / search / mark_deleted 间接检查 | 间接验证 | -| **Rust Document Runtime** | 检查文档声称的参与环节 | 确认不参与上传/处理/AI对话链路 | -| **Heavy Runtime** | 检查文档声称的参与环节 | 确认不参与基础对话链路 | - -### 1.2 已检查的文档 - -- `03-资料上传与处理.md` -- `04-知识库与知识点管理.md` -- `05-AI对话与知识库问答.md` -- `06-内容与对话项目矩阵.md` - ---- - -## 2. 审查方法 - -### 2.1 独立还原方法 - -1. **链路一(资料上传与处理):** 从 iOS 文件选择入口 → API 预签名 URL → COS PUT → 确认上传 → KnowledgeSource 创建 → DocumentImport 入队 → Python RAG Worker 轮询/认领/下载/解析/分块/嵌入/索引/候选生成 → 候选审核 → 各状态位更新,逐环节检查代码调用链。 -2. **链路二(知识库与知识点管理):** 从 Prisma 数据模型出发,检查 KnowledgeBase/KnowledgeItem/KnowledgeSource/KnowledgeFolder/DocumentImport/ImportCandidate 之间的关系、CRUD 实现、权限校验、级联删除行为。 -3. **链路三(AI 对话与知识库问答):** 从 rag-chat.service.ts 出发,检查会话 open-or-create、5 种 scope 的上下文加载、AiGateway 路由、SSE 流式传输、Citation 生成,逐行核对代码与文档描述。 - -### 2.2 双向覆盖检查方法 - -- **文档→代码:** 对文档中每一项关于"已完成"的声明,在代码中寻找对应的实现证据。 -- **代码→文档:** 从代码中扫描出的所有 API 端点、服务方法和 Admin 页面,反向检查是否在文档中有对应记录。 - -### 2.3 跨文档一致性检查方法 - -- 逐份文档提取关于同一项目职责、同一业务对象、同一状态的描述,进行横向比对。 - ---- - -## 3. 独立还原的真实业务链路 - -### 3.1 链路一:资料上传与处理 - -**基于代码的独立还原:** - -``` -1. iOS 文件选择 → ImportPage / AddKnowledgePage -2. iOS → POST /files/upload-url (filename, mimeType, sizeBytes) -3. API 内容安全检查文件名 → COS 生成预签名 PUT URL (1h 有效) -4. iOS 直接 PUT 文件到 COS -5. iOS → POST /files/complete (objectKey) → API headObject 验证 → 创建 UploadedFile 记录 -6. iOS → POST /knowledge-bases/:id/sources (fileId, type, title, ...) -7. KnowledgeSourceService.addSource(): - - 创建 KnowledgeSource 记录 (parseStatus/indexStatus/learningStatus = "pending") - - 自动创建 DocumentImport 记录 (status = "QUEUED") - - Redis 状态初始化 (key: job:document-import:{id}:status/progress/message) - - 入队到 'document-import' 队列 -8. Python RAG Worker (每 5s 轮询): - - GET /internal/rag/jobs/next → 获取 QUEUED 任务 - - POST /internal/rag/jobs/{id}/claim → 认领 - - 启动心跳 (每 30s POST /internal/rag/jobs/{id}/heartbeat) - - run_import(): - a. DOWNLOADING: 从 COS 下载文件 - b. PARSING: parse_document(file_path, mime_type) - - PDF → PyMuPDF 文本提取 / OCR 回退 (SiliconFlow Qwen3-VL) - - DOCX → python-docx - - TXT/MD → 直接读取 - - CSV/XLSX → pandas → Markdown table - - Images → SiliconFlow Qwen3-VL OCR - c. CLEANING: (当前为占位——无实际清洗逻辑) - d. CHUNKING: 递归字符分割 (512 tokens, 64 overlap, 中文句子边界保护) - e. EMBEDDING: SiliconFlow BAAI/bge-m3 (1024维, 批次50, 重试2次) - f. INDEXING: Qdrant upsert_points + API save_chunks (KnowledgeChunk 记录) - g. GENERATING_CANDIDATES: DeepSeek deepseek-chat (temperature 0.3) 非致命步骤 - h. COMPLETED: update_job_status("COMPLETED", {progress: 100}) -9. 用户审核候选 → ImportCandidateService.accept/reject/batch-accept -10. 接受候选 → API 创建 KnowledgeItem (itemType: ai_generated) -``` - -**关键确认:** -- parseStatus/indexStatus/learningStatus 三者独立追踪,由 Python RAG Worker 在管线不同阶段更新 -- 候选生成是非致命步骤——即使失败,解析和索引仍可成功 -- 原文件在 RAG 完成前即可阅读(MaterialReaderView 直接渲染文件,不等待 RAG) -- Admin 导入监控页 (ImportMonitor.tsx) 支持查看步骤日志和重试 - -### 3.2 链路二:知识库与知识点管理 - -**基于代码的独立还原:** - -``` -数据对象关系 (Prisma Schema): -KnowledgeBase - ├── KnowledgeSource[] (1:N, 含 parseStatus/indexStatus/learningStatus) - ├── KnowledgeItem[] (1:N, 含 parentId 树状结构) - ├── KnowledgeFolder[] (1:N, 含 parentId 树状结构) - ├── KnowledgeChunk[] (1:N, 含 externalVectorId → Qdrant) - ├── ImportCandidate[] (1:N, 含 status: PENDING/ACCEPTED/REJECTED) - ├── KnowledgeBaseSubscription[] (1:N, userId+KBId 唯一约束) - └── Quiz[] (1:N) - -KnowledgeSource - ├── file: UploadedFile (1:1) - ├── items: KnowledgeItem[] (1:N, 通过 sourceId) - ├── chunks: KnowledgeChunk[] (1:N) - └── candidates: ImportCandidate[] (1:N) - -核心权限: -- KB 创建: 每用户最多 20 个 (MAX_KNOWLEDGE_BASE_COUNT) -- KB 所有权: userId 匹配校验,非所有者对私有 KB 返回 404 -- KB 删除: 仅所有者,软删除 (deletedAt) -- 知识点删除: 所有者校验,itemCount 自动维护 -- 来源资料删除: KnowledgeSourceService.remove() 无所有权检查 (代码缺陷) -- 文件夹更新/删除: updateFolder/deleteFolder 无显式所有权检查 -- 订阅: 不可订阅自己的 KB,KB 必须为公开且未删除 - -当前未实现的功能: -- "官方"知识库: ownerType 字段存在,但无实际官方 KB 创建逻辑 -- 搜索: 仅 discover 端点支持 LIKE 查询 (title/description),无全文搜索 -- 收藏: Favorite 表存在但有 iOS 端 TODO 标记 -- 重命名/移动到文件夹: iOS contextMenu action 为空 -- 知识库管理页: iOS 操作菜单按钮 action 为空 -``` - -### 3.3 链路三:AI 对话与知识库问答 - -**基于代码的独立还原(rag-chat.service.ts 逐行分析):** - -``` -五种 Scope 类型及上下文加载: - -1. global: - - 上下文: 空 (直接返回 isEmpty: true) - - 代码: loadContextByScope → case 'global' → { text: '', citations: [], isEmpty: true } - - iOS 入口: 从 AI 反馈页"深入提问"→ AIChatPage (context: nil) - -2. knowledge_base: - - 上下文: prisma.knowledgeItem.findMany({ knowledgeBaseId: kbId, deletedAt: null }) - - 查询: PostgreSQL KnowledgeItem 表 (非 Qdrant!) - - 限制: 30 条, 每条约 500 字符, 总计最多 4000 字符 - - iOS 入口: 知识库详情页底部 AI 对话按钮 - -3. material: - - 上下文: prisma.knowledgeItem.findMany({ knowledgeBaseId: kbId, sourceRef: scopeId }) - - 查询: PostgreSQL KnowledgeItem 表, 按 sourceRef 过滤 - - iOS 入口: 资料阅读页工具栏 AI 按钮 - -4. knowledge_item: - - 上下文: prisma.knowledgeItem.findMany({ id: scopeId }) - - 查询: PostgreSQL KnowledgeItem 表, 单条知识点 - - iOS 入口: 知识点详情页工具栏菜单 (代码中 scopeType=knowledge_item 存在但 iOS 入口待确认) - -5. folder: - - 上下文: prisma.knowledgeItem.findMany({ knowledgeBaseId: kbId, parentId: scopeId }) - - 查询: PostgreSQL KnowledgeItem 表, 按父文件夹过滤 - -会话机制: -- open-or-create: 相同 scopeType + scopeId + userId 复用未删除未归档会话 -- forceCreate: 跳过查找,强制创建新会话 -- ScopeSnapshot: 每条消息保存 { scopeType, scopeId, parentKnowledgeBaseId } -- Citation: 从 context.citations 前 5 个提取 (chunkId = item.id, sourceTitle, excerptText) -- 自动标题: 首次消息截取前 40 字符 - -模型调用: -- AiGateway.generate() / generateStream() -- tier: 'primary' → DeepSeek deepseek-v4-pro -- promptKey: 'rag-chat', promptVersion: 'v1' -- 流式: SSE 逐块转发 (type: 'content' | 'thinking' | 'error' | 'done') -- 无 Heavy Runtime 参与 - -内容安全: -- 输入检查: this.safety?.check(content, { contentType: 'rag_input' }) -- AI 输出: 不检查 (仅输入被检查) - -Qdrant 参与: 无。知识库范围对话的上下文完全来自 PostgreSQL KnowledgeItem 表。 -``` - ---- - -## 4. 总体结论 - -**审查结论:有条件通过** - -### 不满足"通过"的关键原因 - -| 门禁条件 | 满足? | 说明 | -|---------|--------|------| -| P0 问题为零 | **否** | 1 项 P0(见第 5 节) | -| 没有将未实现功能标记为已完成 | 是 | 文档对未完成项标记基本准确 | -| 没有错误描述项目职责 | 是 | 项目角色分配正确 | -| 没有错误描述数据流向 | 是 | 数据流描述与代码一致 | -| 没有把 RAG 作为原文件阅读前置条件 | 是 | 文档正确指出 RAG 不阻塞阅读 | -| 没有把 Heavy Runtime 默认写入基础对话链 | 是 | 文档正确指出 Heavy Runtime 不参与 | -| 上传/解析/索引/学习提取/候选审核状态已明确区分 | 是 | 五种状态分别描述 | -| 知识库/来源资料/知识点/文件夹/订阅关系清晰 | 是 | 关系描述基本准确 | -| 四类对话 Scope 的实际状态清晰 | 是 | 五种 Scope 的上下文来源均正确描述 | -| 所有暂未确认项均已明确标记 | 是 | 7 项暂未确认均已列出 | - ---- - -## 5. P0 严重问题 - -### P0-1:文档 05 声称知识库对话"不通过 Qdrant 向量搜索"——实际确认正确,但资料范围的 Qdrant 语义检索被标注为"暂未确认",而代码中明确未启用 - -- **涉及文档:** 05-AI对话与知识库问答.md 第 3.3 节 -- **文档当前描述:** "如果 Python RAG Worker 已完成向量索引,可通过 Qdrant 进行语义检索补充上下文(具体是否启用 Qdrant 取决于 API 实现,当前代码中资料范围上下文的加载方式与知识库范围类似,以 KnowledgeItem 为主)" -- **实际情况:** rag-chat.service.ts 的 `loadContextByScope` 方法中,`case 'material'` 仅查询 `prisma.knowledgeItem.findMany({ sourceRef: scopeId })`,**完全不涉及 Qdrant**。Qdrant 的 `search()` 函数虽然在 indexer.py 中已实现,但在 rag-chat.service.ts 中零引用。 -- **影响:** 文档暗示资料范围对话"可能"使用 Qdrant 语义检索,给读者造成该功能存在但不稳定的印象。实际上该功能**未被实现**——资料范围对话从不调用 Qdrant。 -- **建议修正:** 将第 3.3 节中关于 Qdrant 的描述改为明确结论:"资料范围对话的上下文**完全来自 PostgreSQL KnowledgeItem 表**,不涉及 Qdrant 向量搜索。Qdrant 语义检索已实现但**未集成到对话链路**。" -- **修正优先级:** P0 — 涉及核心数据流向描述 - ---- - -## 6. P1 主要问题 - -### P1-1:文档 03 声称"API 验证文件类型(8 种)和大小(最大 20MB)" —— files.service.ts 中未发现验证 - -- **涉及文档:** 03-资料上传与处理.md 第 5 节"主 API" -- **文档描述:** "验证文件类型(PDF/TXT/Markdown/CSV/XLSX/DOCX/PNG/JPEG/WebP)和大小(最大 20MB)" -- **实际情况:** files.service.ts 的 `requestUploadUrl()` 方法仅执行内容安全检查(文件名敏感词),未进行文件类型白名单校验或大小检查。类型/大小校验可能在 Controller 层的 DTO 或 Guard 中进行。 -- **建议修正:** 若校验确实存在(需进一步确认 DTO/Guard),补充具体位置;若不存在,修正为"暂未确认"或标注实际校验范围。 -- **修正优先级:** P1 - -### P1-2:文档 03 声称"清理"步骤在管线中执行——实际为空操作 - -- **涉及文档:** 03-资料上传与处理.md 第 3 节用户主流程 -- **文档描述:** 流程中包含"清洗"步骤 -- **实际情况:** import_pipeline.py 第 63 行 `await update_job_status(job_id, "CLEANING", ...)` 仅设置状态和进度百分比,**无任何实际文本清洗逻辑**。注释为 `# 3. 清洗` 但之后立即进入分块阶段。 -- **建议修正:** 在管线描述中标注"清洗步骤当前为占位(仅更新状态,无实际逻辑)"。 -- **修正优先级:** P1 - -### P1-3:文档 04 声称"文件夹创建时间步创建 folder 类型的 KnowledgeItem"——但更新文件夹名称时 KnowledgeItem 标题不更新 - -- **涉及文档:** 04-知识库与知识点管理.md 第 4.7 节 -- **文档描述:** 文件夹操作包括重命名 -- **实际情况:** `createFolder()` 同步创建 folder 类型的 KnowledgeItem(标题 = 文件夹名称),且 `deleteFolder()` 也同步删除 KnowledgeItem。但 `updateFolder()` 仅更新 KnowledgeFolder 记录,**不同步更新对应的 KnowledgeItem 标题**,导致重命名后文件夹和对应 KnowledgeItem 的标题不一致。 -- **建议修正:** 在文档中标注"重命名文件夹时,对应的 folder 类型 KnowledgeItem 标题不会同步更新"。 -- **修正优先级:** P1 - -### P1-4:文档 04 缺失对文件夹创建数量限制的描述 - -- **涉及文档:** 04-知识库与知识点管理.md -- **遗漏:** 代码中未发现文件夹创建数量的上限限制,也未发现每 KB 的文件夹数量限制。 -- **建议:** 标注为"文件夹创建数量无明确限制"。 -- **修正优先级:** P1 - ---- - -## 7. P2 一般问题 - -### P2-1:文档 03 中"RAG 解析失败"一节描述 PDF OCR 为"回退"——与实际代码顺序相反 - -- **文档描述:** "PDF 通过 PyMuPDF + OCR 回退" -- **实际代码 (parser.py):** PDF 先检查 `pdf_needs_ocr()` (<50 字符/页),若需要 OCR 则先用 PyMuPDF 提取文本,若结果 <100 字符才用 SiliconFlow OCR。顺序是:PyMuPDF 文本提取 → 不够则 OCR,而非"OCR 回退"。 -- **建议修正:** 改为"PDF 优先文本提取,不足时 OCR 补充"。 -- **修正优先级:** P2 - -### P2-2:文档 03 声称 Admin "RAG 调试"为"已完成"——实际为基础实现 - -- **涉及文档:** 03-资料上传与处理.md 第 11 节完成状态表 -- **文档状态:** "Admin RAG 调试 | 已完成 | 向量搜索测试" -- **实际 Admin 代码:** KnowledgeOps.tsx 的 rag-debug Tab 仅有一个搜索输入框和按钮,结果通过 `message.info()` toast 展示,无结构化表格。页面注释:"完整 RAG 管道在 M3 阶段完善"。 -- **建议修正:** 状态从"已完成"改为"部分完成",备注"基础 UI 可用,结果展示为 toast 而非结构化表格"。 -- **修正优先级:** P2 - -### P2-3:文档 05 中 Qdrant 和 Python RAG Worker 的角色描述在两个文档中略有差异 - -- **文档 03 描述:** Python RAG Worker 是"主链路参与者" -- **文档 05 描述:** Python RAG Worker 是"上游支撑(间接参与)" -- **分析:** 从单个文档看均正确——文档 03 中 Worker 确为主链路(上传处理必须经过它),文档 05 中 Worker 为支撑(对话不依赖它)。但读者跨文档阅读时可能困惑。 -- **建议:** 在文档 06 项目矩阵中已正确区分两场景的角色。无需修改正文,但建议在 05 文档中增加一句"Worker 在对话链路中的角色与在上传链路中不同"。 -- **修正优先级:** P2 - -### P2-4:文档 04 中"API 知识库数量限制"的实际值是硬编码的 - -- **文档描述:** "每用户最多 20 个" -- **代码验证:** `MAX_KNOWLEDGE_BASE_COUNT` 从 constants 文件导入,其值未在当前打开的代码中显示,但文档声称 20 与 iOS 早期审查发现一致。 -- **建议:** 准确。无需修改。 -- **修正优先级:** 无需修正 - ---- - -## 8. 遗漏项 - -### 8.1 遗漏入口 - -无重大遗漏。文档 03 的 C 端入口表覆盖了 iOS 所有上传入口。 - -### 8.2 遗漏功能 - -| 遗漏项 | 应出现在 | 说明 | -|--------|---------|------| -| KnowledgeSourceService.triggerParse() | 03 文档第 8 节"系统自动修改"或第 9 节"修改入口" | 该端点支持管理员触发重新解析,文档在 Admin 操作中提到了"触发重新解析"但未描述其具体实现(创建新 DocumentImport 并入队) | -| ChatSession.knowledgeItemIds 字段 | 05 文档第 7.1 节 | Prisma 中 ChatSession 有 knowledgeItemIds Json? 字段(默认 []),但 rag-chat.service.ts 中使用 knowledgeItemIds 的实际行为未在文档中描述 | -| Folder scope 对话 | 05 文档第 3 节 | 代码中明确支持 scopeType='folder',但文档仅提了 4 种 scope(global/knowledge_base/material/knowledge_item),遗漏了 folder | - -### 8.3 遗漏业务对象 - -| 遗漏对象 | 说明 | -|---------|------| -| SourceReference | Prisma 中定义的 SourceReference 模型(关联 KnowledgeSource/KnowledgeChunk,追踪 AI 产物引用来源),Admin 中有引用追踪功能,但文档 03 第 7 节数据对象清单中遗漏 | - -### 8.4 遗漏异常恢复 - -| 遗漏场景 | 应出现在 | 说明 | -|---------|---------|------| -| Worker 心跳超时后任务回收的具体机制 | 03 文档第 10.5 节 | 文档提到"心跳超过 5 分钟的 CLAIMED 任务被重置为 QUEUED",但未描述哪个组件执行此回收(API 定时任务?Worker 自身?) | - ---- - -## 9. 错误项 - -### 9.1 文档 03 第 10.6 节"RAG 解析失败"描述 - -- **文档描述:** "Worker 处理:解析阶段失败 → 任务标记 FAILED" -- **实际代码:** import_pipeline.py 中异常捕获后调用 `update_job_status(job_id, "FAILED_RETRYABLE", ...)`,不是 "FAILED"(无后缀)。状态字符串的差异可能影响重试逻辑。 -- **建议修正:** 将文档中的 "FAILED" 改为 "FAILED_RETRYABLE",或统一描述为"失败(可重试)"。 - -### 9.2 文档 05 第 3.4 节"知识点范围"描述 - -- **文档描述:** 关联页面中有"知识点详情页 → 工具栏菜单(如有)" -- **实际代码:** KnowledgeDetailPage 的工具栏菜单在 `vm.detectedType` 存在且 previewMode 为 nativeReader 或 platformPreview 时才显示,且菜单中仅有"笔记"和"预览"选项,**未发现直接跳转到 AI 对话页的入口**。知识点范围的 AI 对话入口在 iOS 端可能不存在。 -- **建议修正:** 将"知识点详情页"从 C 端入口表中移除,或将 iOS 入口标记为"暂未确认"。 - ---- - -## 10. 跨文档冲突 - -### 冲突 1:导入任务状态命名 - -| 文档 | 状态描述 | 实际代码 | -|------|---------|---------| -| 03 第 4.2 节 | 文本解析状态 "pending → processing → complete/failed" | parseStatus 的可能值为 "pending"(默认),但 Worker 不更新 parseStatus,仅更新 job status 为 "PARSING" | -| 03 第 4.3 节 | 向量索引状态 "pending → processing → complete/failed" | 同样,indexStatus 为 KnowledgeSource 字段,默认 "pending",Worker 不直接更新它 | - -**分析:** 文档描述的状态流转(pending → processing → complete/failed)是设计意图,但 Python RAG Worker 代码中只通过 `update_job_status()` 更新 Redis 状态,KnowledgeSource 的三个状态字段(parseStatus/indexStatus/learningStatus)的更新逻辑在 API 内部的 `/internal/rag/jobs/{id}/status` 处理中——该端点代码未在本轮审查中阅读。 - -**建议:** 标注为"暂未确认 — KnowledgeSource 状态字段的更新逻辑在 API 内部端点中,需进一步检查 `/internal/rag/jobs/{id}/status` 的处理代码"。 - -### 冲突 2:Admin 角色描述 - -| 文档 | 描述 | -|------|------| -| 03 第 5 节 | Admin 标注为"运维与可观测" | -| 05 第 5 节 | Admin 标注为"运维与可观测" | -| 06 项目矩阵 | Admin 全部标注为"运维可观测" | - -**分析:** 一致,无冲突。 - -### 冲突 3:无其他关键冲突 - -经逐项比对,03/04/05/06 四份文档之间无其他关键冲突。同一项目职责在不同文档中的描述基本一致,同一业务对象名称一致,同一能力的完成状态标记一致。 - ---- - -## 11. 暂未确认项 - -| 序号 | 内容 | 原因 | 后续需检查 | -|------|------|------|-----------| -| U01 | API 文件类型/大小校验的具体位置 | files.service.ts 中未发现,可能在 DTO/Guard/Pipe 层 | files.controller.ts 和 DTO 定义 | -| U02 | KnowledgeSource 三个状态字段的实际更新逻辑 | Python RAG Worker 通过 `/internal/rag/jobs/{id}/status` 更新,但该端点的处理代码未阅读 | API 内部 RAG job status 处理端点 | -| U03 | 导入任务自动重试(最多 3 次)的实际实现 | 文档声称 API 自动重试,但 Worker 仅标记 FAILED_RETRYABLE,重试调度逻辑的位置未确认 | API 的 job 重试调度逻辑 | -| U04 | 孤儿文件自动清理 (file-cleanup) 的实际执行策略 | file-cleanup.processor.ts 文件存在,但未阅读 | file-cleanup.processor.ts 的具体实现 | -| U05 | 处理中删除源时 Worker 是否检查软删除标志 | 文档标注为"暂未确认" | Python RAG Worker 在处理前是否查询 source 的 deletedAt | -| U06 | 用户自定义 API Key 在对话中的实际使用 | 文档 05 声称支持用户自定义 DeepSeek Key,AiGateway 代码未阅读 | AiGateway 的 credential 选择逻辑 | -| U07 | 流式响应中断时已生成部分内容的保存行为 | rag-chat.service.ts 的 sendMessageStream 在 fullContent 非空时才保存,中断时机影响是否保存 | 需实际测试 | - ---- - -## 12. 完成度调整建议 - -| 业务能力或子能力 | 文档当前状态 | 审查建议状态 | 调整原因 | -|----------------|------------|------------|---------| -| 资料范围 Qdrant 语义检索 | 暂未确认 (05) | 未完成 | rag-chat.service.ts 完全不调用 Qdrant | -| Admin RAG 调试 | 已完成 (03) | 部分完成 | UI 为基础实现,结果显示为 toast,代码注释"完整 RAG 管道在 M3 阶段完善" | -| 文本清洗步骤 | 已完成 (03 隐含) | 占位实现 | import_pipeline.py 中 CLEANING 状态后无实际逻辑 | -| 文件夹 Scope 对话 | 未提及 (05) | 已完成 | rag-chat.service.ts 中 folder case 已完整实现 | -| 知识点范围对话 iOS 入口 | 暂未确认 (05) | 未完成 | iOS KnowledgeDetailPage 工具栏未发现 AI 对话入口 | -| API 文件类型校验 | 已完成 (03) | 暂未确认 | files.service.ts 中未发现校验逻辑 | -| SourceReference 数据对象 | 遗漏 (03 第 7 节) | 已存在 | Prisma 中已定义,Admin 中有引用追踪功能 | -| Python RAG Worker 在对话中的角色 | 间接参与 (05) | 准确 | 不直接参与对话流程 | - ---- - -## 13. 文档修正清单 - -### 03-资料上传与处理.md - -| 章节 | 当前内容 | 修正方向 | -|------|---------|---------| -| 第 3 节流程 | "清洗"步骤 | 标注"清洗步骤当前为占位(无实际逻辑)" | -| 第 5 节主 API | "验证文件类型(8 种)和大小(最大 20MB)" | 若未找到校验位置,改为暂未确认 | -| 第 7.4 节 | 数据对象清单遗漏 SourceReference | 补充 SourceReference 对象描述 | -| 第 10.6 节 | "任务标记 FAILED" | 改为 "FAILED_RETRYABLE" | -| 第 10.2 节 | "PyMuPDF + OCR 回退" | 改为 "PyMuPDF 文本提取优先,不足时 OCR 补充" | -| 第 11 节状态表 | Admin RAG 调试 = 已完成 | 改为"部分完成",备注基础 UI | -| 第 11 节 | 孤儿文件清理 = 暂未确认 | 保持,但补充 file-cleanup.processor.ts 引用 | -| 第 11 节 | 处理中删除源 = 暂未确认 | 保持 | - -### 04-知识库与知识点管理.md - -| 章节 | 当前内容 | 修正方向 | -|------|---------|---------| -| 第 4.7 节 | 文件夹重命名 | 补充"重命名文件夹时,对应 folder 类型 KnowledgeItem 标题不同步更新" | -| 第 3 节业务对象关系 | Folder → 同步创建 folder 类型 KnowledgeItem | 补充 updateFolder 不同步的说明 | -| 第 10 节状态表 | 来源资料删除权限 = 暂未确认 | 确认为"存在缺陷——无所有权检查" | - -### 05-AI对话与知识库问答.md - -| 章节 | 当前内容 | 修正方向 | -|------|---------|---------| -| 第 2 节 C 端入口 | 知识点详情页 → 工具栏菜单 | 移除或标记为暂未确认(iOS 端未发现入口) | -| 第 3 节对话范围 | 仅列 4 种 scope | 补充 folder scope(代码已实现) | -| 第 3.3 节资料范围 | Qdrant 语义检索"可能"使用 | 明确改为"不使用 Qdrant——上下文完全来自 PostgreSQL KnowledgeItem 表" | -| 第 10 节状态表 | Qdrant 语义检索 = 暂未确认 | 改为"未完成——Qdrant 向量搜索未集成到对话链路" | - -### 06-内容与对话项目矩阵.md - -| 章节 | 当前内容 | 修正方向 | -|------|---------|---------| -| 三、Qdrant 行"资料范围语义检索时使用" | "资料范围语义检索时使用" | 与文档 05 保持一致——标注为"未启用" | - ---- - -## 14. 审查门禁结果 - -| 门禁条件 | 结果 | 说明 | -|---------|------|------| -| 三条业务链路已独立还原 | 满足 | 逐链路从代码验证 | -| 所有相关仓库已检查 | 满足 | iOS、API、Python RAG Worker、Admin、Prisma Schema、COS、Qdrant 均已检查 | -| 页面和调用入口已检查 | 满足 | iOS 全部入口 + API 全部端点 | -| API 和异步任务已检查 | 满足 | REST API + Queue + Python Worker 管线 | -| 数据对象关系已检查 | 满足 | Prisma Schema 全部 120+ 模型关系 | -| 权限与删除影响已检查 | 满足 | 软删除级联行为、所有权校验 | -| 异常与恢复已检查 | 满足 | 上传失败、Worker 失败、RAG 未完成、模型调用失败等 | -| Admin 查询和操作入口已检查 | 满足 | 40+ Admin 页面全部盘点 | -| 双向覆盖检查已完成 | 满足 | 文档→代码 + 代码→文档 | -| 跨文档一致性检查已完成 | 满足 | 4 份文档逐项比对 | -| P0 问题为零 | **不满足** | 1 项 P0:文档 05 资料范围 Qdrant 语义检索状态描述不准确 | -| 未确认项已明确记录 | 满足 | 7 项暂未确认均已列出 | - ---- - -## 15. 是否可以进入下一阶段 - -### 结论:可以进入下一阶段(AI 学习分析、主动回忆、题目与复习能力整理),但建议先完成 1 项 P0 修正。 - -**P0 阻塞项:** 文档 05 关于资料范围 Qdrant 语义检索的描述需修正——代码中 rag-chat.service.ts 完全不调用 Qdrant,文档暗示"可能启用"会造成误解。 - -**P1 问题(4 项)不阻塞下一阶段:** -1. API 文件类型/大小校验位置待确认 -2. 文本清洗步骤为空操作 -3. 文件夹重命名不同步 KnowledgeItem 标题 -4. 文件夹创建数量无限制 - -**P2 问题(4 项)不阻塞:** 命名修正和细节补充可在后续阶段一并处理。 - -**当前文档的整体可靠性评价:** -- 三条业务链路的主流程描述与代码一致 -- 项目职责分配正确(Rust Document Runtime 和 Heavy Runtime 的角色描述准确) -- 数据对象关系基本正确 -- 完成状态标记在核心链路上准确,边缘功能有少量偏差 -- 跨文档一致性良好,无关键矛盾