fix: M-CHAT-A5/A9/A11 修复三个暂缓 issue

- A5: MaterialReaderView 传入 knowledgeBaseId (Route 新增参数)
- A9: 会话列表项显示 scope 图标 + 类型标签
- A11: 会话列表左滑归档 + 右滑置顶/取消置顶

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-06 18:02:26 +08:00
parent 491c3e7ef0
commit 0af5b6554d
3 changed files with 54 additions and 8 deletions

View File

@ -31,7 +31,7 @@ enum Route: Hashable {
case quizResult(quizId: String, attemptId: String)
// Material Reader
case materialReader(materialId: String, filePath: String, materialType: MaterialType)
case materialReader(materialId: String, filePath: String, materialType: MaterialType, knowledgeBaseId: String? = nil, title: String = "")
case materialDetail(knowledgeBaseId: String, fileName: String, fileType: MaterialType, fileSize: UInt64, filePath: String, uploadDate: String?)
// Profile
@ -109,7 +109,7 @@ extension Route {
case .quizList(let kbId): AnyView(QuizListView(knowledgeBaseId: kbId))
case .quizTake(let id): AnyView(QuizTakerView(quizId: id))
case .quizResult(let qid, let aid): AnyView(QuizResultView(quizId: qid, attemptId: aid))
case .materialReader(let mid, let path, let mt): AnyView(MaterialReaderView(materialId: mid, filePath: path, materialType: mt))
case .materialReader(let mid, let path, let mt, let kbId, let title): AnyView(MaterialReaderView(materialId: mid, filePath: path, materialType: mt, knowledgeBaseId: kbId, title: title))
case .materialDetail(let kbId, let name, let type, let size, let path, let date):
AnyView(MaterialDetailView(knowledgeBaseId: kbId, fileName: name, fileType: type, fileSize: size, filePath: path, uploadDate: date))
}

View File

@ -104,11 +104,22 @@ struct AIChatPage: View {
Task { await vm.loadSession(s.id) }
} label: {
HStack {
// Scope icon
Image(systemName: scopeIcon(for: s.scopeType))
.font(.system(size: 12))
.foregroundColor(Color.zxF04)
.frame(width: 20)
VStack(alignment: .leading, spacing: 2) {
Text(s.title ?? "对话").font(.system(size: 14)).foregroundColor(Color.zxF0)
Text(s.updatedAt?.prefix(10).description ?? "").font(.system(size: 11)).foregroundColor(Color.zxF04)
HStack(spacing: 4) {
Text(scopeLabel(for: s)).font(.system(size: 10)).foregroundColor(Color.zxF04)
Text(s.updatedAt?.prefix(10).description ?? "").font(.system(size: 10)).foregroundColor(Color.zxF04)
}
}
Spacer()
if s.isPinned == true {
Image(systemName: "pin.fill").font(.system(size: 10)).foregroundColor(Color.zxF04)
}
}.padding(.vertical, 4)
}.foregroundColor(.primary)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
@ -117,11 +128,25 @@ struct AIChatPage: View {
do {
try await RagChatService.shared.deleteSession(id: s.id)
sessions.removeAll { $0.id == s.id }
} catch {
// Keep session in list if delete failed
}
} catch { }
}
} label: { Label("删除", systemImage: "trash") }
Button {
Task {
try? await RagChatService.shared.updateSession(id: s.id, dto: UpdateChatSessionRequest(isArchived: true))
sessions.removeAll { $0.id == s.id }
}
} label: { Label("归档", systemImage: "archivebox") }
.tint(Color.zxF04)
}
.swipeActions(edge: .leading, allowsFullSwipe: true) {
Button {
Task {
try? await RagChatService.shared.updateSession(id: s.id, dto: UpdateChatSessionRequest(isPinned: !(s.isPinned ?? false)))
if let updated = try? await RagChatService.shared.listSessions() { sessions = updated }
}
} label: { Label(s.isPinned == true ? "取消置顶" : "置顶", systemImage: s.isPinned == true ? "pin.slash" : "pin") }
.tint(Color.zxPurple)
}
}
}
@ -149,6 +174,25 @@ struct AIChatPage: View {
proxy.scrollTo(vm.messages.last?.id, anchor: .bottom)
}
}
private func scopeIcon(for type: String?) -> String {
switch type {
case "knowledge_base": return "books.vertical"
case "folder": return "folder"
case "material": return "doc.text"
case "knowledge_item": return "lightbulb"
default: return "bubble.left"
}
}
private func scopeLabel(for session: ChatSession) -> String {
guard let t = session.scopeType, t != "global" else { return "" }
let map: [String: String] = [
"knowledge_base": "知识库", "folder": "分类",
"material": "资料", "knowledge_item": "知识点"
]
return map[t] ?? t
}
}
// MARK: - Message Card

View File

@ -102,13 +102,15 @@ struct MaterialReaderView: View {
@State private var restoreBlockId: String?
private let title: String
private let knowledgeBaseId: String?
// Event collector records reading events during the session
private let collector = ReadingEventCollector.shared
private let positionStore = ReadingPositionStore.shared
init(materialId: String, filePath: String, materialType: MaterialType, title: String = "", showQuickLook: Binding<Bool> = .constant(false), showNoteSheet: Binding<Bool> = .constant(false)) {
init(materialId: String, filePath: String, materialType: MaterialType, knowledgeBaseId: String? = nil, title: String = "", showQuickLook: Binding<Bool> = .constant(false), showNoteSheet: Binding<Bool> = .constant(false)) {
self.title = title
self.knowledgeBaseId = knowledgeBaseId
self._showQuickLook = showQuickLook
self._showNoteSheet = showNoteSheet
_vm = StateObject(wrappedValue: MaterialReaderViewModel(
@ -157,7 +159,7 @@ struct MaterialReaderView: View {
scopeType: .material,
scopeId: vm.materialId,
scopeName: title,
parentKnowledgeBaseId: nil,
parentKnowledgeBaseId: knowledgeBaseId,
createdFrom: "material_reader"
))) {
Image(systemName: "bubble.left.and.bubble.right")