wangdl 90ad19aad1 feat(ios): IOS-M1-05 我的页面资产摘要 + 存储空间
- ProfileView 新增学习资产行(KB数/知识点/复习卡)
- 新增存储空间行(已用/总量)
- 新增消息中心快捷入口
- UserService 新增 fetchAssets()/fetchStorage()
- ProfileViewModel 新增 loadStats()

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 19:50:26 +08:00

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
}