You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
94 lines
2.9 KiB
94 lines
2.9 KiB
import 'dart:convert';
|
|
|
|
import 'package:get/get.dart';
|
|
import 'package:get_storage/get_storage.dart';
|
|
|
|
import '../../core/utils/logger.dart';
|
|
import '../models/emai_message.dart';
|
|
|
|
/// EMAI 助手的对话记录,手机页面和耳机会话**共用同一份**。
|
|
///
|
|
/// 之所以要独立成一个常驻服务而不是挂在页面控制器上:耳机按键唤醒的对话
|
|
/// 发生时页面很可能根本没打开(人在路上、手机在兜里)。用户要求这些内容
|
|
/// 在打开界面时也能看到,所以记录必须活在页面之外。
|
|
///
|
|
/// 落盘用 GetStorage,只保留最近 [_maxKeep] 条。
|
|
///
|
|
/// ⚠️ 注意语义:这里存的是**展示用的聊天记录**,不是模型上下文。
|
|
/// 百炼的上下文按 `dialog_id` 维护在服务端,每次重新连接就是一轮新对话——
|
|
/// 也就是说界面上能看到旧消息,但模型并不记得它们。
|
|
class EmaiChatStore extends GetxService {
|
|
static const String _tag = 'EmaiChatStore';
|
|
static EmaiChatStore get to => Get.find<EmaiChatStore>();
|
|
|
|
static const String _storageKey = 'emai_chat_history';
|
|
static const int _maxKeep = 200;
|
|
|
|
final GetStorage _storage = GetStorage();
|
|
|
|
final messages = <EmaiMessage>[].obs;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_load();
|
|
}
|
|
|
|
void _load() {
|
|
try {
|
|
final raw = _storage.read<String>(_storageKey);
|
|
if (raw == null || raw.isEmpty) return;
|
|
final list = json.decode(raw) as List;
|
|
messages
|
|
.assignAll(list.map((e) => EmaiMessage.fromJson(e as Map)).toList());
|
|
Logger.i(_tag, '恢复历史对话 ${messages.length} 条');
|
|
} catch (e) {
|
|
Logger.w(_tag, '历史对话读取失败,丢弃: $e');
|
|
_storage.remove(_storageKey);
|
|
}
|
|
}
|
|
|
|
/// 落盘。流式过程中每帧都写太浪费,只在一句话收完时调。
|
|
void persist() {
|
|
try {
|
|
final keep = messages.length > _maxKeep
|
|
? messages.sublist(messages.length - _maxKeep)
|
|
: messages;
|
|
_storage.write(
|
|
_storageKey, json.encode(keep.map((m) => m.toJson()).toList()));
|
|
} catch (e) {
|
|
Logger.w(_tag, '历史对话落盘失败: $e');
|
|
}
|
|
}
|
|
|
|
EmaiMessage append(EmaiMessage m) {
|
|
messages.add(m);
|
|
return m;
|
|
}
|
|
|
|
/// 流式文本一律整体替换,不做拼接——理由见 EmaiController._applyText
|
|
void applyText(EmaiMessage m, String incoming) {
|
|
if (incoming.isEmpty) return;
|
|
m.text = incoming;
|
|
messages.refresh();
|
|
}
|
|
|
|
/// 一个字都没收到的流式气泡:从列表里撤掉,别留白气泡
|
|
void dropIfEmpty(EmaiMessage? m) {
|
|
if (m == null) return;
|
|
if (m.text.trim().isEmpty) messages.remove(m);
|
|
}
|
|
|
|
void finalize(EmaiMessage? m) {
|
|
if (m == null) return;
|
|
m.isFinal = true;
|
|
dropIfEmpty(m);
|
|
messages.refresh();
|
|
persist();
|
|
}
|
|
|
|
void clear() {
|
|
messages.clear();
|
|
_storage.remove(_storageKey);
|
|
}
|
|
}
|
|
|