From 2a09943415bbabaca8b9ca18a939c20daa17eaa4 Mon Sep 17 00:00:00 2001 From: "patrick.whelan" Date: Fri, 13 Dec 2024 12:18:31 +0100 Subject: [PATCH 1/4] fix: fix event-subscriptions link. --- lib/src/events/events_list.dart | 152 +++++++++++++++----------------- 1 file changed, 73 insertions(+), 79 deletions(-) diff --git a/lib/src/events/events_list.dart b/lib/src/events/events_list.dart index 40924e9..340ef9e 100644 --- a/lib/src/events/events_list.dart +++ b/lib/src/events/events_list.dart @@ -34,52 +34,51 @@ class EventsList extends ConsumerWidget { return events.isEmpty ? Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - 'No events'.small, - SmallLinkedText( - 'Click here for details', - tip: 'Open Help page in a browser', - onPressed: () { - launchUrlString( - 'https://lmos-ai.github.io/arc/docs/graphql#event-subscriptions'); - }, - ), - ], - ) - : ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: events.length, - itemBuilder: (context, index) { - final event = events[index]; - final json = jsonDecode(event.payload) as Map; - final contextLabel = _getEventLabel(json); - - return Card( - elevation: 0, - child: ExpansionTile( - expandedAlignment: Alignment.topLeft, - childrenPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - title: EventRowItem(event: event, json: json), - subtitle: [ - SmallText(contextLabel), - Spacer(), - SmallText(json['duration'] != null - ? '${(json['duration'] as double?)?.toStringAsPrecision( - 3)} seconds' - : ''), - ].row(), + mainAxisAlignment: MainAxisAlignment.center, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: - _transformEvent(event.type, context, json).toList(), + 'No events'.small, + SmallLinkedText( + 'Click here for details', + tip: 'Open Help page in a browser', + onPressed: () { + launchUrlString( + 'https://lmos-ai.github.io/arc/docs/spring/graphql#event-subscriptions'); + }, ), ], - ), - ); - }, - ); + ) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: events.length, + itemBuilder: (context, index) { + final event = events[index]; + final json = jsonDecode(event.payload) as Map; + final contextLabel = _getEventLabel(json); + + return Card( + elevation: 0, + child: ExpansionTile( + expandedAlignment: Alignment.topLeft, + childrenPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + title: EventRowItem(event: event, json: json), + subtitle: [ + SmallText(contextLabel), + Spacer(), + SmallText(json['duration'] != null + ? '${(json['duration'] as double?)?.toStringAsPrecision(3)} seconds' + : ''), + ].row(), + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: + _transformEvent(event.type, context, json).toList(), + ), + ], + ), + ); + }, + ); } String _getEventLabel(Map json) { @@ -92,42 +91,37 @@ class EventsList extends ConsumerWidget { List _transformEvent(String type, BuildContext context, json) { return switch (type) { - 'AgentFinishedEvent' => - [ - SmallText('Name: ${json['agent']['name']}'), - SmallText('FlowBreak: ${json['flowBreak']}'), - SmallText('Tools: ${json['tools']}'), - ], - 'AgentLoadedEvent' => - [ - SmallText('ErrorMessage: ${json['errorMessage']}'), - ], - 'FunctionLoadedEvent' => - [ - SmallText('ErrorMessage: ${json['errorMessage']}'), - ], - 'LLMFinishedEvent' => - [ - SmallText('Model: ${json['model']}'), - SmallText('TotalTokens: ${json['totalTokens']}'), - SmallText('FunctionCallCount: ${json['functionCallCount']}'), - [ - SmallText('System Prompt:'), - SmallLinkedText('Click to open', tip: 'Open System prompt', - onPressed: () { - showDialog( - context: context, - builder: (_) => PromptView(json['messages'][0]['content'])); - }) - ].row(min: true), - SmallText('Content: ${_tryGetContent(json)}'), - ], - 'LLMFunctionCalledEvent' => - [ - SmallText('Name: ${json['name']}'), - SmallText('Params: ${json['param']}'), - SmallText('Result: ${_tryGetValue(json)}') - ], + 'AgentFinishedEvent' => [ + SmallText('Name: ${json['agent']['name']}'), + SmallText('FlowBreak: ${json['flowBreak']}'), + SmallText('Tools: ${json['tools']}'), + ], + 'AgentLoadedEvent' => [ + SmallText('ErrorMessage: ${json['errorMessage']}'), + ], + 'FunctionLoadedEvent' => [ + SmallText('ErrorMessage: ${json['errorMessage']}'), + ], + 'LLMFinishedEvent' => [ + SmallText('Model: ${json['model']}'), + SmallText('TotalTokens: ${json['totalTokens']}'), + SmallText('FunctionCallCount: ${json['functionCallCount']}'), + [ + SmallText('System Prompt:'), + SmallLinkedText('Click to open', tip: 'Open System prompt', + onPressed: () { + showDialog( + context: context, + builder: (_) => PromptView(json['messages'][0]['content'])); + }) + ].row(min: true), + SmallText('Content: ${_tryGetContent(json)}'), + ], + 'LLMFunctionCalledEvent' => [ + SmallText('Name: ${json['name']}'), + SmallText('Params: ${json['param']}'), + SmallText('Result: ${_tryGetValue(json)}') + ], _ => [SmallText(json.toString())], }; } From db2b6fcec575bc2c04b5a1fd8494aec7c2dbb68d Mon Sep 17 00:00:00 2001 From: "patrick.whelan" Date: Wed, 4 Dec 2024 12:15:02 +0100 Subject: [PATCH 2/4] feat: add audio. --- devtools_options.yaml | 3 + lib/src/chat/buttons/record_button.dart | 99 +++++++++ lib/src/chat/chat_panel.dart | 4 +- .../notifiers/agent_ws_client_notifier.dart | 32 +++ .../notifiers/agent_ws_client_notifier.g.dart | 27 +++ lib/src/client/oneai_client.dart | 6 + lib/src/client/oneai_ws_client.dart | 85 ++++++++ .../models/conversation_message.dart | 13 +- .../models/conversation_message.freezed.dart | 206 +++++++++++++++++- .../models/conversation_message.g.dart | 16 ++ linux/flutter/generated_plugin_registrant.cc | 8 + linux/flutter/generated_plugins.cmake | 2 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 64 ++++++ pubspec.yaml | 7 +- .../flutter/generated_plugin_registrant.cc | 6 + windows/flutter/generated_plugins.cmake | 2 + 17 files changed, 575 insertions(+), 9 deletions(-) create mode 100644 devtools_options.yaml create mode 100644 lib/src/chat/buttons/record_button.dart create mode 100644 lib/src/client/notifiers/agent_ws_client_notifier.dart create mode 100644 lib/src/client/notifiers/agent_ws_client_notifier.g.dart create mode 100644 lib/src/client/oneai_ws_client.dart diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/src/chat/buttons/record_button.dart b/lib/src/chat/buttons/record_button.dart new file mode 100644 index 0000000..e88b966 --- /dev/null +++ b/lib/src/chat/buttons/record_button.dart @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import 'dart:typed_data'; + +import 'package:arc_view/src/client/models/system_context.dart'; +import 'package:arc_view/src/client/models/user_context.dart'; +import 'package:arc_view/src/client/notifiers/agent_ws_client_notifier.dart'; +import 'package:arc_view/src/client/notifiers/agents_notifier.dart'; +import 'package:arc_view/src/conversation/models/conversation.dart'; +import 'package:arc_view/src/conversation/models/conversation_message.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; +import 'package:record/record.dart'; +import 'package:smiles/smiles.dart'; +import 'package:web/web.dart' as web; + +class RecordButton extends ConsumerWidget { + const RecordButton({super.key, required this.onPressed}); + + final VoidCallback onPressed; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return 'Record'.onButtonPressed(disabled: false, () async { + final record = AudioRecorder(); + if (await record.hasPermission()) { + // Start recording to file + await record.start( + const RecordConfig( + numChannels: 1, + sampleRate: 24000, + encoder: AudioEncoder.pcm16bits), + path: 'audio.wav'); + // ... or to stream + //final stream = await record + // .startStream(const RecordConfig(encoder: AudioEncoder.wav)); + + Future.delayed(const Duration(seconds: 3), () async { + // Stop recording... + final path = await record.stop(); + + final response = await http.get( + Uri.parse(path!), + ); + Uint8List data = response.bodyBytes; + + final stream = + await ref.read(agentWSClientNotifierProvider).sendMessage( + Conversation( + createdAt: DateTime.now(), + conversationId: '1234', + messages: [ + ConversationMessage( + conversationId: '1234', + content: 'User input', + type: MessageType.user, + binaryData: [ + BinaryData( + data: 'stream', + mimeType: 'audio/wav', + ) + ]) + ], + systemContext: SystemContext(entries: List.empty()), + userContext: + UserContext(userId: '1234', profile: List.empty())), + data, + ); + stream.listen((event) { + print('Received event: $event'); + }); + + record.dispose(); + }); + } + }, tooltip: 'Send the current message'); + } + + void downloadWebData(String path) { + // Simple download code for web testing + final anchor = web.document.createElement('a') as web.HTMLAnchorElement + ..href = path + ..style.display = 'none' + ..download = 'audio.wav'; + web.document.body!.appendChild(anchor); + anchor.click(); + web.document.body!.removeChild(anchor); + } + + _agentAvailable(WidgetRef ref) { + final agents = ref.watch(agentsNotifierProvider); + return agents.hasValue && agents.value?.names.isNotEmpty == true; + } +} diff --git a/lib/src/chat/chat_panel.dart b/lib/src/chat/chat_panel.dart index 44abeb3..21c9895 100644 --- a/lib/src/chat/chat_panel.dart +++ b/lib/src/chat/chat_panel.dart @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import 'package:arc_view/src/chat/buttons/send_message_button.dart'; +import 'package:arc_view/src/chat/buttons/record_button.dart'; import 'package:arc_view/src/chat/chat_field.dart'; import 'package:arc_view/src/chat/chat_list.dart'; import 'package:arc_view/src/chat/toolbar/chat_tool_bar.dart'; @@ -89,7 +89,7 @@ class _ChatPanelState extends State { showPromptList(context); }).tip('Show previous prompts'); - _sendButton(WidgetRef ref) => SendMessageButton(onPressed: () => _send(ref)); + _sendButton(WidgetRef ref) => RecordButton(onPressed: () => _send(ref)); _send(WidgetRef ref) { if (_textController.text.isEmpty) return; diff --git a/lib/src/client/notifiers/agent_ws_client_notifier.dart b/lib/src/client/notifiers/agent_ws_client_notifier.dart new file mode 100644 index 0000000..7e7000b --- /dev/null +++ b/lib/src/client/notifiers/agent_ws_client_notifier.dart @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import 'package:arc_view/src/client/oneai_client.dart'; +import 'package:arc_view/src/client/oneai_ws_client.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'agent_ws_client_notifier.g.dart'; + +typedef AgentUrlData = ({Uri url, bool secure, String? agent}); + +@Riverpod(keepAlive: true) +class AgentWSClientNotifier extends _$AgentWSClientNotifier { + @override + OneAIWSClient build() { + final url = Uri.base.isScheme('http') + ? '${Uri.base.scheme}://${Uri.base.host}:${Uri.base.port}' + : 'http://localhost:8080'; + final client = OneAIWSClient( + (url: Uri.parse(url), secure: false, agent: null), + ); + ref.onDispose(() => client.close()); + return client; + } + + setUrl(String url) {} + + setAgent(String agent) {} +} diff --git a/lib/src/client/notifiers/agent_ws_client_notifier.g.dart b/lib/src/client/notifiers/agent_ws_client_notifier.g.dart new file mode 100644 index 0000000..2c80109 --- /dev/null +++ b/lib/src/client/notifiers/agent_ws_client_notifier.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'agent_ws_client_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$agentWSClientNotifierHash() => + r'c322d8b71efe2f4114c36e62d79a0b319143e7d7'; + +/// See also [AgentWSClientNotifier]. +@ProviderFor(AgentWSClientNotifier) +final agentWSClientNotifierProvider = + NotifierProvider.internal( + AgentWSClientNotifier.new, + name: r'agentWSClientNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$agentWSClientNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AgentWSClientNotifier = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/src/client/oneai_client.dart b/lib/src/client/oneai_client.dart index 9e36c45..c39b06d 100644 --- a/lib/src/client/oneai_client.dart +++ b/lib/src/client/oneai_client.dart @@ -57,6 +57,12 @@ class OneAIClient { 'content': e.content, 'role': e.type == MessageType.user ? 'user' : 'assistant', 'format': 'text', + 'binaryData': e.binaryData + ?.map((e) => { + 'data': e.data, + 'mimeType': e.mimeType, + }) + .toList(), }) .toList(), }, diff --git a/lib/src/client/oneai_ws_client.dart b/lib/src/client/oneai_ws_client.dart new file mode 100644 index 0000000..5398b78 --- /dev/null +++ b/lib/src/client/oneai_ws_client.dart @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:arc_view/src/client/notifiers/agent_client_notifier.dart'; +import 'package:arc_view/src/conversation/models/conversation.dart'; +import 'package:arc_view/src/conversation/models/conversation_message.dart'; +import 'package:audioplayers/audioplayers.dart'; +import 'package:logging/logging.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +class OneAIWSClient { + OneAIWSClient(this.agentUrl); + + final AgentUrlData agentUrl; + final _log = Logger('OneAIClient'); + + Future> getAgents() async { + return ['voice-agent']; + } + + Future> sendMessage( + Conversation conversation, Uint8List data) async { + if (conversation.messages.isEmpty) return const Stream.empty(); + final channel = + WebSocketChannel.connect(Uri.parse('http://localhost:8080/ws/agent')); + + await channel.ready; + final payload = { + "agentName": agentUrl.agent ?? 'default', + "payload": { + "conversationContext": { + "conversationId": conversation.conversationId, + "anonymizationEntities": [] + }, + "systemContext": conversation.systemContext.entries + .map((e) => { + 'key': e.key, + 'value': e.value, + }) + .toList(), + "userContext": conversation.userContext.toJson(), + "messages": conversation.messages + .where((e) => e.type != MessageType.loading) + .map((e) => { + 'content': e.content, + 'role': e.type == MessageType.user ? 'user' : 'assistant', + 'format': 'text', + 'binaryData': e.binaryData + }) + .toList(), + } + }; + channel.sink.add(jsonEncode(payload)); + channel.sink.add(data); + channel.sink.add(''); + + print('Sent message: ${data.length}'); + + return channel.stream.map((message) { + final agent = agentUrl.agent ?? 'default'; + _log.fine('Received message: $message'); + + var player = AudioPlayer(); + player.setSourceBytes(Base64Decoder().convert(message)).then((a) { + player.resume(); + }); + + return (message: message.toString(), responseTime: -1, agent: agent); + }); + } + + Future isConnected() async { + return true; + } + + close() async {} +} + +typedef MessageResult = ({String message, double? responseTime, String agent}); diff --git a/lib/src/conversation/models/conversation_message.dart b/lib/src/conversation/models/conversation_message.dart index 61b794a..3de6097 100644 --- a/lib/src/conversation/models/conversation_message.dart +++ b/lib/src/conversation/models/conversation_message.dart @@ -7,7 +7,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'conversation_message.freezed.dart'; - part 'conversation_message.g.dart'; enum MessageType { user, bot, loading } @@ -18,6 +17,7 @@ class ConversationMessage with _$ConversationMessage { required MessageType type, required String conversationId, required String content, + List? binaryData, double? responseTime, String? agent, }) = _ConversationMessage; @@ -32,3 +32,14 @@ ConversationMessage loadingMessage(String conversationId) => conversationId: conversationId, content: '...', ); + +@freezed +class BinaryData with _$BinaryData { + factory BinaryData({ + required String data, + required String mimeType, + }) = _BinaryData; + + factory BinaryData.fromJson(Map json) => + _$BinaryDataFromJson(json); +} diff --git a/lib/src/conversation/models/conversation_message.freezed.dart b/lib/src/conversation/models/conversation_message.freezed.dart index 35acb57..cad4faa 100644 --- a/lib/src/conversation/models/conversation_message.freezed.dart +++ b/lib/src/conversation/models/conversation_message.freezed.dart @@ -23,6 +23,7 @@ mixin _$ConversationMessage { MessageType get type => throw _privateConstructorUsedError; String get conversationId => throw _privateConstructorUsedError; String get content => throw _privateConstructorUsedError; + List? get binaryData => throw _privateConstructorUsedError; double? get responseTime => throw _privateConstructorUsedError; String? get agent => throw _privateConstructorUsedError; @@ -46,6 +47,7 @@ abstract class $ConversationMessageCopyWith<$Res> { {MessageType type, String conversationId, String content, + List? binaryData, double? responseTime, String? agent}); } @@ -68,6 +70,7 @@ class _$ConversationMessageCopyWithImpl<$Res, $Val extends ConversationMessage> Object? type = null, Object? conversationId = null, Object? content = null, + Object? binaryData = freezed, Object? responseTime = freezed, Object? agent = freezed, }) { @@ -84,6 +87,10 @@ class _$ConversationMessageCopyWithImpl<$Res, $Val extends ConversationMessage> ? _value.content : content // ignore: cast_nullable_to_non_nullable as String, + binaryData: freezed == binaryData + ? _value.binaryData + : binaryData // ignore: cast_nullable_to_non_nullable + as List?, responseTime: freezed == responseTime ? _value.responseTime : responseTime // ignore: cast_nullable_to_non_nullable @@ -108,6 +115,7 @@ abstract class _$$ConversationMessageImplCopyWith<$Res> {MessageType type, String conversationId, String content, + List? binaryData, double? responseTime, String? agent}); } @@ -128,6 +136,7 @@ class __$$ConversationMessageImplCopyWithImpl<$Res> Object? type = null, Object? conversationId = null, Object? content = null, + Object? binaryData = freezed, Object? responseTime = freezed, Object? agent = freezed, }) { @@ -144,6 +153,10 @@ class __$$ConversationMessageImplCopyWithImpl<$Res> ? _value.content : content // ignore: cast_nullable_to_non_nullable as String, + binaryData: freezed == binaryData + ? _value._binaryData + : binaryData // ignore: cast_nullable_to_non_nullable + as List?, responseTime: freezed == responseTime ? _value.responseTime : responseTime // ignore: cast_nullable_to_non_nullable @@ -163,8 +176,10 @@ class _$ConversationMessageImpl implements _ConversationMessage { {required this.type, required this.conversationId, required this.content, + final List? binaryData, this.responseTime, - this.agent}); + this.agent}) + : _binaryData = binaryData; factory _$ConversationMessageImpl.fromJson(Map json) => _$$ConversationMessageImplFromJson(json); @@ -175,6 +190,16 @@ class _$ConversationMessageImpl implements _ConversationMessage { final String conversationId; @override final String content; + final List? _binaryData; + @override + List? get binaryData { + final value = _binaryData; + if (value == null) return null; + if (_binaryData is EqualUnmodifiableListView) return _binaryData; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + @override final double? responseTime; @override @@ -182,7 +207,7 @@ class _$ConversationMessageImpl implements _ConversationMessage { @override String toString() { - return 'ConversationMessage(type: $type, conversationId: $conversationId, content: $content, responseTime: $responseTime, agent: $agent)'; + return 'ConversationMessage(type: $type, conversationId: $conversationId, content: $content, binaryData: $binaryData, responseTime: $responseTime, agent: $agent)'; } @override @@ -194,6 +219,8 @@ class _$ConversationMessageImpl implements _ConversationMessage { (identical(other.conversationId, conversationId) || other.conversationId == conversationId) && (identical(other.content, content) || other.content == content) && + const DeepCollectionEquality() + .equals(other._binaryData, _binaryData) && (identical(other.responseTime, responseTime) || other.responseTime == responseTime) && (identical(other.agent, agent) || other.agent == agent)); @@ -201,8 +228,8 @@ class _$ConversationMessageImpl implements _ConversationMessage { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, type, conversationId, content, responseTime, agent); + int get hashCode => Object.hash(runtimeType, type, conversationId, content, + const DeepCollectionEquality().hash(_binaryData), responseTime, agent); /// Create a copy of ConversationMessage /// with the given fields replaced by the non-null parameter values. @@ -226,6 +253,7 @@ abstract class _ConversationMessage implements ConversationMessage { {required final MessageType type, required final String conversationId, required final String content, + final List? binaryData, final double? responseTime, final String? agent}) = _$ConversationMessageImpl; @@ -239,6 +267,8 @@ abstract class _ConversationMessage implements ConversationMessage { @override String get content; @override + List? get binaryData; + @override double? get responseTime; @override String? get agent; @@ -250,3 +280,171 @@ abstract class _ConversationMessage implements ConversationMessage { _$$ConversationMessageImplCopyWith<_$ConversationMessageImpl> get copyWith => throw _privateConstructorUsedError; } + +BinaryData _$BinaryDataFromJson(Map json) { + return _BinaryData.fromJson(json); +} + +/// @nodoc +mixin _$BinaryData { + String get data => throw _privateConstructorUsedError; + String get mimeType => throw _privateConstructorUsedError; + + /// Serializes this BinaryData to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of BinaryData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $BinaryDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BinaryDataCopyWith<$Res> { + factory $BinaryDataCopyWith( + BinaryData value, $Res Function(BinaryData) then) = + _$BinaryDataCopyWithImpl<$Res, BinaryData>; + @useResult + $Res call({String data, String mimeType}); +} + +/// @nodoc +class _$BinaryDataCopyWithImpl<$Res, $Val extends BinaryData> + implements $BinaryDataCopyWith<$Res> { + _$BinaryDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of BinaryData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = null, + Object? mimeType = null, + }) { + return _then(_value.copyWith( + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String, + mimeType: null == mimeType + ? _value.mimeType + : mimeType // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BinaryDataImplCopyWith<$Res> + implements $BinaryDataCopyWith<$Res> { + factory _$$BinaryDataImplCopyWith( + _$BinaryDataImpl value, $Res Function(_$BinaryDataImpl) then) = + __$$BinaryDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String data, String mimeType}); +} + +/// @nodoc +class __$$BinaryDataImplCopyWithImpl<$Res> + extends _$BinaryDataCopyWithImpl<$Res, _$BinaryDataImpl> + implements _$$BinaryDataImplCopyWith<$Res> { + __$$BinaryDataImplCopyWithImpl( + _$BinaryDataImpl _value, $Res Function(_$BinaryDataImpl) _then) + : super(_value, _then); + + /// Create a copy of BinaryData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = null, + Object? mimeType = null, + }) { + return _then(_$BinaryDataImpl( + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as String, + mimeType: null == mimeType + ? _value.mimeType + : mimeType // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BinaryDataImpl implements _BinaryData { + _$BinaryDataImpl({required this.data, required this.mimeType}); + + factory _$BinaryDataImpl.fromJson(Map json) => + _$$BinaryDataImplFromJson(json); + + @override + final String data; + @override + final String mimeType; + + @override + String toString() { + return 'BinaryData(data: $data, mimeType: $mimeType)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BinaryDataImpl && + (identical(other.data, data) || other.data == data) && + (identical(other.mimeType, mimeType) || + other.mimeType == mimeType)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, data, mimeType); + + /// Create a copy of BinaryData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BinaryDataImplCopyWith<_$BinaryDataImpl> get copyWith => + __$$BinaryDataImplCopyWithImpl<_$BinaryDataImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BinaryDataImplToJson( + this, + ); + } +} + +abstract class _BinaryData implements BinaryData { + factory _BinaryData( + {required final String data, + required final String mimeType}) = _$BinaryDataImpl; + + factory _BinaryData.fromJson(Map json) = + _$BinaryDataImpl.fromJson; + + @override + String get data; + @override + String get mimeType; + + /// Create a copy of BinaryData + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BinaryDataImplCopyWith<_$BinaryDataImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/conversation/models/conversation_message.g.dart b/lib/src/conversation/models/conversation_message.g.dart index d7b1e12..45686ca 100644 --- a/lib/src/conversation/models/conversation_message.g.dart +++ b/lib/src/conversation/models/conversation_message.g.dart @@ -12,6 +12,9 @@ _$ConversationMessageImpl _$$ConversationMessageImplFromJson( type: $enumDecode(_$MessageTypeEnumMap, json['type']), conversationId: json['conversationId'] as String, content: json['content'] as String, + binaryData: (json['binaryData'] as List?) + ?.map((e) => BinaryData.fromJson(e as Map)) + .toList(), responseTime: (json['responseTime'] as num?)?.toDouble(), agent: json['agent'] as String?, ); @@ -22,6 +25,7 @@ Map _$$ConversationMessageImplToJson( 'type': _$MessageTypeEnumMap[instance.type]!, 'conversationId': instance.conversationId, 'content': instance.content, + 'binaryData': instance.binaryData, 'responseTime': instance.responseTime, 'agent': instance.agent, }; @@ -31,3 +35,15 @@ const _$MessageTypeEnumMap = { MessageType.bot: 'bot', MessageType.loading: 'loading', }; + +_$BinaryDataImpl _$$BinaryDataImplFromJson(Map json) => + _$BinaryDataImpl( + data: json['data'] as String, + mimeType: json['mimeType'] as String, + ); + +Map _$$BinaryDataImplToJson(_$BinaryDataImpl instance) => + { + 'data': instance.data, + 'mimeType': instance.mimeType, + }; diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 7299b5c..7b54566 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,13 +6,21 @@ #include "generated_plugin_registrant.h" +#include #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) record_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); + record_linux_plugin_register_with_registrar(record_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 786ff5c..daf934b 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,7 +3,9 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux file_selector_linux + record_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d993eb7..aceb894 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,14 +5,18 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import file_selector_macos import path_provider_foundation +import record_darwin import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 5b4f681..52dfdbb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -54,6 +54,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: c346ba5a39dc208f1bab55fc239855f573d69b0e832402114bf0b793622adc4d + url: "https://pub.dev" + source: hosted + version: "6.1.0" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: de576b890befe27175c2f511ba8b742bec83765fa97c3ce4282bba46212f58e4 + url: "https://pub.dev" + source: hosted + version: "5.0.0" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: e507887f3ff18d8e5a10a668d7bedc28206b12e10b98347797257c6ae1019c3b + url: "https://pub.dev" + source: hosted + version: "6.0.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: "3d3d244c90436115417f170426ce768856d8fe4dfc5ed66a049d2890acfa82f9" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "6834dd48dfb7bc6c2404998ebdd161f79cd3774a7e6779e1348d54a3bfdcfaa5" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: "3609bdf0e05e66a3d9750ee40b1e37f2a622c4edb796cc600b53a90a30a2ace4" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "8605762dddba992138d476f6a0c3afd9df30ac5b96039929063eceed416795c2" + url: "https://pub.dev" + source: hosted + version: "4.0.0" boolean_selector: dependency: transitive description: @@ -1139,6 +1195,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 07bc21d..f38d442 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,7 +35,7 @@ dependencies: flutter: sdk: flutter - intl: ^0.19.0 + intl: ^0.20.0 state_notifier: ^1.0.0 go_router: ^14.2.7 flutter_riverpod: ^3.0.0-dev.3 @@ -65,6 +65,9 @@ dependencies: url: https://github.com/patwlan/smiles ref: '0.6.0' url_launcher: ^6.3.1 + record: ^5.2.0 + web_socket_channel: ^2.4.0 + audioplayers: ^6.1.0 dev_dependencies: flutter_test: @@ -135,7 +138,7 @@ flutter: fonts: - family: RobotoMono fonts: - - asset: fonts/static/RobotoMono-Bold.ttf + - asset: fonts/static/RobotoMono-SemiBold.ttf weight: 700 # flutter pub run flutter_launcher_icons diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 043a96f..ad4bdd1 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,18 @@ #include "generated_plugin_registrant.h" +#include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + RecordWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a95e267..37f3fed 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,7 +3,9 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows file_selector_windows + record_windows url_launcher_windows ) From f63cf7c1e7023c40cb519a56e2affdc4f4bf039e Mon Sep 17 00:00:00 2001 From: "patrick.whelan" Date: Wed, 11 Dec 2024 17:50:02 +0100 Subject: [PATCH 3/4] fix: fix duplicate context keys. --- lib/src/client/oneai_ws_client.dart | 36 ++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/src/client/oneai_ws_client.dart b/lib/src/client/oneai_ws_client.dart index 5398b78..184ff48 100644 --- a/lib/src/client/oneai_ws_client.dart +++ b/lib/src/client/oneai_ws_client.dart @@ -60,18 +60,33 @@ class OneAIWSClient { channel.sink.add(data); channel.sink.add(''); - print('Sent message: ${data.length}'); + _log.fine('Sent message: ${data.length}'); return channel.stream.map((message) { final agent = agentUrl.agent ?? 'default'; _log.fine('Received message: $message'); - var player = AudioPlayer(); - player.setSourceBytes(Base64Decoder().convert(message)).then((a) { - player.resume(); - }); - - return (message: message.toString(), responseTime: -1, agent: agent); + if (message is String) { + final jsonResult = jsonDecode(message); + return ( + message: jsonResult['messages'][0]['content'], + responseTime: -1, + agent: agent, + binaryData: null + ); + } else { + var player = AudioPlayer(); + player.setSourceBytes(message).then((a) { + player.resume(); + }); + // TODO CLOSE PLAYER + } + return ( + message: null, + responseTime: -1, + agent: agent, + binaryData: message + ); }); } @@ -82,4 +97,9 @@ class OneAIWSClient { close() async {} } -typedef MessageResult = ({String message, double? responseTime, String agent}); +typedef MessageResult = ({ + String? message, + double? responseTime, + String agent, + Uint8List? binaryData +}); From c115c95511d25ef6d2c4efca691d82611f5943fa Mon Sep 17 00:00:00 2001 From: "patrick.whelan" Date: Fri, 13 Dec 2024 20:24:13 +0100 Subject: [PATCH 4/4] feat: add audio. --- .gitignore | 2 + analysis_options.yaml | 6 +- lib/main.g.dart | 4 +- lib/src/audio/notifiers/voice_notifier.dart | 59 +++ lib/src/audio/notifiers/voice_notifier.g.dart | 26 ++ lib/src/audio/services/voice_recorder.dart | 45 +++ lib/src/chat/address_bar.dart | 4 + lib/src/chat/buttons/record_button.dart | 104 +---- lib/src/chat/chat_panel.dart | 61 +-- lib/src/chat/message/chat_message_card.dart | 5 +- lib/src/client/message.dart | 35 ++ lib/src/client/message.freezed.dart | 355 ++++++++++++++++++ lib/src/client/message.g.dart | 33 ++ lib/src/client/message_result.dart | 14 + .../notifiers/agent_client_notifier.g.dart | 4 +- .../agent_stream_client_notifier.dart | 48 +++ .../agent_stream_client_notifier.g.dart | 27 ++ .../notifiers/agent_ws_client_notifier.dart | 32 -- .../notifiers/agent_ws_client_notifier.g.dart | 27 -- lib/src/client/notifiers/agents_notifier.dart | 2 + .../client/notifiers/agents_notifier.g.dart | 4 +- lib/src/client/oneai_client.dart | 14 +- ...s_client.dart => oneai_stream_client.dart} | 63 ++-- .../models/conversation_message.dart | 19 + .../models/conversation_message.freezed.dart | 8 +- .../notifiers/conversations_notifier.dart | 118 ++++-- .../notifiers/conversations_notifier.g.dart | 4 +- .../services/conversation_exporter.g.dart | 4 +- .../services/conversation_importer.g.dart | 4 +- .../notifiers/agent_events_notifier.g.dart | 2 +- .../notifiers/agent_metrics_notifier.g.dart | 2 +- .../events_to_metrics_converter.g.dart | 4 +- .../metrics/services/metrics_exporter.g.dart | 4 +- .../metrics/services/metrics_importer.g.dart | 4 +- .../notifiers/prompt_history_notifier.g.dart | 2 +- .../repositories/history_repository.g.dart | 4 +- .../notifiers/settings_notifier.g.dart | 2 +- lib/src/theme_notifier.g.dart | 2 +- macos/Runner/AppDelegate.swift | 4 + pubspec.lock | 238 ++++++------ pubspec.yaml | 17 +- 41 files changed, 1010 insertions(+), 406 deletions(-) create mode 100644 lib/src/audio/notifiers/voice_notifier.dart create mode 100644 lib/src/audio/notifiers/voice_notifier.g.dart create mode 100644 lib/src/audio/services/voice_recorder.dart create mode 100644 lib/src/client/message.dart create mode 100644 lib/src/client/message.freezed.dart create mode 100644 lib/src/client/message.g.dart create mode 100644 lib/src/client/message_result.dart create mode 100644 lib/src/client/notifiers/agent_stream_client_notifier.dart create mode 100644 lib/src/client/notifiers/agent_stream_client_notifier.g.dart delete mode 100644 lib/src/client/notifiers/agent_ws_client_notifier.dart delete mode 100644 lib/src/client/notifiers/agent_ws_client_notifier.g.dart rename lib/src/client/{oneai_ws_client.dart => oneai_stream_client.dart} (60%) diff --git a/.gitignore b/.gitignore index 29a3a50..79c113f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/analysis_options.yaml b/analysis_options.yaml index dbc49bd..1421ccb 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,7 +11,7 @@ include: package:flutter_lints/flutter.yaml analyzer: plugins: - - custom_lint + #- custom_lint linter: # The lint rules applied to this project can be customized in the @@ -25,8 +25,8 @@ linter: # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/lib/main.g.dart b/lib/main.g.dart index 9f5907e..8fca57e 100644 --- a/lib/main.g.dart +++ b/lib/main.g.dart @@ -21,6 +21,8 @@ final sharedPreferencesProvider = allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef SharedPreferencesRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/audio/notifiers/voice_notifier.dart b/lib/src/audio/notifiers/voice_notifier.dart new file mode 100644 index 0000000..09744b4 --- /dev/null +++ b/lib/src/audio/notifiers/voice_notifier.dart @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import 'dart:typed_data'; + +import 'package:arc_view/src/audio/services/voice_recorder.dart'; +import 'package:arc_view/src/conversation/notifiers/conversations_notifier.dart'; +import 'package:audioplayers/audioplayers.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'voice_notifier.g.dart'; + +@riverpod +class VoiceNotifier extends _$VoiceNotifier { + VoiceRecorder? _voiceRecorder; + + @override + VoiceStatus build() { + ref.onDispose(() { + stopRecording(); + }); + return VoiceStatus.idle; + } + + playAudio(Uint8List audio) { + var player = AudioPlayer(); + player.setSourceBytes(audio).then((a) { + player.resume(); + }); + player.onPlayerStateChanged.listen((event) { + if (event == PlayerState.completed) { + player.dispose(); + } + }); + } + + startRecording() async { + _voiceRecorder = VoiceRecorder(); + final data = await _voiceRecorder?.startRecordingStream(); + if (data == null) return; + state = VoiceStatus.recording; + await ref.read(conversationsNotifierProvider.notifier).addUserVoice(data); + stopRecording(); + } + + stopRecording() async { + await _voiceRecorder?.stopRecording(); + state = VoiceStatus.idle; + } +} + +enum VoiceStatus { + idle, + recording, + error, +} diff --git a/lib/src/audio/notifiers/voice_notifier.g.dart b/lib/src/audio/notifiers/voice_notifier.g.dart new file mode 100644 index 0000000..9e5a65e --- /dev/null +++ b/lib/src/audio/notifiers/voice_notifier.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'voice_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$voiceNotifierHash() => r'71b5d1a89bad5ac304263e50779721044fd7d65b'; + +/// See also [VoiceNotifier]. +@ProviderFor(VoiceNotifier) +final voiceNotifierProvider = + AutoDisposeNotifierProvider.internal( + VoiceNotifier.new, + name: r'voiceNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$voiceNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$VoiceNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/audio/services/voice_recorder.dart b/lib/src/audio/services/voice_recorder.dart new file mode 100644 index 0000000..93f0a84 --- /dev/null +++ b/lib/src/audio/services/voice_recorder.dart @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG + * + * SPDX-License-Identifier: Apache-2.0 + */ +import 'dart:typed_data'; + +import 'package:http/http.dart' as http; +import 'package:record/record.dart'; + +class VoiceRecorder { + final _recordConfig = const RecordConfig( + numChannels: 1, + sampleRate: 24000, + encoder: AudioEncoder.pcm16bits, + ); + + final _record = AudioRecorder(); + + Future startRecording() async { + if (await _record.hasPermission()) { + await _record.start(_recordConfig, path: 'audio.pcm'); + return Future.delayed(const Duration(seconds: 3), () async { + final path = await _record.stop(); + final response = await http.get(Uri.parse(path!)); + Uint8List data = response.bodyBytes; + _record.dispose(); + return data; + }); + } + return null; + } + + Future?> startRecordingStream() async { + if (await _record.hasPermission()) { + final stream = await _record.startStream(_recordConfig); + return stream; + } + return null; + } + + Future stopRecording() async { + await _record.stop(); + } +} diff --git a/lib/src/chat/address_bar.dart b/lib/src/chat/address_bar.dart index 5fd1d57..8d8e716 100644 --- a/lib/src/chat/address_bar.dart +++ b/lib/src/chat/address_bar.dart @@ -5,6 +5,7 @@ */ import 'package:arc_view/src/client/notifiers/agent_client_notifier.dart'; +import 'package:arc_view/src/client/notifiers/agent_stream_client_notifier.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:smiles/smiles.dart'; @@ -38,6 +39,9 @@ class _AddressBarState extends State { ), onChanged: (value) { ref.read(agentClientNotifierProvider.notifier).setUrl(value); + ref + .read(agentStreamClientNotifierProvider.notifier) + .setUrl(value); }, ).expand(), ], diff --git a/lib/src/chat/buttons/record_button.dart b/lib/src/chat/buttons/record_button.dart index e88b966..9e2c97c 100644 --- a/lib/src/chat/buttons/record_button.dart +++ b/lib/src/chat/buttons/record_button.dart @@ -4,96 +4,32 @@ * SPDX-License-Identifier: Apache-2.0 */ -import 'dart:typed_data'; - -import 'package:arc_view/src/client/models/system_context.dart'; -import 'package:arc_view/src/client/models/user_context.dart'; -import 'package:arc_view/src/client/notifiers/agent_ws_client_notifier.dart'; -import 'package:arc_view/src/client/notifiers/agents_notifier.dart'; -import 'package:arc_view/src/conversation/models/conversation.dart'; -import 'package:arc_view/src/conversation/models/conversation_message.dart'; +import 'package:arc_view/src/audio/notifiers/voice_notifier.dart'; +import 'package:arc_view/src/core/secondary_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:http/http.dart' as http; -import 'package:record/record.dart'; -import 'package:smiles/smiles.dart'; -import 'package:web/web.dart' as web; +// +// Starts the recording of the voice message. +// class RecordButton extends ConsumerWidget { - const RecordButton({super.key, required this.onPressed}); - - final VoidCallback onPressed; + const RecordButton({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - return 'Record'.onButtonPressed(disabled: false, () async { - final record = AudioRecorder(); - if (await record.hasPermission()) { - // Start recording to file - await record.start( - const RecordConfig( - numChannels: 1, - sampleRate: 24000, - encoder: AudioEncoder.pcm16bits), - path: 'audio.wav'); - // ... or to stream - //final stream = await record - // .startStream(const RecordConfig(encoder: AudioEncoder.wav)); - - Future.delayed(const Duration(seconds: 3), () async { - // Stop recording... - final path = await record.stop(); - - final response = await http.get( - Uri.parse(path!), - ); - Uint8List data = response.bodyBytes; - - final stream = - await ref.read(agentWSClientNotifierProvider).sendMessage( - Conversation( - createdAt: DateTime.now(), - conversationId: '1234', - messages: [ - ConversationMessage( - conversationId: '1234', - content: 'User input', - type: MessageType.user, - binaryData: [ - BinaryData( - data: 'stream', - mimeType: 'audio/wav', - ) - ]) - ], - systemContext: SystemContext(entries: List.empty()), - userContext: - UserContext(userId: '1234', profile: List.empty())), - data, - ); - stream.listen((event) { - print('Received event: $event'); - }); - - record.dispose(); - }); - } - }, tooltip: 'Send the current message'); - } - - void downloadWebData(String path) { - // Simple download code for web testing - final anchor = web.document.createElement('a') as web.HTMLAnchorElement - ..href = path - ..style.display = 'none' - ..download = 'audio.wav'; - web.document.body!.appendChild(anchor); - anchor.click(); - web.document.body!.removeChild(anchor); - } - - _agentAvailable(WidgetRef ref) { - final agents = ref.watch(agentsNotifierProvider); - return agents.hasValue && agents.value?.names.isNotEmpty == true; + return SecondaryButton( + description: 'Record voice', + icon: VoiceStatus.recording == ref.watch(voiceNotifierProvider) + ? Icons.mic_off + : Icons.mic, + onPressed: () async { + final status = ref.read(voiceNotifierProvider); + if (VoiceStatus.idle == status) { + ref.read(voiceNotifierProvider.notifier).startRecording(); + } else { + ref.read(voiceNotifierProvider.notifier).stopRecording(); + } + }, + ); } } diff --git a/lib/src/chat/chat_panel.dart b/lib/src/chat/chat_panel.dart index 21c9895..372fc29 100644 --- a/lib/src/chat/chat_panel.dart +++ b/lib/src/chat/chat_panel.dart @@ -4,7 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +import 'package:arc_view/src/audio/notifiers/voice_notifier.dart'; import 'package:arc_view/src/chat/buttons/record_button.dart'; +import 'package:arc_view/src/chat/buttons/send_message_button.dart'; import 'package:arc_view/src/chat/chat_field.dart'; import 'package:arc_view/src/chat/chat_list.dart'; import 'package:arc_view/src/chat/toolbar/chat_tool_bar.dart'; @@ -49,26 +51,33 @@ class _ChatPanelState extends State { children: [ const ChatToolBar(), const ChatList().expand(), - Card( - elevation: 6, - margin: const EdgeInsets.fromLTRB(0, 16, 0, 16), - child: Row( - children: [ - _newConversationButton(ref, theme), - _previousPromptButton(ref, theme), - _chatField(ref), - _sendButton(ref), - ], - ).padding(), - ), + [ + Card( + elevation: 6, + margin: const EdgeInsets.fromLTRB(0, 16, 0, 16), + child: Row( + children: [ + _newConversationButton(ref, theme), + _previousPromptButton(ref, theme), + _chatField(ref), + SendMessageButton(onPressed: () => _send(ref)), + ], + ).padding(), + ).size(height: 100).expand(), + HGap.small(), + Card( + elevation: 6, + margin: const EdgeInsets.fromLTRB(0, 16, 0, 16), + child: RecordButton().padding(), + ).size(height: 100), + ].row(), ], ); }, ); } - _chatField(WidgetRef ref) => - Expanded( + _chatField(WidgetRef ref) => Expanded( child: Consumer(builder: (ctx, ref, child) { _textController.text = ref.watch(currentPromptNotifierProvider); return ChatField( @@ -78,18 +87,15 @@ class _ChatPanelState extends State { }), ); - _previousPromptButton(WidgetRef ref, ThemeData theme) => - IconButton( - icon: Icon( - Icons.line_weight_sharp, - color: theme.colorScheme.onSurface, - ), - onPressed: () { - //ref.read(currentPromptControllerProvider.notifier).rotate(); - showPromptList(context); - }).tip('Show previous prompts'); - - _sendButton(WidgetRef ref) => RecordButton(onPressed: () => _send(ref)); + _previousPromptButton(WidgetRef ref, ThemeData theme) => IconButton( + icon: Icon( + Icons.line_weight_sharp, + color: theme.colorScheme.onSurface, + ), + onPressed: () { + //ref.read(currentPromptControllerProvider.notifier).rotate(); + showPromptList(context); + }).tip('Show previous prompts'); _send(WidgetRef ref) { if (_textController.text.isEmpty) return; @@ -101,8 +107,7 @@ class _ChatPanelState extends State { _textController.text = ''; } - _newConversationButton(WidgetRef ref, ThemeData theme) => - IconButton( + _newConversationButton(WidgetRef ref, ThemeData theme) => IconButton( icon: Icon( Icons.add, color: theme.colorScheme.onSurface, diff --git a/lib/src/chat/message/chat_message_card.dart b/lib/src/chat/message/chat_message_card.dart index 585340c..4ae587b 100644 --- a/lib/src/chat/message/chat_message_card.dart +++ b/lib/src/chat/message/chat_message_card.dart @@ -23,7 +23,10 @@ class ChatMessageCard extends StatelessWidget { margin: const EdgeInsets.all(8), child: Stack( children: [ - chatMessage.content.txt.pad(16, 16, 32, 16), + if (chatMessage.isBinary()) + 'Loading transcript...'.txt.pad(16, 16, 32, 16), + if (!chatMessage.isBinary()) + chatMessage.content.txt.pad(16, 16, 32, 16), Positioned( bottom: 0, right: 0, diff --git a/lib/src/client/message.dart b/lib/src/client/message.dart new file mode 100644 index 0000000..d21da8d --- /dev/null +++ b/lib/src/client/message.dart @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'message.freezed.dart'; +part 'message.g.dart'; + +/// +/// Message Type from the Arc API. +/// +@freezed +class AgentResult with _$AgentResult { + factory AgentResult({ + required List messages, + required double responseTime, + }) = _AgentResult; + + factory AgentResult.fromJson(Map json) => + _$AgentResultFromJson(json); +} + +@freezed +class Message with _$Message { + factory Message({ + required String role, + required String content, + }) = _Message; + + factory Message.fromJson(Map json) => + _$MessageFromJson(json); +} diff --git a/lib/src/client/message.freezed.dart b/lib/src/client/message.freezed.dart new file mode 100644 index 0000000..b3bf810 --- /dev/null +++ b/lib/src/client/message.freezed.dart @@ -0,0 +1,355 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'message.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +AgentResult _$AgentResultFromJson(Map json) { + return _AgentResult.fromJson(json); +} + +/// @nodoc +mixin _$AgentResult { + List get messages => throw _privateConstructorUsedError; + double get responseTime => throw _privateConstructorUsedError; + + /// Serializes this AgentResult to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of AgentResult + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AgentResultCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AgentResultCopyWith<$Res> { + factory $AgentResultCopyWith( + AgentResult value, $Res Function(AgentResult) then) = + _$AgentResultCopyWithImpl<$Res, AgentResult>; + @useResult + $Res call({List messages, double responseTime}); +} + +/// @nodoc +class _$AgentResultCopyWithImpl<$Res, $Val extends AgentResult> + implements $AgentResultCopyWith<$Res> { + _$AgentResultCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AgentResult + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? messages = null, + Object? responseTime = null, + }) { + return _then(_value.copyWith( + messages: null == messages + ? _value.messages + : messages // ignore: cast_nullable_to_non_nullable + as List, + responseTime: null == responseTime + ? _value.responseTime + : responseTime // ignore: cast_nullable_to_non_nullable + as double, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AgentResultImplCopyWith<$Res> + implements $AgentResultCopyWith<$Res> { + factory _$$AgentResultImplCopyWith( + _$AgentResultImpl value, $Res Function(_$AgentResultImpl) then) = + __$$AgentResultImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({List messages, double responseTime}); +} + +/// @nodoc +class __$$AgentResultImplCopyWithImpl<$Res> + extends _$AgentResultCopyWithImpl<$Res, _$AgentResultImpl> + implements _$$AgentResultImplCopyWith<$Res> { + __$$AgentResultImplCopyWithImpl( + _$AgentResultImpl _value, $Res Function(_$AgentResultImpl) _then) + : super(_value, _then); + + /// Create a copy of AgentResult + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? messages = null, + Object? responseTime = null, + }) { + return _then(_$AgentResultImpl( + messages: null == messages + ? _value._messages + : messages // ignore: cast_nullable_to_non_nullable + as List, + responseTime: null == responseTime + ? _value.responseTime + : responseTime // ignore: cast_nullable_to_non_nullable + as double, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$AgentResultImpl implements _AgentResult { + _$AgentResultImpl( + {required final List messages, required this.responseTime}) + : _messages = messages; + + factory _$AgentResultImpl.fromJson(Map json) => + _$$AgentResultImplFromJson(json); + + final List _messages; + @override + List get messages { + if (_messages is EqualUnmodifiableListView) return _messages; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_messages); + } + + @override + final double responseTime; + + @override + String toString() { + return 'AgentResult(messages: $messages, responseTime: $responseTime)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AgentResultImpl && + const DeepCollectionEquality().equals(other._messages, _messages) && + (identical(other.responseTime, responseTime) || + other.responseTime == responseTime)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(_messages), responseTime); + + /// Create a copy of AgentResult + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AgentResultImplCopyWith<_$AgentResultImpl> get copyWith => + __$$AgentResultImplCopyWithImpl<_$AgentResultImpl>(this, _$identity); + + @override + Map toJson() { + return _$$AgentResultImplToJson( + this, + ); + } +} + +abstract class _AgentResult implements AgentResult { + factory _AgentResult( + {required final List messages, + required final double responseTime}) = _$AgentResultImpl; + + factory _AgentResult.fromJson(Map json) = + _$AgentResultImpl.fromJson; + + @override + List get messages; + @override + double get responseTime; + + /// Create a copy of AgentResult + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AgentResultImplCopyWith<_$AgentResultImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Message _$MessageFromJson(Map json) { + return _Message.fromJson(json); +} + +/// @nodoc +mixin _$Message { + String get role => throw _privateConstructorUsedError; + String get content => throw _privateConstructorUsedError; + + /// Serializes this Message to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Message + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $MessageCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MessageCopyWith<$Res> { + factory $MessageCopyWith(Message value, $Res Function(Message) then) = + _$MessageCopyWithImpl<$Res, Message>; + @useResult + $Res call({String role, String content}); +} + +/// @nodoc +class _$MessageCopyWithImpl<$Res, $Val extends Message> + implements $MessageCopyWith<$Res> { + _$MessageCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Message + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? role = null, + Object? content = null, + }) { + return _then(_value.copyWith( + role: null == role + ? _value.role + : role // ignore: cast_nullable_to_non_nullable + as String, + content: null == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$MessageImplCopyWith<$Res> implements $MessageCopyWith<$Res> { + factory _$$MessageImplCopyWith( + _$MessageImpl value, $Res Function(_$MessageImpl) then) = + __$$MessageImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String role, String content}); +} + +/// @nodoc +class __$$MessageImplCopyWithImpl<$Res> + extends _$MessageCopyWithImpl<$Res, _$MessageImpl> + implements _$$MessageImplCopyWith<$Res> { + __$$MessageImplCopyWithImpl( + _$MessageImpl _value, $Res Function(_$MessageImpl) _then) + : super(_value, _then); + + /// Create a copy of Message + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? role = null, + Object? content = null, + }) { + return _then(_$MessageImpl( + role: null == role + ? _value.role + : role // ignore: cast_nullable_to_non_nullable + as String, + content: null == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$MessageImpl implements _Message { + _$MessageImpl({required this.role, required this.content}); + + factory _$MessageImpl.fromJson(Map json) => + _$$MessageImplFromJson(json); + + @override + final String role; + @override + final String content; + + @override + String toString() { + return 'Message(role: $role, content: $content)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MessageImpl && + (identical(other.role, role) || other.role == role) && + (identical(other.content, content) || other.content == content)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, role, content); + + /// Create a copy of Message + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$MessageImplCopyWith<_$MessageImpl> get copyWith => + __$$MessageImplCopyWithImpl<_$MessageImpl>(this, _$identity); + + @override + Map toJson() { + return _$$MessageImplToJson( + this, + ); + } +} + +abstract class _Message implements Message { + factory _Message( + {required final String role, + required final String content}) = _$MessageImpl; + + factory _Message.fromJson(Map json) = _$MessageImpl.fromJson; + + @override + String get role; + @override + String get content; + + /// Create a copy of Message + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$MessageImplCopyWith<_$MessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/client/message.g.dart b/lib/src/client/message.g.dart new file mode 100644 index 0000000..fd96970 --- /dev/null +++ b/lib/src/client/message.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'message.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$AgentResultImpl _$$AgentResultImplFromJson(Map json) => + _$AgentResultImpl( + messages: (json['messages'] as List) + .map((e) => Message.fromJson(e as Map)) + .toList(), + responseTime: (json['responseTime'] as num).toDouble(), + ); + +Map _$$AgentResultImplToJson(_$AgentResultImpl instance) => + { + 'messages': instance.messages, + 'responseTime': instance.responseTime, + }; + +_$MessageImpl _$$MessageImplFromJson(Map json) => + _$MessageImpl( + role: json['role'] as String, + content: json['content'] as String, + ); + +Map _$$MessageImplToJson(_$MessageImpl instance) => + { + 'role': instance.role, + 'content': instance.content, + }; diff --git a/lib/src/client/message_result.dart b/lib/src/client/message_result.dart new file mode 100644 index 0000000..acca8d2 --- /dev/null +++ b/lib/src/client/message_result.dart @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import 'package:arc_view/src/client/message.dart'; + +typedef MessageResult = ({ + List messages, + double? responseTime, + String agent, + String? error +}); diff --git a/lib/src/client/notifiers/agent_client_notifier.g.dart b/lib/src/client/notifiers/agent_client_notifier.g.dart index dbd37bd..f4ce00b 100644 --- a/lib/src/client/notifiers/agent_client_notifier.g.dart +++ b/lib/src/client/notifiers/agent_client_notifier.g.dart @@ -7,7 +7,7 @@ part of 'agent_client_notifier.dart'; // ************************************************************************** String _$agentClientNotifierHash() => - r'8a1f03ed0ec91f658367fff4727333d20bcec983'; + r'e783b5d4e6b036ffc2c3c1b9f2d04c2693938b53'; /// See also [AgentClientNotifier]. @ProviderFor(AgentClientNotifier) @@ -24,4 +24,4 @@ final agentClientNotifierProvider = typedef _$AgentClientNotifier = Notifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/client/notifiers/agent_stream_client_notifier.dart b/lib/src/client/notifiers/agent_stream_client_notifier.dart new file mode 100644 index 0000000..3236c75 --- /dev/null +++ b/lib/src/client/notifiers/agent_stream_client_notifier.dart @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import 'package:arc_view/src/client/oneai_stream_client.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'agent_stream_client_notifier.g.dart'; + +typedef AgentUrlData = ({Uri url, bool secure, String? agent}); + +@Riverpod(keepAlive: true) +class AgentStreamClientNotifier extends _$AgentStreamClientNotifier { + @override + OneAIStreamClient build() { + final url = Uri.base.isScheme('http') + ? '${Uri.base.scheme}://${Uri.base.host}:${Uri.base.port}' + : 'http://localhost:8080'; + final secure = Uri.base.isScheme('https'); + final client = OneAIStreamClient( + (url: Uri.parse(url), secure: secure, agent: null), + ); + ref.onDispose(() => client.close()); + return client; + } + + setUrl(String url) { + final uri = Uri.parse(url); + final secure = uri.isScheme('https'); + state.close(); + state = OneAIStreamClient(( + url: uri, + secure: secure, + agent: state.agentUrl.agent, + )); + } + + setAgent(String agent) { + state.close(); + state = OneAIStreamClient(( + url: state.agentUrl.url, + secure: state.agentUrl.secure, + agent: agent, + )); + } +} diff --git a/lib/src/client/notifiers/agent_stream_client_notifier.g.dart b/lib/src/client/notifiers/agent_stream_client_notifier.g.dart new file mode 100644 index 0000000..ac0b7bf --- /dev/null +++ b/lib/src/client/notifiers/agent_stream_client_notifier.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'agent_stream_client_notifier.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$agentStreamClientNotifierHash() => + r'0e55cf4a59f8637bb5d5232e6023daf626703d30'; + +/// See also [AgentStreamClientNotifier]. +@ProviderFor(AgentStreamClientNotifier) +final agentStreamClientNotifierProvider = + NotifierProvider.internal( + AgentStreamClientNotifier.new, + name: r'agentStreamClientNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$agentStreamClientNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AgentStreamClientNotifier = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/client/notifiers/agent_ws_client_notifier.dart b/lib/src/client/notifiers/agent_ws_client_notifier.dart deleted file mode 100644 index 7e7000b..0000000 --- a/lib/src/client/notifiers/agent_ws_client_notifier.dart +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 Deutsche Telekom AG - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import 'package:arc_view/src/client/oneai_client.dart'; -import 'package:arc_view/src/client/oneai_ws_client.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'agent_ws_client_notifier.g.dart'; - -typedef AgentUrlData = ({Uri url, bool secure, String? agent}); - -@Riverpod(keepAlive: true) -class AgentWSClientNotifier extends _$AgentWSClientNotifier { - @override - OneAIWSClient build() { - final url = Uri.base.isScheme('http') - ? '${Uri.base.scheme}://${Uri.base.host}:${Uri.base.port}' - : 'http://localhost:8080'; - final client = OneAIWSClient( - (url: Uri.parse(url), secure: false, agent: null), - ); - ref.onDispose(() => client.close()); - return client; - } - - setUrl(String url) {} - - setAgent(String agent) {} -} diff --git a/lib/src/client/notifiers/agent_ws_client_notifier.g.dart b/lib/src/client/notifiers/agent_ws_client_notifier.g.dart deleted file mode 100644 index 2c80109..0000000 --- a/lib/src/client/notifiers/agent_ws_client_notifier.g.dart +++ /dev/null @@ -1,27 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'agent_ws_client_notifier.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$agentWSClientNotifierHash() => - r'c322d8b71efe2f4114c36e62d79a0b319143e7d7'; - -/// See also [AgentWSClientNotifier]. -@ProviderFor(AgentWSClientNotifier) -final agentWSClientNotifierProvider = - NotifierProvider.internal( - AgentWSClientNotifier.new, - name: r'agentWSClientNotifierProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$agentWSClientNotifierHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$AgentWSClientNotifier = Notifier; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package diff --git a/lib/src/client/notifiers/agents_notifier.dart b/lib/src/client/notifiers/agents_notifier.dart index 45ccc74..68557fb 100644 --- a/lib/src/client/notifiers/agents_notifier.dart +++ b/lib/src/client/notifiers/agents_notifier.dart @@ -5,6 +5,7 @@ */ import 'package:arc_view/src/client/notifiers/agent_client_notifier.dart'; +import 'package:arc_view/src/client/notifiers/agent_stream_client_notifier.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -25,6 +26,7 @@ class AgentsNotifier extends _$AgentsNotifier { setActivated(String activated, List names) { state = AsyncData((activated: activated, names: names)); ref.read(agentClientNotifierProvider.notifier).setAgent(activated); + ref.read(agentStreamClientNotifierProvider.notifier).setAgent(activated); } refresh() async { diff --git a/lib/src/client/notifiers/agents_notifier.g.dart b/lib/src/client/notifiers/agents_notifier.g.dart index 6be1730..d838866 100644 --- a/lib/src/client/notifiers/agents_notifier.g.dart +++ b/lib/src/client/notifiers/agents_notifier.g.dart @@ -6,7 +6,7 @@ part of 'agents_notifier.dart'; // RiverpodGenerator // ************************************************************************** -String _$agentsNotifierHash() => r'9f7704aa310dffaf1f06609a8e4874c4d9958ceb'; +String _$agentsNotifierHash() => r'd6997ef4010f7d0ba6131ca522b2e14a542c4d8c'; /// See also [AgentsNotifier]. @ProviderFor(AgentsNotifier) @@ -23,4 +23,4 @@ final agentsNotifierProvider = typedef _$AgentsNotifier = AutoDisposeAsyncNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/client/oneai_client.dart b/lib/src/client/oneai_client.dart index c39b06d..2279598 100644 --- a/lib/src/client/oneai_client.dart +++ b/lib/src/client/oneai_client.dart @@ -7,6 +7,8 @@ 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/graphql/event_subscription.dart'; +import 'package:arc_view/src/client/message.dart'; +import 'package:arc_view/src/client/message_result.dart'; import 'package:arc_view/src/client/notifiers/agent_client_notifier.dart'; import 'package:arc_view/src/conversation/models/conversation.dart'; import 'package:arc_view/src/conversation/models/conversation_message.dart'; @@ -72,17 +74,19 @@ class OneAIClient { final agent = agentUrl.agent ?? 'default'; if (e.hasException) { return ( - message: e.exception.toString(), + messages: List.empty(), responseTime: -1.0, - agent: agent + agent: agent, + error: e.exception.toString(), ); } final data = e.data!['agent']; _log.fine('Received message: $data'); return ( - message: data['messages'][0]['content'], + messages: data['messages'].map((e) => Message.fromJson(e)), responseTime: data['responseTime'], - agent: agent + agent: agent, + error: null ); }); } @@ -123,5 +127,3 @@ class OneAIClient { return GraphQLClient(cache: GraphQLCache(), link: link); } } - -typedef MessageResult = ({String message, double? responseTime, String agent}); diff --git a/lib/src/client/oneai_ws_client.dart b/lib/src/client/oneai_stream_client.dart similarity index 60% rename from lib/src/client/oneai_ws_client.dart rename to lib/src/client/oneai_stream_client.dart index 184ff48..864e8c8 100644 --- a/lib/src/client/oneai_ws_client.dart +++ b/lib/src/client/oneai_stream_client.dart @@ -4,9 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; +import 'package:arc_view/src/client/message.dart'; +import 'package:arc_view/src/client/message_result.dart'; import 'package:arc_view/src/client/notifiers/agent_client_notifier.dart'; import 'package:arc_view/src/conversation/models/conversation.dart'; import 'package:arc_view/src/conversation/models/conversation_message.dart'; @@ -14,23 +17,23 @@ import 'package:audioplayers/audioplayers.dart'; import 'package:logging/logging.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; -class OneAIWSClient { - OneAIWSClient(this.agentUrl); +class OneAIStreamClient { + OneAIStreamClient(this.agentUrl); final AgentUrlData agentUrl; + late WebSocketChannel _channel; final _log = Logger('OneAIClient'); - Future> getAgents() async { - return ['voice-agent']; - } + Stream sendMessage( + Conversation conversation, Stream data) { + if (conversation.messages.isEmpty) return Stream.empty(); + final url = + '${agentUrl.secure ? 'https://' : 'http://'}${agentUrl.url.host}:${agentUrl.url.port}/stream/agent'; - Future> sendMessage( - Conversation conversation, Uint8List data) async { - if (conversation.messages.isEmpty) return const Stream.empty(); - final channel = - WebSocketChannel.connect(Uri.parse('http://localhost:8080/ws/agent')); + _log.fine('Connecting to $url...'); + _channel = WebSocketChannel.connect(Uri.parse(url)); + // await _channel.ready; - await channel.ready; final payload = { "agentName": agentUrl.agent ?? 'default', "payload": { @@ -56,36 +59,43 @@ class OneAIWSClient { .toList(), } }; - channel.sink.add(jsonEncode(payload)); - channel.sink.add(data); - channel.sink.add(''); + _channel.sink.add(jsonEncode(payload)); + data.listen((d) { + _channel.sink.add(d); + }); + //_channel.sink.add(''); _log.fine('Sent message: ${data.length}'); - return channel.stream.map((message) { + return _channel.stream.map((message) { final agent = agentUrl.agent ?? 'default'; - _log.fine('Received message: $message'); if (message is String) { - final jsonResult = jsonDecode(message); + _log.fine('Received message: $message'); + final agentResult = AgentResult.fromJson(jsonDecode(message)); return ( - message: jsonResult['messages'][0]['content'], - responseTime: -1, + messages: agentResult.messages, + responseTime: agentResult.responseTime, agent: agent, - binaryData: null + error: null ); } else { + _log.fine('Received binary message'); var player = AudioPlayer(); player.setSourceBytes(message).then((a) { player.resume(); }); - // TODO CLOSE PLAYER + player.onPlayerStateChanged.listen((event) { + if (event == PlayerState.completed) { + player.dispose(); + } + }); } return ( - message: null, + messages: List.empty(), responseTime: -1, agent: agent, - binaryData: message + error: null ); }); } @@ -96,10 +106,3 @@ class OneAIWSClient { close() async {} } - -typedef MessageResult = ({ - String? message, - double? responseTime, - String agent, - Uint8List? binaryData -}); diff --git a/lib/src/conversation/models/conversation_message.dart b/lib/src/conversation/models/conversation_message.dart index 3de6097..1689e14 100644 --- a/lib/src/conversation/models/conversation_message.dart +++ b/lib/src/conversation/models/conversation_message.dart @@ -22,6 +22,10 @@ class ConversationMessage with _$ConversationMessage { String? agent, }) = _ConversationMessage; + const ConversationMessage._(); + + isBinary() => binaryData != null && binaryData!.isNotEmpty && content.isEmpty; + factory ConversationMessage.fromJson(Map json) => _$ConversationMessageFromJson(json); } @@ -43,3 +47,18 @@ class BinaryData with _$BinaryData { factory BinaryData.fromJson(Map json) => _$BinaryDataFromJson(json); } + +// +// Extension on List of ConversationMessages. +// +extension MessagesExtension on List { + List filterLoading() { + final newMessages = []; + for (final message in this) { + if (message.type != MessageType.loading) { + newMessages.add(message); + } + } + return newMessages; + } +} diff --git a/lib/src/conversation/models/conversation_message.freezed.dart b/lib/src/conversation/models/conversation_message.freezed.dart index cad4faa..1da568d 100644 --- a/lib/src/conversation/models/conversation_message.freezed.dart +++ b/lib/src/conversation/models/conversation_message.freezed.dart @@ -171,7 +171,7 @@ class __$$ConversationMessageImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$ConversationMessageImpl implements _ConversationMessage { +class _$ConversationMessageImpl extends _ConversationMessage { _$ConversationMessageImpl( {required this.type, required this.conversationId, @@ -179,7 +179,8 @@ class _$ConversationMessageImpl implements _ConversationMessage { final List? binaryData, this.responseTime, this.agent}) - : _binaryData = binaryData; + : _binaryData = binaryData, + super._(); factory _$ConversationMessageImpl.fromJson(Map json) => _$$ConversationMessageImplFromJson(json); @@ -248,7 +249,7 @@ class _$ConversationMessageImpl implements _ConversationMessage { } } -abstract class _ConversationMessage implements ConversationMessage { +abstract class _ConversationMessage extends ConversationMessage { factory _ConversationMessage( {required final MessageType type, required final String conversationId, @@ -256,6 +257,7 @@ abstract class _ConversationMessage implements ConversationMessage { final List? binaryData, final double? responseTime, final String? agent}) = _$ConversationMessageImpl; + _ConversationMessage._() : super._(); factory _ConversationMessage.fromJson(Map json) = _$ConversationMessageImpl.fromJson; diff --git a/lib/src/conversation/notifiers/conversations_notifier.dart b/lib/src/conversation/notifiers/conversations_notifier.dart index 45a8473..ce711fa 100644 --- a/lib/src/conversation/notifiers/conversations_notifier.dart +++ b/lib/src/conversation/notifiers/conversations_notifier.dart @@ -6,12 +6,15 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:arc_view/main.dart'; +import 'package:arc_view/src/client/message.dart'; +import 'package:arc_view/src/client/message_result.dart'; import 'package:arc_view/src/client/models/system_context.dart'; import 'package:arc_view/src/client/models/user_context.dart'; import 'package:arc_view/src/client/notifiers/agent_client_notifier.dart'; -import 'package:arc_view/src/client/oneai_client.dart'; +import 'package:arc_view/src/client/notifiers/agent_stream_client_notifier.dart'; import 'package:arc_view/src/conversation/models/conversation.dart'; import 'package:arc_view/src/conversation/models/conversation_message.dart'; import 'package:arc_view/src/conversation/models/conversations.dart'; @@ -129,35 +132,48 @@ class ConversationsNotifier extends _$ConversationsNotifier { } Future addUserMessage(String msg) { - return addMessage(ConversationMessage( - type: MessageType.user, - content: msg, - conversationId: state.current.conversationId, - )); + return addMessage( + ConversationMessage( + type: MessageType.user, + content: msg, + conversationId: state.current.conversationId, + ), + null, + ); + } + + Future addUserVoice(Stream data) { + _log.fine('Adding voice message'); + return addMessage( + ConversationMessage( + type: MessageType.user, + content: '', + conversationId: state.current.conversationId, + binaryData: [ + BinaryData(data: 'STREAM_SOURCE', mimeType: 'audio/pcm') + ], + ), + data); } - Future addMessage(ConversationMessage msg) { + Future addMessage( + ConversationMessage msg, + Stream? data, + ) async { final callback = Completer(); - final conversation = state.current.add([ - msg, - loadingMessage(state.current.conversationId), - ]); + + final conversation = _setLoading(msg); state = state.update(conversation); - ref - .read(agentClientNotifierProvider) - .sendMessage(conversation) - .listen((value) { - final newMessages = []; - for (final message in conversation.messages) { - if (message.type != MessageType.loading) { - newMessages.add(message); - } - } + + final stream = _sendMessage(conversation, data); + stream.listen((value) { + if (value.messages.isEmpty) return; + final newMessages = conversation.messages.filterLoading(); state = state.update( conversation.copyWith( messages: [ - ...newMessages, - _handleBotMessage(value, conversation), + ...newMessages.take(newMessages.length - 1), // TODO + ..._handleBotMessages(value, conversation), ], ), ); @@ -166,18 +182,50 @@ class ConversationsNotifier extends _$ConversationsNotifier { return callback.future; } - ConversationMessage _handleBotMessage( - MessageResult value, Conversation conversation) { - return switch (value.message) { - '' => loadingMessage(conversation.conversationId), - _ => ConversationMessage( - type: MessageType.bot, - content: value.message, - conversationId: conversation.conversationId, - responseTime: value.responseTime, - agent: value.agent, - ) - }; + Stream _sendMessage( + Conversation conversation, + Stream? data, + ) { + if (data != null) { + return ref + .read(agentStreamClientNotifierProvider) + .sendMessage(conversation, data); + } + return ref.read(agentClientNotifierProvider).sendMessage(conversation); + } + + Conversation _setLoading(ConversationMessage msg) { + final conversation = state.current.add([ + msg, + loadingMessage(state.current.conversationId), + ]); + return conversation; + } + + List _handleBotMessages( + MessageResult value, + Conversation conversation, + ) { + return value.messages.map((message) { + return switch (message) { + Message(content: '') => + loadingMessage(conversation.conversationId), + Message(role: 'user') => ConversationMessage( + type: MessageType.user, + content: message.content, + conversationId: conversation.conversationId, + responseTime: value.responseTime, + agent: value.agent, + ), + _ => ConversationMessage( + type: MessageType.bot, + content: message.content, + conversationId: conversation.conversationId, + responseTime: value.responseTime, + agent: value.agent, + ) + }; + }).toList(); } newConversation() { diff --git a/lib/src/conversation/notifiers/conversations_notifier.g.dart b/lib/src/conversation/notifiers/conversations_notifier.g.dart index f966fac..959ee8d 100644 --- a/lib/src/conversation/notifiers/conversations_notifier.g.dart +++ b/lib/src/conversation/notifiers/conversations_notifier.g.dart @@ -7,7 +7,7 @@ part of 'conversations_notifier.dart'; // ************************************************************************** String _$conversationsNotifierHash() => - r'8f190ae770121cc30620736689e992750ae603d6'; + r'50eda60248b3ed441a9415edb7bd9e4737046ca0'; /// See also [ConversationsNotifier]. @ProviderFor(ConversationsNotifier) @@ -24,4 +24,4 @@ final conversationsNotifierProvider = typedef _$ConversationsNotifier = Notifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/conversation/services/conversation_exporter.g.dart b/lib/src/conversation/services/conversation_exporter.g.dart index 0c746e7..46c7fff 100644 --- a/lib/src/conversation/services/conversation_exporter.g.dart +++ b/lib/src/conversation/services/conversation_exporter.g.dart @@ -22,6 +22,8 @@ final conversationExporterProvider = allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef ConversationExporterRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/conversation/services/conversation_importer.g.dart b/lib/src/conversation/services/conversation_importer.g.dart index 47d36e8..9eaaae2 100644 --- a/lib/src/conversation/services/conversation_importer.g.dart +++ b/lib/src/conversation/services/conversation_importer.g.dart @@ -22,6 +22,8 @@ final conversationImporterProvider = allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef ConversationImporterRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/events/notifiers/agent_events_notifier.g.dart b/lib/src/events/notifiers/agent_events_notifier.g.dart index 188e35c..315b54b 100644 --- a/lib/src/events/notifiers/agent_events_notifier.g.dart +++ b/lib/src/events/notifiers/agent_events_notifier.g.dart @@ -24,4 +24,4 @@ final agentEventsNotifierProvider = typedef _$AgentEventsNotifier = Notifier>; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/metrics/notifiers/agent_metrics_notifier.g.dart b/lib/src/metrics/notifiers/agent_metrics_notifier.g.dart index a598e6a..7f3dc90 100644 --- a/lib/src/metrics/notifiers/agent_metrics_notifier.g.dart +++ b/lib/src/metrics/notifiers/agent_metrics_notifier.g.dart @@ -24,4 +24,4 @@ final agentMetricsNotifierProvider = typedef _$AgentMetricsNotifier = AsyncNotifier>; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/metrics/services/events_to_metrics_converter.g.dart b/lib/src/metrics/services/events_to_metrics_converter.g.dart index e259110..227b2af 100644 --- a/lib/src/metrics/services/events_to_metrics_converter.g.dart +++ b/lib/src/metrics/services/events_to_metrics_converter.g.dart @@ -22,7 +22,9 @@ final eventsToMetricsConverterProvider = allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef EventsToMetricsConverterRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/metrics/services/metrics_exporter.g.dart b/lib/src/metrics/services/metrics_exporter.g.dart index 6e3c18a..4cf75da 100644 --- a/lib/src/metrics/services/metrics_exporter.g.dart +++ b/lib/src/metrics/services/metrics_exporter.g.dart @@ -20,6 +20,8 @@ final metricsExporterProvider = AutoDisposeProvider.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef MetricsExporterRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/metrics/services/metrics_importer.g.dart b/lib/src/metrics/services/metrics_importer.g.dart index ee8e770..e54ded9 100644 --- a/lib/src/metrics/services/metrics_importer.g.dart +++ b/lib/src/metrics/services/metrics_importer.g.dart @@ -20,6 +20,8 @@ final metricsImporterProvider = AutoDisposeProvider.internal( allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef MetricsImporterRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/prompts/notifiers/prompt_history_notifier.g.dart b/lib/src/prompts/notifiers/prompt_history_notifier.g.dart index f9e89c2..d660c1b 100644 --- a/lib/src/prompts/notifiers/prompt_history_notifier.g.dart +++ b/lib/src/prompts/notifiers/prompt_history_notifier.g.dart @@ -45,4 +45,4 @@ final currentPromptNotifierProvider = typedef _$CurrentPromptNotifier = AutoDisposeNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/prompts/repositories/history_repository.g.dart b/lib/src/prompts/repositories/history_repository.g.dart index bf7bda2..7348fb2 100644 --- a/lib/src/prompts/repositories/history_repository.g.dart +++ b/lib/src/prompts/repositories/history_repository.g.dart @@ -21,6 +21,8 @@ final historyRepositoryProvider = allTransitiveDependencies: null, ); +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element typedef HistoryRepositoryRef = AutoDisposeProviderRef; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/settings/notifiers/settings_notifier.g.dart b/lib/src/settings/notifiers/settings_notifier.g.dart index 4a30da3..b6ed250 100644 --- a/lib/src/settings/notifiers/settings_notifier.g.dart +++ b/lib/src/settings/notifiers/settings_notifier.g.dart @@ -23,4 +23,4 @@ final settingsNotifierProvider = typedef _$SettingsNotifier = AutoDisposeNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/src/theme_notifier.g.dart b/lib/src/theme_notifier.g.dart index fefeca6..ff5379c 100644 --- a/lib/src/theme_notifier.g.dart +++ b/lib/src/theme_notifier.g.dart @@ -23,4 +23,4 @@ final themeNotifierProvider = typedef _$ThemeNotifier = AutoDisposeNotifier; // ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter, deprecated_member_use_from_same_package +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index 41489b7..c0d9ea9 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -11,4 +11,8 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/pubspec.lock b/pubspec.lock index 52dfdbb..5f59861 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,23 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" analyzer_plugin: dependency: transitive description: @@ -34,10 +34,10 @@ packages: dependency: transitive description: name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + sha256: "08064924cbf0ab88280a0c3f60db9dd24fec693927e725ecb176f16c629d1cb8" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "4.0.1" args: dependency: transitive description: @@ -198,14 +198,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - ci: - dependency: transitive - description: - name: ci - sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" - url: "https://pub.dev" - source: hosted - version: "0.1.0" cli_util: dependency: transitive description: @@ -234,10 +226,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -262,30 +254,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" - custom_lint: - dependency: transitive - description: - name: custom_lint - sha256: "22bd87a362f433ba6aae127a7bac2838645270737f3721b180916d7c5946cb5d" - url: "https://pub.dev" - source: hosted - version: "0.5.11" - custom_lint_builder: + custom_lint_core: dependency: transitive description: - name: custom_lint_builder - sha256: "0d48e002438950f9582e574ef806b2bea5719d8d14c0f9f754fbad729bcf3b19" + name: custom_lint_core + sha256: "02450c3e45e2a6e8b26c4d16687596ab3c4644dd5792e3313aa9ceba5a49b7f5" url: "https://pub.dev" source: hosted - version: "0.5.14" - custom_lint_core: + version: "0.7.0" + custom_lint_visitor: dependency: transitive description: - name: custom_lint_core - sha256: "2952837953022de610dacb464f045594854ced6506ac7f76af28d4a6490e189b" + name: custom_lint_visitor + sha256: bfe9b7a09c4775a587b58d10ebb871d4fe618237639b1e84d5ec62d7dfef25f9 url: "https://pub.dev" source: hosted - version: "0.5.14" + version: "1.0.0+6.11.0" dart_style: dependency: transitive description: @@ -314,10 +298,10 @@ packages: dependency: transitive description: name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" fake_async: dependency: transitive description: @@ -354,10 +338,10 @@ packages: dependency: transitive description: name: file_selector_android - sha256: ec439df07c4999faad319ce8ad9e971795c2f1d7132ad5a793b9370a863c6128 + sha256: "98ac58e878b05ea2fdb204e7f4fc4978d90406c9881874f901428e01d3b18fbc" url: "https://pub.dev" source: hosted - version: "0.5.1+10" + version: "0.5.1+12" file_selector_ios: dependency: transitive description: @@ -370,10 +354,10 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2" + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.9.3+2" file_selector_macos: dependency: transitive description: @@ -418,10 +402,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef" + sha256: "74959b99b92b9eebeed1a4049426fd67c4abc3c5a0f4d12e2877097d6a11ae08" url: "https://pub.dev" source: hosted - version: "0.69.0" + version: "0.69.2" flutter: dependency: "direct main" description: flutter @@ -431,18 +415,18 @@ packages: dependency: "direct main" description: name: flutter_animate - sha256: "7c8a6594a9252dad30cc2ef16e33270b6248c4dedc3b3d06c86c4f3f4dc05ae5" + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.5.2" flutter_launcher_icons: dependency: "direct main" description: name: flutter_launcher_icons - sha256: "619817c4b65b322b5104b6bb6dfe6cda62d9729bd7ad4303ecc8b4e690a67a77" + sha256: "31cd0885738e87c72d6f055564d37fabcdacee743b396b78c7636c169cac64f5" url: "https://pub.dev" source: hosted - version: "0.14.1" + version: "0.14.2" flutter_lints: dependency: "direct dev" description: @@ -455,18 +439,18 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: f0e599ba89c9946c8e051780f0ec99aba4ba15895e0380a7ab68f420046fc44e + sha256: "255b00afa1a7bad19727da6a7780cf3db6c3c12e68d302d85e0ff1fdf173db9e" url: "https://pub.dev" source: hosted - version: "0.7.4+1" + version: "0.7.4+3" flutter_riverpod: dependency: "direct main" description: name: flutter_riverpod - sha256: "2fd9f58a39b7269cb3495b09245000fcd267243518157a7c2f832189fb64f013" + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" url: "https://pub.dev" source: hosted - version: "3.0.0-dev.3" + version: "2.6.1" flutter_shaders: dependency: transitive description: @@ -529,10 +513,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc" + sha256: "2fd11229f59e23e967b0775df8d5948a519cd7e1e8b6e849729e010587b46539" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.6.2" google_fonts: dependency: "direct main" description: @@ -545,10 +529,10 @@ packages: dependency: transitive description: name: gql - sha256: "8ecd3585bb9e40d671aa58f52575d950670f99e5ffab18e2b34a757e071a6693" + sha256: "650e79ed60c21579ca3bd17ebae8a8c8d22cde267b03a19bf3b35996baaa843a" url: "https://pub.dev" source: hosted - version: "1.0.1-alpha+1717789143880" + version: "1.0.1-alpha+1730759315362" gql_dedupe_link: dependency: transitive description: @@ -585,10 +569,10 @@ packages: dependency: transitive description: name: gql_link - sha256: "70fd5b5cbcc50601679f4b9fea3bcc994e583f59cfec7e1fec11113074b1a565" + sha256: c2b0adb2f6a60c2599b9128fb095316db5feb99ce444c86fb141a6964acedfa4 url: "https://pub.dev" source: hosted - version: "1.0.1-alpha+1717789143896" + version: "1.0.1-alpha+1730759315378" gql_transform_link: dependency: transitive description: @@ -621,14 +605,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.3" - hotreloader: - dependency: transitive - description: - name: hotreloader - sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e - url: "https://pub.dev" - source: hosted - version: "4.2.0" http: dependency: "direct main" description: @@ -649,18 +625,18 @@ packages: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.1" image: dependency: transitive description: name: image - sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d + sha256: "599d08e369969bdf83138f5b4e0a7e823d3f992f23b8a64dd626877c37013533" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" intl: dependency: "direct main" description: @@ -673,10 +649,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: @@ -697,26 +673,26 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.9.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -729,10 +705,10 @@ packages: dependency: transitive description: name: lints - sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + sha256: "4a16b3f03741e1252fda5de3ce712666d010ba2122f8e912c94f9f7b90e1a4c3" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.0" loading_animation_widget: dependency: "direct main" description: @@ -753,10 +729,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" markdown: dependency: "direct main" description: @@ -817,10 +793,10 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" path: dependency: transitive description: @@ -841,18 +817,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -909,6 +885,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" provider: dependency: transitive description: @@ -921,10 +905,10 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pubspec_parse: dependency: transitive description: @@ -993,42 +977,34 @@ packages: dependency: transitive description: name: riverpod - sha256: "0f41a697a17609a7ac18e5fe0d5bdbe4c1ff7e7da6523baf46a203df0c44eaf2" + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" url: "https://pub.dev" source: hosted - version: "3.0.0-dev.3" + version: "2.6.1" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: b6e782db97522de3ad797210bd3babbdb0a67da899aaa6ffbb6572108bdbf48d + sha256: c6b8222b2b483cb87ae77ad147d6408f400c64f060df7a225b127f4afef4f8c8 url: "https://pub.dev" source: hosted - version: "1.0.0-dev.1" + version: "0.5.8" riverpod_annotation: dependency: "direct main" description: name: riverpod_annotation - sha256: "79452c7ba2e8f48c7309c73be5aaa101eec5fe7948dfd26659b883fb276858b4" + sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 url: "https://pub.dev" source: hosted - version: "3.0.0-dev.3" + version: "2.6.1" riverpod_generator: dependency: "direct dev" description: name: riverpod_generator - sha256: "9f3cb7b43e9151fef1cc80031b3ad9fb5d0fe64577cc18e1627061d743823213" + sha256: "63546d70952015f0981361636bf8f356d9cfd9d7f6f0815e3c07789a41233188" url: "https://pub.dev" source: hosted - version: "3.0.0-dev.11" - riverpod_lint: - dependency: "direct dev" - description: - name: riverpod_lint - sha256: "8ddb6be92f0de4704d6109405aebc7436b15b847abf0d9f647039afe48dc0050" - url: "https://pub.dev" - source: hosted - version: "3.0.0-dev.4" + version: "2.6.3" rxdart: dependency: transitive description: @@ -1041,26 +1017,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" + sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1097,23 +1073,23 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" smiles: dependency: "direct main" description: @@ -1159,10 +1135,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" state_notifier: dependency: "direct main" description: @@ -1191,10 +1167,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" synchronized: dependency: transitive description: @@ -1215,18 +1191,18 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" timing: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" typed_data: dependency: transitive description: @@ -1255,26 +1231,26 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1319,10 +1295,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" watcher: dependency: transitive description: @@ -1372,5 +1348,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.5.3 <4.0.0" + dart: ">=3.6.0-0 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index f38d442..b820318 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,13 +38,13 @@ dependencies: intl: ^0.20.0 state_notifier: ^1.0.0 go_router: ^14.2.7 - flutter_riverpod: ^3.0.0-dev.3 + flutter_riverpod: ^2.6.1 #^3.0.0-dev.3 flutter_state_notifier: ^1.0.0 - riverpod_annotation: ^3.0.0-dev.3 - freezed_annotation: ^2.4.1 + riverpod_annotation: ^2.6.1 # ^3.0.0-dev.3 + freezed_annotation: ^2.4.4 json_annotation: ^4.9.0 flutter_animate: ^4.3.0 - shared_preferences: ^2.2.1 + shared_preferences: ^2.3.3 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -73,9 +73,9 @@ dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.4.6 - riverpod_generator: ^3.0.0-dev.4 - freezed: ^2.4.5 + build_runner: ^2.4.13 + riverpod_generator: ^2.6.3 # 3.0.0-dev.11 + freezed: ^2.5.7 json_serializable: ^6.7.1 # The "flutter_lints" package below contains a set of recommended lints to @@ -84,7 +84,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 - riverpod_lint: ^3.0.0-dev.3 + #riverpod_lint: ^3.0.0-dev.4 + #custom_lint: ^0.7.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec