AI 接入 V0.1

This commit is contained in:
2026-09-23 17:23:59 +08:00
parent a83cda63f5
commit 34bc2e04b0
9 changed files with 1018 additions and 116 deletions
+111
View File
@@ -0,0 +1,111 @@
import 'json_util.dart';
/// 会话列表项(不含消息)
class ChatSessionItem {
final int id;
final String title;
final String updateTime;
const ChatSessionItem({
required this.id,
required this.title,
required this.updateTime,
});
factory ChatSessionItem.fromJson(Map<String, dynamic> json) {
return ChatSessionItem(
id: asInt(json['id']),
title: asNonEmpty(json['title'], '新对话'),
updateTime: formatDateTime(json['updateTime']),
);
}
}
/// 会话里的一条消息。工具调用的中间过程后端不入库,所以只有 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);
}