From 0af5b6554d0b2e730af67f60cd486676ec426d8c Mon Sep 17 00:00:00 2001 From: wangdl Date: Sat, 6 Jun 2026 18:02:26 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20M-CHAT-A5/A9/A11=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=89=E4=B8=AA=E6=9A=82=E7=BC=93=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - A5: MaterialReaderView 传入 knowledgeBaseId (Route 新增参数) - A9: 会话列表项显示 scope 图标 + 类型标签 - A11: 会话列表左滑归档 + 右滑置顶/取消置顶 Co-Authored-By: Claude Opus 4.7 --- .../AIStudyApp/Core/Navigation/Route.swift | 4 +- .../AIStudyApp/Features/AI/AIChatPage.swift | 52 +++++++++++++++++-- .../MaterialReader/MaterialReaderView.swift | 6 ++- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/AIStudyApp/AIStudyApp/Core/Navigation/Route.swift b/AIStudyApp/AIStudyApp/Core/Navigation/Route.swift index 38c24b0..1287116 100644 --- a/AIStudyApp/AIStudyApp/Core/Navigation/Route.swift +++ b/AIStudyApp/AIStudyApp/Core/Navigation/Route.swift @@ -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)) } diff --git a/AIStudyApp/AIStudyApp/Features/AI/AIChatPage.swift b/AIStudyApp/AIStudyApp/Features/AI/AIChatPage.swift index 0ccda93..29bc172 100644 --- a/AIStudyApp/AIStudyApp/Features/AI/AIChatPage.swift +++ b/AIStudyApp/AIStudyApp/Features/AI/AIChatPage.swift @@ -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 diff --git a/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift b/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift index 99698aa..abc4768 100644 --- a/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift +++ b/AIStudyApp/AIStudyApp/Features/MaterialReader/MaterialReaderView.swift @@ -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 = .constant(false), showNoteSheet: Binding = .constant(false)) { + init(materialId: String, filePath: String, materialType: MaterialType, knowledgeBaseId: String? = nil, title: String = "", showQuickLook: Binding = .constant(false), showNoteSheet: Binding = .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")