feat(IOS-INFO-043): add 详情 and 整理知识点 toolbar buttons to MaterialReaderView
- MaterialReaderView: add info.circle button → MaterialDetailView - MaterialReaderView: add brain button → trigger parse API + status polling - MaterialReaderView: add triggerParse() with 2s poll loop - KnowledgeSourceService: add parse() method (POST /sources/:id/parse) - ImportStatusResponse: add jobId, progress fields - ParseResponse: new model for parse trigger response Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
fbf359ffe9
commit
123efb1612
Binary file not shown.
@ -577,13 +577,21 @@ struct CreateImportRequest: Codable {
|
|||||||
|
|
||||||
struct ImportStatusResponse: Codable {
|
struct ImportStatusResponse: Codable {
|
||||||
let id: String
|
let id: String
|
||||||
|
let jobId: String?
|
||||||
let status: String?
|
let status: String?
|
||||||
let fileName: String?
|
let fileName: String?
|
||||||
let knowledgeBaseId: String?
|
let knowledgeBaseId: String?
|
||||||
let errorMessage: String?
|
let errorMessage: String?
|
||||||
|
let progress: Int?
|
||||||
let itemCount: Int?
|
let itemCount: Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ParseResponse: Codable {
|
||||||
|
let id: String?
|
||||||
|
let jobId: String?
|
||||||
|
let status: String?
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Import Candidate
|
// MARK: - Import Candidate
|
||||||
|
|
||||||
struct ImportCandidate: Codable, Identifiable {
|
struct ImportCandidate: Codable, Identifiable {
|
||||||
|
|||||||
@ -546,6 +546,10 @@ class KnowledgeSourceService {
|
|||||||
func delete(kbId: String, id: String) async throws -> GenericSuccessResponse {
|
func delete(kbId: String, id: String) async throws -> GenericSuccessResponse {
|
||||||
return try await client.request("/knowledge-bases/\(kbId)/sources/\(id)", method: "DELETE")
|
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
|
// MARK: - Quiz
|
||||||
|
|||||||
@ -107,6 +107,8 @@ struct MaterialReaderView: View {
|
|||||||
@State private var apiRestorePosition: ReadingPosition?
|
@State private var apiRestorePosition: ReadingPosition?
|
||||||
|
|
||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
@State private var isParsing = false
|
||||||
|
@State private var parseMessage = ""
|
||||||
|
|
||||||
private let title: String
|
private let title: String
|
||||||
private let knowledgeBaseId: String?
|
private let knowledgeBaseId: String?
|
||||||
@ -191,6 +193,19 @@ struct MaterialReaderView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let kbId = knowledgeBaseId {
|
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(
|
NavigationLink(value: Route.learningSession(
|
||||||
taskTitle: title.isEmpty ? "学习资料" : title,
|
taskTitle: title.isEmpty ? "学习资料" : title,
|
||||||
taskType: "study",
|
taskType: "study",
|
||||||
@ -200,6 +215,14 @@ struct MaterialReaderView: View {
|
|||||||
.font(.system(size: 16))
|
.font(.system(size: 16))
|
||||||
.foregroundColor(Color.zxF05)
|
.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"
|
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 {
|
private func formatFileSize(_ bytes: UInt64) -> String {
|
||||||
if bytes < 1024 { return "\(bytes) B" }
|
if bytes < 1024 { return "\(bytes) B" }
|
||||||
if bytes < 1024 * 1024 { return String(format: "%.1f KB", Double(bytes) / 1024) }
|
if bytes < 1024 * 1024 { return String(format: "%.1f KB", Double(bytes) / 1024) }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user