- ProfileView 新增学习资产行(KB数/知识点/复习卡) - 新增存储空间行(已用/总量) - 新增消息中心快捷入口 - UserService 新增 fetchAssets()/fetchStorage() - ProfileViewModel 新增 loadStats() Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
501 lines
18 KiB
Swift
501 lines
18 KiB
Swift
//
|
|
// APIService.swift - 各模块 API 调用封装
|
|
//
|
|
|
|
import Foundation
|
|
|
|
// MARK: - Waitlist
|
|
|
|
@MainActor
|
|
class WaitlistService {
|
|
static let shared = WaitlistService()
|
|
private let client = APIClient.shared
|
|
|
|
func join(email: String, nickname: String?, devices: [String]?,
|
|
interests: [String]?, painpoint: String?, willingBeta: Bool = true) async throws -> WaitlistEntry {
|
|
let body = WaitlistCreateRequest(email: email, nickname: nickname, devices: devices,
|
|
interests: interests, painpoint: painpoint, willingBeta: willingBeta)
|
|
return try await client.request("/waitlist", method: "POST", body: body)
|
|
}
|
|
|
|
func stats() async throws -> WaitlistStats {
|
|
return try await client.request("/waitlist/stats")
|
|
}
|
|
}
|
|
|
|
// MARK: - Auth
|
|
|
|
@MainActor
|
|
class AuthService {
|
|
static let shared = AuthService()
|
|
private let client = APIClient.shared
|
|
|
|
func appleLogin(identityToken: String, authorizationCode: String? = nil, givenName: String?, familyName: String?, nonce: String? = nil) async throws -> AuthResponse {
|
|
let body = AppleAuthRequest(
|
|
identityToken: identityToken,
|
|
authorizationCode: authorizationCode,
|
|
fullName: givenName != nil
|
|
? AppleAuthRequest.AppleFullName(givenName: givenName, familyName: familyName)
|
|
: nil,
|
|
nonce: nonce
|
|
)
|
|
return try await client.request("/auth/apple", method: "POST", body: body)
|
|
}
|
|
}
|
|
|
|
// MARK: - User
|
|
|
|
@MainActor
|
|
class UserService {
|
|
static let shared = UserService()
|
|
private let client = APIClient.shared
|
|
|
|
func myProfile() async throws -> UserProfileResponse {
|
|
return try await client.request("/users/me")
|
|
}
|
|
|
|
func updateProfile(_ dto: UpdateProfileRequest) async throws -> UserProfileResponse {
|
|
return try await client.request("/users/me", method: "PATCH", body: dto)
|
|
}
|
|
|
|
func updatePreferences(_ dto: UpdatePreferencesRequest) async throws -> UserPreferences {
|
|
return try await client.request("/users/me/preferences", method: "PATCH", body: dto)
|
|
}
|
|
|
|
func getProfileDetail() async throws -> UserProfileData {
|
|
return try await client.request("/users/me/profile")
|
|
}
|
|
|
|
func updateProfileDetail(_ dto: UpdateProfileDataRequest) async throws -> UserProfileData {
|
|
return try await client.request("/users/me/profile", method: "PATCH", body: dto)
|
|
}
|
|
|
|
func requestAccountDeletion() async throws -> GenericSuccessResponse {
|
|
return try await client.request("/users/me/deletion-request", method: "POST")
|
|
}
|
|
|
|
func cancelDeletion() async throws -> GenericSuccessResponse {
|
|
return try await client.request("/users/me/deletion-request", method: "DELETE")
|
|
}
|
|
|
|
func listDevices() async throws -> [UserDevice] {
|
|
return try await client.request("/users/me/devices")
|
|
}
|
|
|
|
func removeDevice(id: String) async throws -> GenericSuccessResponse {
|
|
return try await client.request("/users/me/devices/\(id)", method: "DELETE")
|
|
}
|
|
|
|
func fetchAssets() async throws -> AssetsResponse {
|
|
return try await client.request("/users/me/assets-summary")
|
|
}
|
|
|
|
func fetchStorage() async throws -> StorageResponse {
|
|
return try await client.request("/users/me/storage")
|
|
}
|
|
}
|
|
|
|
struct UserDevice: Codable, Identifiable {
|
|
let id: String
|
|
let deviceName: String?
|
|
let deviceType: String?
|
|
let lastSeenAt: String?
|
|
}
|
|
|
|
// MARK: - Knowledge Base
|
|
|
|
@MainActor
|
|
class KnowledgeBaseService {
|
|
static let shared = KnowledgeBaseService()
|
|
private let client = APIClient.shared
|
|
|
|
func list(page: Int = 1, limit: Int = 20) async throws -> [KnowledgeBase] {
|
|
return try await client.request("/knowledge-bases", queryItems: [
|
|
URLQueryItem(name: "page", value: String(page)),
|
|
URLQueryItem(name: "limit", value: String(limit)),
|
|
])
|
|
}
|
|
|
|
func create(title: String, description: String?, coverKey: String? = nil) async throws -> KnowledgeBase {
|
|
let body = CreateKnowledgeBaseRequest(title: title, description: description, coverKey: coverKey)
|
|
return try await client.request("/knowledge-bases", method: "POST", body: body)
|
|
}
|
|
|
|
func detail(id: String) async throws -> KnowledgeBase {
|
|
return try await client.request("/knowledge-bases/\(id)")
|
|
}
|
|
|
|
func update(id: String, title: String?, description: String?) async throws -> KnowledgeBase {
|
|
let body = CreateKnowledgeBaseRequest(title: title ?? "", description: description)
|
|
return try await client.request("/knowledge-bases/\(id)", method: "PATCH", body: body)
|
|
}
|
|
|
|
func listSubscribed(page: Int = 1, limit: Int = 20) async throws -> [KnowledgeBase] {
|
|
return try await client.request("/knowledge-bases/subscribed", queryItems: [
|
|
URLQueryItem(name: "page", value: String(page)),
|
|
URLQueryItem(name: "limit", value: String(limit)),
|
|
])
|
|
}
|
|
|
|
func delete(id: String) async throws -> GenericSuccessResponse {
|
|
return try await client.request("/knowledge-bases/\(id)", method: "DELETE")
|
|
}
|
|
}
|
|
|
|
// MARK: - Knowledge Items
|
|
|
|
@MainActor
|
|
class KnowledgeItemService {
|
|
static let shared = KnowledgeItemService()
|
|
private let client = APIClient.shared
|
|
|
|
func list(knowledgeBaseId: String) async throws -> [KnowledgeItem] {
|
|
return try await client.request("/knowledge-items", queryItems: [
|
|
URLQueryItem(name: "knowledgeBaseId", value: knowledgeBaseId),
|
|
])
|
|
}
|
|
|
|
func detail(id: String) async throws -> KnowledgeItem {
|
|
return try await client.request("/knowledge-items/\(id)")
|
|
}
|
|
|
|
func create(knowledgeBaseId: String, title: String, content: String?, itemType: String? = nil) async throws -> KnowledgeItem {
|
|
let body = CreateKnowledgeItemRequest(
|
|
knowledgeBaseId: knowledgeBaseId,
|
|
title: title,
|
|
content: content,
|
|
itemType: itemType
|
|
)
|
|
return try await client.request("/knowledge-items", method: "POST", body: body)
|
|
}
|
|
|
|
func update(id: String, title: String?, content: String?, summary: String?) async throws -> KnowledgeItem {
|
|
let body = UpdateKnowledgeItemRequest(title: title, content: content, summary: summary)
|
|
return try await client.request("/knowledge-items/\(id)", method: "PATCH", body: body)
|
|
}
|
|
|
|
func delete(id: String) async throws {
|
|
let _: GenericSuccessResponse = try await client.request("/knowledge-items/\(id)", method: "DELETE")
|
|
}
|
|
|
|
func batchDelete(ids: [String]) async throws -> BatchDeleteResponse {
|
|
return try await client.request("/knowledge-items/batch-delete", method: "POST", body: ["ids": ids])
|
|
}
|
|
}
|
|
|
|
// MARK: - Active Recall
|
|
|
|
@MainActor
|
|
class ActiveRecallService {
|
|
static let shared = ActiveRecallService()
|
|
private let client = APIClient.shared
|
|
|
|
func questions(page: Int = 1, limit: Int = 20) async throws -> [ActiveRecallQuestion] {
|
|
return try await client.request("/active-recalls", queryItems: [
|
|
URLQueryItem(name: "page", value: String(page)),
|
|
URLQueryItem(name: "limit", value: String(limit)),
|
|
])
|
|
}
|
|
|
|
func submit(questionId: String, answerText: String) async throws -> ActiveRecallAnswer {
|
|
let body = SubmitAnswerRequest(answerText: answerText)
|
|
return try await client.request("/active-recalls/\(questionId)/submit", method: "POST", body: body)
|
|
}
|
|
}
|
|
|
|
// MARK: - AI Analysis
|
|
|
|
@MainActor
|
|
class AIAnalysisService {
|
|
static let shared = AIAnalysisService()
|
|
private let client = APIClient.shared
|
|
|
|
func analyze(questionText: String, knowledgeItemContent: String, userAnswer: String) async throws -> AIAnalysisResult {
|
|
let body = AIAnalysisRequest(
|
|
questionText: questionText,
|
|
knowledgeItemContent: knowledgeItemContent,
|
|
userAnswer: userAnswer,
|
|
text: nil,
|
|
type: nil
|
|
)
|
|
return try await client.request("/ai-analysis", method: "POST", body: body)
|
|
}
|
|
}
|
|
|
|
// MARK: - Learning Sessions
|
|
|
|
@MainActor
|
|
class LearningSessionService {
|
|
static let shared = LearningSessionService()
|
|
private let client = APIClient.shared
|
|
|
|
func list(page: Int = 1, limit: Int = 20) async throws -> [LearningSession] {
|
|
return try await client.request("/learning-sessions", queryItems: [
|
|
URLQueryItem(name: "page", value: String(page)),
|
|
URLQueryItem(name: "limit", value: String(limit)),
|
|
])
|
|
}
|
|
|
|
func start(knowledgeBaseId: String? = nil, knowledgeItemId: String? = nil, mode: String? = nil) async throws -> LearningSession {
|
|
let body = CreateLearningSessionRequest(
|
|
knowledgeBaseId: knowledgeBaseId,
|
|
knowledgeItemId: knowledgeItemId,
|
|
mode: mode
|
|
)
|
|
return try await client.request("/learning-sessions", method: "POST", body: body)
|
|
}
|
|
|
|
func end(id: String) async throws -> LearningSession {
|
|
return try await client.request("/learning-sessions/\(id)/end", method: "POST")
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Review
|
|
|
|
@MainActor
|
|
class ReviewService {
|
|
static let shared = ReviewService()
|
|
private let client = APIClient.shared
|
|
|
|
func dueCards() async throws -> [ReviewCard] {
|
|
return try await client.request("/reviews/due")
|
|
}
|
|
|
|
func submit(id: String, rating: String, responseText: String? = nil) async throws -> GenericSuccessResponse {
|
|
let body = SubmitReviewRequest(rating: rating, responseText: responseText)
|
|
return try await client.request("/reviews/\(id)/submit", method: "POST", body: body)
|
|
}
|
|
|
|
func generateCards() async throws -> GenericSuccessResponse {
|
|
return try await client.request("/reviews/generate-cards", method: "POST")
|
|
}
|
|
}
|
|
|
|
// MARK: - Focus Items
|
|
|
|
@MainActor
|
|
class FocusItemService {
|
|
static let shared = FocusItemService()
|
|
private let client = APIClient.shared
|
|
|
|
func list(page: Int = 1, limit: Int = 20) async throws -> [FocusItem] {
|
|
return try await client.request("/focus-items", queryItems: [
|
|
URLQueryItem(name: "page", value: String(page)),
|
|
URLQueryItem(name: "limit", value: String(limit)),
|
|
])
|
|
}
|
|
|
|
func update(id: String, masteryScore: Int? = nil, title: String? = nil) async throws -> FocusItem {
|
|
let body = FocusItemUpdateRequest(masteryScore: masteryScore, title: title)
|
|
return try await client.request("/focus-items/\(id)", method: "PATCH", body: body)
|
|
}
|
|
|
|
func complete(id: String) async throws -> GenericSuccessResponse {
|
|
return try await client.request("/focus-items/\(id)/complete", method: "POST")
|
|
}
|
|
}
|
|
|
|
// MARK: - Activity & Stats
|
|
|
|
@MainActor
|
|
class ActivityService {
|
|
static let shared = ActivityService()
|
|
private let client = APIClient.shared
|
|
|
|
func summary() async throws -> ActivitySummary {
|
|
return try await client.request("/activity/summary")
|
|
}
|
|
|
|
func heatmap() async throws -> [String: Int] {
|
|
return try await client.request("/activity/heatmap")
|
|
}
|
|
|
|
func trend(days: Int = 7) async throws -> [ActivityTrend] {
|
|
return try await client.request("/activity/trend?days=\(days)")
|
|
}
|
|
|
|
func streak() async throws -> ActivityStreak {
|
|
return try await client.request("/activity/streak")
|
|
}
|
|
|
|
func recommendations() async throws -> [ActivityRecommendation] {
|
|
return try await client.request("/activity/recommendations")
|
|
}
|
|
}
|
|
|
|
// MARK: - Feedback
|
|
|
|
@MainActor
|
|
class FeedbackService {
|
|
static let shared = FeedbackService()
|
|
private let client = APIClient.shared
|
|
|
|
func submit(category: String = "general", content: String, email: String? = nil) async throws -> FeedbackData {
|
|
let body = FeedbackCreateRequest(category: category, content: content, email: email)
|
|
return try await client.request("/feedback", method: "POST", body: body)
|
|
}
|
|
}
|
|
|
|
// MARK: - RAG Chat
|
|
|
|
@MainActor
|
|
class RagChatService {
|
|
static let shared = RagChatService()
|
|
private let client = APIClient.shared
|
|
|
|
func createSession(knowledgeBaseId: String, title: String? = nil) async throws -> ChatSession {
|
|
let body = CreateSessionRequest(knowledgeBaseId: knowledgeBaseId, title: title)
|
|
return try await client.request("/rag-chat/sessions", method: "POST", body: body)
|
|
}
|
|
|
|
func listSessions() async throws -> [ChatSession] {
|
|
return try await client.request("/rag-chat/sessions")
|
|
}
|
|
|
|
func getMessages(sessionId: String) async throws -> [ChatMessage] {
|
|
return try await client.request("/rag-chat/sessions/\(sessionId)/messages")
|
|
}
|
|
|
|
func sendMessage(sessionId: String, content: String) async throws -> SendMessageResponse {
|
|
let body = SendMessageRequest(content: content)
|
|
return try await client.request("/rag-chat/sessions/\(sessionId)/messages", method: "POST", body: body)
|
|
}
|
|
|
|
func deleteSession(id: String) async throws {
|
|
let _: GenericSuccessResponse = try await client.request("/rag-chat/sessions/\(id)", method: "DELETE")
|
|
}
|
|
}
|
|
|
|
// MARK: - Document Import
|
|
|
|
@MainActor
|
|
class DocumentImportService {
|
|
static let shared = DocumentImportService()
|
|
private let client = APIClient.shared
|
|
|
|
func create(knowledgeBaseId: String, fileName: String? = nil, sourceType: String? = "file", rawText: String? = nil) async throws -> ImportStatusResponse {
|
|
let body = CreateImportRequest(knowledgeBaseId: knowledgeBaseId, fileName: fileName, sourceType: sourceType, rawText: rawText)
|
|
return try await client.request("/imports", method: "POST", body: body)
|
|
}
|
|
|
|
func getStatus(id: String) async throws -> ImportStatusResponse {
|
|
return try await client.request("/imports/\(id)/status")
|
|
}
|
|
}
|
|
|
|
// MARK: - Import Candidate
|
|
|
|
@MainActor
|
|
class ImportCandidateService {
|
|
static let shared = ImportCandidateService()
|
|
private let client = APIClient.shared
|
|
|
|
func listBySource(sourceId: String) async throws -> [ImportCandidate] {
|
|
return try await client.request("/knowledge-sources/\(sourceId)/import-candidates")
|
|
}
|
|
|
|
func detail(id: String) async throws -> ImportCandidate {
|
|
return try await client.request("/import-candidates/\(id)")
|
|
}
|
|
|
|
func update(id: String, title: String?, content: String?) async throws -> ImportCandidate {
|
|
var body: [String: String] = [:]
|
|
if let t = title { body["title"] = t }
|
|
if let c = content { body["content"] = c }
|
|
return try await client.request("/import-candidates/\(id)", method: "PATCH", body: body)
|
|
}
|
|
|
|
func accept(id: String) async throws -> GenericSuccessResponse {
|
|
return try await client.request("/import-candidates/\(id)/accept", method: "POST")
|
|
}
|
|
|
|
func reject(id: String) async throws -> GenericSuccessResponse {
|
|
return try await client.request("/import-candidates/\(id)/reject", method: "POST")
|
|
}
|
|
|
|
func batchAccept(sourceId: String) async throws -> GenericSuccessResponse {
|
|
let body = BatchAcceptRequest(sourceId: sourceId)
|
|
return try await client.request("/import-candidates/batch-accept", method: "POST", body: body)
|
|
}
|
|
}
|
|
|
|
// MARK: - Knowledge Source
|
|
|
|
@MainActor
|
|
class KnowledgeSourceService {
|
|
static let shared = KnowledgeSourceService()
|
|
private let client = APIClient.shared
|
|
|
|
func list(kbId: String) async throws -> [KnowledgeSource] {
|
|
return try await client.request("/knowledge-bases/\(kbId)/sources")
|
|
}
|
|
|
|
func detail(kbId: String, id: String) async throws -> KnowledgeSource {
|
|
return try await client.request("/knowledge-bases/\(kbId)/sources/\(id)")
|
|
}
|
|
|
|
func add(kbId: String, fileId: String? = nil, type: String? = nil, title: String? = nil) async throws -> KnowledgeSource {
|
|
let body = AddSourceRequest(fileId: fileId, type: type, title: title)
|
|
return try await client.request("/knowledge-bases/\(kbId)/sources", method: "POST", body: body)
|
|
}
|
|
|
|
func delete(kbId: String, id: String) async throws -> GenericSuccessResponse {
|
|
return try await client.request("/knowledge-bases/\(kbId)/sources/\(id)", method: "DELETE")
|
|
}
|
|
}
|
|
|
|
// MARK: - Notifications
|
|
|
|
@MainActor
|
|
class NotificationService {
|
|
static let shared = NotificationService()
|
|
private let client = APIClient.shared
|
|
|
|
func list(page: Int = 1, limit: Int = 20) async throws -> [NotificationItem] {
|
|
return try await client.request("/notifications", queryItems: [
|
|
URLQueryItem(name: "page", value: String(page)),
|
|
URLQueryItem(name: "limit", value: String(limit)),
|
|
])
|
|
}
|
|
|
|
func markRead(id: String) async throws -> NotificationItem {
|
|
return try await client.request("/notifications/\(id)/read", method: "PATCH")
|
|
}
|
|
|
|
func markAllRead() async throws -> GenericSuccessResponse {
|
|
return try await client.request("/notifications/read-all", method: "POST")
|
|
}
|
|
|
|
func getPreferences() async throws -> NotificationPreferences {
|
|
return try await client.request("/notifications/preferences")
|
|
}
|
|
|
|
func updatePreferences(_ dto: NotificationPreferences) async throws -> NotificationPreferences {
|
|
return try await client.request("/notifications/preferences", method: "PATCH", body: dto)
|
|
}
|
|
|
|
func registerPushToken(_ token: String) async throws -> GenericSuccessResponse {
|
|
return try await client.request("/notifications/push-token", method: "POST", body: ["token": token])
|
|
}
|
|
|
|
func removePushToken(_ token: String) async throws -> GenericSuccessResponse {
|
|
return try await client.request("/notifications/push-tokens/\(token)", method: "DELETE")
|
|
}
|
|
}
|
|
|
|
struct NotificationPreferences: Codable {
|
|
var reviewReminder: Bool?
|
|
var newFeatures: Bool?
|
|
var systemNotice: Bool?
|
|
}
|
|
|
|
struct FocusItemUpdateRequest: Codable {
|
|
let masteryScore: Int?
|
|
let title: String?
|
|
}
|
|
|
|
struct FocusItemCompleteRequest: Codable {
|
|
let id: String
|
|
}
|