From 9a75228e3846b6425eff701ae3667200d4c25242 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 16 Nov 2025 02:45:02 +0800 Subject: [PATCH] :sparkles: Multi model support in thought --- lib/models/thought.dart | 24 + lib/models/thought.freezed.dart | 572 +++++++++++++++++++++++- lib/models/thought.g.dart | 33 ++ lib/screens/thought/think.dart | 7 + lib/screens/thought/think.g.dart | 20 + lib/widgets/thought/thought_shared.dart | 140 ++++++ pubspec.lock | 8 +- 7 files changed, 780 insertions(+), 24 deletions(-) diff --git a/lib/models/thought.dart b/lib/models/thought.dart index e49d1958..e34330d7 100644 --- a/lib/models/thought.dart +++ b/lib/models/thought.dart @@ -71,6 +71,7 @@ sealed class StreamThinkingRequest with _$StreamThinkingRequest { @Default([]) List accpetProposals, List? attachedPosts, List>? attachedMessages, + @JsonKey(name: 'service_id') String? serviceId, }) = _StreamThinkingRequest; factory StreamThinkingRequest.fromJson(Map json) => @@ -175,3 +176,26 @@ sealed class SnThinkingThought with _$SnThinkingThought { factory SnThinkingThought.fromJson(Map json) => _$SnThinkingThoughtFromJson(json); } + +@freezed +sealed class ThoughtService with _$ThoughtService { + const factory ThoughtService({ + @JsonKey(name: 'service_id') required String serviceId, + required double billingMultiplier, + required int perkLevel, + }) = _ThoughtService; + + factory ThoughtService.fromJson(Map json) => + _$ThoughtServiceFromJson(json); +} + +@freezed +sealed class ThoughtServicesResponse with _$ThoughtServicesResponse { + const factory ThoughtServicesResponse({ + @JsonKey(name: 'default_service') required String defaultService, + required List services, + }) = _ThoughtServicesResponse; + + factory ThoughtServicesResponse.fromJson(Map json) => + _$ThoughtServicesResponseFromJson(json); +} diff --git a/lib/models/thought.freezed.dart b/lib/models/thought.freezed.dart index 8ce76071..188e56a3 100644 --- a/lib/models/thought.freezed.dart +++ b/lib/models/thought.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$StreamThinkingRequest { - String get userMessage; String? get sequenceId; List get accpetProposals; List? get attachedPosts; List>? get attachedMessages; + String get userMessage; String? get sequenceId; List get accpetProposals; List? get attachedPosts; List>? get attachedMessages;@JsonKey(name: 'service_id') String? get serviceId; /// Create a copy of StreamThinkingRequest /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $StreamThinkingRequestCopyWith get copyWith => _$StreamTh @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is StreamThinkingRequest&&(identical(other.userMessage, userMessage) || other.userMessage == userMessage)&&(identical(other.sequenceId, sequenceId) || other.sequenceId == sequenceId)&&const DeepCollectionEquality().equals(other.accpetProposals, accpetProposals)&&const DeepCollectionEquality().equals(other.attachedPosts, attachedPosts)&&const DeepCollectionEquality().equals(other.attachedMessages, attachedMessages)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is StreamThinkingRequest&&(identical(other.userMessage, userMessage) || other.userMessage == userMessage)&&(identical(other.sequenceId, sequenceId) || other.sequenceId == sequenceId)&&const DeepCollectionEquality().equals(other.accpetProposals, accpetProposals)&&const DeepCollectionEquality().equals(other.attachedPosts, attachedPosts)&&const DeepCollectionEquality().equals(other.attachedMessages, attachedMessages)&&(identical(other.serviceId, serviceId) || other.serviceId == serviceId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,userMessage,sequenceId,const DeepCollectionEquality().hash(accpetProposals),const DeepCollectionEquality().hash(attachedPosts),const DeepCollectionEquality().hash(attachedMessages)); +int get hashCode => Object.hash(runtimeType,userMessage,sequenceId,const DeepCollectionEquality().hash(accpetProposals),const DeepCollectionEquality().hash(attachedPosts),const DeepCollectionEquality().hash(attachedMessages),serviceId); @override String toString() { - return 'StreamThinkingRequest(userMessage: $userMessage, sequenceId: $sequenceId, accpetProposals: $accpetProposals, attachedPosts: $attachedPosts, attachedMessages: $attachedMessages)'; + return 'StreamThinkingRequest(userMessage: $userMessage, sequenceId: $sequenceId, accpetProposals: $accpetProposals, attachedPosts: $attachedPosts, attachedMessages: $attachedMessages, serviceId: $serviceId)'; } @@ -48,7 +48,7 @@ abstract mixin class $StreamThinkingRequestCopyWith<$Res> { factory $StreamThinkingRequestCopyWith(StreamThinkingRequest value, $Res Function(StreamThinkingRequest) _then) = _$StreamThinkingRequestCopyWithImpl; @useResult $Res call({ - String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages + String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages,@JsonKey(name: 'service_id') String? serviceId }); @@ -65,14 +65,15 @@ class _$StreamThinkingRequestCopyWithImpl<$Res> /// Create a copy of StreamThinkingRequest /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? userMessage = null,Object? sequenceId = freezed,Object? accpetProposals = null,Object? attachedPosts = freezed,Object? attachedMessages = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? userMessage = null,Object? sequenceId = freezed,Object? accpetProposals = null,Object? attachedPosts = freezed,Object? attachedMessages = freezed,Object? serviceId = freezed,}) { return _then(_self.copyWith( userMessage: null == userMessage ? _self.userMessage : userMessage // ignore: cast_nullable_to_non_nullable as String,sequenceId: freezed == sequenceId ? _self.sequenceId : sequenceId // ignore: cast_nullable_to_non_nullable as String?,accpetProposals: null == accpetProposals ? _self.accpetProposals : accpetProposals // ignore: cast_nullable_to_non_nullable as List,attachedPosts: freezed == attachedPosts ? _self.attachedPosts : attachedPosts // ignore: cast_nullable_to_non_nullable as List?,attachedMessages: freezed == attachedMessages ? _self.attachedMessages : attachedMessages // ignore: cast_nullable_to_non_nullable -as List>?, +as List>?,serviceId: freezed == serviceId ? _self.serviceId : serviceId // ignore: cast_nullable_to_non_nullable +as String?, )); } @@ -154,10 +155,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages, @JsonKey(name: 'service_id') String? serviceId)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _StreamThinkingRequest() when $default != null: -return $default(_that.userMessage,_that.sequenceId,_that.accpetProposals,_that.attachedPosts,_that.attachedMessages);case _: +return $default(_that.userMessage,_that.sequenceId,_that.accpetProposals,_that.attachedPosts,_that.attachedMessages,_that.serviceId);case _: return orElse(); } @@ -175,10 +176,10 @@ return $default(_that.userMessage,_that.sequenceId,_that.accpetProposals,_that.a /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages, @JsonKey(name: 'service_id') String? serviceId) $default,) {final _that = this; switch (_that) { case _StreamThinkingRequest(): -return $default(_that.userMessage,_that.sequenceId,_that.accpetProposals,_that.attachedPosts,_that.attachedMessages);} +return $default(_that.userMessage,_that.sequenceId,_that.accpetProposals,_that.attachedPosts,_that.attachedMessages,_that.serviceId);} } /// A variant of `when` that fallback to returning `null` /// @@ -192,10 +193,10 @@ return $default(_that.userMessage,_that.sequenceId,_that.accpetProposals,_that.a /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages, @JsonKey(name: 'service_id') String? serviceId)? $default,) {final _that = this; switch (_that) { case _StreamThinkingRequest() when $default != null: -return $default(_that.userMessage,_that.sequenceId,_that.accpetProposals,_that.attachedPosts,_that.attachedMessages);case _: +return $default(_that.userMessage,_that.sequenceId,_that.accpetProposals,_that.attachedPosts,_that.attachedMessages,_that.serviceId);case _: return null; } @@ -207,7 +208,7 @@ return $default(_that.userMessage,_that.sequenceId,_that.accpetProposals,_that.a @JsonSerializable() class _StreamThinkingRequest implements StreamThinkingRequest { - const _StreamThinkingRequest({required this.userMessage, this.sequenceId, final List accpetProposals = const [], final List? attachedPosts, final List>? attachedMessages}): _accpetProposals = accpetProposals,_attachedPosts = attachedPosts,_attachedMessages = attachedMessages; + const _StreamThinkingRequest({required this.userMessage, this.sequenceId, final List accpetProposals = const [], final List? attachedPosts, final List>? attachedMessages, @JsonKey(name: 'service_id') this.serviceId}): _accpetProposals = accpetProposals,_attachedPosts = attachedPosts,_attachedMessages = attachedMessages; factory _StreamThinkingRequest.fromJson(Map json) => _$StreamThinkingRequestFromJson(json); @override final String userMessage; @@ -237,6 +238,7 @@ class _StreamThinkingRequest implements StreamThinkingRequest { return EqualUnmodifiableListView(value); } +@override@JsonKey(name: 'service_id') final String? serviceId; /// Create a copy of StreamThinkingRequest /// with the given fields replaced by the non-null parameter values. @@ -251,16 +253,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _StreamThinkingRequest&&(identical(other.userMessage, userMessage) || other.userMessage == userMessage)&&(identical(other.sequenceId, sequenceId) || other.sequenceId == sequenceId)&&const DeepCollectionEquality().equals(other._accpetProposals, _accpetProposals)&&const DeepCollectionEquality().equals(other._attachedPosts, _attachedPosts)&&const DeepCollectionEquality().equals(other._attachedMessages, _attachedMessages)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _StreamThinkingRequest&&(identical(other.userMessage, userMessage) || other.userMessage == userMessage)&&(identical(other.sequenceId, sequenceId) || other.sequenceId == sequenceId)&&const DeepCollectionEquality().equals(other._accpetProposals, _accpetProposals)&&const DeepCollectionEquality().equals(other._attachedPosts, _attachedPosts)&&const DeepCollectionEquality().equals(other._attachedMessages, _attachedMessages)&&(identical(other.serviceId, serviceId) || other.serviceId == serviceId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,userMessage,sequenceId,const DeepCollectionEquality().hash(_accpetProposals),const DeepCollectionEquality().hash(_attachedPosts),const DeepCollectionEquality().hash(_attachedMessages)); +int get hashCode => Object.hash(runtimeType,userMessage,sequenceId,const DeepCollectionEquality().hash(_accpetProposals),const DeepCollectionEquality().hash(_attachedPosts),const DeepCollectionEquality().hash(_attachedMessages),serviceId); @override String toString() { - return 'StreamThinkingRequest(userMessage: $userMessage, sequenceId: $sequenceId, accpetProposals: $accpetProposals, attachedPosts: $attachedPosts, attachedMessages: $attachedMessages)'; + return 'StreamThinkingRequest(userMessage: $userMessage, sequenceId: $sequenceId, accpetProposals: $accpetProposals, attachedPosts: $attachedPosts, attachedMessages: $attachedMessages, serviceId: $serviceId)'; } @@ -271,7 +273,7 @@ abstract mixin class _$StreamThinkingRequestCopyWith<$Res> implements $StreamThi factory _$StreamThinkingRequestCopyWith(_StreamThinkingRequest value, $Res Function(_StreamThinkingRequest) _then) = __$StreamThinkingRequestCopyWithImpl; @override @useResult $Res call({ - String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages + String userMessage, String? sequenceId, List accpetProposals, List? attachedPosts, List>? attachedMessages,@JsonKey(name: 'service_id') String? serviceId }); @@ -288,14 +290,15 @@ class __$StreamThinkingRequestCopyWithImpl<$Res> /// Create a copy of StreamThinkingRequest /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? userMessage = null,Object? sequenceId = freezed,Object? accpetProposals = null,Object? attachedPosts = freezed,Object? attachedMessages = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? userMessage = null,Object? sequenceId = freezed,Object? accpetProposals = null,Object? attachedPosts = freezed,Object? attachedMessages = freezed,Object? serviceId = freezed,}) { return _then(_StreamThinkingRequest( userMessage: null == userMessage ? _self.userMessage : userMessage // ignore: cast_nullable_to_non_nullable as String,sequenceId: freezed == sequenceId ? _self.sequenceId : sequenceId // ignore: cast_nullable_to_non_nullable as String?,accpetProposals: null == accpetProposals ? _self._accpetProposals : accpetProposals // ignore: cast_nullable_to_non_nullable as List,attachedPosts: freezed == attachedPosts ? _self._attachedPosts : attachedPosts // ignore: cast_nullable_to_non_nullable as List?,attachedMessages: freezed == attachedMessages ? _self._attachedMessages : attachedMessages // ignore: cast_nullable_to_non_nullable -as List>?, +as List>?,serviceId: freezed == serviceId ? _self.serviceId : serviceId // ignore: cast_nullable_to_non_nullable +as String?, )); } @@ -2011,4 +2014,533 @@ $SnThinkingSequenceCopyWith<$Res>? get sequence { } } + +/// @nodoc +mixin _$ThoughtService { + +@JsonKey(name: 'service_id') String get serviceId; double get billingMultiplier; int get perkLevel; +/// Create a copy of ThoughtService +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ThoughtServiceCopyWith get copyWith => _$ThoughtServiceCopyWithImpl(this as ThoughtService, _$identity); + + /// Serializes this ThoughtService to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ThoughtService&&(identical(other.serviceId, serviceId) || other.serviceId == serviceId)&&(identical(other.billingMultiplier, billingMultiplier) || other.billingMultiplier == billingMultiplier)&&(identical(other.perkLevel, perkLevel) || other.perkLevel == perkLevel)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,serviceId,billingMultiplier,perkLevel); + +@override +String toString() { + return 'ThoughtService(serviceId: $serviceId, billingMultiplier: $billingMultiplier, perkLevel: $perkLevel)'; +} + + +} + +/// @nodoc +abstract mixin class $ThoughtServiceCopyWith<$Res> { + factory $ThoughtServiceCopyWith(ThoughtService value, $Res Function(ThoughtService) _then) = _$ThoughtServiceCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'service_id') String serviceId, double billingMultiplier, int perkLevel +}); + + + + +} +/// @nodoc +class _$ThoughtServiceCopyWithImpl<$Res> + implements $ThoughtServiceCopyWith<$Res> { + _$ThoughtServiceCopyWithImpl(this._self, this._then); + + final ThoughtService _self; + final $Res Function(ThoughtService) _then; + +/// Create a copy of ThoughtService +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? serviceId = null,Object? billingMultiplier = null,Object? perkLevel = null,}) { + return _then(_self.copyWith( +serviceId: null == serviceId ? _self.serviceId : serviceId // ignore: cast_nullable_to_non_nullable +as String,billingMultiplier: null == billingMultiplier ? _self.billingMultiplier : billingMultiplier // ignore: cast_nullable_to_non_nullable +as double,perkLevel: null == perkLevel ? _self.perkLevel : perkLevel // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ThoughtService]. +extension ThoughtServicePatterns on ThoughtService { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ThoughtService value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ThoughtService() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ThoughtService value) $default,){ +final _that = this; +switch (_that) { +case _ThoughtService(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ThoughtService value)? $default,){ +final _that = this; +switch (_that) { +case _ThoughtService() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'service_id') String serviceId, double billingMultiplier, int perkLevel)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ThoughtService() when $default != null: +return $default(_that.serviceId,_that.billingMultiplier,_that.perkLevel);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'service_id') String serviceId, double billingMultiplier, int perkLevel) $default,) {final _that = this; +switch (_that) { +case _ThoughtService(): +return $default(_that.serviceId,_that.billingMultiplier,_that.perkLevel);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'service_id') String serviceId, double billingMultiplier, int perkLevel)? $default,) {final _that = this; +switch (_that) { +case _ThoughtService() when $default != null: +return $default(_that.serviceId,_that.billingMultiplier,_that.perkLevel);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ThoughtService implements ThoughtService { + const _ThoughtService({@JsonKey(name: 'service_id') required this.serviceId, required this.billingMultiplier, required this.perkLevel}); + factory _ThoughtService.fromJson(Map json) => _$ThoughtServiceFromJson(json); + +@override@JsonKey(name: 'service_id') final String serviceId; +@override final double billingMultiplier; +@override final int perkLevel; + +/// Create a copy of ThoughtService +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ThoughtServiceCopyWith<_ThoughtService> get copyWith => __$ThoughtServiceCopyWithImpl<_ThoughtService>(this, _$identity); + +@override +Map toJson() { + return _$ThoughtServiceToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ThoughtService&&(identical(other.serviceId, serviceId) || other.serviceId == serviceId)&&(identical(other.billingMultiplier, billingMultiplier) || other.billingMultiplier == billingMultiplier)&&(identical(other.perkLevel, perkLevel) || other.perkLevel == perkLevel)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,serviceId,billingMultiplier,perkLevel); + +@override +String toString() { + return 'ThoughtService(serviceId: $serviceId, billingMultiplier: $billingMultiplier, perkLevel: $perkLevel)'; +} + + +} + +/// @nodoc +abstract mixin class _$ThoughtServiceCopyWith<$Res> implements $ThoughtServiceCopyWith<$Res> { + factory _$ThoughtServiceCopyWith(_ThoughtService value, $Res Function(_ThoughtService) _then) = __$ThoughtServiceCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'service_id') String serviceId, double billingMultiplier, int perkLevel +}); + + + + +} +/// @nodoc +class __$ThoughtServiceCopyWithImpl<$Res> + implements _$ThoughtServiceCopyWith<$Res> { + __$ThoughtServiceCopyWithImpl(this._self, this._then); + + final _ThoughtService _self; + final $Res Function(_ThoughtService) _then; + +/// Create a copy of ThoughtService +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? serviceId = null,Object? billingMultiplier = null,Object? perkLevel = null,}) { + return _then(_ThoughtService( +serviceId: null == serviceId ? _self.serviceId : serviceId // ignore: cast_nullable_to_non_nullable +as String,billingMultiplier: null == billingMultiplier ? _self.billingMultiplier : billingMultiplier // ignore: cast_nullable_to_non_nullable +as double,perkLevel: null == perkLevel ? _self.perkLevel : perkLevel // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + + +/// @nodoc +mixin _$ThoughtServicesResponse { + +@JsonKey(name: 'default_service') String get defaultService; List get services; +/// Create a copy of ThoughtServicesResponse +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ThoughtServicesResponseCopyWith get copyWith => _$ThoughtServicesResponseCopyWithImpl(this as ThoughtServicesResponse, _$identity); + + /// Serializes this ThoughtServicesResponse to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ThoughtServicesResponse&&(identical(other.defaultService, defaultService) || other.defaultService == defaultService)&&const DeepCollectionEquality().equals(other.services, services)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,defaultService,const DeepCollectionEquality().hash(services)); + +@override +String toString() { + return 'ThoughtServicesResponse(defaultService: $defaultService, services: $services)'; +} + + +} + +/// @nodoc +abstract mixin class $ThoughtServicesResponseCopyWith<$Res> { + factory $ThoughtServicesResponseCopyWith(ThoughtServicesResponse value, $Res Function(ThoughtServicesResponse) _then) = _$ThoughtServicesResponseCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'default_service') String defaultService, List services +}); + + + + +} +/// @nodoc +class _$ThoughtServicesResponseCopyWithImpl<$Res> + implements $ThoughtServicesResponseCopyWith<$Res> { + _$ThoughtServicesResponseCopyWithImpl(this._self, this._then); + + final ThoughtServicesResponse _self; + final $Res Function(ThoughtServicesResponse) _then; + +/// Create a copy of ThoughtServicesResponse +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? defaultService = null,Object? services = null,}) { + return _then(_self.copyWith( +defaultService: null == defaultService ? _self.defaultService : defaultService // ignore: cast_nullable_to_non_nullable +as String,services: null == services ? _self.services : services // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ThoughtServicesResponse]. +extension ThoughtServicesResponsePatterns on ThoughtServicesResponse { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ThoughtServicesResponse value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ThoughtServicesResponse() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ThoughtServicesResponse value) $default,){ +final _that = this; +switch (_that) { +case _ThoughtServicesResponse(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ThoughtServicesResponse value)? $default,){ +final _that = this; +switch (_that) { +case _ThoughtServicesResponse() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'default_service') String defaultService, List services)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ThoughtServicesResponse() when $default != null: +return $default(_that.defaultService,_that.services);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'default_service') String defaultService, List services) $default,) {final _that = this; +switch (_that) { +case _ThoughtServicesResponse(): +return $default(_that.defaultService,_that.services);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'default_service') String defaultService, List services)? $default,) {final _that = this; +switch (_that) { +case _ThoughtServicesResponse() when $default != null: +return $default(_that.defaultService,_that.services);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ThoughtServicesResponse implements ThoughtServicesResponse { + const _ThoughtServicesResponse({@JsonKey(name: 'default_service') required this.defaultService, required final List services}): _services = services; + factory _ThoughtServicesResponse.fromJson(Map json) => _$ThoughtServicesResponseFromJson(json); + +@override@JsonKey(name: 'default_service') final String defaultService; + final List _services; +@override List get services { + if (_services is EqualUnmodifiableListView) return _services; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_services); +} + + +/// Create a copy of ThoughtServicesResponse +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ThoughtServicesResponseCopyWith<_ThoughtServicesResponse> get copyWith => __$ThoughtServicesResponseCopyWithImpl<_ThoughtServicesResponse>(this, _$identity); + +@override +Map toJson() { + return _$ThoughtServicesResponseToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ThoughtServicesResponse&&(identical(other.defaultService, defaultService) || other.defaultService == defaultService)&&const DeepCollectionEquality().equals(other._services, _services)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,defaultService,const DeepCollectionEquality().hash(_services)); + +@override +String toString() { + return 'ThoughtServicesResponse(defaultService: $defaultService, services: $services)'; +} + + +} + +/// @nodoc +abstract mixin class _$ThoughtServicesResponseCopyWith<$Res> implements $ThoughtServicesResponseCopyWith<$Res> { + factory _$ThoughtServicesResponseCopyWith(_ThoughtServicesResponse value, $Res Function(_ThoughtServicesResponse) _then) = __$ThoughtServicesResponseCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'default_service') String defaultService, List services +}); + + + + +} +/// @nodoc +class __$ThoughtServicesResponseCopyWithImpl<$Res> + implements _$ThoughtServicesResponseCopyWith<$Res> { + __$ThoughtServicesResponseCopyWithImpl(this._self, this._then); + + final _ThoughtServicesResponse _self; + final $Res Function(_ThoughtServicesResponse) _then; + +/// Create a copy of ThoughtServicesResponse +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? defaultService = null,Object? services = null,}) { + return _then(_ThoughtServicesResponse( +defaultService: null == defaultService ? _self.defaultService : defaultService // ignore: cast_nullable_to_non_nullable +as String,services: null == services ? _self._services : services // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + // dart format on diff --git a/lib/models/thought.g.dart b/lib/models/thought.g.dart index 5778060e..cd8bb138 100644 --- a/lib/models/thought.g.dart +++ b/lib/models/thought.g.dart @@ -24,6 +24,7 @@ _StreamThinkingRequest _$StreamThinkingRequestFromJson( (json['attached_messages'] as List?) ?.map((e) => e as Map) .toList(), + serviceId: json['service_id'] as String?, ); Map _$StreamThinkingRequestToJson( @@ -34,6 +35,7 @@ Map _$StreamThinkingRequestToJson( 'accpet_proposals': instance.accpetProposals, 'attached_posts': instance.attachedPosts, 'attached_messages': instance.attachedMessages, + 'service_id': instance.serviceId, }; _SnThinkingChunk _$SnThinkingChunkFromJson(Map json) => @@ -185,3 +187,34 @@ Map _$SnThinkingThoughtToJson(_SnThinkingThought instance) => 'updated_at': instance.updatedAt.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(), }; + +_ThoughtService _$ThoughtServiceFromJson(Map json) => + _ThoughtService( + serviceId: json['service_id'] as String, + billingMultiplier: (json['billing_multiplier'] as num).toDouble(), + perkLevel: (json['perk_level'] as num).toInt(), + ); + +Map _$ThoughtServiceToJson(_ThoughtService instance) => + { + 'service_id': instance.serviceId, + 'billing_multiplier': instance.billingMultiplier, + 'perk_level': instance.perkLevel, + }; + +_ThoughtServicesResponse _$ThoughtServicesResponseFromJson( + Map json, +) => _ThoughtServicesResponse( + defaultService: json['default_service'] as String, + services: + (json['services'] as List) + .map((e) => ThoughtService.fromJson(e as Map)) + .toList(), +); + +Map _$ThoughtServicesResponseToJson( + _ThoughtServicesResponse instance, +) => { + 'default_service': instance.defaultService, + 'services': instance.services.map((e) => e.toJson()).toList(), +}; diff --git a/lib/screens/thought/think.dart b/lib/screens/thought/think.dart index 36c9684c..3131c9c1 100644 --- a/lib/screens/thought/think.dart +++ b/lib/screens/thought/think.dart @@ -36,6 +36,13 @@ Future> thoughtSequence( .toList(); } +@riverpod +Future thoughtServices(Ref ref) async { + final apiClient = ref.watch(apiClientProvider); + final response = await apiClient.get('/insight/thought/services'); + return ThoughtServicesResponse.fromJson(response.data); +} + class ThoughtScreen extends HookConsumerWidget { const ThoughtScreen({super.key}); diff --git a/lib/screens/thought/think.g.dart b/lib/screens/thought/think.g.dart index 318d97a7..e457db28 100644 --- a/lib/screens/thought/think.g.dart +++ b/lib/screens/thought/think.g.dart @@ -171,5 +171,25 @@ class _ThoughtSequenceProviderElement String get sequenceId => (origin as ThoughtSequenceProvider).sequenceId; } +String _$thoughtServicesHash() => r'0ddeaec713ecfcdc9786c197f3d4cb41d36c26a5'; + +/// See also [thoughtServices]. +@ProviderFor(thoughtServices) +final thoughtServicesProvider = + AutoDisposeFutureProvider.internal( + thoughtServices, + name: r'thoughtServicesProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$thoughtServicesHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef ThoughtServicesRef = + AutoDisposeFutureProviderRef; // 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/widgets/thought/thought_shared.dart b/lib/widgets/thought/thought_shared.dart index c259cec9..6decfb91 100644 --- a/lib/widgets/thought/thought_shared.dart +++ b/lib/widgets/thought/thought_shared.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:math' as math; import 'package:dio/dio.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -9,6 +10,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/thought.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; +import 'package:island/screens/thought/think.dart'; import 'package:island/screens/posts/compose.dart'; import 'package:island/talker.dart'; import 'package:island/widgets/alert.dart'; @@ -40,6 +42,8 @@ class ThoughtChatState { final ValueNotifier sequenceId; final ValueNotifier> localThoughts; final ValueNotifier currentTopic; + final ValueNotifier> services; + final ValueNotifier selectedServiceId; final TextEditingController messageController; final ScrollController scrollController; final ValueNotifier isStreaming; @@ -52,6 +56,8 @@ class ThoughtChatState { required this.sequenceId, required this.localThoughts, required this.currentTopic, + required this.services, + required this.selectedServiceId, required this.messageController, required this.scrollController, required this.isStreaming, @@ -77,6 +83,25 @@ ThoughtChatState useThoughtChat( ); final currentTopic = useState(initialTopic ?? 'aiThought'.tr()); + // Watch the provider for services + final servicesAsync = ref.watch(thoughtServicesProvider); + + // Initialize services and selected service from provider + final services = useState>([]); + final selectedServiceId = useState(''); + + // Update state when provider data arrives + useEffect(() { + if (servicesAsync.hasValue) { + final response = servicesAsync.value!; + services.value = response.services; + if (selectedServiceId.value.isEmpty) { + selectedServiceId.value = response.defaultService; + } + } + return null; + }, [servicesAsync]); + final messageController = useTextEditingController(); final scrollController = useScrollController(); final isStreaming = useState(false); @@ -151,6 +176,8 @@ ThoughtChatState useThoughtChat( accpetProposals: ['post_create'], attachedMessages: attachedMessages, attachedPosts: attachedPosts, + serviceId: + selectedServiceId.value.isNotEmpty ? selectedServiceId.value : null, ); try { @@ -344,6 +371,8 @@ ThoughtChatState useThoughtChat( sequenceId: sequenceId, localThoughts: localThoughts, currentTopic: currentTopic, + services: services, + selectedServiceId: selectedServiceId, messageController: messageController, scrollController: scrollController, isStreaming: isStreaming, @@ -469,6 +498,8 @@ class ThoughtChatInterface extends HookConsumerWidget { attachedMessages: attachedMessages, attachedPosts: attachedPosts, isDisabled: isDisabled, + services: chatState.services.value, + selectedServiceId: chatState.selectedServiceId, ), ), ), @@ -513,6 +544,8 @@ class ThoughtInput extends HookWidget { final List>? attachedMessages; final List? attachedPosts; final bool isDisabled; + final List services; + final ValueNotifier selectedServiceId; const ThoughtInput({ super.key, @@ -522,6 +555,8 @@ class ThoughtInput extends HookWidget { this.attachedMessages, this.attachedPosts, this.isDisabled = false, + required this.services, + required this.selectedServiceId, }); @override @@ -605,6 +640,7 @@ class ThoughtInput extends HookWidget { ], ), ), + Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -644,6 +680,110 @@ class ThoughtInput extends HookWidget { ), ], ), + Padding( + padding: const EdgeInsets.fromLTRB(8, 4, 8, 4), + child: Row( + children: [ + if (services.isNotEmpty) + DropdownButtonHideUnderline( + child: DropdownButton2( + value: + selectedServiceId.value.isEmpty + ? null + : selectedServiceId.value, + customButton: Container( + padding: EdgeInsets.all(4), + decoration: BoxDecoration( + border: BoxBorder.all( + color: Theme.of(context).colorScheme.outline, + width: 1, + ), + borderRadius: const BorderRadius.all( + Radius.circular(16), + ), + ), + child: Row( + spacing: 8, + children: [ + const Icon( + Symbols.network_intelligence, + size: 20, + ), + Text(selectedServiceId.value), + const Icon( + Symbols.keyboard_arrow_down, + size: 14, + ).padding(right: 4), + ], + ).padding(vertical: 2, horizontal: 6), + ), + items: + services + .map( + (service) => DropdownMenuItem( + value: service.serviceId, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + service.serviceId, + style: DefaultTextStyle.of( + context, + ).style.copyWith( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + Text( + 'Rate: ${service.billingMultiplier}x, Level: P${service.perkLevel}', + style: DefaultTextStyle.of( + context, + ).style.copyWith( + fontSize: 12, + color: + Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ], + ), + ), + ) + .toList(), + onChanged: + !isStreaming && !isDisabled + ? (value) { + if (value != null) { + selectedServiceId.value = value; + } + } + : null, + hint: const Text('Select Service'), + isDense: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(16), + ), + ), + ), + menuItemStyleData: MenuItemStyleData( + selectedMenuItemBuilder: (context, child) { + return child; + }, + height: 56, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8, + ), + ), + ), + ), + ], + ), + ), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index efda9711..7e142455 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: "direct main" description: name: animations - sha256: a8031b276f0a7986ac907195f10ca7cd04ecf2a8a566bd6dbe03018a9b02b427 + sha256: "18938cefd7dcc04e1ecac0db78973761a01e4bc2d6bfae0cfa596bfeac9e96ab" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" ansicolor: dependency: transitive description: @@ -1593,10 +1593,10 @@ packages: dependency: "direct main" description: name: media_kit - sha256: "52a8e989babc431db0aa242f32a4a08e55f60662477ea09759a105d7cd6410da" + sha256: dfd5ab85d49a1806b1314a0b81f3d14da48f0db0a657336b2d77c5f17db28944 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" media_kit_libs_android_video: dependency: transitive description: