feat(agent): add voice input capability and standardize tool naming

- Add voice recording with transcribe endpoint (ASR) for multimodal input
- Android: add RECORD_AUDIO and INTERNET permissions
- Refactor tool naming: frontend tools use 'front.' prefix, backend tools use 'back.'
- Migrate calendar tools: create_calendar_event -> back.mutate/list/delete events
- Add calendar_event_list.v1 and calendar_operation.v1 UI card types
- Update all Flutter and Python tests to match new tool naming conventions
- Add record package dependency for voice recording
This commit is contained in:
zl-q
2026-03-09 00:10:09 +08:00
parent 6c83e35a69
commit 3ac09475ad
30 changed files with 1593 additions and 438 deletions
@@ -2,11 +2,11 @@ import 'dart:convert';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:social_app/core/api/i_api_client.dart';
import 'package:social_app/core/api/mock_api_client.dart';
import 'package:social_app/core/di/injection.dart';
import '../../data/models/ag_ui_event.dart';
import '../../data/models/chat_list_item.dart';
import '../../data/models/tool_result.dart';
import '../../data/services/ag_ui_service.dart';
class ChatState {
@@ -57,7 +57,14 @@ class ChatBloc extends Cubit<ChatState> {
ChatBloc({AgUiService? service, IApiClient? apiClient})
: _service =
service ?? AgUiService(apiClient: apiClient ?? sl<IApiClient>()),
service ??
AgUiService(
apiClient:
apiClient ??
(sl.isRegistered<IApiClient>()
? sl<IApiClient>()
: MockApiClient()),
),
super(const ChatState()) {
_service.onEvent = _handleEvent;
}
@@ -162,13 +169,10 @@ class ChatBloc extends Cubit<ChatState> {
_toolCallArgsBuffer.remove(endEvent.toolCallId);
final updatedItems = state.items.map((item) {
if (item.id == endEvent.toolCallId && item is ToolCallItem) {
final nextStatus = item.toolName == 'navigate_to_route'
final nextStatus = item.toolName == 'front.navigate_to_route'
? ToolCallStatus.pending
: ToolCallStatus.executing;
return item.copyWith(
args: parsedArgs,
status: nextStatus,
);
return item.copyWith(args: parsedArgs, status: nextStatus);
}
return item;
}).toList();
@@ -344,7 +348,10 @@ class ChatBloc extends Cubit<ChatState> {
}
final updatedItems = state.items.map((item) {
if (item is ToolCallItem && item.callId == toolCallId) {
return item.copyWith(status: ToolCallStatus.executing, errorMessage: null);
return item.copyWith(
status: ToolCallStatus.executing,
errorMessage: null,
);
}
return item;
}).toList();
@@ -365,10 +372,20 @@ class ChatBloc extends Cubit<ChatState> {
}
return item;
}).toList();
emit(state.copyWith(items: failedItems, isLoading: false, error: error.toString()));
emit(
state.copyWith(
items: failedItems,
isLoading: false,
error: error.toString(),
),
);
}
}
Future<String> transcribeAudioFile(String filePath) {
return _service.transcribeAudio(filePath);
}
void clearError() {
emit(state.copyWith(error: null));
}