fix: 阅读事件发送 + 首页继续学习 + AI Markdown 渲染 + 文档清理
- MaterialReaderView: 修复 onDisappear 顺序(先 close 再 export 再 cleanup) - MaterialReaderView: 新增每 2 分钟定时发送,离开页面立即发送 - ReadingRuntimeSessionManager: closeMaterial() 不再 unregister - ReadingRuntimeSessionManager: 新增 cleanup() 独立方法 - StudyHomeViewModel: 移除未完成会话回退路径(缺失 materialId) - AIChatPage: 支持 Markdown 渲染(LocalizedStringKey) - 删除 ios/docs/system-map/review/ 和 ios/review/ 审查文档 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
eb7228c725
commit
1d8cbb94f9
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "2600"
|
LastUpgradeVersion = "2600"
|
||||||
version = "1.7">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES"
|
buildImplicitDependencies = "YES"
|
||||||
@ -27,11 +27,9 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
enableAddressSanitizer = "NO"
|
<Testables>
|
||||||
enableThreadSanitizer = "NO"
|
</Testables>
|
||||||
enableUBSanitizer = "NO"
|
|
||||||
disableMainThreadChecker = "NO">
|
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
@ -43,9 +41,6 @@
|
|||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<StoreKitConfigurationFileReference
|
|
||||||
identifier = "Products.storekit">
|
|
||||||
</StoreKitConfigurationFileReference>
|
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
@ -56,6 +51,9 @@
|
|||||||
ReferencedContainer = "container:AIStudyApp.xcodeproj">
|
ReferencedContainer = "container:AIStudyApp.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
<StoreKitConfigurationFileReference
|
||||||
|
identifier = "Products.storekit">
|
||||||
|
</StoreKitConfigurationFileReference>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
|
|||||||
@ -190,6 +190,13 @@ struct KnowledgeBaseCoverUploadResult {
|
|||||||
|
|
||||||
// MARK: - Knowledge Items (matches Prisma KnowledgeItem model)
|
// MARK: - Knowledge Items (matches Prisma KnowledgeItem model)
|
||||||
|
|
||||||
|
|
||||||
|
struct GenerateKnowledgeResponse: Codable {
|
||||||
|
let items: [KnowledgeItem]
|
||||||
|
let count: Int
|
||||||
|
let sourceId: String
|
||||||
|
}
|
||||||
|
|
||||||
struct KnowledgeItem: Codable, Identifiable, Hashable {
|
struct KnowledgeItem: Codable, Identifiable, Hashable {
|
||||||
let id: String
|
let id: String
|
||||||
let userId: String?
|
let userId: String?
|
||||||
@ -692,6 +699,7 @@ struct FileUploadUrlRequest: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct FileUploadUrlResponse: Codable {
|
struct FileUploadUrlResponse: Codable {
|
||||||
|
let uploadId: String
|
||||||
let uploadUrl: String
|
let uploadUrl: String
|
||||||
let objectKey: String
|
let objectKey: String
|
||||||
let bucket: String
|
let bucket: String
|
||||||
@ -700,6 +708,7 @@ struct FileUploadUrlResponse: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct FileConfirmUploadRequest: Codable {
|
struct FileConfirmUploadRequest: Codable {
|
||||||
|
let uploadId: String
|
||||||
let objectKey: String
|
let objectKey: String
|
||||||
let checksum: String?
|
let checksum: String?
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ enum Route: Hashable {
|
|||||||
// Library
|
// Library
|
||||||
case librarySearch
|
case librarySearch
|
||||||
case libraryDetail(knowledgeBaseId: String)
|
case libraryDetail(knowledgeBaseId: String)
|
||||||
|
case knowledgeBaseManage(knowledgeBaseId: String, title: String)
|
||||||
case discoverPublic
|
case discoverPublic
|
||||||
case libraryImport
|
case libraryImport
|
||||||
case libraryCreate
|
case libraryCreate
|
||||||
@ -58,6 +59,7 @@ extension Route {
|
|||||||
case .reviewCard: "ReviewCardView"
|
case .reviewCard: "ReviewCardView"
|
||||||
case .librarySearch: "LibrarySearchView"
|
case .librarySearch: "LibrarySearchView"
|
||||||
case .libraryDetail: "LibraryDetailPage"
|
case .libraryDetail: "LibraryDetailPage"
|
||||||
|
case .knowledgeBaseManage: "KnowledgeBaseManageView"
|
||||||
case .discoverPublic: "DiscoverView"
|
case .discoverPublic: "DiscoverView"
|
||||||
case .libraryImport: "ImportPage"
|
case .libraryImport: "ImportPage"
|
||||||
case .libraryCreate: "CreateLibraryPage"
|
case .libraryCreate: "CreateLibraryPage"
|
||||||
@ -95,6 +97,7 @@ extension Route {
|
|||||||
|
|
||||||
case .librarySearch: AnyView(LibrarySearchView())
|
case .librarySearch: AnyView(LibrarySearchView())
|
||||||
case .libraryDetail(let id): AnyView(LibraryDetailPage(knowledgeBaseId: id))
|
case .libraryDetail(let id): AnyView(LibraryDetailPage(knowledgeBaseId: id))
|
||||||
|
case .knowledgeBaseManage(let kbId, let t): AnyView(KnowledgeBaseManageView(knowledgeBaseId: kbId))
|
||||||
case .discoverPublic: AnyView(DiscoverView())
|
case .discoverPublic: AnyView(DiscoverView())
|
||||||
case .libraryImport: AnyView(ImportPage())
|
case .libraryImport: AnyView(ImportPage())
|
||||||
case .libraryCreate: AnyView(CreateLibraryPage())
|
case .libraryCreate: AnyView(CreateLibraryPage())
|
||||||
|
|||||||
@ -530,8 +530,11 @@ class KnowledgeSourceService {
|
|||||||
static let shared = KnowledgeSourceService()
|
static let shared = KnowledgeSourceService()
|
||||||
private let client = APIClient.shared
|
private let client = APIClient.shared
|
||||||
|
|
||||||
func list(kbId: String) async throws -> [KnowledgeSource] {
|
func list(kbId: String, sortBy: String? = nil, order: String? = nil) async throws -> [KnowledgeSource] {
|
||||||
return try await client.request("/knowledge-bases/\(kbId)/sources")
|
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 {
|
func detail(kbId: String, id: String) async throws -> KnowledgeSource {
|
||||||
@ -550,6 +553,11 @@ class KnowledgeSourceService {
|
|||||||
func parse(kbId: String, sourceId: String) async throws -> ParseResponse {
|
func parse(kbId: String, sourceId: String) async throws -> ParseResponse {
|
||||||
return try await client.request("/knowledge-bases/\(kbId)/sources/\(sourceId)/parse", method: "POST")
|
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
|
// MARK: - Quiz
|
||||||
|
|||||||
@ -40,7 +40,7 @@ class FileUploadService {
|
|||||||
|
|
||||||
let confirmResp: FileConfirmUploadResponse = try await client.request(
|
let confirmResp: FileConfirmUploadResponse = try await client.request(
|
||||||
"/files/complete", method: "POST",
|
"/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)
|
return (fileId: confirmResp.id, objectKey: urlResp.objectKey)
|
||||||
@ -68,7 +68,7 @@ class FileUploadService {
|
|||||||
|
|
||||||
let confirmResp: FileConfirmUploadResponse = try await client.request(
|
let confirmResp: FileConfirmUploadResponse = try await client.request(
|
||||||
"/files/complete", method: "POST",
|
"/files/complete", method: "POST",
|
||||||
body: FileConfirmUploadRequest(objectKey: urlResp.objectKey, checksum: nil)
|
body: FileConfirmUploadRequest(uploadId: urlResp.uploadId, objectKey: urlResp.objectKey, checksum: nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
return confirmResp.id
|
return confirmResp.id
|
||||||
|
|||||||
@ -220,7 +220,8 @@ struct MessageCard: View {
|
|||||||
|
|
||||||
// Content
|
// Content
|
||||||
if hasContent {
|
if hasContent {
|
||||||
Text(message.content).zxFontScaled(size: 14)
|
Text(LocalizedStringKey(message.content))
|
||||||
|
.zxFontScaled(size: 14)
|
||||||
.foregroundColor(isAI ? Color.zxF007 : .white)
|
.foregroundColor(isAI ? Color.zxF007 : .white)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -136,6 +136,7 @@ struct LibraryDetailPage: View {
|
|||||||
let knowledgeBaseId: String
|
let knowledgeBaseId: String
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@StateObject private var viewModel = LibraryDetailViewModel()
|
@StateObject private var viewModel = LibraryDetailViewModel()
|
||||||
|
@State private var showManageView = false
|
||||||
@State private var showDeleteConfirm = false
|
@State private var showDeleteConfirm = false
|
||||||
@State private var isDeleting = false
|
@State private var isDeleting = false
|
||||||
@State private var isSelectMode = false
|
@State private var isSelectMode = false
|
||||||
@ -342,13 +343,16 @@ struct LibraryDetailPage: View {
|
|||||||
Label("默认排序", systemImage: sortOption == 0 ? "checkmark" : "")
|
Label("默认排序", systemImage: sortOption == 0 ? "checkmark" : "")
|
||||||
}
|
}
|
||||||
Button { sortOption = 1 } label: {
|
Button { sortOption = 1 } label: {
|
||||||
Label("文件大小", systemImage: sortOption == 1 ? "checkmark" : "")
|
Label("文件名排序", systemImage: sortOption == 1 ? "checkmark" : "")
|
||||||
}
|
}
|
||||||
Button { sortOption = 2 } label: {
|
Button { sortOption = 2 } label: {
|
||||||
Label("创建日期", systemImage: sortOption == 2 ? "checkmark" : "")
|
Label("文件大小", systemImage: sortOption == 2 ? "checkmark" : "")
|
||||||
}
|
}
|
||||||
Button { sortOption = 3 } label: {
|
Button { sortOption = 3 } label: {
|
||||||
Label("更新日期", systemImage: sortOption == 3 ? "checkmark" : "")
|
Label("创建日期", systemImage: sortOption == 3 ? "checkmark" : "")
|
||||||
|
}
|
||||||
|
Button { sortOption = 4 } label: {
|
||||||
|
Label("更新日期", systemImage: sortOption == 4 ? "checkmark" : "")
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrow.up.arrow.down")
|
Image(systemName: "arrow.up.arrow.down")
|
||||||
@ -375,7 +379,7 @@ struct LibraryDetailPage: View {
|
|||||||
}
|
}
|
||||||
Divider()
|
Divider()
|
||||||
Button {
|
Button {
|
||||||
// TODO: knowledge base management page
|
showManageView = true
|
||||||
} label: {
|
} label: {
|
||||||
Label("知识库管理", image: "icon-settings")
|
Label("知识库管理", image: "icon-settings")
|
||||||
}
|
}
|
||||||
@ -400,6 +404,9 @@ struct LibraryDetailPage: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text("删除后将无法恢复,包括其中的所有知识点。确定要删除吗?")
|
Text("删除后将无法恢复,包括其中的所有知识点。确定要删除吗?")
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showManageView) {
|
||||||
|
KnowledgeBaseManageView(knowledgeBaseId: knowledgeBaseId)
|
||||||
|
}
|
||||||
.alert("批量删除", isPresented: $showBatchDeleteConfirm) {
|
.alert("批量删除", isPresented: $showBatchDeleteConfirm) {
|
||||||
Button("取消", role: .cancel) {}
|
Button("取消", role: .cancel) {}
|
||||||
Button("删除 \(selectedIds.count) 个", role: .destructive) {
|
Button("删除 \(selectedIds.count) 个", role: .destructive) {
|
||||||
@ -416,9 +423,9 @@ struct LibraryDetailPage: View {
|
|||||||
.sheet(isPresented: $showFolderManager) {
|
.sheet(isPresented: $showFolderManager) {
|
||||||
FolderManageView(knowledgeBaseId: knowledgeBaseId)
|
FolderManageView(knowledgeBaseId: knowledgeBaseId)
|
||||||
}
|
}
|
||||||
.onChange(of: sortOption) { _ in
|
.onChange(of: sortOption) { newValue in
|
||||||
let (sb, od) = sortParams(sortOption)
|
let (sb, od) = sortParams(newValue)
|
||||||
Task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId, sortBy: sb, order: od) }
|
Task { await loadSources(sortBy: sb, order: od) }
|
||||||
}
|
}
|
||||||
.safeAreaInset(edge: .bottom) {
|
.safeAreaInset(edge: .bottom) {
|
||||||
NavigationLink(value: Route.aiChat(context: ChatEntryContext(scopeType: .knowledgeBase, scopeId: knowledgeBaseId, scopeName: "知识库", parentKnowledgeBaseId: knowledgeBaseId, createdFrom: "knowledge_base_detail"))) {
|
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?) {
|
private func sortParams(_ option: Int) -> (String?, String?) {
|
||||||
switch option {
|
switch option {
|
||||||
case 1: return ("fileSize", "desc")
|
case 1: return ("title", "asc")
|
||||||
case 2: return ("createdAt", "desc")
|
case 2: return ("sizeBytes", "desc")
|
||||||
case 3: return ("updatedAt", "desc")
|
case 3: return ("createdAt", "desc")
|
||||||
default: return (nil, nil)
|
case 4: return ("updatedAt", "desc")
|
||||||
|
default: return ("title", "asc")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func fileTypeText(for item: KnowledgeItem) -> String {
|
private func fileTypeText(for item: KnowledgeItem) -> String {
|
||||||
if let t = item.sourceType?.trimmingCharacters(in: .whitespaces), !t.isEmpty {
|
if let t = item.sourceType?.trimmingCharacters(in: .whitespaces), !t.isEmpty {
|
||||||
return t.uppercased()
|
return t.uppercased()
|
||||||
@ -639,11 +648,11 @@ struct LibraryDetailPage: View {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadSources() async {
|
private func loadSources(sortBy: String? = nil, order: String? = nil) async {
|
||||||
isLoadingSources = true
|
isLoadingSources = true
|
||||||
print("[LibraryDetail] loadSources START, kbId=\(knowledgeBaseId)")
|
print("[LibraryDetail] loadSources START, kbId=\(knowledgeBaseId)")
|
||||||
do {
|
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)")
|
print("[LibraryDetail] loadSources OK, count=\(sources.count)")
|
||||||
for src in sources {
|
for src in sources {
|
||||||
print("[LibraryDetail] source: id=\(src.id), title=\(src.title ?? "-"), parseStatus=\(src.parseStatus ?? "-")")
|
print("[LibraryDetail] source: id=\(src.id), title=\(src.title ?? "-"), parseStatus=\(src.parseStatus ?? "-")")
|
||||||
|
|||||||
@ -105,6 +105,7 @@ struct MaterialReaderView: View {
|
|||||||
@State private var readingStatus: String?
|
@State private var readingStatus: String?
|
||||||
@State private var readingProgressSeconds: Int = 0
|
@State private var readingProgressSeconds: Int = 0
|
||||||
@State private var apiRestorePosition: ReadingPosition?
|
@State private var apiRestorePosition: ReadingPosition?
|
||||||
|
@State private var flushTimer: Timer?
|
||||||
|
|
||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
@State private var isParsing = false
|
@State private var isParsing = false
|
||||||
@ -275,9 +276,26 @@ struct MaterialReaderView: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
openReadingSession()
|
openReadingSession()
|
||||||
hasRestoredPosition = false
|
hasRestoredPosition = false
|
||||||
|
// 定时发送阅读事件(每 2 分钟),防止长时间阅读不退出导致数据丢失
|
||||||
|
flushTimer = Timer.scheduledTimer(withTimeInterval: 120, repeats: true) { _ in
|
||||||
|
Task {
|
||||||
|
ReadingEventUploadPipeline.shared.exportAndEnqueue()
|
||||||
|
await ReadingEventUploadPipeline.shared.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
|
flushTimer?.invalidate()
|
||||||
|
flushTimer = nil
|
||||||
|
// 1. 关闭 Rust 会话,push material_closed 事件到缓冲区
|
||||||
closeReadingSession()
|
closeReadingSession()
|
||||||
|
// 2. 导出所有事件(含 material_closed)+ 发送到后端
|
||||||
|
// 3. 发送完成后再清理 context
|
||||||
|
Task {
|
||||||
|
ReadingEventUploadPipeline.shared.exportAndEnqueue()
|
||||||
|
await ReadingEventUploadPipeline.shared.flush()
|
||||||
|
sessionManager.cleanup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: scenePhase) { _, newPhase in
|
.onChange(of: scenePhase) { _, newPhase in
|
||||||
switch newPhase {
|
switch newPhase {
|
||||||
@ -597,17 +615,12 @@ struct MaterialReaderView: View {
|
|||||||
private func triggerParse(kbId: String, sourceId: String) async {
|
private func triggerParse(kbId: String, sourceId: String) async {
|
||||||
guard !isParsing else { return }
|
guard !isParsing else { return }
|
||||||
isParsing = true
|
isParsing = true
|
||||||
parseMessage = "正在整理知识点..."
|
parseMessage = "AI 正在整理知识点..."
|
||||||
do {
|
do {
|
||||||
let result = try await KnowledgeSourceService.shared.parse(kbId: kbId, sourceId: sourceId)
|
let result = try await KnowledgeSourceService.shared.generateKnowledge(kbId: kbId, sourceId: sourceId)
|
||||||
parseMessage = "知识点整理任务已提交"
|
parseMessage = result.count > 0
|
||||||
for _ in 0..<60 {
|
? "整理完成,生成了 \(result.count) 个知识点"
|
||||||
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)%" }
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
parseMessage = "整理失败: \(error.localizedDescription)"
|
parseMessage = "整理失败: \(error.localizedDescription)"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,11 +97,10 @@ final class ReadingRuntimeSessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close the current session. Can be called from any non-idle state.
|
/// Push close event and close the Rust session. Does NOT unregister context.
|
||||||
/// Close the current session. Idempotent — safe to call from any state.
|
/// Call `cleanup()` after events have been exported & flushed.
|
||||||
func closeMaterial() {
|
func closeMaterial() {
|
||||||
guard state == .active || state == .paused, let sessionId = activeSessionId, let ctx = activeContext else {
|
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 }
|
if state == .closed || state == .idle { return }
|
||||||
state = .idle
|
state = .idle
|
||||||
return
|
return
|
||||||
@ -117,12 +116,18 @@ final class ReadingRuntimeSessionManager {
|
|||||||
_ = try? adapter.closeSession(sessionId)
|
_ = try? adapter.closeSession(sessionId)
|
||||||
|
|
||||||
activeSessionId = nil
|
activeSessionId = nil
|
||||||
if let ctx = activeContext { ReadingContextRegistry.shared.unregister(materialId: ctx.materialId) }
|
|
||||||
activeContext = nil
|
|
||||||
lastPosition = nil
|
lastPosition = nil
|
||||||
state = .closed
|
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() {
|
func pause() {
|
||||||
guard state == .active, let sessionId = activeSessionId else { return }
|
guard state == .active, let sessionId = activeSessionId else { return }
|
||||||
stopHeartbeat()
|
stopHeartbeat()
|
||||||
|
|||||||
@ -119,24 +119,23 @@ struct MembershipView: View {
|
|||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
ForEach(plans.plans) { plan in
|
ForEach(plans.plans) { plan in
|
||||||
|
if plan.planCode != "free" {
|
||||||
|
ForEach(["monthly", "yearly"], id: \.self) { period in
|
||||||
Button {
|
Button {
|
||||||
Task { await store.purchase(plan: plan) }
|
Task { await store.purchase(plan: plan, period: period) }
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(plan.name).font(.system(size: 15, weight: .semibold)).foregroundColor(Color.zxF0)
|
Text(plan.name + (period == "yearly" ? " 年付" : " 月付")).font(.system(size: 15, weight: .semibold)).foregroundColor(Color.zxF0)
|
||||||
Text(plan.planCode.contains("yearly") ? "每年自动续订,更优惠" : "每月自动续订")
|
Text(period == "yearly" ? "每年自动续订,省 41%" : "每月自动续订")
|
||||||
.font(.system(size: 11)).foregroundColor(Color.zxF04)
|
.font(.system(size: 11)).foregroundColor(Color.zxF04)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
if store.purchasingPlanId == plan.planCode {
|
if store.purchasingPlanId == plan.planCode + "_" + period {
|
||||||
ProgressView().tint(Color.zxPurple)
|
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 {
|
} else {
|
||||||
Text("价格暂不可用")
|
Text(store.displayPrice(for: plan, period: period))
|
||||||
.font(.system(size: 12)).foregroundColor(Color.zxF04)
|
.font(.system(size: 14, weight: .bold)).foregroundColor(Color.zxPurple)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(16).background(Color.zxFill004)
|
.padding(16).background(Color.zxFill004)
|
||||||
@ -147,6 +146,8 @@ struct MembershipView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Usage
|
// MARK: - Usage
|
||||||
|
|
||||||
@ -259,14 +260,17 @@ final class MembershipStore: ObservableObject {
|
|||||||
do {
|
do {
|
||||||
async let membership = MembershipService.shared.myMembership()
|
async let membership = MembershipService.shared.myMembership()
|
||||||
async let plans = MembershipService.shared.getPlans()
|
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)
|
state = try await .loaded(membership: membership, plans: plans)
|
||||||
} catch {
|
} catch {
|
||||||
state = .error("加载失败:\(error.localizedDescription)")
|
state = .error("加载失败:\(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func purchase(plan: PlanItem) async {
|
func purchase(plan: PlanItem, period: String = "monthly") async {
|
||||||
purchasingPlanId = plan.planCode
|
purchasingPlanId = plan.planCode + "_" + period
|
||||||
guard let tokenStr = try? await MembershipService.shared.myMembership().appAccountToken,
|
guard let tokenStr = try? await MembershipService.shared.myMembership().appAccountToken,
|
||||||
let uuid = UUID(uuidString: tokenStr) else {
|
let uuid = UUID(uuidString: tokenStr) else {
|
||||||
show("无法获取账号令牌")
|
show("无法获取账号令牌")
|
||||||
@ -276,7 +280,7 @@ final class MembershipStore: ObservableObject {
|
|||||||
let pm = StoreKitManager.shared
|
let pm = StoreKitManager.shared
|
||||||
await pm.loadProducts()
|
await pm.loadProducts()
|
||||||
guard let product = pm.products.first(where: {
|
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.yearly"
|
||||||
: "cloud.longde.aistudyapp.premium.monthly")
|
: "cloud.longde.aistudyapp.premium.monthly")
|
||||||
}) else {
|
}) else {
|
||||||
@ -302,12 +306,11 @@ final class MembershipStore: ObservableObject {
|
|||||||
isRestoring = false
|
isRestoring = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从 StoreKit Product 获取本地化价格。未加载时返回 nil。
|
/// 从 PlanItem 获取格式化价格(分→元),不依赖 StoreKit。
|
||||||
func displayPrice(for planCode: String) -> String? {
|
func displayPrice(for plan: PlanItem, period: String) -> String {
|
||||||
let productId = planCode.contains("yearly")
|
let cents = period == "yearly" ? plan.priceYearly : plan.priceMonthly
|
||||||
? "cloud.longde.aistudyapp.premium.yearly"
|
let yuan = Double(cents) / 100.0
|
||||||
: "cloud.longde.aistudyapp.premium.monthly"
|
return String(format: "¥%.2f/%@", yuan, period == "yearly" ? "年" : "月")
|
||||||
return StoreKitManager.shared.products.first(where: { $0.id == productId })?.displayPrice
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// N2: 使用系统 API 打开订阅管理
|
/// N2: 使用系统 API 打开订阅管理
|
||||||
|
|||||||
@ -183,15 +183,6 @@ final class StudyHomeViewModel: ObservableObject {
|
|||||||
return .continueSession(materialId: materialId, filePath: resolved.filePath, materialType: resolved.materialType, kbTitle: kbTitle, elapsed: elapsed, resumePosition: nil)
|
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 {
|
if !dueCards.isEmpty {
|
||||||
return .todaysReview(count: dueCards.count, estimatedMinutes: max(1, dueCards.count))
|
return .todaysReview(count: dueCards.count, estimatedMinutes: max(1, dueCards.count))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 修正后可达"通过"标准。**
|
|
||||||
@ -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 端入口。
|
|
||||||
@ -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 重取 | 已完成 | 检测 <Error> 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** |
|
|
||||||
@ -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%+。主要问题集中在页面完成状态的部分误判,以及个别状态的遗漏。**
|
|
||||||
@ -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-页面不一致未记录。**
|
|
||||||
@ -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 学习分析"建议合并、依赖关系图缺少几个交叉引用。**
|
|
||||||
@ -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 持久化)
|
|
||||||
@ -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 项**
|
|
||||||
@ -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 的角色描述准确)
|
|
||||||
- 数据对象关系基本正确
|
|
||||||
- 完成状态标记在核心链路上准确,边缘功能有少量偏差
|
|
||||||
- 跨文档一致性良好,无关键矛盾
|
|
||||||
Loading…
x
Reference in New Issue
Block a user