feat: 添加 Agent 步骤事件与图片附件功能

- 新增 stepStarted/stepFinished 事件类型支持
- 前端实现图片附件上传和预览功能
- 后端增强工具结果存储和事件处理
- 完善相关单元测试和集成测试
This commit is contained in:
zl-q
2026-03-12 09:29:57 +08:00
parent 87215f9d41
commit 7b8865e256
45 changed files with 3869 additions and 308 deletions
@@ -6,11 +6,14 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:image_picker/image_picker.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:social_app/core/api/api_exception.dart';
import 'package:social_app/core/api/mock_api_client.dart';
import 'package:social_app/core/di/injection.dart';
import 'package:social_app/features/chat/data/models/ag_ui_event.dart';
import 'package:social_app/features/chat/data/services/ag_ui_service.dart';
import 'package:social_app/features/chat/presentation/bloc/chat_bloc.dart';
import 'package:social_app/features/home/data/voice_recorder.dart';
import 'package:social_app/features/home/ui/screens/home_screen.dart';
import 'package:social_app/features/messages/data/inbox_api.dart';
class _FakeVoiceRecorder implements VoiceRecorder {
bool started = false;
@@ -43,9 +46,19 @@ class _WaitingAgUiService extends AgUiService {
onEvent(RunStartedEvent(threadId: 't1', runId: 'r1'));
return _pending.future;
}
void emitStepStarted(String stepName) {
onEvent(StepStartedEvent(stepName: stepName));
}
}
void main() {
setUpAll(() {
if (!sl.isRegistered<InboxApi>()) {
sl.registerSingleton<InboxApi>(InboxApi(MockApiClient()));
}
});
IconData _inputActionIcon(WidgetTester tester) {
final icon = tester.widget<Icon>(
find.byKey(const ValueKey('home_input_action_icon')),
@@ -275,7 +288,8 @@ void main() {
testWidgets('shows stop icon and waiting indicator while waiting agent', (
WidgetTester tester,
) async {
final chatBloc = ChatBloc(service: _WaitingAgUiService());
final waitingService = _WaitingAgUiService();
final chatBloc = ChatBloc(service: waitingService);
await tester.pumpWidget(
MaterialApp(
home: HomeScreen(autoLoadHistory: false, chatBloc: chatBloc),
@@ -291,6 +305,12 @@ void main() {
expect(_inputActionIcon(tester), LucideIcons.square);
expect(find.text('正在思考...'), findsOneWidget);
waitingService.emitStepStarted('intent');
await tester.pump();
expect(find.text('意图识别中'), findsOneWidget);
expect(find.text('正在思考...'), findsNothing);
await tester.tap(find.byKey(const ValueKey('home_input_action_button')));
await tester.pump();