xworkmate-app/lib/runtime/runtime_controllers_gateway.dart
2026-05-13 12:37:34 +08:00

291 lines
8.6 KiB
Dart

// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'gateway_runtime.dart';
import 'runtime_models.dart';
import 'secure_config_store.dart';
import 'runtime_controllers_settings.dart';
import 'runtime_controllers_entities.dart';
import 'runtime_controllers_derived_tasks.dart';
class AiGatewayResponseExceptionInternal implements Exception {
const AiGatewayResponseExceptionInternal({
required this.statusCode,
required this.message,
});
final int statusCode;
final String message;
}
class GatewayAgentsController extends ChangeNotifier {
GatewayAgentsController(this.runtimeInternal);
final GatewayRuntime runtimeInternal;
List<GatewayAgentSummary> agentsInternal = const <GatewayAgentSummary>[];
String selectedAgentIdInternal = '';
bool loadingInternal = false;
String? errorInternal;
List<GatewayAgentSummary> get agents => agentsInternal;
String get selectedAgentId => selectedAgentIdInternal;
bool get loading => loadingInternal;
String? get error => errorInternal;
GatewayAgentSummary? get selectedAgent {
final selected = selectedAgentIdInternal.trim();
if (selected.isEmpty) {
return null;
}
for (final agent in agentsInternal) {
if (agent.id == selected) {
return agent;
}
}
return null;
}
String get activeAgentName => selectedAgent?.name ?? 'Main';
void restoreSelection(String agentId) {
selectedAgentIdInternal = agentId.trim();
notifyListeners();
}
void selectAgent(String? agentId) {
selectedAgentIdInternal = agentId?.trim() ?? '';
notifyListeners();
}
Future<void> refresh() async {
if (!runtimeInternal.isConnected) {
agentsInternal = const <GatewayAgentSummary>[];
errorInternal = null;
notifyListeners();
return;
}
loadingInternal = true;
errorInternal = null;
notifyListeners();
try {
agentsInternal = await runtimeInternal.listAgents();
if (selectedAgentIdInternal.isNotEmpty &&
!agentsInternal.any((item) => item.id == selectedAgentIdInternal)) {
selectedAgentIdInternal = '';
}
} catch (error) {
errorInternal = error.toString();
} finally {
loadingInternal = false;
notifyListeners();
}
}
}
class GatewaySessionsController extends ChangeNotifier {
GatewaySessionsController(this.runtimeInternal);
final GatewayRuntime runtimeInternal;
List<GatewaySessionSummary> sessionsInternal =
const <GatewaySessionSummary>[];
String currentSessionKeyInternal = '';
String selectedAgentIdInternal = '';
String defaultAgentIdInternal = '';
bool loadingInternal = false;
String? errorInternal;
List<GatewaySessionSummary> get sessions => sessionsInternal;
String get currentSessionKey => currentSessionKeyInternal;
bool get loading => loadingInternal;
String? get error => errorInternal;
void configure({
required String selectedAgentId,
required String defaultAgentId,
}) {
selectedAgentIdInternal = selectedAgentId.trim();
defaultAgentIdInternal = defaultAgentId.trim();
notifyListeners();
}
Future<void> refresh() async {
if (!runtimeInternal.isConnected) {
sessionsInternal = const <GatewaySessionSummary>[];
errorInternal = null;
notifyListeners();
return;
}
loadingInternal = true;
errorInternal = null;
notifyListeners();
try {
sessionsInternal = await runtimeInternal.listSessions(limit: 50);
} catch (error) {
errorInternal = error.toString();
} finally {
loadingInternal = false;
notifyListeners();
}
}
Future<void> switchSession(String sessionKey) async {
final trimmed = sessionKey.trim();
if (trimmed.isEmpty || trimmed == currentSessionKeyInternal) {
return;
}
currentSessionKeyInternal = trimmed;
notifyListeners();
}
}
class GatewayChatController extends ChangeNotifier {
GatewayChatController(this.runtimeInternal);
final GatewayRuntime runtimeInternal;
List<GatewayChatMessage> messagesInternal = const <GatewayChatMessage>[];
String sessionKeyInternal = 'main';
bool loadingInternal = false;
String? errorInternal;
String? streamingAssistantTextInternal;
final Set<String> pendingRunsInternal = <String>{};
List<GatewayChatMessage> get messages => messagesInternal;
String get sessionKey => sessionKeyInternal;
bool get loading => loadingInternal;
String? get error => errorInternal;
String? get streamingAssistantText => streamingAssistantTextInternal;
bool get hasPendingRun => pendingRunsInternal.isNotEmpty;
String? get activeRunId =>
pendingRunsInternal.isEmpty ? null : pendingRunsInternal.first;
Future<void> loadSession(String sessionKey) async {
final next = sessionKey.trim().isEmpty ? 'main' : sessionKey.trim();
sessionKeyInternal = next;
messagesInternal = const <GatewayChatMessage>[];
streamingAssistantTextInternal = null;
if (!runtimeInternal.isConnected) {
errorInternal = null;
notifyListeners();
return;
}
loadingInternal = true;
errorInternal = null;
notifyListeners();
try {
messagesInternal = await runtimeInternal.loadHistory(next);
} catch (error) {
messagesInternal = const <GatewayChatMessage>[];
errorInternal = error.toString();
} finally {
loadingInternal = false;
notifyListeners();
}
}
void handleEvent(GatewayPushEvent event) {
if (event.event == 'chat.run') {
handleChatRunEventInternal(asMap(event.payload));
return;
}
if (event.event == 'chat') {
handleChatEventInternal(asMap(event.payload));
return;
}
if (event.event == 'agent') {
handleAgentEventInternal(asMap(event.payload));
}
}
void clear() {
messagesInternal = const <GatewayChatMessage>[];
pendingRunsInternal.clear();
streamingAssistantTextInternal = null;
errorInternal = null;
notifyListeners();
}
void resetSession(String sessionKey) {
sessionKeyInternal = sessionKey.trim().isEmpty ? 'main' : sessionKey.trim();
messagesInternal = const <GatewayChatMessage>[];
pendingRunsInternal.clear();
streamingAssistantTextInternal = null;
errorInternal = null;
notifyListeners();
}
void handleChatRunEventInternal(Map<String, dynamic> payload) {
final runId = stringValue(payload['runId']);
final state = stringValue(payload['state']) ?? '';
final incomingSessionKey =
stringValue(payload['sessionKey']) ?? sessionKeyInternal;
final isOurRun = runId != null && pendingRunsInternal.contains(runId);
if (!matchesSessionKey(incomingSessionKey, sessionKeyInternal) &&
!isOurRun) {
return;
}
final assistantText = stringValue(payload['assistantText']) ?? '';
if (assistantText.isNotEmpty && (state == 'delta' || state == 'final')) {
streamingAssistantTextInternal = assistantText;
}
if (state == 'error') {
errorInternal = stringValue(payload['errorMessage']) ?? 'Chat failed';
}
final terminal =
boolValue(payload['terminal']) ??
false || state == 'final' || state == 'aborted' || state == 'error';
if (terminal) {
if (runId != null) {
pendingRunsInternal.remove(runId);
} else {
pendingRunsInternal.clear();
}
unawaited(loadSession(sessionKeyInternal));
notifyListeners();
return;
}
notifyListeners();
}
void handleChatEventInternal(Map<String, dynamic> payload) {
final message = asMap(payload['message']);
final role = (stringValue(message['role']) ?? '').toLowerCase();
handleChatRunEventInternal(<String, dynamic>{
'runId': payload['runId'],
'sessionKey': payload['sessionKey'],
'state': payload['state'],
if (role == 'assistant') 'assistantText': extractMessageText(message),
'errorMessage': payload['errorMessage'],
'terminal': false,
});
}
void handleAgentEventInternal(Map<String, dynamic> payload) {
final runId = stringValue(payload['runId']);
if (runId == null || !pendingRunsInternal.contains(runId)) {
return;
}
final stream = stringValue(payload['stream']);
final data = asMap(payload['data']);
if (stream == 'assistant') {
final nextText = stringValue(data['text']) ?? extractMessageText(data);
if (nextText.isNotEmpty) {
handleChatRunEventInternal(<String, dynamic>{
'runId': runId,
'sessionKey': payload['sessionKey'] ?? data['sessionKey'],
'state': 'delta',
'assistantText': nextText,
'source': 'agent',
'terminal': false,
});
}
}
}
}