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 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 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); }