Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 46 additions & 46 deletions lib/src/chat/toolbar/agent_tabs.dart
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
/*
* SPDX-FileCopyrightText: 2024 Deutsche Telekom AG
*
* SPDX-License-Identifier: Apache-2.0
*/

import 'package:arc_view/src/client/agents_notifier.dart';
import 'package:arc_view/src/core/extensions.dart';
import 'package:arc_view/src/core/secondary_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class AgentTabs extends ConsumerWidget {
const AgentTabs({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final agents = ref.watch(agentsNotifierProvider).valueOrNull;

return Row(
children: [
SecondaryButton(
icon: Icons.refresh,
onPressed: () => ref.refreshAgents(),
),
if (agents == null || agents.names.isEmpty)
'No Agents found'
.style(color: context.colorScheme.onSurface.withOpacity(0.5))
.pad(4, 8, 4, 8),
if (agents != null)
for (var e in agents.names)
((e == agents.activated)
? Container(
decoration: BoxDecoration(
color:
context.colorScheme.primaryContainer.withOpacity(0.5),
borderRadius: BorderRadius.circular(20),
),
child: e.txt.pad(4, 8, 4, 8))
: e.onPressed(() {
ref.activateAgent(e, agents.names);
}))
],
);
}
}
// /*
// * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG
// *
// * SPDX-License-Identifier: Apache-2.0
// */
//
// import 'package:arc_view/src/client/agents_notifier.dart';
// import 'package:arc_view/src/core/extensions.dart';
// import 'package:arc_view/src/core/secondary_button.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_riverpod/flutter_riverpod.dart';
//
// class AgentTabs extends ConsumerWidget {
// const AgentTabs({super.key});
//
// @override
// Widget build(BuildContext context, WidgetRef ref) {
// final agents = ref.watch(agentsNotifierProvider).valueOrNull;
//
// return Row(
// children: [
// SecondaryButton(
// icon: Icons.refresh,
// onPressed: () => ref.refreshAgents(),
// ),
// if (agents == null || agents.names.isEmpty)
// 'No Agents found'
// .style(color: context.colorScheme.onSurface.withOpacity(0.5))
// .pad(4, 8, 4, 8),
// if (agents != null)
// for (var e in agents.names)
// ((e == agents.activated)
// ? Container(
// decoration: BoxDecoration(
// color:
// context.colorScheme.primaryContainer.withOpacity(0.5),
// borderRadius: BorderRadius.circular(20),
// ),
// child: e.txt.pad(4, 8, 4, 8))
// : e.onPressed(() {
// ref.activateAgent(e, agents.names);
// }))
// ],
// );
// }
// }
2 changes: 1 addition & 1 deletion lib/src/chat/toolbar/tool_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ToolBar extends ConsumerWidget {
final colorScheme = Theme.of(context).colorScheme;
return Row(
children: [
const AgentTabs().pad(0, 0, 0, 8).expand(),
// const AgentTabs().pad(0, 0, 0, 8).expand(),
SecondaryButton(
onPressed: () {
context.push("/settings");
Expand Down
15 changes: 9 additions & 6 deletions lib/src/client/agent_client_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import 'package:arc_view/src/client/oneai_client.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../conversation/conversation_notifier.dart';

part 'agent_client_notifier.g.dart';

typedef AgentUrlData = ({Uri url, bool secure, String? agent});
Expand All @@ -16,27 +18,28 @@ typedef AgentUrlData = ({Uri url, bool secure, String? agent});
class AgentClientNotifier extends _$AgentClientNotifier {
@override
OneAIClient build() {
final url = Uri.base.isScheme('http')
? '${Uri.base.scheme}://${Uri.base.host}:${Uri.base.port}'
: 'http://localhost:8080';
return OneAIClient((url: Uri.parse(url), secure: false, agent: null));
final url = 'http://localhost:8081';
final conversationNotifier = ref.read(conversationNotifierProvider.notifier);
return OneAIClient((url: Uri.parse(url), secure: false, agent: null), conversationNotifier);
}

setUrl(String url) {
final uri = Uri.parse(url);
final secure = uri.isScheme('https');
final conversationNotifier = ref.read(conversationNotifierProvider.notifier);
state = OneAIClient((
url: uri,
secure: secure,
agent: state.agentUrl.agent,
));
), conversationNotifier);
}

setAgent(String agent) {
final conversationNotifier = ref.read(conversationNotifierProvider.notifier);
state = OneAIClient((
url: state.agentUrl.url,
secure: state.agentUrl.secure,
agent: agent,
));
), conversationNotifier);
}
}
165 changes: 113 additions & 52 deletions lib/src/client/oneai_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,74 +7,135 @@
import 'package:arc_view/src/client/agent_client_notifier.dart';
import 'package:arc_view/src/client/graphql/agent_query.dart';
import 'package:arc_view/src/client/graphql/agent_subscription.dart';
import 'package:arc_view/src/client/system_context.dart';
import 'package:arc_view/src/client/user_context.dart';
import 'package:arc_view/src/conversation/conversation.dart';
import 'package:arc_view/src/conversation/conversation_message.dart';
import 'package:arc_view/src/conversation/conversation_notifier.dart';
import 'package:graphql/client.dart';
import 'package:logging/logging.dart';
import 'dart:convert';
import 'package:arc_view/src/client/agent_client_notifier.dart';
import 'package:arc_view/src/conversation/conversation.dart';
import 'package:arc_view/src/conversation/conversation_message.dart';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';

class OneAIClient {
OneAIClient(this.agentUrl) : _client = _buildGraphQLClient(agentUrl);
OneAIClient(this.agentUrl, this.conversationNotifier) : _log = Logger('OneAIClient');

final AgentUrlData agentUrl;
final GraphQLClient _client;
final _log = Logger('OneAIClient');
final ConversationNotifier conversationNotifier;

var _log = Logger('OneAIClient');

Future<List<String>> getAgents() async {
final result = await _client.query(QueryOptions(document: agentQuery()));
if (result.hasException) return List.empty();
return (result.data!['agent']['names'] as List)
.map((e) => e.toString())
.toList();
final url = Uri.parse('${agentUrl.url}/actuator/health');

final response = await http.get(url);
if (response.statusCode == 200) {
return ['Runtime'];
} else {
return ['Health check failed for lmos-runtime at url: $url'];
}
}

Stream<(String, double?)> sendMessage(Conversation conversation) {
if (conversation.messages.isEmpty) return const Stream.empty();

final subscription = _client.subscribe(
SubscriptionOptions(
document: agentSubscription(),
variables: {
'conversationId': conversation.conversationId,
'userContext': conversation.userContext.toJson(),
'systemContext': conversation.systemContext.entries
.map((e) => {
'key': e.key,
'value': e.value,
})
.toList(),
'messages': conversation.messages
.where((e) => e.type != MessageType.loading)
.map((e) => {
'content': e.content,
'role': e.type == MessageType.user ? 'user' : 'assistant',
'format': 'text',
})
.toList(),
},
),
);
return subscription.map((e) {
if (e.hasException) return (e.exception.toString(), -1.0);
final data = e.data!['agent'];
_log.fine('Received message: $data');
return (data['messages'][0]['content'], data['responseTime']);
Future<ConversationMessage> sendMessage(Conversation conversation) async {
var defaultResponse = Future.value(ConversationMessage(type: MessageType.bot, conversationId: conversation.conversationId, content: '',));
if (conversation.messages.isEmpty) return defaultResponse;

final filteredMessages = conversation.messages
.where((message) => message.type != MessageType.loading)
.toList();

final List<Map<String, String>> formattedMessages = filteredMessages.map((message) {
return {
'role': message.type == MessageType.user ? 'user' : 'assistant',
'format': 'text',
'content': message.content,
};
}).toList();

var systemContext = getSystemContext();
var userContext = getUserContext();

var tenantId = systemContext.entries.firstWhere((entry) => entry.key == 'tenantId').value;
var channelId = systemContext.entries.firstWhere((entry) => entry.key == 'channelId').value;
final url = Uri.parse('${agentUrl.url}/lmos/runtime/apis/v1/$tenantId/chat/${conversation.conversationId}/message');

final headers = {
'Content-Type': 'application/json',
'x-turn-id': '1',
};

final body = jsonEncode({
'inputContext': {
'messages': formattedMessages,
},
'systemContext': {
'channelId': channelId,
},
'userContext': {
'userId': userContext.userId,
},
});
}

Future<bool> isConnected() async {
final result = await _client.query(QueryOptions(document: agentQuery()));
return !result.hasException;
print(body);

try {
final response = await http.post(url, headers: headers, body: body);
if (response.statusCode == 200) {
final data = json.decode(response.body);
// Extract message from response and handle accordingly
// Assume response data contains 'content' and 'responseTime' fields for simplicity
final messageContent = data['content'];
final responseTime = 0.0;
// data['outputContext']['messages'][0]['responseTime'];

// Update conversation state here
// Typically, this code would be moved to a notifier or state manager
final newMessage = ConversationMessage(
type: MessageType.bot,
content: messageContent,
conversationId: conversation.conversationId,
responseTime: responseTime,
);
return newMessage;
// Update the conversation state accordingly
} else {
_log.warning('Failed to send message: ${response.statusCode} ${response.reasonPhrase}');
defaultResponse = Future.value(ConversationMessage(type: MessageType.bot, conversationId: conversation.conversationId, content: response.body));
}
} catch (e) {
_log.severe('Error during sending the message: $e');
defaultResponse = Future.value(ConversationMessage(type: MessageType.bot, conversationId: conversation.conversationId, content: e.toString()));
}
return defaultResponse;
}

static GraphQLClient _buildGraphQLClient(AgentUrlData agentUrl) {
final httpLink = HttpLink('${agentUrl.url}/graphql');
SystemContext getSystemContext() {
return conversationNotifier.loadSystemContext();
}

final websocketLink = WebSocketLink(
'${agentUrl.secure ? 'wss://' : 'ws://'}${agentUrl.url.host}:${agentUrl.url.port}/subscriptions',
subProtocol: GraphQLProtocol.graphqlTransportWs,
);
Link link = Link.split(
(request) => request.isSubscription, websocketLink, httpLink);
return GraphQLClient(cache: GraphQLCache(), link: link);
UserContext getUserContext() {
return conversationNotifier.loadUserContext();
}
}

// Future<bool> isConnected() async {
// final result = await _client.query(QueryOptions(document: agentQuery()));
// return !result.hasException;
// }
//
// static GraphQLClient _buildGraphQLClient(AgentUrlData agentUrl) {
// final httpLink = HttpLink('${agentUrl.url}/graphql');
//
// final websocketLink = WebSocketLink(
// '${agentUrl.secure ? 'wss://' : 'ws://'}${agentUrl.url.host}:${agentUrl.url.port}/subscriptions',
// subProtocol: GraphQLProtocol.graphqlTransportWs,
// );
// Link link = Link.split(
// (request) => request.isSubscription, websocketLink, httpLink);
// return GraphQLClient(cache: GraphQLCache(), link: link);
// }
// }
Loading