diff --git a/lib/models/thought.dart b/lib/models/thought.dart new file mode 100644 index 00000000..73e823cc --- /dev/null +++ b/lib/models/thought.dart @@ -0,0 +1,66 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:island/models/file.dart'; + +part 'thought.freezed.dart'; +part 'thought.g.dart'; + +enum ThinkingThoughtRole { + assistant(0), + user(1); + + const ThinkingThoughtRole(this.value); + final int value; + + static ThinkingThoughtRole fromValue(int value) { + return values.firstWhere((e) => e.value == value); + } +} + +class ThinkingThoughtRoleConverter + implements JsonConverter { + const ThinkingThoughtRoleConverter(); + + @override + ThinkingThoughtRole fromJson(int json) => ThinkingThoughtRole.fromValue(json); + + @override + int toJson(ThinkingThoughtRole object) => object.value; +} + +@freezed +sealed class StreamThinkingRequest with _$StreamThinkingRequest { + const factory StreamThinkingRequest({ + required String userMessage, + String? sequenceId, + }) = _StreamThinkingRequest; + + factory StreamThinkingRequest.fromJson(Map json) => + _$StreamThinkingRequestFromJson(json); +} + +@freezed +sealed class SnThinkingSequence with _$SnThinkingSequence { + const factory SnThinkingSequence({ + required String id, + String? topic, + required String accountId, + }) = _SnThinkingSequence; + + factory SnThinkingSequence.fromJson(Map json) => + _$SnThinkingSequenceFromJson(json); +} + +@freezed +sealed class SnThinkingThought with _$SnThinkingThought { + const factory SnThinkingThought({ + required String id, + String? content, + @Default([]) List files, + @ThinkingThoughtRoleConverter() required ThinkingThoughtRole role, + required String sequenceId, + SnThinkingSequence? sequence, + }) = _SnThinkingThought; + + factory SnThinkingThought.fromJson(Map json) => + _$SnThinkingThoughtFromJson(json); +} diff --git a/lib/models/thought.freezed.dart b/lib/models/thought.freezed.dart new file mode 100644 index 00000000..ec273c2e --- /dev/null +++ b/lib/models/thought.freezed.dart @@ -0,0 +1,839 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// 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 'thought.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$StreamThinkingRequest { + + String get userMessage; String? get sequenceId; +/// Create a copy of StreamThinkingRequest +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$StreamThinkingRequestCopyWith get copyWith => _$StreamThinkingRequestCopyWithImpl(this as StreamThinkingRequest, _$identity); + + /// Serializes this StreamThinkingRequest to a JSON map. + 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)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,userMessage,sequenceId); + +@override +String toString() { + return 'StreamThinkingRequest(userMessage: $userMessage, sequenceId: $sequenceId)'; +} + + +} + +/// @nodoc +abstract mixin class $StreamThinkingRequestCopyWith<$Res> { + factory $StreamThinkingRequestCopyWith(StreamThinkingRequest value, $Res Function(StreamThinkingRequest) _then) = _$StreamThinkingRequestCopyWithImpl; +@useResult +$Res call({ + String userMessage, String? sequenceId +}); + + + + +} +/// @nodoc +class _$StreamThinkingRequestCopyWithImpl<$Res> + implements $StreamThinkingRequestCopyWith<$Res> { + _$StreamThinkingRequestCopyWithImpl(this._self, this._then); + + final StreamThinkingRequest _self; + final $Res Function(StreamThinkingRequest) _then; + +/// 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,}) { + 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?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [StreamThinkingRequest]. +extension StreamThinkingRequestPatterns on StreamThinkingRequest { +/// 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( _StreamThinkingRequest value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _StreamThinkingRequest() 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( _StreamThinkingRequest value) $default,){ +final _that = this; +switch (_that) { +case _StreamThinkingRequest(): +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( _StreamThinkingRequest value)? $default,){ +final _that = this; +switch (_that) { +case _StreamThinkingRequest() 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( String userMessage, String? sequenceId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _StreamThinkingRequest() when $default != null: +return $default(_that.userMessage,_that.sequenceId);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( String userMessage, String? sequenceId) $default,) {final _that = this; +switch (_that) { +case _StreamThinkingRequest(): +return $default(_that.userMessage,_that.sequenceId);} +} +/// 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( String userMessage, String? sequenceId)? $default,) {final _that = this; +switch (_that) { +case _StreamThinkingRequest() when $default != null: +return $default(_that.userMessage,_that.sequenceId);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _StreamThinkingRequest implements StreamThinkingRequest { + const _StreamThinkingRequest({required this.userMessage, this.sequenceId}); + factory _StreamThinkingRequest.fromJson(Map json) => _$StreamThinkingRequestFromJson(json); + +@override final String userMessage; +@override final String? sequenceId; + +/// Create a copy of StreamThinkingRequest +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$StreamThinkingRequestCopyWith<_StreamThinkingRequest> get copyWith => __$StreamThinkingRequestCopyWithImpl<_StreamThinkingRequest>(this, _$identity); + +@override +Map toJson() { + return _$StreamThinkingRequestToJson(this, ); +} + +@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)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,userMessage,sequenceId); + +@override +String toString() { + return 'StreamThinkingRequest(userMessage: $userMessage, sequenceId: $sequenceId)'; +} + + +} + +/// @nodoc +abstract mixin class _$StreamThinkingRequestCopyWith<$Res> implements $StreamThinkingRequestCopyWith<$Res> { + factory _$StreamThinkingRequestCopyWith(_StreamThinkingRequest value, $Res Function(_StreamThinkingRequest) _then) = __$StreamThinkingRequestCopyWithImpl; +@override @useResult +$Res call({ + String userMessage, String? sequenceId +}); + + + + +} +/// @nodoc +class __$StreamThinkingRequestCopyWithImpl<$Res> + implements _$StreamThinkingRequestCopyWith<$Res> { + __$StreamThinkingRequestCopyWithImpl(this._self, this._then); + + final _StreamThinkingRequest _self; + final $Res Function(_StreamThinkingRequest) _then; + +/// 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,}) { + 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?, + )); +} + + +} + + +/// @nodoc +mixin _$SnThinkingSequence { + + String get id; String? get topic; String get accountId; +/// Create a copy of SnThinkingSequence +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnThinkingSequenceCopyWith get copyWith => _$SnThinkingSequenceCopyWithImpl(this as SnThinkingSequence, _$identity); + + /// Serializes this SnThinkingSequence to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnThinkingSequence&&(identical(other.id, id) || other.id == id)&&(identical(other.topic, topic) || other.topic == topic)&&(identical(other.accountId, accountId) || other.accountId == accountId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,topic,accountId); + +@override +String toString() { + return 'SnThinkingSequence(id: $id, topic: $topic, accountId: $accountId)'; +} + + +} + +/// @nodoc +abstract mixin class $SnThinkingSequenceCopyWith<$Res> { + factory $SnThinkingSequenceCopyWith(SnThinkingSequence value, $Res Function(SnThinkingSequence) _then) = _$SnThinkingSequenceCopyWithImpl; +@useResult +$Res call({ + String id, String? topic, String accountId +}); + + + + +} +/// @nodoc +class _$SnThinkingSequenceCopyWithImpl<$Res> + implements $SnThinkingSequenceCopyWith<$Res> { + _$SnThinkingSequenceCopyWithImpl(this._self, this._then); + + final SnThinkingSequence _self; + final $Res Function(SnThinkingSequence) _then; + +/// Create a copy of SnThinkingSequence +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? topic = freezed,Object? accountId = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,topic: freezed == topic ? _self.topic : topic // ignore: cast_nullable_to_non_nullable +as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SnThinkingSequence]. +extension SnThinkingSequencePatterns on SnThinkingSequence { +/// 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( _SnThinkingSequence value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SnThinkingSequence() 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( _SnThinkingSequence value) $default,){ +final _that = this; +switch (_that) { +case _SnThinkingSequence(): +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( _SnThinkingSequence value)? $default,){ +final _that = this; +switch (_that) { +case _SnThinkingSequence() 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( String id, String? topic, String accountId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SnThinkingSequence() when $default != null: +return $default(_that.id,_that.topic,_that.accountId);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( String id, String? topic, String accountId) $default,) {final _that = this; +switch (_that) { +case _SnThinkingSequence(): +return $default(_that.id,_that.topic,_that.accountId);} +} +/// 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( String id, String? topic, String accountId)? $default,) {final _that = this; +switch (_that) { +case _SnThinkingSequence() when $default != null: +return $default(_that.id,_that.topic,_that.accountId);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SnThinkingSequence implements SnThinkingSequence { + const _SnThinkingSequence({required this.id, this.topic, required this.accountId}); + factory _SnThinkingSequence.fromJson(Map json) => _$SnThinkingSequenceFromJson(json); + +@override final String id; +@override final String? topic; +@override final String accountId; + +/// Create a copy of SnThinkingSequence +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnThinkingSequenceCopyWith<_SnThinkingSequence> get copyWith => __$SnThinkingSequenceCopyWithImpl<_SnThinkingSequence>(this, _$identity); + +@override +Map toJson() { + return _$SnThinkingSequenceToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnThinkingSequence&&(identical(other.id, id) || other.id == id)&&(identical(other.topic, topic) || other.topic == topic)&&(identical(other.accountId, accountId) || other.accountId == accountId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,topic,accountId); + +@override +String toString() { + return 'SnThinkingSequence(id: $id, topic: $topic, accountId: $accountId)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnThinkingSequenceCopyWith<$Res> implements $SnThinkingSequenceCopyWith<$Res> { + factory _$SnThinkingSequenceCopyWith(_SnThinkingSequence value, $Res Function(_SnThinkingSequence) _then) = __$SnThinkingSequenceCopyWithImpl; +@override @useResult +$Res call({ + String id, String? topic, String accountId +}); + + + + +} +/// @nodoc +class __$SnThinkingSequenceCopyWithImpl<$Res> + implements _$SnThinkingSequenceCopyWith<$Res> { + __$SnThinkingSequenceCopyWithImpl(this._self, this._then); + + final _SnThinkingSequence _self; + final $Res Function(_SnThinkingSequence) _then; + +/// Create a copy of SnThinkingSequence +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? topic = freezed,Object? accountId = null,}) { + return _then(_SnThinkingSequence( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,topic: freezed == topic ? _self.topic : topic // ignore: cast_nullable_to_non_nullable +as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$SnThinkingThought { + + String get id; String? get content; List get files;@ThinkingThoughtRoleConverter() ThinkingThoughtRole get role; String get sequenceId; SnThinkingSequence? get sequence; +/// Create a copy of SnThinkingThought +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnThinkingThoughtCopyWith get copyWith => _$SnThinkingThoughtCopyWithImpl(this as SnThinkingThought, _$identity); + + /// Serializes this SnThinkingThought to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnThinkingThought&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&const DeepCollectionEquality().equals(other.files, files)&&(identical(other.role, role) || other.role == role)&&(identical(other.sequenceId, sequenceId) || other.sequenceId == sequenceId)&&(identical(other.sequence, sequence) || other.sequence == sequence)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,content,const DeepCollectionEquality().hash(files),role,sequenceId,sequence); + +@override +String toString() { + return 'SnThinkingThought(id: $id, content: $content, files: $files, role: $role, sequenceId: $sequenceId, sequence: $sequence)'; +} + + +} + +/// @nodoc +abstract mixin class $SnThinkingThoughtCopyWith<$Res> { + factory $SnThinkingThoughtCopyWith(SnThinkingThought value, $Res Function(SnThinkingThought) _then) = _$SnThinkingThoughtCopyWithImpl; +@useResult +$Res call({ + String id, String? content, List files,@ThinkingThoughtRoleConverter() ThinkingThoughtRole role, String sequenceId, SnThinkingSequence? sequence +}); + + +$SnThinkingSequenceCopyWith<$Res>? get sequence; + +} +/// @nodoc +class _$SnThinkingThoughtCopyWithImpl<$Res> + implements $SnThinkingThoughtCopyWith<$Res> { + _$SnThinkingThoughtCopyWithImpl(this._self, this._then); + + final SnThinkingThought _self; + final $Res Function(SnThinkingThought) _then; + +/// Create a copy of SnThinkingThought +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? content = freezed,Object? files = null,Object? role = null,Object? sequenceId = null,Object? sequence = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as String?,files: null == files ? _self.files : files // ignore: cast_nullable_to_non_nullable +as List,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable +as ThinkingThoughtRole,sequenceId: null == sequenceId ? _self.sequenceId : sequenceId // ignore: cast_nullable_to_non_nullable +as String,sequence: freezed == sequence ? _self.sequence : sequence // ignore: cast_nullable_to_non_nullable +as SnThinkingSequence?, + )); +} +/// Create a copy of SnThinkingThought +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnThinkingSequenceCopyWith<$Res>? get sequence { + if (_self.sequence == null) { + return null; + } + + return $SnThinkingSequenceCopyWith<$Res>(_self.sequence!, (value) { + return _then(_self.copyWith(sequence: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [SnThinkingThought]. +extension SnThinkingThoughtPatterns on SnThinkingThought { +/// 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( _SnThinkingThought value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SnThinkingThought() 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( _SnThinkingThought value) $default,){ +final _that = this; +switch (_that) { +case _SnThinkingThought(): +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( _SnThinkingThought value)? $default,){ +final _that = this; +switch (_that) { +case _SnThinkingThought() 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( String id, String? content, List files, @ThinkingThoughtRoleConverter() ThinkingThoughtRole role, String sequenceId, SnThinkingSequence? sequence)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SnThinkingThought() when $default != null: +return $default(_that.id,_that.content,_that.files,_that.role,_that.sequenceId,_that.sequence);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( String id, String? content, List files, @ThinkingThoughtRoleConverter() ThinkingThoughtRole role, String sequenceId, SnThinkingSequence? sequence) $default,) {final _that = this; +switch (_that) { +case _SnThinkingThought(): +return $default(_that.id,_that.content,_that.files,_that.role,_that.sequenceId,_that.sequence);} +} +/// 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( String id, String? content, List files, @ThinkingThoughtRoleConverter() ThinkingThoughtRole role, String sequenceId, SnThinkingSequence? sequence)? $default,) {final _that = this; +switch (_that) { +case _SnThinkingThought() when $default != null: +return $default(_that.id,_that.content,_that.files,_that.role,_that.sequenceId,_that.sequence);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SnThinkingThought implements SnThinkingThought { + const _SnThinkingThought({required this.id, this.content, final List files = const [], @ThinkingThoughtRoleConverter() required this.role, required this.sequenceId, this.sequence}): _files = files; + factory _SnThinkingThought.fromJson(Map json) => _$SnThinkingThoughtFromJson(json); + +@override final String id; +@override final String? content; + final List _files; +@override@JsonKey() List get files { + if (_files is EqualUnmodifiableListView) return _files; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_files); +} + +@override@ThinkingThoughtRoleConverter() final ThinkingThoughtRole role; +@override final String sequenceId; +@override final SnThinkingSequence? sequence; + +/// Create a copy of SnThinkingThought +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnThinkingThoughtCopyWith<_SnThinkingThought> get copyWith => __$SnThinkingThoughtCopyWithImpl<_SnThinkingThought>(this, _$identity); + +@override +Map toJson() { + return _$SnThinkingThoughtToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnThinkingThought&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&const DeepCollectionEquality().equals(other._files, _files)&&(identical(other.role, role) || other.role == role)&&(identical(other.sequenceId, sequenceId) || other.sequenceId == sequenceId)&&(identical(other.sequence, sequence) || other.sequence == sequence)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,content,const DeepCollectionEquality().hash(_files),role,sequenceId,sequence); + +@override +String toString() { + return 'SnThinkingThought(id: $id, content: $content, files: $files, role: $role, sequenceId: $sequenceId, sequence: $sequence)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnThinkingThoughtCopyWith<$Res> implements $SnThinkingThoughtCopyWith<$Res> { + factory _$SnThinkingThoughtCopyWith(_SnThinkingThought value, $Res Function(_SnThinkingThought) _then) = __$SnThinkingThoughtCopyWithImpl; +@override @useResult +$Res call({ + String id, String? content, List files,@ThinkingThoughtRoleConverter() ThinkingThoughtRole role, String sequenceId, SnThinkingSequence? sequence +}); + + +@override $SnThinkingSequenceCopyWith<$Res>? get sequence; + +} +/// @nodoc +class __$SnThinkingThoughtCopyWithImpl<$Res> + implements _$SnThinkingThoughtCopyWith<$Res> { + __$SnThinkingThoughtCopyWithImpl(this._self, this._then); + + final _SnThinkingThought _self; + final $Res Function(_SnThinkingThought) _then; + +/// Create a copy of SnThinkingThought +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? content = freezed,Object? files = null,Object? role = null,Object? sequenceId = null,Object? sequence = freezed,}) { + return _then(_SnThinkingThought( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as String?,files: null == files ? _self._files : files // ignore: cast_nullable_to_non_nullable +as List,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable +as ThinkingThoughtRole,sequenceId: null == sequenceId ? _self.sequenceId : sequenceId // ignore: cast_nullable_to_non_nullable +as String,sequence: freezed == sequence ? _self.sequence : sequence // ignore: cast_nullable_to_non_nullable +as SnThinkingSequence?, + )); +} + +/// Create a copy of SnThinkingThought +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnThinkingSequenceCopyWith<$Res>? get sequence { + if (_self.sequence == null) { + return null; + } + + return $SnThinkingSequenceCopyWith<$Res>(_self.sequence!, (value) { + return _then(_self.copyWith(sequence: value)); + }); +} +} + +// dart format on diff --git a/lib/models/thought.g.dart b/lib/models/thought.g.dart new file mode 100644 index 00000000..a86ea58b --- /dev/null +++ b/lib/models/thought.g.dart @@ -0,0 +1,66 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'thought.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_StreamThinkingRequest _$StreamThinkingRequestFromJson( + Map json, +) => _StreamThinkingRequest( + userMessage: json['user_message'] as String, + sequenceId: json['sequence_id'] as String?, +); + +Map _$StreamThinkingRequestToJson( + _StreamThinkingRequest instance, +) => { + 'user_message': instance.userMessage, + 'sequence_id': instance.sequenceId, +}; + +_SnThinkingSequence _$SnThinkingSequenceFromJson(Map json) => + _SnThinkingSequence( + id: json['id'] as String, + topic: json['topic'] as String?, + accountId: json['account_id'] as String, + ); + +Map _$SnThinkingSequenceToJson(_SnThinkingSequence instance) => + { + 'id': instance.id, + 'topic': instance.topic, + 'account_id': instance.accountId, + }; + +_SnThinkingThought _$SnThinkingThoughtFromJson(Map json) => + _SnThinkingThought( + id: json['id'] as String, + content: json['content'] as String?, + files: + (json['files'] as List?) + ?.map((e) => SnCloudFile.fromJson(e as Map)) + .toList() ?? + const [], + role: const ThinkingThoughtRoleConverter().fromJson( + (json['role'] as num).toInt(), + ), + sequenceId: json['sequence_id'] as String, + sequence: + json['sequence'] == null + ? null + : SnThinkingSequence.fromJson( + json['sequence'] as Map, + ), + ); + +Map _$SnThinkingThoughtToJson(_SnThinkingThought instance) => + { + 'id': instance.id, + 'content': instance.content, + 'files': instance.files.map((e) => e.toJson()).toList(), + 'role': const ThinkingThoughtRoleConverter().toJson(instance.role), + 'sequence_id': instance.sequenceId, + 'sequence': instance.sequence?.toJson(), + }; diff --git a/lib/route.dart b/lib/route.dart index a5ebdd5a..47ac3fa2 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -37,6 +37,7 @@ import 'package:island/screens/chat/room.dart'; import 'package:island/screens/chat/room_detail.dart'; import 'package:island/screens/chat/call.dart'; import 'package:island/screens/chat/search_messages.dart'; +import 'package:island/screens/thought/think.dart'; import 'package:island/screens/creators/hub.dart'; import 'package:island/screens/creators/posts/post_manage_list.dart'; import 'package:island/screens/creators/stickers/stickers.dart'; @@ -131,6 +132,11 @@ final routerProvider = Provider((ref) { return CallScreen(roomId: id); }, ), + GoRoute( + name: 'thought', + path: '/thought', + builder: (context, state) => const ThoughtScreen(), + ), GoRoute( name: 'logs', path: '/logs', diff --git a/lib/screens/tabs.dart b/lib/screens/tabs.dart index bab5c7ef..a4da277a 100644 --- a/lib/screens/tabs.dart +++ b/lib/screens/tabs.dart @@ -168,6 +168,17 @@ class TabsScreen extends HookConsumerWidget { await PostComposeDialog.show(context); }, ), + ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 24, + ), + leading: const Icon(Symbols.bubble_chart), + title: Text('让我寻思寻思'), + onTap: () async { + Navigator.of(context).pop(); + context.pushNamed('thought'); + }, + ), Consumer( builder: (context, ref, _) { final notificationCount = ref.watch( diff --git a/lib/screens/thought/think.dart b/lib/screens/thought/think.dart new file mode 100644 index 00000000..7ebc67b6 --- /dev/null +++ b/lib/screens/thought/think.dart @@ -0,0 +1,372 @@ +import "dart:async"; +import "dart:convert"; +import "package:dio/dio.dart"; +import "package:flutter/material.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; +import "package:gap/gap.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:island/models/thought.dart"; +import "package:island/pods/network.dart"; +import "package:island/widgets/alert.dart"; +import "package:island/widgets/app_scaffold.dart"; +import "package:island/widgets/content/markdown.dart"; +import "package:island/widgets/response.dart"; +import "package:material_symbols_icons/material_symbols_icons.dart"; +import "package:super_sliver_list/super_sliver_list.dart"; + +// State management providers +final thoughtSequencesProvider = FutureProvider>(( + ref, +) async { + final apiClient = ref.watch(apiClientProvider); + final response = await apiClient.get('/insight/thought/sequences'); + return (response.data as List) + .map((e) => SnThinkingSequence.fromJson(e)) + .toList(); +}); + +final thoughtSequenceProvider = + FutureProvider.family, String>(( + ref, + sequenceId, + ) async { + final apiClient = ref.watch(apiClientProvider); + final response = await apiClient.get( + '/insight/thought/sequences/$sequenceId', + ); + return (response.data as List) + .map((e) => SnThinkingThought.fromJson(e)) + .toList(); + }); + +class ThoughtScreen extends HookConsumerWidget { + const ThoughtScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final sequences = ref.watch(thoughtSequencesProvider); + final selectedSequenceId = useState(null); + final thoughts = + selectedSequenceId.value != null + ? ref.watch(thoughtSequenceProvider(selectedSequenceId.value!)) + : const AsyncValue>.data([]); + + final localThoughts = useState>([]); + + final messageController = useTextEditingController(); + final scrollController = useScrollController(); + final isStreaming = useState(false); + final streamingText = useState(''); + + final listController = useMemoized(() => ListController(), []); + + // Update local thoughts when provider data changes + useEffect(() { + thoughts.whenData((data) => localThoughts.value = data); + return null; + }, [thoughts]); + + // Scroll to bottom when thoughts change or streaming state changes + useEffect(() { + if (localThoughts.value.isNotEmpty || isStreaming.value) { + WidgetsBinding.instance.addPostFrameCallback((_) { + scrollController.animateTo( + scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + }); + } + return null; + }, [localThoughts.value.length, isStreaming.value]); + + void sendMessage() async { + if (messageController.text.trim().isEmpty) return; + + final userMessage = messageController.text.trim(); + + // Add user message to local thoughts + final userThought = SnThinkingThought( + id: 'temp-user-${DateTime.now().millisecondsSinceEpoch}', + content: userMessage, + files: [], + role: ThinkingThoughtRole.user, + sequenceId: selectedSequenceId.value ?? '', + sequence: + selectedSequenceId.value != null + ? thoughts.value?.firstOrNull?.sequence ?? + SnThinkingSequence( + id: selectedSequenceId.value!, + accountId: '', + ) + : SnThinkingSequence(id: '', accountId: ''), + ); + localThoughts.value = [userThought, ...localThoughts.value]; + + final request = StreamThinkingRequest( + userMessage: userMessage, + sequenceId: selectedSequenceId.value, + ); + + try { + isStreaming.value = true; + streamingText.value = ''; + + final apiClient = ref.read(apiClientProvider); + final response = await apiClient.post( + '/insight/thought', + data: request.toJson(), + options: Options( + responseType: ResponseType.stream, + sendTimeout: Duration(minutes: 1), + receiveTimeout: Duration(minutes: 1), + ), + ); + + final stream = response.data.stream; + final completer = Completer(); + final buffer = StringBuffer(); + + stream.listen( + (data) { + final chunk = utf8.decode(data); + buffer.write(chunk); + streamingText.value = buffer.toString(); + }, + onDone: () { + completer.complete(buffer.toString()); + isStreaming.value = false; + // Parse the response and add AI thought + try { + final lines = buffer.toString().split('\n'); + final lastLine = lines.lastWhere( + (line) => line.trim().isNotEmpty, + ); + final responseJson = jsonDecode(lastLine); + final aiThought = SnThinkingThought.fromJson(responseJson); + localThoughts.value = [aiThought, ...localThoughts.value]; + } catch (e) { + showErrorAlert('Failed to parse AI response'); + } + }, + onError: (error) { + completer.completeError(error); + isStreaming.value = false; + // Handle streaming response errors differently + if (error is DioException && error.response?.data is ResponseBody) { + // For streaming responses, show a generic error message + showErrorAlert('Failed to get AI response. Please try again.'); + } else { + showErrorAlert(error); + } + }, + ); + + messageController.clear(); + } catch (error) { + isStreaming.value = false; + showErrorAlert(error); + } + } + + Widget thoughtItem(SnThinkingThought thought) => Container( + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: + thought.role == ThinkingThoughtRole.assistant + ? Theme.of(context).colorScheme.surfaceContainerHighest + : Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + thought.role == ThinkingThoughtRole.assistant + ? Symbols.smart_toy + : Symbols.person, + size: 20, + ), + const Gap(8), + Text( + thought.role == ThinkingThoughtRole.assistant + ? 'AI Assistant' + : 'You', + style: Theme.of(context).textTheme.titleSmall, + ), + ], + ), + const Gap(8), + if (thought.content != null) + MarkdownTextContent( + content: thought.content!, + textStyle: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ); + + Widget streamingThoughtItem() => Container( + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Symbols.smart_toy, size: 20), + const Gap(8), + Text( + 'AI Assistant', + style: Theme.of(context).textTheme.titleSmall, + ), + const Spacer(), + SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ], + ), + const Gap(8), + MarkdownTextContent( + content: streamingText.value, + textStyle: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ); + + return AppScaffold( + appBar: AppBar( + title: const Text('AI Thought'), + actions: [ + IconButton( + icon: const Icon(Symbols.history), + onPressed: () { + // Show sequence selector + showModalBottomSheet( + context: context, + builder: + (context) => sequences.when( + data: + (seqs) => ListView.builder( + itemCount: seqs.length, + itemBuilder: (context, index) { + final seq = seqs[index]; + return ListTile( + title: Text( + seq.topic ?? 'Untitled Conversation', + ), + onTap: () { + selectedSequenceId.value = seq.id; + Navigator.of(context).pop(); + }, + ); + }, + ), + loading: + () => + const Center(child: CircularProgressIndicator()), + error: + (error, _) => ResponseErrorWidget( + error: error, + onRetry: + () => ref.invalidate(thoughtSequencesProvider), + ), + ), + ); + }, + ), + ], + ), + body: Column( + children: [ + Expanded( + child: thoughts.when( + data: + (thoughtList) => SuperListView.builder( + listController: listController, + controller: scrollController, + padding: const EdgeInsets.only(bottom: 16), + reverse: true, + itemCount: + localThoughts.value.length + + (isStreaming.value ? 1 : 0), + itemBuilder: (context, index) { + if (isStreaming.value && index == 0) { + return streamingThoughtItem(); + } + final thoughtIndex = + isStreaming.value ? index - 1 : index; + final thought = localThoughts.value[thoughtIndex]; + return thoughtItem(thought); + }, + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: + (error, _) => ResponseErrorWidget( + error: error, + onRetry: + () => + selectedSequenceId.value != null + ? ref.invalidate( + thoughtSequenceProvider( + selectedSequenceId.value!, + ), + ) + : null, + ), + ), + ), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + border: Border( + top: BorderSide( + color: Theme.of(context).dividerColor, + width: 1, + ), + ), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: messageController, + decoration: InputDecoration( + hintText: 'Ask me anything...', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(24), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + maxLines: null, + textInputAction: TextInputAction.send, + onSubmitted: (_) => sendMessage(), + ), + ), + const Gap(8), + IconButton.filled( + onPressed: isStreaming.value ? null : sendMessage, + icon: Icon(isStreaming.value ? Symbols.stop : Symbols.send), + ), + ], + ), + ), + ], + ), + ); + } +}