129 lines
3.6 KiB
Dart
129 lines
3.6 KiB
Dart
import 'json_util.dart';
|
|
|
|
/// 会话列表项(不含消息)
|
|
class ChatSessionItem {
|
|
final int id;
|
|
final String title;
|
|
final String updateTime;
|
|
|
|
/// 本会话绑定的人设 id。null = 内置默认人设。
|
|
///
|
|
/// 会话的人设在**新建时确定、之后不可改** —— 一次对话从头到尾是同一个人。
|
|
final int? personaId;
|
|
|
|
/// 人设显示名。人设被删了会回落成「默认人设」,历史会话不会因此打不开
|
|
final String personaName;
|
|
|
|
/// 人设头像相对路径。null = 用默认头像
|
|
final String? personaAvatar;
|
|
|
|
const ChatSessionItem({
|
|
required this.id,
|
|
required this.title,
|
|
required this.updateTime,
|
|
this.personaId,
|
|
this.personaName = '默认人设',
|
|
this.personaAvatar,
|
|
});
|
|
|
|
factory ChatSessionItem.fromJson(Map<String, dynamic> json) {
|
|
return ChatSessionItem(
|
|
id: asInt(json['id']),
|
|
title: asNonEmpty(json['title'], '新对话'),
|
|
updateTime: formatDateTime(json['updateTime']),
|
|
personaId: asNullableInt(json['personaId']),
|
|
personaName: asNonEmpty(json['personaName'], '默认人设'),
|
|
personaAvatar: asNonEmpty(json['personaAvatar'], ''),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 会话里的一条消息。工具调用的中间过程后端不入库,所以只有 user / assistant
|
|
class ChatMessageItem {
|
|
final int id;
|
|
final String role;
|
|
final String content;
|
|
|
|
const ChatMessageItem({
|
|
required this.id,
|
|
required this.role,
|
|
required this.content,
|
|
});
|
|
|
|
bool get isUser => role == 'user';
|
|
|
|
factory ChatMessageItem.fromJson(Map<String, dynamic> json) {
|
|
return ChatMessageItem(
|
|
id: asInt(json['id']),
|
|
role: asString(json['role'], fallback: 'assistant'),
|
|
content: asString(json['content']),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 页面上的一个气泡。
|
|
///
|
|
/// 和 [ChatMessageItem] 的区别:这个没有 id、带 streaming 标记,
|
|
/// 因为正在生成的那条回答还没落库。
|
|
class ChatBubble {
|
|
final String role;
|
|
final String content;
|
|
|
|
/// 正在生成中(末尾会显示光标 / 工具提示)
|
|
final bool streaming;
|
|
|
|
const ChatBubble({
|
|
required this.role,
|
|
required this.content,
|
|
this.streaming = false,
|
|
});
|
|
|
|
const ChatBubble.user(String content) : this(role: 'user', content: content);
|
|
|
|
const ChatBubble.assistant(String content, {bool streaming = false})
|
|
: this(role: 'assistant', content: content, streaming: streaming);
|
|
|
|
bool get isUser => role == 'user';
|
|
|
|
ChatBubble copyWith({String? content, bool? streaming}) {
|
|
return ChatBubble(
|
|
role: role,
|
|
content: content ?? this.content,
|
|
streaming: streaming ?? this.streaming,
|
|
);
|
|
}
|
|
|
|
factory ChatBubble.fromItem(ChatMessageItem item) =>
|
|
ChatBubble(role: item.role, content: item.content);
|
|
}
|
|
|
|
/// SSE 事件类型
|
|
enum AiEventType { delta, tool, done, error }
|
|
|
|
/// 一次 SSE 事件。后端只会发这四种,见 AiChatService。
|
|
class AiEvent {
|
|
final AiEventType type;
|
|
|
|
/// delta:本次新增的文本
|
|
final String? content;
|
|
|
|
/// tool:正在调用的工具名
|
|
final String? toolName;
|
|
|
|
/// done:会话 ID(新会话时后端才刚创建,App 要靠它接着聊)
|
|
final int? sessionId;
|
|
|
|
/// error:错误提示
|
|
final String? message;
|
|
|
|
const AiEvent._(this.type, {this.content, this.toolName, this.sessionId, this.message});
|
|
|
|
const AiEvent.delta(String content) : this._(AiEventType.delta, content: content);
|
|
|
|
const AiEvent.tool(String name) : this._(AiEventType.tool, toolName: name);
|
|
|
|
const AiEvent.done(int? sessionId) : this._(AiEventType.done, sessionId: sessionId);
|
|
|
|
const AiEvent.error(String message) : this._(AiEventType.error, message: message);
|
|
}
|