🐛 Bug fixes for the AI thought
This commit is contained in:
@@ -310,6 +310,7 @@
|
|||||||
"settingsServerUrl": "Server URL",
|
"settingsServerUrl": "Server URL",
|
||||||
"settingsApplied": "The settings has been applied.",
|
"settingsApplied": "The settings has been applied.",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
|
"notificationsDescription": "See what's happended related to you recently.",
|
||||||
"posts": "Posts",
|
"posts": "Posts",
|
||||||
"settingsBackgroundImage": "Background Image",
|
"settingsBackgroundImage": "Background Image",
|
||||||
"settingsBackgroundImageClear": "Clear Background Image",
|
"settingsBackgroundImageClear": "Clear Background Image",
|
||||||
@@ -1135,6 +1136,7 @@
|
|||||||
"installUpdate": "Install update",
|
"installUpdate": "Install update",
|
||||||
"openReleasePage": "Open release page",
|
"openReleasePage": "Open release page",
|
||||||
"postCompose": "Compose Post",
|
"postCompose": "Compose Post",
|
||||||
|
"postComposeDescription": "Compose a new post",
|
||||||
"postPublish": "Publish Post",
|
"postPublish": "Publish Post",
|
||||||
"restoreDraftTitle": "Restore Draft",
|
"restoreDraftTitle": "Restore Draft",
|
||||||
"restoreDraftMessage": "A draft was found. Do you want to restore it?",
|
"restoreDraftMessage": "A draft was found. Do you want to restore it?",
|
||||||
|
|||||||
@@ -10,17 +10,20 @@ import "package:island/widgets/thought/thought_shared.dart";
|
|||||||
import "package:material_symbols_icons/material_symbols_icons.dart";
|
import "package:material_symbols_icons/material_symbols_icons.dart";
|
||||||
|
|
||||||
class ThoughtSheet extends HookConsumerWidget {
|
class ThoughtSheet extends HookConsumerWidget {
|
||||||
|
final String? initialMessage;
|
||||||
final List<Map<String, dynamic>> attachedMessages;
|
final List<Map<String, dynamic>> attachedMessages;
|
||||||
final List<String> attachedPosts;
|
final List<String> attachedPosts;
|
||||||
|
|
||||||
const ThoughtSheet({
|
const ThoughtSheet({
|
||||||
super.key,
|
super.key,
|
||||||
|
this.initialMessage,
|
||||||
this.attachedMessages = const [],
|
this.attachedMessages = const [],
|
||||||
this.attachedPosts = const [],
|
this.attachedPosts = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
static Future<void> show(
|
static Future<void> show(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
|
String? initialMessage,
|
||||||
List<Map<String, dynamic>> attachedMessages = const [],
|
List<Map<String, dynamic>> attachedMessages = const [],
|
||||||
List<String> attachedPosts = const [],
|
List<String> attachedPosts = const [],
|
||||||
}) {
|
}) {
|
||||||
@@ -28,11 +31,11 @@ class ThoughtSheet extends HookConsumerWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
useSafeArea: true,
|
useSafeArea: true,
|
||||||
builder:
|
builder: (context) => ThoughtSheet(
|
||||||
(context) => ThoughtSheet(
|
initialMessage: initialMessage,
|
||||||
attachedMessages: attachedMessages,
|
attachedMessages: attachedMessages,
|
||||||
attachedPosts: attachedPosts,
|
attachedPosts: attachedPosts,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +43,7 @@ class ThoughtSheet extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final chatState = useThoughtChat(
|
final chatState = useThoughtChat(
|
||||||
ref,
|
ref,
|
||||||
|
initialMessage: initialMessage,
|
||||||
attachedMessages: attachedMessages,
|
attachedMessages: attachedMessages,
|
||||||
attachedPosts: attachedPosts,
|
attachedPosts: attachedPosts,
|
||||||
);
|
);
|
||||||
@@ -75,31 +79,30 @@ class ThoughtSheet extends HookConsumerWidget {
|
|||||||
return status
|
return status
|
||||||
? chatInterface
|
? chatInterface
|
||||||
: Column(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
MaterialBanner(
|
MaterialBanner(
|
||||||
leading: const Icon(Symbols.error),
|
leading: const Icon(Symbols.error),
|
||||||
content: const Text(
|
content: const Text(
|
||||||
'You have unpaid orders. Please settle your payment to continue using the service.',
|
'You have unpaid orders. Please settle your payment to continue using the service.',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
retry();
|
|
||||||
},
|
|
||||||
child: Text('retry'.tr()),
|
|
||||||
),
|
),
|
||||||
],
|
actions: [
|
||||||
),
|
TextButton(
|
||||||
Expanded(child: chatInterface),
|
onPressed: () {
|
||||||
],
|
retry();
|
||||||
);
|
},
|
||||||
|
child: Text('retry'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(child: chatInterface),
|
||||||
|
],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
orElse:
|
orElse: () => ThoughtChatInterface(
|
||||||
() => ThoughtChatInterface(
|
attachedMessages: attachedMessages,
|
||||||
attachedMessages: attachedMessages,
|
attachedPosts: attachedPosts,
|
||||||
attachedPosts: attachedPosts,
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,3 +38,16 @@ class ShowComposeSheetEvent {
|
|||||||
class ShowNotificationSheetEvent {
|
class ShowNotificationSheetEvent {
|
||||||
const ShowNotificationSheetEvent();
|
const ShowNotificationSheetEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Event fired to show the thought sheet
|
||||||
|
class ShowThoughtSheetEvent {
|
||||||
|
final String? initialMessage;
|
||||||
|
final List<Map<String, dynamic>> attachedMessages;
|
||||||
|
final List<String> attachedPosts;
|
||||||
|
|
||||||
|
const ShowThoughtSheetEvent({
|
||||||
|
this.initialMessage,
|
||||||
|
this.attachedMessages = const [],
|
||||||
|
this.attachedPosts = const [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import 'package:island/widgets/content/network_status_sheet.dart';
|
|||||||
import 'package:island/widgets/tour/tour.dart';
|
import 'package:island/widgets/tour/tour.dart';
|
||||||
import 'package:island/widgets/post/compose_sheet.dart';
|
import 'package:island/widgets/post/compose_sheet.dart';
|
||||||
import 'package:island/screens/notification.dart';
|
import 'package:island/screens/notification.dart';
|
||||||
|
import 'package:island/screens/thought/think_sheet.dart';
|
||||||
import 'package:island/services/event_bus.dart';
|
import 'package:island/services/event_bus.dart';
|
||||||
import 'package:tray_manager/tray_manager.dart';
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
@@ -38,6 +39,7 @@ class _AppWrapperState extends ConsumerState<AppWrapper>
|
|||||||
|
|
||||||
StreamSubscription? composeSheetSubs;
|
StreamSubscription? composeSheetSubs;
|
||||||
StreamSubscription? notificationSheetSubs;
|
StreamSubscription? notificationSheetSubs;
|
||||||
|
StreamSubscription? thoughtSheetSubs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -70,6 +72,12 @@ class _AppWrapperState extends ConsumerState<AppWrapper>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
thoughtSheetSubs = eventBus.on<ShowThoughtSheetEvent>().listen((event) {
|
||||||
|
if (mounted) {
|
||||||
|
_showThoughtSheet(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
final initialUrl = await protocolHandler.getInitialUrl();
|
final initialUrl = await protocolHandler.getInitialUrl();
|
||||||
if (initialUrl != null && mounted) {
|
if (initialUrl != null && mounted) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@@ -87,6 +95,7 @@ class _AppWrapperState extends ConsumerState<AppWrapper>
|
|||||||
ntySubs?.cancel();
|
ntySubs?.cancel();
|
||||||
composeSheetSubs?.cancel();
|
composeSheetSubs?.cancel();
|
||||||
notificationSheetSubs?.cancel();
|
notificationSheetSubs?.cancel();
|
||||||
|
thoughtSheetSubs?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +163,15 @@ class _AppWrapperState extends ConsumerState<AppWrapper>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showThoughtSheet(ShowThoughtSheetEvent event) {
|
||||||
|
ThoughtSheet.show(
|
||||||
|
context,
|
||||||
|
initialMessage: event.initialMessage,
|
||||||
|
attachedMessages: event.attachedMessages,
|
||||||
|
attachedPosts: event.attachedPosts,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _handleDeepLink(Uri uri, WidgetRef ref) async {
|
void _handleDeepLink(Uri uri, WidgetRef ref) async {
|
||||||
String path = '/${uri.host}${uri.path}';
|
String path = '/${uri.host}${uri.path}';
|
||||||
|
|
||||||
|
|||||||
@@ -29,16 +29,17 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
static List<SpecialAction> _getSpecialActions(BuildContext context) {
|
static List<SpecialAction> _getSpecialActions(BuildContext context) {
|
||||||
return [
|
return [
|
||||||
SpecialAction(
|
SpecialAction(
|
||||||
name: 'Compose Post',
|
name: 'postCompose'.tr(),
|
||||||
description: 'Create a new post',
|
description: 'postComposeDescription'.tr(),
|
||||||
icon: Symbols.edit,
|
icon: Symbols.edit,
|
||||||
action: () {
|
action: () {
|
||||||
eventBus.fire(const ShowComposeSheetEvent());
|
eventBus.fire(const ShowComposeSheetEvent());
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SpecialAction(
|
SpecialAction(
|
||||||
name: 'Notifications',
|
name: 'notifications'.tr(),
|
||||||
description: 'View your notifications',
|
description: 'notificationsDescription'.tr(),
|
||||||
|
searchableAliases: ['notifications', 'alert', 'bell'],
|
||||||
icon: Symbols.notifications,
|
icon: Symbols.notifications,
|
||||||
action: () {
|
action: () {
|
||||||
eventBus.fire(const ShowNotificationSheetEvent());
|
eventBus.fire(const ShowNotificationSheetEvent());
|
||||||
@@ -149,7 +150,7 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
filteredChats.isEmpty &&
|
filteredChats.isEmpty &&
|
||||||
filteredSpecialActions.isEmpty &&
|
filteredSpecialActions.isEmpty &&
|
||||||
filteredRoutes.isEmpty
|
filteredRoutes.isEmpty
|
||||||
? _getFallbackActions(searchQuery.value)
|
? _getFallbackActions(context, searchQuery.value)
|
||||||
: <FallbackAction>[];
|
: <FallbackAction>[];
|
||||||
|
|
||||||
// Combine results: fallbacks first, then chats, special actions, routes
|
// Combine results: fallbacks first, then chats, special actions, routes
|
||||||
@@ -202,17 +203,7 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
if (event.logicalKey == LogicalKeyboardKey.enter ||
|
if (event.logicalKey == LogicalKeyboardKey.enter ||
|
||||||
event.logicalKey == LogicalKeyboardKey.numpadEnter) {
|
event.logicalKey == LogicalKeyboardKey.numpadEnter) {
|
||||||
final item = allResults[focusedIndex.value ?? 0];
|
final item = allResults[focusedIndex.value ?? 0];
|
||||||
if (item is SnChatRoom) {
|
_executeItem(context, ref, item);
|
||||||
_navigateToChat(context, ref, item);
|
|
||||||
} else if (item is SpecialAction) {
|
|
||||||
onDismiss();
|
|
||||||
item.action();
|
|
||||||
} else if (item is RouteItem) {
|
|
||||||
_navigateToRoute(context, ref, item);
|
|
||||||
} else if (item is FallbackAction) {
|
|
||||||
onDismiss();
|
|
||||||
item.action();
|
|
||||||
}
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
if (allResults.isNotEmpty) {
|
if (allResults.isNotEmpty) {
|
||||||
if (focusedIndex.value == null) {
|
if (focusedIndex.value == null) {
|
||||||
@@ -291,6 +282,13 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
child: const Icon(Symbols.keyboard_command_key),
|
child: const Icon(Symbols.keyboard_command_key),
|
||||||
).padding(horizontal: 8),
|
).padding(horizontal: 8),
|
||||||
|
onSubmitted: !isDesktop() && allResults.isNotEmpty
|
||||||
|
? (value) => _executeItem(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
allResults[0],
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
AnimatedSize(
|
AnimatedSize(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
@@ -389,43 +387,80 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
ref.read(routerProvider).go(route.path);
|
ref.read(routerProvider).go(route.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<FallbackAction> _getFallbackActions(String query) {
|
void _executeItem(BuildContext context, WidgetRef ref, dynamic item) {
|
||||||
|
if (item is SnChatRoom) {
|
||||||
|
_navigateToChat(context, ref, item);
|
||||||
|
} else if (item is SpecialAction) {
|
||||||
|
onDismiss();
|
||||||
|
item.action();
|
||||||
|
} else if (item is RouteItem) {
|
||||||
|
_navigateToRoute(context, ref, item);
|
||||||
|
} else if (item is FallbackAction) {
|
||||||
|
onDismiss();
|
||||||
|
item.action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FallbackAction> _getFallbackActions(
|
||||||
|
BuildContext context,
|
||||||
|
String query,
|
||||||
|
) {
|
||||||
final List<FallbackAction> actions = [];
|
final List<FallbackAction> actions = [];
|
||||||
|
|
||||||
// Check if query is a URL
|
// Check if query is a URL
|
||||||
final Uri? uri = Uri.tryParse(query);
|
final Uri? uri = Uri.tryParse(query);
|
||||||
if (uri != null && (uri.scheme == 'http' || uri.scheme == 'https')) {
|
final isValidUrl =
|
||||||
|
uri != null && (uri.scheme == 'http' || uri.scheme == 'https');
|
||||||
|
final isDomain = RegExp(
|
||||||
|
r'^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$',
|
||||||
|
).hasMatch(query);
|
||||||
|
|
||||||
|
if (isValidUrl || isDomain) {
|
||||||
|
final finalUri = isDomain ? Uri.parse('https://$query') : uri!;
|
||||||
actions.add(
|
actions.add(
|
||||||
FallbackAction(
|
FallbackAction(
|
||||||
name: 'Open URL',
|
name: 'Open URL',
|
||||||
description: 'Open $query in browser',
|
description: 'Open ${finalUri.toString()} in browser',
|
||||||
icon: Symbols.open_in_new,
|
icon: Symbols.open_in_new,
|
||||||
action: () async {
|
action: () async {
|
||||||
if (await canLaunchUrl(uri)) {
|
if (await canLaunchUrl(finalUri)) {
|
||||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
await launchUrl(finalUri, mode: LaunchMode.externalApplication);
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Search the web
|
|
||||||
actions.add(
|
|
||||||
FallbackAction(
|
|
||||||
name: 'Search the web',
|
|
||||||
description: 'Search "$query" on Google',
|
|
||||||
icon: Symbols.search,
|
|
||||||
action: () async {
|
|
||||||
final searchUri = Uri.https('www.google.com', '/search', {
|
|
||||||
'q': query,
|
|
||||||
});
|
|
||||||
if (await canLaunchUrl(searchUri)) {
|
|
||||||
await launchUrl(searchUri, mode: LaunchMode.externalApplication);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ask the AI
|
||||||
|
// Bugged, DO NOT USE
|
||||||
|
// actions.add(
|
||||||
|
// FallbackAction(
|
||||||
|
// name: 'Ask the AI',
|
||||||
|
// description: 'Ask "$query" to the AI',
|
||||||
|
// icon: Symbols.bubble_chart,
|
||||||
|
// action: () {
|
||||||
|
// eventBus.fire(ShowThoughtSheetEvent(initialMessage: query));
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Search the web
|
||||||
|
actions.add(
|
||||||
|
FallbackAction(
|
||||||
|
name: 'Search the web',
|
||||||
|
description: 'Search "$query" on Google',
|
||||||
|
icon: Symbols.search,
|
||||||
|
action: () async {
|
||||||
|
final searchUri = Uri.https('www.google.com', '/search', {
|
||||||
|
'q': query,
|
||||||
|
});
|
||||||
|
if (await canLaunchUrl(searchUri)) {
|
||||||
|
await launchUrl(searchUri, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,93 +28,45 @@ class ThoughtContent extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (isStreaming) {
|
final content = streamingText.isNotEmpty
|
||||||
// Streaming text with spinner
|
? streamingText
|
||||||
if (streamingText.isNotEmpty) {
|
: thought != null
|
||||||
final isStreamingError = streamingText.startsWith('Error:');
|
? thought!.parts
|
||||||
return Container(
|
.where((p) => p.type == ThinkingMessagePartType.text)
|
||||||
padding: isStreamingError ? const EdgeInsets.all(8) : EdgeInsets.zero,
|
.map((p) => p.text ?? '')
|
||||||
decoration:
|
.join('')
|
||||||
isStreamingError
|
: '';
|
||||||
? BoxDecoration(
|
|
||||||
border: Border.all(
|
if (content.isEmpty) return const SizedBox.shrink();
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
width: 1,
|
final isError = content.startsWith('Error:') || _isErrorMessage;
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
return Container(
|
||||||
)
|
padding: isError ? const EdgeInsets.all(8) : EdgeInsets.zero,
|
||||||
: null,
|
decoration: isError
|
||||||
child: MarkdownTextContent(
|
? BoxDecoration(
|
||||||
isSelectable: true,
|
border: Border.all(
|
||||||
content: streamingText,
|
color: Theme.of(context).colorScheme.error,
|
||||||
extraBlockSyntaxList: [ProposalBlockSyntax()],
|
width: 1,
|
||||||
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color:
|
|
||||||
isStreamingError ? Theme.of(context).colorScheme.error : null,
|
|
||||||
),
|
|
||||||
extraGenerators: [
|
|
||||||
ProposalGenerator(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
foregroundColor:
|
|
||||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
|
||||||
borderColor: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
),
|
||||||
],
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
child: MarkdownTextContent(
|
||||||
|
isSelectable: true,
|
||||||
|
content: content,
|
||||||
|
extraBlockSyntaxList: [ProposalBlockSyntax()],
|
||||||
|
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
color: isError ? Theme.of(context).colorScheme.error : null,
|
||||||
|
),
|
||||||
|
extraGenerators: [
|
||||||
|
ProposalGenerator(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
|
borderColor: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
}
|
),
|
||||||
return const SizedBox.shrink();
|
);
|
||||||
} else {
|
|
||||||
// Regular thought content - render parts
|
|
||||||
if (thought!.parts.isNotEmpty) {
|
|
||||||
final textParts = thought!.parts
|
|
||||||
.where((p) => p.type == ThinkingMessagePartType.text)
|
|
||||||
.map((p) => p.text ?? '')
|
|
||||||
.join('');
|
|
||||||
if (textParts.isNotEmpty) {
|
|
||||||
return Container(
|
|
||||||
padding:
|
|
||||||
_isErrorMessage
|
|
||||||
? const EdgeInsets.symmetric(horizontal: 12, vertical: 4)
|
|
||||||
: EdgeInsets.zero,
|
|
||||||
decoration:
|
|
||||||
_isErrorMessage
|
|
||||||
? BoxDecoration(
|
|
||||||
color: Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.error.withOpacity(0.1),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
child: MarkdownTextContent(
|
|
||||||
isSelectable: true,
|
|
||||||
content: textParts,
|
|
||||||
extraBlockSyntaxList: [ProposalBlockSyntax()],
|
|
||||||
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color:
|
|
||||||
_isErrorMessage
|
|
||||||
? Theme.of(context).colorScheme.error
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
extraGenerators: [
|
|
||||||
ProposalGenerator(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
foregroundColor:
|
|
||||||
Theme.of(context).colorScheme.onSecondaryContainer,
|
|
||||||
borderColor: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ ThoughtChatState useThoughtChat(
|
|||||||
String? initialSequenceId,
|
String? initialSequenceId,
|
||||||
List<SnThinkingThought>? initialThoughts,
|
List<SnThinkingThought>? initialThoughts,
|
||||||
String? initialTopic,
|
String? initialTopic,
|
||||||
|
String? initialMessage,
|
||||||
List<Map<String, dynamic>> attachedMessages = const [],
|
List<Map<String, dynamic>> attachedMessages = const [],
|
||||||
List<String> attachedPosts = const [],
|
List<String> attachedPosts = const [],
|
||||||
VoidCallback? onSequenceIdChanged,
|
VoidCallback? onSequenceIdChanged,
|
||||||
@@ -117,11 +118,13 @@ ThoughtChatState useThoughtChat(
|
|||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (localThoughts.value.isNotEmpty || isStreaming.value) {
|
if (localThoughts.value.isNotEmpty || isStreaming.value) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
scrollController.animateTo(
|
if (scrollController.hasClients) {
|
||||||
0,
|
scrollController.animateTo(
|
||||||
duration: const Duration(milliseconds: 300),
|
0,
|
||||||
curve: Curves.easeOut,
|
duration: const Duration(milliseconds: 300),
|
||||||
);
|
curve: Curves.easeOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -141,10 +144,12 @@ ThoughtChatState useThoughtChat(
|
|||||||
return () => scrollController.removeListener(onScroll);
|
return () => scrollController.removeListener(onScroll);
|
||||||
}, [scrollController]);
|
}, [scrollController]);
|
||||||
|
|
||||||
Future<void> sendMessage() async {
|
Future<void> sendMessage({String? message}) async {
|
||||||
if (messageController.text.trim().isEmpty) return;
|
if (message == null && messageController.text.trim().isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final userMessage = messageController.text.trim();
|
final userMessage = message ?? messageController.text.trim();
|
||||||
|
|
||||||
// Add user message to local thoughts
|
// Add user message to local thoughts
|
||||||
final userInfo = ref.read(userInfoProvider);
|
final userInfo = ref.read(userInfoProvider);
|
||||||
@@ -177,8 +182,9 @@ ThoughtChatState useThoughtChat(
|
|||||||
accpetProposals: ['post_create'],
|
accpetProposals: ['post_create'],
|
||||||
attachedMessages: attachedMessages,
|
attachedMessages: attachedMessages,
|
||||||
attachedPosts: attachedPosts,
|
attachedPosts: attachedPosts,
|
||||||
serviceId:
|
serviceId: selectedServiceId.value.isNotEmpty
|
||||||
selectedServiceId.value.isNotEmpty ? selectedServiceId.value : null,
|
? selectedServiceId.value
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -309,8 +315,8 @@ ThoughtChatState useThoughtChat(
|
|||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final errorMessage =
|
final errorMessage =
|
||||||
error is DioException && error.response?.data is ResponseBody
|
error is DioException && error.response?.data is ResponseBody
|
||||||
? 'toughtParseError'.tr()
|
? 'toughtParseError'.tr()
|
||||||
: error.toString();
|
: error.toString();
|
||||||
final errorThought = SnThinkingThought(
|
final errorThought = SnThinkingThought(
|
||||||
id: 'error-${DateTime.now().millisecondsSinceEpoch}',
|
id: 'error-${DateTime.now().millisecondsSinceEpoch}',
|
||||||
parts: [
|
parts: [
|
||||||
@@ -368,6 +374,15 @@ ThoughtChatState useThoughtChat(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (initialMessage?.isNotEmpty ?? false) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
sendMessage(message: initialMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [initialMessage]);
|
||||||
|
|
||||||
return ThoughtChatState(
|
return ThoughtChatState(
|
||||||
sequenceId: sequenceId,
|
sequenceId: sequenceId,
|
||||||
localThoughts: localThoughts,
|
localThoughts: localThoughts,
|
||||||
@@ -388,6 +403,7 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
final List<SnThinkingThought>? initialThoughts;
|
final List<SnThinkingThought>? initialThoughts;
|
||||||
final String? initialSequenceId;
|
final String? initialSequenceId;
|
||||||
final String? initialTopic;
|
final String? initialTopic;
|
||||||
|
final String? initialMessage;
|
||||||
final List<Map<String, dynamic>> attachedMessages;
|
final List<Map<String, dynamic>> attachedMessages;
|
||||||
final List<String> attachedPosts;
|
final List<String> attachedPosts;
|
||||||
final bool isDisabled;
|
final bool isDisabled;
|
||||||
@@ -397,6 +413,7 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
this.initialThoughts,
|
this.initialThoughts,
|
||||||
this.initialSequenceId,
|
this.initialSequenceId,
|
||||||
this.initialTopic,
|
this.initialTopic,
|
||||||
|
this.initialMessage,
|
||||||
this.attachedMessages = const [],
|
this.attachedMessages = const [],
|
||||||
this.attachedPosts = const [],
|
this.attachedPosts = const [],
|
||||||
this.isDisabled = false,
|
this.isDisabled = false,
|
||||||
@@ -415,6 +432,7 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
initialSequenceId: initialSequenceId,
|
initialSequenceId: initialSequenceId,
|
||||||
initialThoughts: initialThoughts,
|
initialThoughts: initialThoughts,
|
||||||
initialTopic: initialTopic,
|
initialTopic: initialTopic,
|
||||||
|
initialMessage: initialMessage,
|
||||||
attachedMessages: attachedMessages,
|
attachedMessages: attachedMessages,
|
||||||
attachedPosts: attachedPosts,
|
attachedPosts: attachedPosts,
|
||||||
);
|
);
|
||||||
@@ -445,84 +463,78 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child:
|
child:
|
||||||
previousInputHeight != null &&
|
previousInputHeight != null &&
|
||||||
previousInputHeight != inputHeight.value
|
previousInputHeight != inputHeight.value
|
||||||
? TweenAnimationBuilder<double>(
|
? TweenAnimationBuilder<double>(
|
||||||
tween: Tween<double>(
|
tween: Tween<double>(
|
||||||
begin: previousInputHeight,
|
begin: previousInputHeight,
|
||||||
end: inputHeight.value,
|
end: inputHeight.value,
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
curve: Curves.easeOut,
|
curve: Curves.easeOut,
|
||||||
builder:
|
builder: (context, height, child) =>
|
||||||
(context, height, child) =>
|
SuperListView.builder(
|
||||||
SuperListView.builder(
|
listController: chatState.listController,
|
||||||
listController: chatState.listController,
|
controller: chatState.scrollController,
|
||||||
controller: chatState.scrollController,
|
padding: EdgeInsets.only(
|
||||||
padding: EdgeInsets.only(
|
top: 16,
|
||||||
top: 16,
|
bottom:
|
||||||
bottom:
|
MediaQuery.of(context).padding.bottom +
|
||||||
MediaQuery.of(
|
8 +
|
||||||
context,
|
height,
|
||||||
).padding.bottom +
|
),
|
||||||
8 +
|
reverse: true,
|
||||||
height,
|
itemCount:
|
||||||
),
|
chatState.localThoughts.value.length +
|
||||||
reverse: true,
|
(chatState.isStreaming.value ? 1 : 0),
|
||||||
itemCount:
|
itemBuilder: (context, index) {
|
||||||
chatState.localThoughts.value.length +
|
if (chatState.isStreaming.value &&
|
||||||
(chatState.isStreaming.value ? 1 : 0),
|
index == 0) {
|
||||||
itemBuilder: (context, index) {
|
return ThoughtItem(
|
||||||
if (chatState.isStreaming.value &&
|
isStreaming: true,
|
||||||
index == 0) {
|
streamingItems:
|
||||||
return ThoughtItem(
|
chatState.streamingItems.value,
|
||||||
isStreaming: true,
|
);
|
||||||
streamingItems:
|
}
|
||||||
chatState.streamingItems.value,
|
final thoughtIndex =
|
||||||
);
|
chatState.isStreaming.value
|
||||||
}
|
|
||||||
final thoughtIndex =
|
|
||||||
chatState.isStreaming.value
|
|
||||||
? index - 1
|
|
||||||
: index;
|
|
||||||
final thought =
|
|
||||||
chatState
|
|
||||||
.localThoughts
|
|
||||||
.value[thoughtIndex];
|
|
||||||
return ThoughtItem(thought: thought);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: SuperListView.builder(
|
|
||||||
listController: chatState.listController,
|
|
||||||
controller: chatState.scrollController,
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: 16,
|
|
||||||
bottom:
|
|
||||||
MediaQuery.of(context).padding.bottom +
|
|
||||||
8 +
|
|
||||||
inputHeight.value,
|
|
||||||
),
|
|
||||||
reverse: true,
|
|
||||||
itemCount:
|
|
||||||
chatState.localThoughts.value.length +
|
|
||||||
(chatState.isStreaming.value ? 1 : 0),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
if (chatState.isStreaming.value && index == 0) {
|
|
||||||
return ThoughtItem(
|
|
||||||
isStreaming: true,
|
|
||||||
streamingItems:
|
|
||||||
chatState.streamingItems.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final thoughtIndex =
|
|
||||||
chatState.isStreaming.value
|
|
||||||
? index - 1
|
? index - 1
|
||||||
: index;
|
: index;
|
||||||
final thought =
|
final thought = chatState
|
||||||
chatState.localThoughts.value[thoughtIndex];
|
.localThoughts
|
||||||
return ThoughtItem(thought: thought);
|
.value[thoughtIndex];
|
||||||
},
|
return ThoughtItem(thought: thought);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SuperListView.builder(
|
||||||
|
listController: chatState.listController,
|
||||||
|
controller: chatState.scrollController,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: 16,
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom +
|
||||||
|
8 +
|
||||||
|
inputHeight.value,
|
||||||
),
|
),
|
||||||
|
reverse: true,
|
||||||
|
itemCount:
|
||||||
|
chatState.localThoughts.value.length +
|
||||||
|
(chatState.isStreaming.value ? 1 : 0),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (chatState.isStreaming.value && index == 0) {
|
||||||
|
return ThoughtItem(
|
||||||
|
isStreaming: true,
|
||||||
|
streamingItems: chatState.streamingItems.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final thoughtIndex = chatState.isStreaming.value
|
||||||
|
? index - 1
|
||||||
|
: index;
|
||||||
|
final thought =
|
||||||
|
chatState.localThoughts.value[thoughtIndex];
|
||||||
|
return ThoughtItem(thought: thought);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -531,35 +543,31 @@ class ThoughtChatInterface extends HookConsumerWidget {
|
|||||||
// Bottom gradient - appears when scrolling towards newer thoughts (behind thought input)
|
// Bottom gradient - appears when scrolling towards newer thoughts (behind thought input)
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: chatState.bottomGradientNotifier.value,
|
animation: chatState.bottomGradientNotifier.value,
|
||||||
builder:
|
builder: (context, child) => Positioned(
|
||||||
(context, child) => Positioned(
|
left: 0,
|
||||||
left: 0,
|
right: 0,
|
||||||
right: 0,
|
bottom: 0,
|
||||||
bottom: 0,
|
child: Opacity(
|
||||||
child: Opacity(
|
opacity: chatState.bottomGradientNotifier.value.value,
|
||||||
opacity: chatState.bottomGradientNotifier.value.value,
|
child: Container(
|
||||||
child: Container(
|
height: math.min(MediaQuery.of(context).size.height * 0.1, 128),
|
||||||
height: math.min(
|
decoration: BoxDecoration(
|
||||||
MediaQuery.of(context).size.height * 0.1,
|
gradient: LinearGradient(
|
||||||
128,
|
begin: Alignment.bottomCenter,
|
||||||
),
|
end: Alignment.topCenter,
|
||||||
decoration: BoxDecoration(
|
colors: [
|
||||||
gradient: LinearGradient(
|
Theme.of(
|
||||||
begin: Alignment.bottomCenter,
|
context,
|
||||||
end: Alignment.topCenter,
|
).colorScheme.surfaceContainer.withOpacity(0.8),
|
||||||
colors: [
|
Theme.of(
|
||||||
Theme.of(
|
context,
|
||||||
context,
|
).colorScheme.surfaceContainer.withOpacity(0.0),
|
||||||
).colorScheme.surfaceContainer.withOpacity(0.8),
|
],
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.surfaceContainer.withOpacity(0.0),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
// Thought Input positioned above gradient (higher z-index)
|
// Thought Input positioned above gradient (higher z-index)
|
||||||
Positioned(
|
Positioned(
|
||||||
@@ -746,58 +754,42 @@ class ThoughtInput extends HookWidget {
|
|||||||
maxLines: 5,
|
maxLines: 5,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
textInputAction: TextInputAction.send,
|
textInputAction: TextInputAction.send,
|
||||||
onSubmitted:
|
onSubmitted: (!isStreaming && !isDisabled)
|
||||||
(!isStreaming && !isDisabled)
|
? (_) => onSend()
|
||||||
? (_) => onSend()
|
: null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
Row(
|
||||||
icon: Icon(isStreaming ? Symbols.stop : Icons.send),
|
children: [
|
||||||
color: Theme.of(context).colorScheme.primary,
|
if (services.isNotEmpty)
|
||||||
onPressed: (!isStreaming && !isDisabled) ? onSend : null,
|
SizedBox(
|
||||||
),
|
height: 40,
|
||||||
],
|
child: DropdownButtonHideUnderline(
|
||||||
),
|
child: DropdownButton2<String>(
|
||||||
Padding(
|
value: selectedServiceId.value.isEmpty
|
||||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
if (services.isNotEmpty)
|
|
||||||
DropdownButtonHideUnderline(
|
|
||||||
child: DropdownButton2<String>(
|
|
||||||
value:
|
|
||||||
selectedServiceId.value.isEmpty
|
|
||||||
? null
|
? null
|
||||||
: selectedServiceId.value,
|
: selectedServiceId.value,
|
||||||
customButton: Container(
|
customButton: Container(
|
||||||
padding: EdgeInsets.all(4),
|
padding: const EdgeInsets.symmetric(
|
||||||
decoration: BoxDecoration(
|
horizontal: 4,
|
||||||
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),
|
decoration: BoxDecoration(
|
||||||
const Icon(
|
borderRadius: const BorderRadius.all(
|
||||||
Symbols.keyboard_arrow_down,
|
Radius.circular(24),
|
||||||
size: 14,
|
),
|
||||||
).padding(right: 4),
|
),
|
||||||
],
|
child: Row(
|
||||||
).padding(vertical: 2, horizontal: 6),
|
spacing: 8,
|
||||||
),
|
children: [
|
||||||
items:
|
Text(selectedServiceId.value),
|
||||||
services
|
const Icon(
|
||||||
|
Symbols.keyboard_arrow_down,
|
||||||
|
size: 14,
|
||||||
|
).padding(right: 4),
|
||||||
|
],
|
||||||
|
).padding(vertical: 2, horizontal: 6),
|
||||||
|
),
|
||||||
|
items: services
|
||||||
.map(
|
.map(
|
||||||
(service) => DropdownMenuItem<String>(
|
(service) => DropdownMenuItem<String>(
|
||||||
value: service.serviceId,
|
value: service.serviceId,
|
||||||
@@ -807,61 +799,66 @@ class ThoughtInput extends HookWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
service.serviceId,
|
service.serviceId,
|
||||||
style: DefaultTextStyle.of(
|
style: DefaultTextStyle.of(context)
|
||||||
context,
|
.style
|
||||||
).style.copyWith(
|
.copyWith(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Rate: ${service.billingMultiplier}x, Level: P${service.perkLevel}',
|
'${service.billingMultiplier}x, T${service.perkLevel}',
|
||||||
style: DefaultTextStyle.of(
|
style: DefaultTextStyle.of(context)
|
||||||
context,
|
.style
|
||||||
).style.copyWith(
|
.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color:
|
color: Theme.of(context)
|
||||||
Theme.of(context)
|
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onSurfaceVariant,
|
.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged:
|
onChanged: !isStreaming && !isDisabled
|
||||||
!isStreaming && !isDisabled
|
|
||||||
? (value) {
|
? (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
selectedServiceId.value = value;
|
selectedServiceId.value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
: null,
|
: null,
|
||||||
hint: const Text('Select Service'),
|
hint: const Text('Select Service'),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
buttonStyleData: ButtonStyleData(
|
buttonStyleData: ButtonStyleData(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(
|
borderRadius: const BorderRadius.all(
|
||||||
Radius.circular(16),
|
Radius.circular(24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
menuItemStyleData: MenuItemStyleData(
|
||||||
|
selectedMenuItemBuilder: (context, child) {
|
||||||
|
return child;
|
||||||
|
},
|
||||||
|
height: 56,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
menuItemStyleData: MenuItemStyleData(
|
|
||||||
selectedMenuItemBuilder: (context, child) {
|
|
||||||
return child;
|
|
||||||
},
|
|
||||||
height: 56,
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 20,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
IconButton(
|
||||||
|
icon: Icon(isStreaming ? Symbols.stop : Icons.send),
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
onPressed: (!isStreaming && !isDisabled) ? onSend : null,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -923,42 +920,40 @@ class ThoughtItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> buildWidgetsList() {
|
List<Widget> buildWidgetsList() {
|
||||||
final List<StreamItem> items =
|
final List<StreamItem> items = isStreaming
|
||||||
isStreaming
|
? (streamingItems ?? [])
|
||||||
? (streamingItems ?? [])
|
: thought!.parts.map((p) {
|
||||||
: thought!.parts.map((p) {
|
String type;
|
||||||
String type;
|
switch (p.type) {
|
||||||
switch (p.type) {
|
case ThinkingMessagePartType.text:
|
||||||
case ThinkingMessagePartType.text:
|
type = 'text';
|
||||||
type = 'text';
|
break;
|
||||||
break;
|
case ThinkingMessagePartType.functionCall:
|
||||||
case ThinkingMessagePartType.functionCall:
|
type = 'function_call';
|
||||||
type = 'function_call';
|
break;
|
||||||
break;
|
case ThinkingMessagePartType.functionResult:
|
||||||
case ThinkingMessagePartType.functionResult:
|
type = 'function_result';
|
||||||
type = 'function_result';
|
break;
|
||||||
break;
|
}
|
||||||
}
|
return StreamItem(
|
||||||
return StreamItem(
|
type,
|
||||||
type,
|
p.type == ThinkingMessagePartType.text
|
||||||
p.type == ThinkingMessagePartType.text
|
? p.text ?? ''
|
||||||
? p.text ?? ''
|
: p.functionCall ?? p.functionResult,
|
||||||
: p.functionCall ?? p.functionResult,
|
);
|
||||||
);
|
}).toList();
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final isAI =
|
final isAI =
|
||||||
isStreaming ||
|
isStreaming ||
|
||||||
(!isStreaming && thought!.role == ThinkingThoughtRole.assistant);
|
(!isStreaming && thought!.role == ThinkingThoughtRole.assistant);
|
||||||
final List<Map<String, String>> proposals =
|
final List<Map<String, String>> proposals = !isStreaming
|
||||||
!isStreaming
|
? _extractProposals(
|
||||||
? _extractProposals(
|
thought!.parts
|
||||||
thought!.parts
|
.where((p) => p.type == ThinkingMessagePartType.text)
|
||||||
.where((p) => p.type == ThinkingMessagePartType.text)
|
.map((p) => p.text ?? '')
|
||||||
.map((p) => p.text ?? '')
|
.join(),
|
||||||
.join(),
|
)
|
||||||
)
|
: [];
|
||||||
: [];
|
|
||||||
|
|
||||||
final List<Widget> widgets = [];
|
final List<Widget> widgets = [];
|
||||||
String currentText = '';
|
String currentText = '';
|
||||||
@@ -986,10 +981,9 @@ class ThoughtItem extends StatelessWidget {
|
|||||||
isFinish: result != null,
|
isFinish: result != null,
|
||||||
isStreaming: isStreaming,
|
isStreaming: isStreaming,
|
||||||
callData: JsonEncoder.withIndent(' ').convert(item.data.toJson()),
|
callData: JsonEncoder.withIndent(' ').convert(item.data.toJson()),
|
||||||
resultData:
|
resultData: result != null
|
||||||
result != null
|
? JsonEncoder.withIndent(' ').convert(result.data.toJson())
|
||||||
? JsonEncoder.withIndent(' ').convert(result.data.toJson())
|
: null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (item.type == 'function_result') {
|
} else if (item.type == 'function_result') {
|
||||||
|
|||||||
Reference in New Issue
Block a user