马良AI写作初始化仓库
This commit is contained in:
456
AINoval/lib/models/chat_models.dart
Normal file
456
AINoval/lib/models/chat_models.dart
Normal file
@@ -0,0 +1,456 @@
|
||||
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<String, dynamic> 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<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
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<String, dynamic>? metadata;
|
||||
|
||||
// 复制方法,用于创建会话的副本
|
||||
ChatSession copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? selectedModelConfigId,
|
||||
DateTime? createdAt,
|
||||
DateTime? lastUpdatedAt,
|
||||
String? novelId,
|
||||
String? chapterId,
|
||||
String? status,
|
||||
int? messageCount,
|
||||
Map<String, dynamic>? 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<String, dynamic> 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<String, dynamic> 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<MessageAction>? parsedActions;
|
||||
if (json['metadata'] != null && json['metadata']['actions'] is List) {
|
||||
parsedActions = (json['metadata']['actions'] as List)
|
||||
.map((e) => MessageAction.fromJson(e as Map<String, dynamic>))
|
||||
.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<String, dynamic>?,
|
||||
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<MessageAction>? actions;
|
||||
final String? sessionId;
|
||||
final String? userId;
|
||||
final String? novelId;
|
||||
final String? modelName;
|
||||
final Map<String, dynamic>? metadata;
|
||||
final MessageSender sender;
|
||||
|
||||
// 复制方法
|
||||
ChatMessage copyWith({
|
||||
String? id,
|
||||
MessageRole? role,
|
||||
String? content,
|
||||
DateTime? timestamp,
|
||||
MessageStatus? status,
|
||||
List<MessageAction>? actions,
|
||||
String? sessionId,
|
||||
String? userId,
|
||||
String? novelId,
|
||||
String? modelName,
|
||||
Map<String, dynamic>? 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<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> 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<String, dynamic> 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<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
final String id;
|
||||
final String label;
|
||||
final ActionType type;
|
||||
final Map<String, dynamic>? data;
|
||||
|
||||
// 转换为JSON方法
|
||||
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic>))
|
||||
.toList()
|
||||
: [],
|
||||
);
|
||||
}
|
||||
final String novelId;
|
||||
final String? chapterId;
|
||||
final String? selectedText;
|
||||
final List<ContextItem> relevantItems;
|
||||
|
||||
// 复制方法
|
||||
ChatContext copyWith({
|
||||
String? novelId,
|
||||
String? chapterId,
|
||||
String? selectedText,
|
||||
List<ContextItem>? relevantItems,
|
||||
}) {
|
||||
return ChatContext(
|
||||
novelId: novelId ?? this.novelId,
|
||||
chapterId: chapterId ?? this.chapterId,
|
||||
selectedText: selectedText ?? this.selectedText,
|
||||
relevantItems: relevantItems ?? this.relevantItems,
|
||||
);
|
||||
}
|
||||
|
||||
// 转换为JSON方法
|
||||
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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 }
|
||||
Reference in New Issue
Block a user