291 lines
8.6 KiB
Dart
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,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|