feat: 优化前端 UI 组件与交互体验
- 优化日历、待办、消息等页面交互 - 更新 ChatBloc 与 UI Schema 渲染 - 优化联系人、首页、设置页面体验
This commit is contained in:
@@ -7,20 +7,15 @@ import 'package:dio/dio.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:social_app/core/api/i_api_client.dart';
|
||||
|
||||
import '../ai/ai_decision_engine.dart';
|
||||
import '../models/ag_ui_event.dart';
|
||||
import '../tools/tool_registry.dart';
|
||||
|
||||
typedef EventCallback = void Function(AgUiEvent event);
|
||||
|
||||
const _runIdPrefix = 'run_';
|
||||
const _messageIdPrefix = 'msg_';
|
||||
const _toolCallIdPrefix = 'tc_';
|
||||
|
||||
class AgUiService {
|
||||
final IApiClient _apiClient;
|
||||
EventCallback onEvent;
|
||||
final AiDecisionEngine _decisionEngine;
|
||||
final Map<String, String> _lastEventIdByThread = {};
|
||||
int _activeStreamToken = 0;
|
||||
|
||||
@@ -29,8 +24,7 @@ class AgUiService {
|
||||
|
||||
AgUiService({EventCallback? onEvent, required IApiClient apiClient})
|
||||
: onEvent = onEvent ?? ((_) {}),
|
||||
_apiClient = apiClient,
|
||||
_decisionEngine = AiDecisionEngine();
|
||||
_apiClient = apiClient;
|
||||
|
||||
Future<void> sendMessage(String content, {List<XFile>? images}) async {
|
||||
final streamToken = ++_activeStreamToken;
|
||||
@@ -51,23 +45,19 @@ class AgUiService {
|
||||
await _streamEventsFromApi(threadId, streamToken: streamToken);
|
||||
}
|
||||
|
||||
Future<void> loadHistory({DateTime? beforeDate}) async {
|
||||
Future<HistorySnapshot> loadHistory({DateTime? beforeDate}) async {
|
||||
final path = _buildHistoryPath(beforeDate: beforeDate);
|
||||
final response = await _apiClient.get<Map<String, dynamic>>(path);
|
||||
final payload = response.data;
|
||||
if (payload is! Map<String, dynamic>) {
|
||||
throw StateError('Invalid /agent/history response');
|
||||
}
|
||||
final event = AgUiEvent.fromJson(payload);
|
||||
if (event is StateSnapshotEvent) {
|
||||
final snapshot = event.snapshot;
|
||||
final threadIdFromSnapshot = snapshot['threadId'] as String?;
|
||||
if (threadIdFromSnapshot != null && threadIdFromSnapshot.isNotEmpty) {
|
||||
_threadId = threadIdFromSnapshot;
|
||||
}
|
||||
_hasMoreHistory = snapshot['hasMore'] == true;
|
||||
final snapshot = HistorySnapshot.fromJson(payload);
|
||||
if (snapshot.threadId != null && snapshot.threadId!.isNotEmpty) {
|
||||
_threadId = snapshot.threadId;
|
||||
}
|
||||
onEvent(event);
|
||||
_hasMoreHistory = snapshot.hasMore;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
Future<Uint8List> fetchAttachmentPreview(String previewPath) async {
|
||||
@@ -105,60 +95,6 @@ class AgUiService {
|
||||
return transcript;
|
||||
}
|
||||
|
||||
Future<void> approveToolCall({
|
||||
required String toolCallId,
|
||||
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');
|
||||
}
|
||||
ToolRegistry.initialize();
|
||||
final nonce = args['__nonce'];
|
||||
if (nonce is! String || nonce.isEmpty) {
|
||||
throw StateError('Missing tool nonce for resume');
|
||||
}
|
||||
final localResult = await ToolRegistry.execute(toolName, args);
|
||||
if (localResult['ok'] != true) {
|
||||
throw StateError('Frontend tool execution failed');
|
||||
}
|
||||
final runInput = {
|
||||
'threadId': threadId,
|
||||
'runId': _nextId(_runIdPrefix),
|
||||
'state': <String, dynamic>{},
|
||||
'messages': [
|
||||
{
|
||||
'id': _nextId('tool_'),
|
||||
'role': 'tool',
|
||||
'toolCallId': toolCallId,
|
||||
'content': jsonEncode({
|
||||
'toolName': toolName,
|
||||
'toolArgs': args,
|
||||
'nonce': nonce,
|
||||
'result': localResult,
|
||||
}),
|
||||
},
|
||||
],
|
||||
'tools': _buildTools(),
|
||||
'context': <Map<String, dynamic>>[],
|
||||
'forwardedProps': <String, dynamic>{},
|
||||
};
|
||||
final response = await _apiClient.post<Map<String, dynamic>>(
|
||||
'/api/v1/agent/runs/$threadId/resume',
|
||||
data: runInput,
|
||||
);
|
||||
final payload = response.data;
|
||||
if (payload is Map<String, dynamic>) {
|
||||
final responseThreadId = payload['threadId'];
|
||||
if (responseThreadId is String && responseThreadId.isNotEmpty) {
|
||||
_threadId = responseThreadId;
|
||||
}
|
||||
}
|
||||
await _streamEventsFromApi(threadId, streamToken: streamToken);
|
||||
}
|
||||
|
||||
bool hasEarlierHistory(DateTime fromDate) {
|
||||
// 历史是否还有更多由后端 history snapshot 的 hasMore 驱动。
|
||||
// 参数保留是为了兼容 ChatBloc 现有调用签名。
|
||||
@@ -199,9 +135,6 @@ class AgUiService {
|
||||
final decoded = jsonDecode(raw);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
final event = AgUiEvent.fromJson(decoded);
|
||||
if (event is StateSnapshotEvent) {
|
||||
_hasMoreHistory = event.snapshot['hasMore'] == true;
|
||||
}
|
||||
onEvent(event);
|
||||
}
|
||||
} catch (_) {
|
||||
@@ -285,7 +218,7 @@ class AgUiService {
|
||||
'messages': [
|
||||
{'id': _nextId('user_'), 'role': 'user', 'content': messageContent},
|
||||
],
|
||||
'tools': _buildTools(),
|
||||
'tools': <Map<String, dynamic>>[],
|
||||
'context': <Map<String, dynamic>>[],
|
||||
'forwardedProps': <String, dynamic>{},
|
||||
};
|
||||
@@ -343,26 +276,6 @@ class AgUiService {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _buildTools() {
|
||||
return [
|
||||
{
|
||||
'name': 'front.navigate_to_route',
|
||||
'description': 'Navigate user to a route in the mobile app.',
|
||||
'parameters': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'target': {'type': 'string', 'description': 'Route path target'},
|
||||
'replace': {
|
||||
'type': 'boolean',
|
||||
'description': 'Use replace navigation',
|
||||
},
|
||||
},
|
||||
'required': ['target'],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
String _buildHistoryPath({DateTime? beforeDate}) {
|
||||
final query = <String>[];
|
||||
if (_threadId != null && _threadId!.isNotEmpty) {
|
||||
|
||||
Reference in New Issue
Block a user