// // APIModels.swift - 对齐 api-server 实际返回结构 // import Foundation // MARK: - API Envelope (ResponseInterceptor wraps all responses) struct APIEnvelope: Decodable { let success: Bool let data: T let timestamp: String? } // MARK: - Pagination struct PaginationMeta: Codable { let page: Int let limit: Int let total: Int } struct PaginatedResponse: Decodable { let data: [T] let meta: PaginationMeta } // MARK: - Waitlist struct WaitlistEntry: Codable, Identifiable { let id: String let nickname: String? let email: String let devices: [String]? let interests: [String]? let painpoint: String? let willingBeta: Bool? let createdAt: String? } struct WaitlistCreateRequest: Codable { let email: String let nickname: String? let devices: [String]? let interests: [String]? let painpoint: String? let willingBeta: Bool? init(email: String, nickname: String? = nil, devices: [String]? = nil, interests: [String]? = nil, painpoint: String? = nil, willingBeta: Bool = true) { self.email = email self.nickname = nickname self.devices = devices self.interests = interests self.painpoint = painpoint self.willingBeta = willingBeta } } struct WaitlistResponse: Codable { let success: Bool? let message: String? let data: WaitlistEntry? } struct WaitlistStats: Codable { let total: Int? let today: Int? let deviceBreakdown: [String: Int]? let interestBreakdown: [String: Int]? } // MARK: - Auth struct AuthResponse: Codable { let accessToken: String let refreshToken: String? let user: AuthUser? } struct AuthUser: Codable, Identifiable { let id: String let email: String? let nickname: String? let avatarUrl: String? let role: String? let status: String? let onboardingCompleted: Bool? } struct AppleAuthRequest: Codable { let identityToken: String let authorizationCode: String? let fullName: AppleFullName? let nonce: String? struct AppleFullName: Codable { let givenName: String? let familyName: String? } } struct RefreshRequest: Codable { let refreshToken: String } // MARK: - User Profile (matches GET /users/me with include: profile + preferences) struct UserProfileResponse: Codable, Identifiable { let id: String let email: String? let nickname: String? let avatarUrl: String? let role: String? let status: String? let onboardingCompleted: Bool? let createdAt: String? let profile: UserProfileData? let preferences: UserPreferences? } struct UserProfileData: Codable { let learningIdentity: String? let learningDirection: String? let bio: String? let currentGoal: String? } struct UserPreferences: Codable, Equatable { let preferredMethods: [String]? let defaultFocusMinutes: Int? let aiSuggestionLevel: String? let language: String? let appearance: String? let notificationEnabled: Bool? } struct UpdateProfileRequest: Codable { let nickname: String? let avatarUrl: String? } struct UpdatePreferencesRequest: Codable { let preferredMethods: [String]? let defaultFocusMinutes: Int? let aiSuggestionLevel: String? let language: String? let appearance: String? let notificationEnabled: Bool? } struct UpdateProfileDataRequest: Codable { let learningIdentity: String? let learningDirection: String? let bio: String? let currentGoal: String? } // MARK: - Knowledge Base (matches Prisma KnowledgeBase model) struct KnowledgeBase: Codable, Identifiable { let id: String let userId: String? let title: String let description: String? let coverKey: String? let coverUrl: String? let coverType: String? let visibility: String? let isPinned: Bool? let ownerType: String? let isVerified: Bool? let status: String? let itemCount: Int? let lastStudiedAt: String? let createdAt: String? let updatedAt: String? } struct CreateKnowledgeBaseRequest: Codable { let title: String let description: String? var coverKey: String? = nil } struct KnowledgeBaseCoverUploadResult { let fileId: String let objectKey: String } // MARK: - Knowledge Items (matches Prisma KnowledgeItem model) struct KnowledgeItem: Codable, Identifiable, Hashable { let id: String let userId: String? let knowledgeBaseId: String? let parentId: String? let itemType: String? let title: String let content: String? let summary: String? let sourceType: String? let sourceRef: String? let orderIndex: Int? let status: String? let createdAt: String? let updatedAt: String? } struct CreateKnowledgeItemRequest: Codable { let knowledgeBaseId: String let title: String let content: String? let itemType: String? } struct UpdateKnowledgeItemRequest: Codable { let title: String? let content: String? let summary: String? } // MARK: - Active Recall (matches ActiveRecallQuestion / Answer models) struct ActiveRecallQuestion: Codable, Identifiable { let id: String let userId: String? let knowledgeItemId: String? let questionText: String let difficulty: String? let createdBy: String? let createdAt: String? } struct ActiveRecallAnswer: Codable, Identifiable { let id: String let userId: String? let questionId: String? let answerType: String? let answerText: String? let submittedAt: String? } struct SubmitAnswerRequest: Codable { let answerText: String } // MARK: - AI Analysis (matches AiAnalysisResult model) struct AIAnalysisRequest: Codable { let questionText: String? let knowledgeItemContent: String? let userAnswer: String? let text: String? let type: String? } struct AIAnalysisResult: Codable, Identifiable { let id: String let userId: String? let summary: String? let masteryScore: Int? let strengths: [String]? let weaknesses: [String]? let suggestions: [String]? let nextActions: [String]? let rawResult: AIAnalysisRawResult? let createdAt: String? } struct AIAnalysisRawResult: Codable { let score: Double? let analysis: String? let focusItems: [String]? } // MARK: - Learning Session (matches Prisma LearningSession model) struct LearningSession: Codable, Identifiable { let id: String let userId: String? let knowledgeBaseId: String? let knowledgeItemId: String? let mode: String? let status: String? let startedAt: String? let endedAt: String? let durationSeconds: Int? let focusMinutes: Int? let createdAt: String? } struct CreateLearningSessionRequest: Codable { let knowledgeBaseId: String? let knowledgeItemId: String? let mode: String? } // MARK: - Review (matches ReviewCard / ReviewLog models) struct ReviewCard: Codable, Identifiable { let id: String let userId: String? let knowledgeItemId: String? let frontText: String let backText: String? let difficulty: String? let status: String? let nextReviewAt: String? let intervalDays: Int? let easeFactor: Double? let repetitionCount: Int? let lapseCount: Int? } struct SubmitReviewRequest: Codable { let rating: String let responseText: String? } // MARK: - Focus Items (matches Prisma FocusItem model) struct FocusItem: Codable, Identifiable { let id: String let userId: String? let knowledgeBaseId: String? let knowledgeItemId: String? let title: String let reason: String? let suggestion: String? let priority: String? let status: String? let masteryScore: Int? let dueAt: String? let completedAt: String? let createdAt: String? } // MARK: - Activity (matches DailyLearningActivity + summary aggregation) struct ActivitySummary: Codable { let totalMinutes: Int? let totalCardsReviewed: Int? let activeDays: Int? let dailyAverage: Int? } struct ActivityHeatmap: Codable { // Dictionary of "YYYY-MM-DD" -> durationSeconds } // MARK: - Feedback struct FeedbackCreateRequest: Codable { let category: String let content: String let email: String? init(category: String = "general", content: String, email: String? = nil) { self.category = category self.content = content self.email = email } } struct FeedbackData: Codable, Identifiable { let id: String? let category: String? let content: String? let status: String? let createdAt: String? } // MARK: - Notifications (matches Prisma Notification model) struct NotificationItem: Codable, Identifiable { let id: String let userId: String? let type: String let title: String let content: String? let data: [String: String]? let readAt: String? let createdAt: String? } // MARK: - Generic struct GenericSuccessResponse: Codable { let success: Bool? let message: String? } struct BatchDeleteResponse: Codable { let success: Bool? let message: String? let count: Int? } // MARK: - Quiz struct Quiz: Codable, Identifiable { let id: String let knowledgeBaseId: String? let title: String? let description: String? let questionCount: Int? let sourceType: String? let status: String? let createdAt: String? let questions: [QuizQuestion]? } struct QuizQuestion: Codable, Identifiable { let id: String let quizId: String? let type: String? let stem: String? let options: [String]? let answer: String? let explanation: String? let orderIndex: Int? } struct QuizAttempt: Codable, Identifiable { let id: String let quizId: String? let totalQuestions: Int? let correctCount: Int? let score: Int? let startedAt: String? let finishedAt: String? let answers: [QuizAnswer]? let quiz: QuizInfo? } struct QuizInfo: Codable { let title: String? } struct QuizAnswer: Codable, Identifiable { let id: String let attemptId: String? let questionId: String? let userAnswer: String? let isCorrect: Bool? let answeredAt: String? let question: QuizQuestion? } struct QuizGenerateRequest: Codable { let knowledgeBaseId: String let questionCount: Int } struct QuizSubmitRequest: Codable { let attemptId: String let answers: [QuizAnswerItem] } struct QuizAnswerItem: Codable { let questionId: String let answer: String } struct QuizSubmitResponse: Codable { let score: Int? let correctCount: Int? let totalQuestions: Int? let finishedAt: String? } // MARK: - RAG Chat // MARK: - Chat Scope enum ChatScopeType: String, Codable { case knowledgeBase = "knowledge_base" case folder = "folder" case material = "material" case knowledgeItem = "knowledge_item" case global = "global" } struct ChatEntryContext: Hashable { let scopeType: ChatScopeType let scopeId: String? let scopeName: String let parentKnowledgeBaseId: String? let createdFrom: String } struct ChatSession: Codable, Identifiable { let id: String let userId: String? let knowledgeBaseId: String? let scopeType: String? let scopeId: String? let parentKnowledgeBaseId: String? let title: String? let createdFrom: String? let isPinned: Bool? let isArchived: Bool? let isDeleted: Bool? let modelMode: String? let modelId: String? let lastMessageAt: String? let createdAt: String? let updatedAt: String? } struct ChatMessage: Codable, Identifiable { let id: String let sessionId: String? let role: String let content: String let tokens: Int? let scopeSnapshot: ChatScopeSnapshot? let createdAt: String? let citations: [ChatCitation]? } struct ChatScopeSnapshot: Codable { let scopeType: String? let scopeId: String? let parentKnowledgeBaseId: String? } struct ChatCitation: Codable, Identifiable { let id: String let chunkId: String? let sourceId: String? let sourceTitle: String? let excerptText: String? let pageNumber: Int? let lineStart: Int? let lineEnd: Int? } struct CreateSessionRequest: Codable { let scopeType: String let scopeId: String? let parentKnowledgeBaseId: String? let createdFrom: String let title: String? let forceCreate: Bool? } struct UpdateChatSessionRequest: Codable { var title: String? var isPinned: Bool? var isArchived: Bool? var modelMode: String? var modelId: String? } struct SendMessageRequest: Codable { let content: String } struct SendMessageResponse: Codable { let id: String? let role: String? let content: String? let tokens: Int? let blocked: Bool? let message: ChatMessage? let citations: [ChatCitation]? } // MARK: - Document Import struct CreateImportRequest: Codable { let knowledgeBaseId: String let fileName: String? let sourceType: String? let rawText: String? } struct ImportStatusResponse: Codable { let id: String let status: String? let fileName: String? let knowledgeBaseId: String? let errorMessage: String? let itemCount: Int? } // MARK: - Import Candidate struct ImportCandidate: Codable, Identifiable { let id: String let sourceId: String? let title: String? let content: String? let status: String? let confidence: Double? let createdAt: String? } struct BatchAcceptRequest: Codable { let sourceId: String } // MARK: - Knowledge Source struct KnowledgeSource: Codable, Identifiable { let id: String let knowledgeBaseId: String? let title: String? let originalFilename: String? let type: String? let mimeType: String? let parseStatus: String? let indexStatus: String? let textLength: Int? let createdAt: String? } struct AddSourceRequest: Codable { let fileId: String? let type: String? let title: String? } // MARK: - Learning Activity struct ActivityTrend: Codable { let date: String? let value: Double? let label: String? } struct ActivityStreak: Codable { let currentStreak: Int? let longestStreak: Int? let lastActiveDate: String? } struct ActivityRecommendation: Codable, Identifiable { var id: String { title ?? UUID().uuidString } let title: String? let description: String? let type: String? let priority: String? } // MARK: - File Upload (COS presigned URL flow) struct FileUploadUrlRequest: Codable { let filename: String let mimeType: String let sizeBytes: Int } struct FileUploadUrlResponse: Codable { let uploadUrl: String let objectKey: String let bucket: String let region: String let expiresIn: Int } struct FileConfirmUploadRequest: Codable { let objectKey: String let checksum: String? } struct FileConfirmUploadResponse: Codable { let id: String let filename: String let objectKey: String let sizeBytes: Int } struct FileDetailResponse: Codable { let file: FileInfo let downloadUrl: String } struct FileInfo: Codable { let id: String let filename: String let mimeType: String? let sizeBytes: Int let objectKey: String? let storagePath: String? let createdAt: String? }