diff --git a/AIStudyApp/AIStudyApp.xcodeproj/project.xcworkspace/xcuserdata/Admin1.xcuserdatad/UserInterfaceState.xcuserstate b/AIStudyApp/AIStudyApp.xcodeproj/project.xcworkspace/xcuserdata/Admin1.xcuserdatad/UserInterfaceState.xcuserstate index 1b90506..400b1d4 100644 Binary files a/AIStudyApp/AIStudyApp.xcodeproj/project.xcworkspace/xcuserdata/Admin1.xcuserdatad/UserInterfaceState.xcuserstate and b/AIStudyApp/AIStudyApp.xcodeproj/project.xcworkspace/xcuserdata/Admin1.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift b/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift index fd85dc3..be2f283 100644 --- a/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift +++ b/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift @@ -577,13 +577,21 @@ struct CreateImportRequest: Codable { struct ImportStatusResponse: Codable { let id: String + let jobId: String? let status: String? let fileName: String? let knowledgeBaseId: String? let errorMessage: String? + let progress: Int? let itemCount: Int? } +struct ParseResponse: Codable { + let id: String? + let jobId: String? + let status: String? +} + // MARK: - Import Candidate struct ImportCandidate: Codable, Identifiable { diff --git a/AIStudyApp/AIStudyApp/Core/Services/APIService.swift b/AIStudyApp/AIStudyApp/Core/Services/APIService.swift index 8fe12b7..31a959f 100644 --- a/AIStudyApp/AIStudyApp/Core/Services/APIService.swift +++ b/AIStudyApp/AIStudyApp/Core/Services/APIService.swift @@ -546,6 +546,10 @@ class KnowledgeSourceService { func delete(kbId: String, id: String) async throws -> GenericSuccessResponse { return try await client.request("/knowledge-bases/\(kbId)/sources/\(id)", method: "DELETE") } + + func parse(kbId: String, sourceId: String) async throws -> ParseResponse { + return try await client.request("/knowledge-bases/\(kbId)/sources/\(sourceId)/parse", method: "POST") + } } // MARK: - Quiz diff --git a/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift b/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift index 7a4875b..c17e960 100644 --- a/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift +++ b/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift @@ -107,6 +107,8 @@ struct MaterialReaderView: View { @State private var apiRestorePosition: ReadingPosition? @Environment(\.scenePhase) private var scenePhase + @State private var isParsing = false + @State private var parseMessage = "" private let title: String private let knowledgeBaseId: String? @@ -191,6 +193,19 @@ struct MaterialReaderView: View { } if let kbId = knowledgeBaseId { + NavigationLink(value: Route.materialDetail( + knowledgeBaseId: kbId, + fileName: title.isEmpty ? "资料" : title, + fileType: vm.materialType, + fileSize: 0, + filePath: vm.filePath, + uploadDate: nil + )) { + Image(systemName: "info.circle") + .font(.system(size: 16)) + .foregroundColor(Color.zxF05) + } + NavigationLink(value: Route.learningSession( taskTitle: title.isEmpty ? "学习资料" : title, taskType: "study", @@ -200,6 +215,14 @@ struct MaterialReaderView: View { .font(.system(size: 16)) .foregroundColor(Color.zxF05) } + + Button { + Task { await triggerParse(kbId: kbId, sourceId: vm.materialId) } + } label: { + Image(systemName: "brain.head.profile") + .font(.system(size: 16)) + .foregroundColor(Color.zxF05) + } } } } @@ -708,6 +731,37 @@ private func formatDuration(_ seconds: Int) -> String { return "\(seconds / 3600)h\(String(format: "%02d", (seconds % 3600) / 60))m" } + private func triggerParse(kbId: String, sourceId: String) async { + guard !isParsing else { return } + isParsing = true + parseMessage = "正在整理知识点..." + print("[MaterialReader] triggerParse START kbId=\(kbId) sourceId=\(sourceId)") + do { + let result = try await KnowledgeSourceService.shared.parse(kbId: kbId, sourceId: sourceId) + print("[MaterialReader] parse queued: id=\(result.jobId ?? "?"), status=\(result.status ?? "?")") + parseMessage = "知识点整理任务已提交" + // Poll status + for _ in 0..<60 { + try await Task.sleep(nanoseconds: 2_000_000_000) + let status = try await DocumentImportService.shared.getStatus(id: result.jobId ?? result.id ?? "") + print("[MaterialReader] parse status: \(status.status ?? "?")") + if status.status == "completed" { + parseMessage = "知识点整理完成" + break + } else if status.status == "failed" { + parseMessage = "整理失败: \(status.errorMessage ?? "未知错误")" + break + } else { + parseMessage = "整理中... \(status.progress ?? 0)%" + } + } + } catch { + print("[MaterialReader] triggerParse ERROR: \(error.localizedDescription)") + parseMessage = "整理失败: \(error.localizedDescription)" + } + isParsing = false + } + private func formatFileSize(_ bytes: UInt64) -> String { if bytes < 1024 { return "\(bytes) B" } if bytes < 1024 * 1024 { return String(format: "%.1f KB", Double(bytes) / 1024) }