import 'package:intl/intl.dart'; import 'package:uuid/uuid.dart'; import '../utils/date_time_parser.dart'; // 聊天会话模型 class ChatSession { ChatSession({ required this.id, required this.title, String? selectedModelConfigId, required this.createdAt, required this.lastUpdatedAt, required this.novelId, this.chapterId, this.status, this.messageCount, this.metadata, }) : selectedModelConfigId = selectedModelConfigId; // 从JSON转换方法 factory ChatSession.fromJson(Map json) { // 辅助函数安全地获取和转换 String String safeString(String key, [String defaultValue = '']) { return json[key] as String? ?? defaultValue; } // 辅助函数安全地获取和解析 DateTime DateTime safeDateTime(String key, DateTime defaultValue) { final value = json[key] as String?; return value != null ? (DateTime.tryParse(value) ?? defaultValue) : defaultValue; } return ChatSession( // 使用 sessionId 作为 id,并提供一个默认空字符串以防万一 id: safeString('sessionId'), title: safeString('title', '无标题会话'), selectedModelConfigId: json['selectedModelConfigId'] as String?, createdAt: parseBackendDateTime(json['createdAt']), lastUpdatedAt: parseBackendDateTime(json['updatedAt']), novelId: safeString('novelId'), chapterId: json['chapterId'] as String?, status: json['status'] as String?, messageCount: (json['messageCount'] as num?)?.toInt() ?? 0, metadata: json['metadata'] as Map?, ); } final String id; final String title; final String? selectedModelConfigId; final DateTime createdAt; final DateTime lastUpdatedAt; final String novelId; final String? chapterId; final String? status; final int? messageCount; final Map? metadata; // 复制方法,用于创建会话的副本 ChatSession copyWith({ String? id, String? title, String? selectedModelConfigId, DateTime? createdAt, DateTime? lastUpdatedAt, String? novelId, String? chapterId, String? status, int? messageCount, Map? metadata, }) { return ChatSession( id: id ?? this.id, title: title ?? this.title, selectedModelConfigId: selectedModelConfigId ?? this.selectedModelConfigId, createdAt: createdAt ?? this.createdAt, lastUpdatedAt: lastUpdatedAt ?? this.lastUpdatedAt, novelId: novelId ?? this.novelId, chapterId: chapterId ?? this.chapterId, status: status ?? this.status, messageCount: messageCount ?? this.messageCount, metadata: metadata ?? this.metadata, ); } // 转换为JSON方法 Map toJson() { return { 'id': id, 'title': title, 'selectedModelConfigId': selectedModelConfigId, 'createdAt': createdAt.toIso8601String(), 'lastUpdatedAt': lastUpdatedAt.toIso8601String(), 'novelId': novelId, 'chapterId': chapterId, 'status': status, 'messageCount': messageCount, 'metadata': metadata, }; } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is ChatSession && other.id == id && other.title == title && other.selectedModelConfigId == selectedModelConfigId && other.createdAt == createdAt && other.lastUpdatedAt == lastUpdatedAt && other.novelId == novelId && other.chapterId == chapterId && other.status == status && other.messageCount == messageCount && other.metadata == metadata; } @override int get hashCode { return id.hashCode ^ title.hashCode ^ selectedModelConfigId.hashCode ^ createdAt.hashCode ^ lastUpdatedAt.hashCode ^ novelId.hashCode ^ chapterId.hashCode ^ status.hashCode ^ messageCount.hashCode ^ metadata.hashCode; } } // 聊天消息模型 class ChatMessage { ChatMessage({ required this.id, required this.role, required this.content, required this.timestamp, this.status = MessageStatus.sent, this.actions, this.sessionId, this.userId, this.novelId, this.modelName, this.metadata, required this.sender, }); // 从JSON转换方法 factory ChatMessage.fromJson(Map json) { // --- Helper for safe string parsing --- String safeString(String key, [String defaultValue = '']) { final value = json[key]; if (value is String) return value; // Log or handle non-string/null cases if needed // AppLogger.w('ChatMessage.fromJson', 'Expected String for key "$key", but got ${value?.runtimeType}. Using default.'); return defaultValue; } List? parsedActions; if (json['metadata'] != null && json['metadata']['actions'] is List) { parsedActions = (json['metadata']['actions'] as List) .map((e) => MessageAction.fromJson(e as Map)) .toList(); } // --- Handle potentially null 'id' safely --- // Provide a temporary unique default if 'id' is null. // The Bloc logic will prioritize its own placeholder ID anyway. final messageId = safeString('id', 'temp_chunk_${const Uuid().v4()}'); return ChatMessage( id: messageId, // Use the safe ID role: MessageRole.values.firstWhere( // Use safeString for role (e) => e.name == safeString('role', MessageRole.system.name).toLowerCase(), orElse: () => MessageRole.system, // Fallback ), // Use safeString for content, important for potentially empty chunks content: safeString('content'), // Assume parseBackendDateTime handles the list format or null // Provide a fallback default DateTime if key is missing or parsing fails timestamp: parseBackendDateTime(json['createdAt'] ?? DateTime.now()), status: MessageStatus.values.firstWhere( // Use safeString for status (e) => e.name == safeString('status', MessageStatus.sent.name).toLowerCase(), orElse: () => MessageStatus.sent, // Fallback ), actions: parsedActions, // These fields allow null, direct access is relatively safe but casting is good practice sessionId: json['sessionId'] as String?, userId: json['userId'] as String?, novelId: json['novelId'] as String?, modelName: json['modelName'] as String?, metadata: json['metadata'] as Map?, sender: MessageSender.values.firstWhere( (e) => e.name == safeString('sender', MessageSender.user.name).toLowerCase(), orElse: () => MessageSender.user, ), ); } final String id; final MessageRole role; final String content; final DateTime timestamp; final MessageStatus status; final List? actions; final String? sessionId; final String? userId; final String? novelId; final String? modelName; final Map? metadata; final MessageSender sender; // 复制方法 ChatMessage copyWith({ String? id, MessageRole? role, String? content, DateTime? timestamp, MessageStatus? status, List? actions, String? sessionId, String? userId, String? novelId, String? modelName, Map? metadata, MessageSender? sender, }) { return ChatMessage( id: id ?? this.id, role: role ?? this.role, content: content ?? this.content, timestamp: timestamp ?? this.timestamp, status: status ?? this.status, actions: actions ?? this.actions, sessionId: sessionId ?? this.sessionId, userId: userId ?? this.userId, novelId: novelId ?? this.novelId, modelName: modelName ?? this.modelName, metadata: metadata ?? this.metadata, sender: sender ?? this.sender, ); } // 转换为JSON方法 Map toJson() { final Map currentMetadata = Map.from(metadata ?? {}); if (actions != null) { currentMetadata['actions'] = actions!.map((e) => e.toJson()).toList(); } return { 'id': id, 'role': role.name, 'content': content, 'createdAt': timestamp.toIso8601String(), 'status': status.name, 'sessionId': sessionId, 'userId': userId, 'novelId': novelId, 'modelName': modelName, 'metadata': currentMetadata.isEmpty ? null : currentMetadata, 'sender': sender.name, }; } // 格式化时间戳 String get formattedTime => DateFormat('HH:mm').format(timestamp); // 格式化日期 String get formattedDate => DateFormat('yyyy-MM-dd').format(timestamp); } // 消息发送者角色 enum MessageRole { user, assistant, system, } // 消息状态 enum MessageStatus { sending, sent, error, pending, delivered, read, streaming, } // 消息关联操作 class MessageAction { MessageAction({ required this.id, required this.label, required this.type, this.data, }); // 从JSON转换方法 factory MessageAction.fromJson(Map json) { return MessageAction( id: json['id'] as String, label: json['label'] as String, type: ActionType.values.firstWhere( (e) => e.toString() == 'ActionType.${json['type']}', ), data: json['data'] as Map?, ); } final String id; final String label; final ActionType type; final Map? data; // 转换为JSON方法 Map toJson() { return { 'id': id, 'label': label, 'type': type.toString().split('.').last, 'data': data, }; } } // 操作类型 enum ActionType { applyToEditor, createCharacter, createLocation, generatePlot, expandScene, createChapter, analyzeSentiment, fixGrammar, } // 聊天上下文模型 class ChatContext { ChatContext({ required this.novelId, this.chapterId, this.selectedText, this.relevantItems = const [], }); // 从JSON转换方法 factory ChatContext.fromJson(Map json) { return ChatContext( novelId: json['novelId'] as String, chapterId: json['chapterId'] as String?, selectedText: json['selectedText'] as String?, relevantItems: json['relevantItems'] != null ? (json['relevantItems'] as List) .map((e) => ContextItem.fromJson(e as Map)) .toList() : [], ); } final String novelId; final String? chapterId; final String? selectedText; final List relevantItems; // 复制方法 ChatContext copyWith({ String? novelId, String? chapterId, String? selectedText, List? relevantItems, }) { return ChatContext( novelId: novelId ?? this.novelId, chapterId: chapterId ?? this.chapterId, selectedText: selectedText ?? this.selectedText, relevantItems: relevantItems ?? this.relevantItems, ); } // 转换为JSON方法 Map toJson() { return { 'novelId': novelId, 'chapterId': chapterId, 'selectedText': selectedText, 'relevantItems': relevantItems.map((e) => e.toJson()).toList(), }; } } // 上下文项目 class ContextItem { ContextItem({ required this.id, required this.type, required this.title, required this.content, required this.relevanceScore, }); // 从JSON转换方法 factory ContextItem.fromJson(Map json) { return ContextItem( id: json['id'] as String, type: ContextItemType.values.firstWhere( (e) => e.toString() == 'ContextItemType.${json['type']}', ), title: json['title'] as String, content: json['content'] as String, relevanceScore: json['relevanceScore'] as double, ); } final String id; final ContextItemType type; final String title; final String content; final double relevanceScore; // 转换为JSON方法 Map toJson() { return { 'id': id, 'type': type.toString().split('.').last, 'title': title, 'content': content, 'relevanceScore': relevanceScore, }; } } // 上下文项目类型 enum ContextItemType { character, location, plot, chapter, scene, note, lore, } // 消息发送者 enum MessageSender { user, ai }