🐛 Fix AI topic won't be set

This commit is contained in:
2025-10-26 00:15:21 +08:00
parent cc5460ea55
commit 4639b00b86

View File

@@ -8,6 +8,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:island/models/thought.dart"; import "package:island/models/thought.dart";
import "package:island/pods/network.dart"; import "package:island/pods/network.dart";
import "package:island/pods/userinfo.dart"; import "package:island/pods/userinfo.dart";
import "package:island/services/time.dart";
import "package:island/widgets/alert.dart"; import "package:island/widgets/alert.dart";
import "package:island/widgets/app_scaffold.dart"; import "package:island/widgets/app_scaffold.dart";
import "package:island/widgets/content/markdown.dart"; import "package:island/widgets/content/markdown.dart";
@@ -42,7 +43,7 @@ class ThoughtScreen extends HookConsumerWidget {
: const AsyncValue<List<SnThinkingThought>>.data([]); : const AsyncValue<List<SnThinkingThought>>.data([]);
final localThoughts = useState<List<SnThinkingThought>>([]); final localThoughts = useState<List<SnThinkingThought>>([]);
final currentTopic = useState<String?>('AI Thought'); final currentTopic = useState<String?>('寻思');
final messageController = useTextEditingController(); final messageController = useTextEditingController();
final scrollController = useScrollController(); final scrollController = useScrollController();
@@ -54,12 +55,13 @@ class ThoughtScreen extends HookConsumerWidget {
// Update local thoughts when provider data changes // Update local thoughts when provider data changes
useEffect(() { useEffect(() {
thoughts.whenData((data) { thoughts.whenData((data) {
// Server returns messages in DESC order (newest first), keep as-is for UI
localThoughts.value = data; localThoughts.value = data;
// Update topic from the first thought's sequence // Update topic from the first thought's sequence
if (data.isNotEmpty && data.first.sequence?.topic != null) { if (data.isNotEmpty && data.first.sequence?.topic != null) {
currentTopic.value = data.first.sequence!.topic; currentTopic.value = data.first.sequence!.topic;
} else { } else {
currentTopic.value = 'AI Thought'; currentTopic.value = '寻思';
} }
}); });
return null; return null;
@@ -170,43 +172,18 @@ class ThoughtScreen extends HookConsumerWidget {
} }
} }
// Update sequence topic if found // Add AI thought to conversation
if (topic != null && aiThought.sequence != null) { localThoughts.value = [aiThought, ...localThoughts.value];
final updatedSequence = aiThought.sequence!.copyWith(
topic: topic,
);
final updatedThought = aiThought.copyWith(
sequence: updatedSequence,
);
localThoughts.value = [updatedThought, ...localThoughts.value];
// Also update topic in existing thoughts with same sequenceId // Update selected sequence ID if it was null (new conversation)
localThoughts.value = if (selectedSequenceId.value == null &&
localThoughts.value.map((thought) { aiThought.sequenceId.isNotEmpty) {
if (thought.sequenceId == aiThought.sequenceId && selectedSequenceId.value = aiThought.sequenceId;
thought.sequence != null) { }
return thought.copyWith(
sequence: thought.sequence!.copyWith(topic: topic),
);
}
return thought;
}).toList();
// Update current topic // Update current topic if found (AI responses don't include sequence to prevent backend loops)
if (topic != null) {
currentTopic.value = topic; currentTopic.value = topic;
// Update selected sequence ID to provide context for AI
if (selectedSequenceId.value != aiThought.sequenceId) {
selectedSequenceId.value = aiThought.sequenceId;
}
} else {
localThoughts.value = [aiThought, ...localThoughts.value];
// Update selected sequence ID if it was null (new conversation)
if (selectedSequenceId.value == null &&
aiThought.sequenceId.isNotEmpty) {
selectedSequenceId.value = aiThought.sequenceId;
}
} }
} catch (e) { } catch (e) {
showErrorAlert('Failed to parse AI response'); showErrorAlert('Failed to parse AI response');
@@ -258,15 +235,39 @@ class ThoughtScreen extends HookConsumerWidget {
size: 20, size: 20,
), ),
const Gap(8), const Gap(8),
Text( Expanded(
thought.role == ThinkingThoughtRole.assistant ? 'SN 酱' : '', child: Row(
style: Theme.of(context).textTheme.titleSmall, crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.ideographic,
spacing: 8,
children: [
Text(
thought.role == ThinkingThoughtRole.assistant
? 'SN 酱'
: '',
style: Theme.of(context).textTheme.titleSmall,
),
Tooltip(
message: thought.createdAt.formatSystem(),
child: Text(
thought.createdAt.formatRelative(context),
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(
color:
Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
), ),
], ],
), ),
const Gap(8), const Gap(8),
if (thought.content != null) if (thought.content != null)
MarkdownTextContent( MarkdownTextContent(
isSelectable: true,
content: thought.content!, content: thought.content!,
textStyle: Theme.of(context).textTheme.bodyMedium, textStyle: Theme.of(context).textTheme.bodyMedium,
), ),
@@ -308,10 +309,7 @@ class ThoughtScreen extends HookConsumerWidget {
children: [ children: [
Icon(Symbols.smart_toy, size: 20), Icon(Symbols.smart_toy, size: 20),
const Gap(8), const Gap(8),
Text( Text('SN 酱', style: Theme.of(context).textTheme.titleSmall),
'AI Assistant',
style: Theme.of(context).textTheme.titleSmall,
),
const Spacer(), const Spacer(),
SizedBox( SizedBox(
width: 16, width: 16,
@@ -331,7 +329,7 @@ class ThoughtScreen extends HookConsumerWidget {
return AppScaffold( return AppScaffold(
appBar: AppBar( appBar: AppBar(
title: Text(currentTopic.value ?? 'AI Thought'), title: Text(currentTopic.value ?? '寻思'),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Symbols.history), icon: const Icon(Symbols.history),
@@ -348,6 +346,7 @@ class ThoughtScreen extends HookConsumerWidget {
); );
}, },
), ),
const Gap(8),
], ],
), ),
body: Column( body: Column(
@@ -358,7 +357,7 @@ class ThoughtScreen extends HookConsumerWidget {
(thoughtList) => SuperListView.builder( (thoughtList) => SuperListView.builder(
listController: listController, listController: listController,
controller: scrollController, controller: scrollController,
padding: const EdgeInsets.only(top: 16), padding: const EdgeInsets.only(top: 16, bottom: 16),
reverse: true, reverse: true,
itemCount: itemCount:
localThoughts.value.length + localThoughts.value.length +
@@ -413,7 +412,7 @@ class ThoughtScreen extends HookConsumerWidget {
hintText: hintText:
isStreaming.value isStreaming.value
? 'Sn-chan is thinking...' ? 'Sn-chan is thinking...'
: 'Ask me anything...', : 'Ask sn-chan anything...',
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(