feat: 应用名称更新为灵可析并增强 Chat 功能

- 更新 Android/iOS 应用名称和图标为灵可析
- Chat 支持取消正在运行的 Agent 对话
- 改进 ChatBloc 状态管理(区分发送/等待/流式/取消状态)
- HomeScreen 支持外部注入 ChatBloc 和显示等待指示器
- 后端 Agent 运行服务优化(消息处理、usage 追踪)
- 补充相关单元测试和 Widget 测试
This commit is contained in:
qzl
2026-03-10 18:39:53 +08:00
parent b48f7abf72
commit 487405aa5b
50 changed files with 768 additions and 284 deletions
@@ -25,6 +25,7 @@ class AgUiService {
final MockHistoryService _historyService;
final Map<String, List<String>> _mockSseLinesByThread = {};
final Map<String, String> _lastEventIdByThread = {};
int _activeStreamToken = 0;
String? _threadId;
bool _hasMoreHistory = false;
@@ -41,6 +42,7 @@ class AgUiService {
}
Future<void> sendMessage(String content) async {
final streamToken = ++_activeStreamToken;
final runInput = _buildRunInput(content: content);
final response = await _apiClient.post<Map<String, dynamic>>(
'/api/v1/agent/runs',
@@ -55,7 +57,7 @@ class AgUiService {
throw StateError('Missing threadId in /agent/runs response');
}
_threadId = threadId;
await _streamEventsFromApi(threadId);
await _streamEventsFromApi(threadId, streamToken: streamToken);
}
Future<void> loadHistory({DateTime? beforeDate}) async {
@@ -105,6 +107,7 @@ class AgUiService {
required String toolName,
required Map<String, dynamic> args,
}) async {
final streamToken = ++_activeStreamToken;
final threadId = _threadId;
if (threadId == null || threadId.isEmpty) {
throw StateError('Missing threadId for resume');
@@ -150,7 +153,7 @@ class AgUiService {
_threadId = responseThreadId;
}
}
await _streamEventsFromApi(threadId);
await _streamEventsFromApi(threadId, streamToken: streamToken);
}
bool hasEarlierHistory(DateTime fromDate) {
@@ -160,7 +163,14 @@ class AgUiService {
return _hasMoreHistory;
}
Future<void> _streamEventsFromApi(String threadId) async {
Future<void> cancelCurrentRun() async {
_activeStreamToken += 1;
}
Future<void> _streamEventsFromApi(
String threadId, {
required int streamToken,
}) async {
final lastEventId = _lastEventIdByThread[threadId];
final headers = <String, String>{'Accept': 'text/event-stream'};
if (lastEventId != null && lastEventId.isNotEmpty) {
@@ -175,6 +185,9 @@ class AgUiService {
String? eventId;
final dataBuffer = StringBuffer();
await for (final line in sseLines) {
if (streamToken != _activeStreamToken) {
break;
}
if (line.isEmpty) {
if (dataBuffer.isNotEmpty) {
final raw = dataBuffer.toString();