diff --git a/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift b/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift index 9bd4a84..79abf09 100644 --- a/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift +++ b/AIStudyApp/AIStudyApp/Core/Models/APIModels.swift @@ -600,6 +600,23 @@ struct BatchAcceptRequest: Codable { let sourceId: String } +// MARK: - Knowledge Folder + +struct KnowledgeFolder: Codable, Identifiable { + let id: String + let knowledgeBaseId: String? + let parentId: String? + let name: String + let sortOrder: Int? + let createdAt: String? + let updatedAt: String? +} + +struct CreateFolderRequest: Codable { + let name: String + let parentId: String? +} + // MARK: - Knowledge Source struct KnowledgeSource: Codable, Identifiable { diff --git a/AIStudyApp/AIStudyApp/Core/Services/APIService.swift b/AIStudyApp/AIStudyApp/Core/Services/APIService.swift index 55526bb..c5e1840 100644 --- a/AIStudyApp/AIStudyApp/Core/Services/APIService.swift +++ b/AIStudyApp/AIStudyApp/Core/Services/APIService.swift @@ -142,6 +142,31 @@ class KnowledgeBaseService { } } +// MARK: - Knowledge Folder + +@MainActor +class KnowledgeFolderService { + static let shared = KnowledgeFolderService() + private let client = APIClient.shared + + func list(kbId: String) async throws -> [KnowledgeFolder] { + return try await client.request("/knowledge-bases/\(kbId)/folders") + } + + func create(kbId: String, name: String, parentId: String? = nil) async throws -> KnowledgeFolder { + let body = CreateFolderRequest(name: name, parentId: parentId) + return try await client.request("/knowledge-bases/\(kbId)/folders", method: "POST", body: body) + } + + func update(kbId: String, folderId: String, name: String) async throws -> KnowledgeFolder { + return try await client.request("/knowledge-bases/\(kbId)/folders/\(folderId)", method: "PATCH", body: ["name": name]) + } + + func delete(kbId: String, folderId: String) async throws -> GenericSuccessResponse { + return try await client.request("/knowledge-bases/\(kbId)/folders/\(folderId)", method: "DELETE") + } +} + // MARK: - Knowledge Items @MainActor diff --git a/AIStudyApp/AIStudyApp/Features/Library/FolderManageView.swift b/AIStudyApp/AIStudyApp/Features/Library/FolderManageView.swift new file mode 100644 index 0000000..83e56da --- /dev/null +++ b/AIStudyApp/AIStudyApp/Features/Library/FolderManageView.swift @@ -0,0 +1,102 @@ +import SwiftUI + +struct FolderManageView: View { + let knowledgeBaseId: String + @Environment(\.dismiss) private var dismiss + @State private var folders: [KnowledgeFolder] = [] + @State private var isLoading = false + @State private var showCreateAlert = false + @State private var showRenameAlert = false + @State private var newFolderName = "" + @State private var renameFolderId: String? + @State private var renameName = "" + + var body: some View { + NavigationStack { + ZStack { + Color.zxBg0.ignoresSafeArea() + if isLoading && folders.isEmpty { + ProgressView().tint(Color.zxPurple) + } else if folders.isEmpty { + Text("暂无分类").font(.system(size: 14)).foregroundColor(Color.zxF04) + } else { + List { + ForEach(folders) { f in + HStack { + Image(systemName: "folder") + .foregroundColor(Color.zxF05) + Text(f.name) + .font(.system(size: 14)) + .foregroundColor(Color.zxF0) + Spacer() + } + .padding(.vertical, 4) + .swipeActions(edge: .trailing) { + Button(role: .destructive) { + Task { + try? await KnowledgeFolderService.shared.delete(kbId: knowledgeBaseId, folderId: f.id) + folders.removeAll { $0.id == f.id } + } + } label: { Label("删除", systemImage: "trash") } + Button { + renameFolderId = f.id + renameName = f.name + showRenameAlert = true + } label: { Label("重命名", systemImage: "pencil") } + .tint(Color.zxPurple) + } + } + } + .listStyle(.plain) + .scrollContentBackground(.hidden) + } + } + .navigationTitle("分类管理") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button { + newFolderName = "" + showCreateAlert = true + } label: { + Image(systemName: "plus").font(.system(size: 16)).foregroundColor(Color.zxF05) + } + } + ToolbarItem(placement: .topBarLeading) { + Button("完成") { dismiss() } + .font(.system(size: 14)).foregroundColor(Color.zxF05) + } + } + .alert("新建分类", isPresented: $showCreateAlert) { + TextField("分类名称", text: $newFolderName) + Button("取消", role: .cancel) {} + Button("创建") { + Task { + if let f = try? await KnowledgeFolderService.shared.create(kbId: knowledgeBaseId, name: newFolderName) { + folders.append(f) + } + } + } + } + .alert("重命名", isPresented: $showRenameAlert) { + TextField("新名称", text: $renameName) + Button("取消", role: .cancel) {} + Button("确定") { + if let fid = renameFolderId { + Task { + try? await KnowledgeFolderService.shared.update(kbId: knowledgeBaseId, folderId: fid, name: renameName) + await load() + } + } + } + } + .task { await load() } + } + } + + private func load() async { + isLoading = true + folders = (try? await KnowledgeFolderService.shared.list(kbId: knowledgeBaseId)) ?? [] + isLoading = false + } +} diff --git a/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift b/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift index a7fe711..d1501e8 100644 --- a/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift +++ b/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift @@ -143,6 +143,7 @@ struct LibraryDetailPage: View { @State private var showBatchDeleteConfirm = false @State private var detailTab = 0 @State private var sortOption = 0 + @State private var showFolderManager = false @State private var sources: [KnowledgeSource] = [] @State private var isLoadingSources = false @@ -340,9 +341,9 @@ struct LibraryDetailPage: View { Label("添加知识点", image: "icon-plus") } Button { - // TODO: create folder + showFolderManager = true } label: { - Label("创建文件夹", image: "icon-folder") + Label("分类管理", image: "icon-folder") } NavigationLink(value: Route.quizList(knowledgeBaseId: knowledgeBaseId)) { Label("答题测验", image: "icon-pencil") @@ -392,6 +393,9 @@ struct LibraryDetailPage: View { Text("确定要删除选中的 \(selectedIds.count) 个知识点吗?此操作不可恢复。") } .task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId) } + .sheet(isPresented: $showFolderManager) { + FolderManageView(knowledgeBaseId: knowledgeBaseId) + } .onChange(of: sortOption) { _ in let (sb, od) = sortParams(sortOption) Task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId, sortBy: sb, order: od) }