refactor: fix lints
This commit is contained in:
@@ -142,7 +142,7 @@ class ChatMessagesNotifier extends StateNotifier<List<ChatMessage>> {
|
||||
final msgId = last.id;
|
||||
final chatId = activeConv?.id;
|
||||
if (apiSvc != null && chatId != null && chatId.isNotEmpty) {
|
||||
final resp = await apiSvc.dio.get('/api/v1/chats/' + chatId);
|
||||
final resp = await apiSvc.dio.get('/api/v1/chats/$chatId');
|
||||
final data = resp.data as Map<String, dynamic>;
|
||||
String content = '';
|
||||
final chatObj = data['chat'] as Map<String, dynamic>?;
|
||||
@@ -948,11 +948,11 @@ Future<void> regenerateMessage(
|
||||
final bool isBackgroundWebSearchPre = webSearchEnabled;
|
||||
|
||||
// Dispatch using unified send pipeline (background tools flow)
|
||||
final bool _isBackgroundFlowPre =
|
||||
final bool isBackgroundFlowPre =
|
||||
isBackgroundToolsFlowPre ||
|
||||
isBackgroundWebSearchPre ||
|
||||
imageGenerationEnabled;
|
||||
final bool _passSocketSession = wantSessionBinding && _isBackgroundFlowPre;
|
||||
final bool passSocketSession = wantSessionBinding && isBackgroundFlowPre;
|
||||
final response = api!.sendMessage(
|
||||
messages: conversationMessages,
|
||||
model: selectedModel.id,
|
||||
@@ -961,7 +961,7 @@ Future<void> regenerateMessage(
|
||||
enableWebSearch: webSearchEnabled,
|
||||
enableImageGeneration: imageGenerationEnabled,
|
||||
modelItem: modelItem,
|
||||
sessionIdOverride: _passSocketSession ? socketSessionId : null,
|
||||
sessionIdOverride: passSocketSession ? socketSessionId : null,
|
||||
toolServers: toolServers,
|
||||
backgroundTasks: bgTasks,
|
||||
responseMessageId: assistantMessageId,
|
||||
@@ -971,7 +971,7 @@ Future<void> regenerateMessage(
|
||||
final sessionId = response.sessionId;
|
||||
|
||||
// New unified streaming path via helper; bypass old inline socket block
|
||||
final bool _isBackgroundFlow =
|
||||
final bool isBackgroundFlow =
|
||||
isBackgroundToolsFlowPre ||
|
||||
isBackgroundWebSearchPre ||
|
||||
imageGenerationEnabled ||
|
||||
@@ -982,7 +982,7 @@ Future<void> regenerateMessage(
|
||||
) {
|
||||
final mergedMeta = {
|
||||
if (m.metadata != null) ...m.metadata!,
|
||||
'backgroundFlow': _isBackgroundFlow,
|
||||
'backgroundFlow': isBackgroundFlow,
|
||||
if (isBackgroundWebSearchPre) 'webSearchFlow': true,
|
||||
if (imageGenerationEnabled) 'imageGenerationFlow': true,
|
||||
};
|
||||
@@ -990,11 +990,11 @@ Future<void> regenerateMessage(
|
||||
});
|
||||
} catch (_) {}
|
||||
|
||||
final _sendStreamSub = attachUnifiedChunkedStreaming(
|
||||
final sendStreamSub = attachUnifiedChunkedStreaming(
|
||||
stream: stream,
|
||||
webSearchEnabled: webSearchEnabled,
|
||||
isBackgroundFlow: _isBackgroundFlow,
|
||||
suppressSocketContentInitially: !_isBackgroundFlow,
|
||||
isBackgroundFlow: isBackgroundFlow,
|
||||
suppressSocketContentInitially: !isBackgroundFlow,
|
||||
usingDynamicChannelInitially: false,
|
||||
assistantMessageId: assistantMessageId,
|
||||
modelId: selectedModel.id,
|
||||
@@ -1014,7 +1014,7 @@ Future<void> regenerateMessage(
|
||||
ref.read(chatMessagesProvider.notifier).finishStreaming(),
|
||||
getMessages: () => ref.read(chatMessagesProvider),
|
||||
);
|
||||
ref.read(chatMessagesProvider.notifier).setMessageStream(_sendStreamSub);
|
||||
ref.read(chatMessagesProvider.notifier).setMessageStream(sendStreamSub);
|
||||
return;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
@@ -1482,7 +1482,7 @@ Future<void> _sendMessageInternal(
|
||||
|
||||
if (socketService != null) {
|
||||
// Activity-based watchdog for chat/channel events (resets on activity)
|
||||
final _chatWatchdog = InactivityWatchdog(
|
||||
final chatWatchdog = InactivityWatchdog(
|
||||
window: const Duration(minutes: 5),
|
||||
onTimeout: () {
|
||||
try {
|
||||
@@ -1510,7 +1510,7 @@ Future<void> _sendMessageInternal(
|
||||
DebugLogger.stream('Socket chat-events: type=$type');
|
||||
// Any chat event indicates activity; reset inactivity watchdog
|
||||
// (watchdog defined below, near handler registration)
|
||||
_chatWatchdog.ping();
|
||||
chatWatchdog.ping();
|
||||
if (type == 'chat:completion' && payload != null) {
|
||||
if (payload is Map<String, dynamic>) {
|
||||
// Provider may emit tool_calls at the top level
|
||||
@@ -1529,9 +1529,7 @@ Future<void> _sendMessageInternal(
|
||||
final exists =
|
||||
(msgs.isNotEmpty) &&
|
||||
RegExp(
|
||||
r'<details\s+type=\"tool_calls\"[^>]*\bname=\"' +
|
||||
RegExp.escape(name) +
|
||||
r'\"',
|
||||
'<details\\s+type="tool_calls"[^>]*\\bname="${RegExp.escape(name)}"',
|
||||
multiLine: true,
|
||||
).hasMatch(msgs.last.content);
|
||||
if (!exists) {
|
||||
@@ -1567,9 +1565,7 @@ Future<void> _sendMessageInternal(
|
||||
final exists =
|
||||
(msgs.isNotEmpty) &&
|
||||
RegExp(
|
||||
r'<details\s+type=\"tool_calls\"[^>]*\bname=\"' +
|
||||
RegExp.escape(name) +
|
||||
r'\"',
|
||||
'<details\\s+type="tool_calls"[^>]*\\bname="${RegExp.escape(name)}"',
|
||||
multiLine: true,
|
||||
).hasMatch(msgs.last.content);
|
||||
if (!exists) {
|
||||
@@ -1628,8 +1624,8 @@ Future<void> _sendMessageInternal(
|
||||
socketService.offChatEvents();
|
||||
} catch (_) {}
|
||||
try {
|
||||
_chatWatchdog.ping(); // ensure timer exists
|
||||
_chatWatchdog.stop();
|
||||
chatWatchdog.ping(); // ensure timer exists
|
||||
chatWatchdog.stop();
|
||||
} catch (_) {}
|
||||
|
||||
// Notify server that chat is completed (mirrors web client)
|
||||
@@ -1744,7 +1740,7 @@ Future<void> _sendMessageInternal(
|
||||
// Normal path: finish now
|
||||
ref.read(chatMessagesProvider.notifier).finishStreaming();
|
||||
try {
|
||||
_chatWatchdog.stop();
|
||||
chatWatchdog.stop();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
@@ -1767,7 +1763,7 @@ Future<void> _sendMessageInternal(
|
||||
final s = line.trim();
|
||||
// Dynamic channel activity
|
||||
try {
|
||||
_chatWatchdog.ping();
|
||||
chatWatchdog.ping();
|
||||
} catch (_) {}
|
||||
DebugLogger.stream(
|
||||
'Socket [$channel] line=${s.length > 160 ? '${s.substring(0, 160)}…' : s}',
|
||||
@@ -1850,9 +1846,7 @@ Future<void> _sendMessageInternal(
|
||||
final exists =
|
||||
(msgs.isNotEmpty) &&
|
||||
RegExp(
|
||||
r'<details\\s+type=\"tool_calls\"[^>]*\\bname=\"' +
|
||||
RegExp.escape(name) +
|
||||
r'\"',
|
||||
'<details\\s+type="tool_calls"[^>]*\\bname="${RegExp.escape(name)}"',
|
||||
multiLine: true,
|
||||
).hasMatch(msgs.last.content);
|
||||
if (!exists) {
|
||||
@@ -1944,7 +1938,7 @@ Future<void> _sendMessageInternal(
|
||||
if (content.isNotEmpty) {
|
||||
ref
|
||||
.read(chatMessagesProvider.notifier)
|
||||
.replaceLastMessageContent('⚠️ ' + content);
|
||||
.replaceLastMessageContent('⚠️ $content');
|
||||
}
|
||||
} catch (_) {}
|
||||
ref.read(chatMessagesProvider.notifier).finishStreaming();
|
||||
@@ -2060,7 +2054,7 @@ Future<void> _sendMessageInternal(
|
||||
.read(chatMessagesProvider.notifier)
|
||||
.appendToLastMessage(content);
|
||||
_updateImagesFromCurrentContent(ref);
|
||||
_chatWatchdog.ping();
|
||||
chatWatchdog.ping();
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
@@ -2068,7 +2062,7 @@ Future<void> _sendMessageInternal(
|
||||
|
||||
socketService.onChannelEvents(channelEventsHandler);
|
||||
// Start activity watchdog
|
||||
_chatWatchdog.ping();
|
||||
chatWatchdog.ping();
|
||||
}
|
||||
|
||||
// Prepare streaming and background handling
|
||||
@@ -2123,14 +2117,14 @@ Future<void> _sendMessageInternal(
|
||||
|
||||
// Helpers were defined above
|
||||
|
||||
int _chunkSeq = 0;
|
||||
int chunkSeq = 0;
|
||||
final streamSubscription = persistentController.stream.listen(
|
||||
(chunk) {
|
||||
_chunkSeq += 1;
|
||||
chunkSeq += 1;
|
||||
try {
|
||||
persistentService.updateStreamProgress(
|
||||
streamId,
|
||||
chunkSequence: _chunkSeq,
|
||||
chunkSequence: chunkSeq,
|
||||
appendedContent: chunk,
|
||||
);
|
||||
} catch (_) {}
|
||||
@@ -3030,7 +3024,7 @@ void _attachSocketStreamingHandlers({
|
||||
final api = ref.read(apiServiceProvider);
|
||||
|
||||
// Activity-based watchdog for socket-driven streaming (resets on activity)
|
||||
final _socketWatchdog = InactivityWatchdog(
|
||||
final socketWatchdog = InactivityWatchdog(
|
||||
window: const Duration(minutes: 5),
|
||||
onTimeout: () {
|
||||
try {
|
||||
@@ -3054,7 +3048,7 @@ void _attachSocketStreamingHandlers({
|
||||
if (line is String) {
|
||||
final s = line.trim();
|
||||
// Any socket line is activity
|
||||
_socketWatchdog.ping();
|
||||
socketWatchdog.ping();
|
||||
if (s == '[DONE]' || s == 'DONE') {
|
||||
try {
|
||||
socketService.offEvent(channel);
|
||||
@@ -3072,7 +3066,7 @@ void _attachSocketStreamingHandlers({
|
||||
);
|
||||
} catch (_) {}
|
||||
ref.read(chatMessagesProvider.notifier).finishStreaming();
|
||||
_socketWatchdog.stop();
|
||||
socketWatchdog.stop();
|
||||
return;
|
||||
}
|
||||
if (s.startsWith('data:')) {
|
||||
@@ -3094,7 +3088,7 @@ void _attachSocketStreamingHandlers({
|
||||
);
|
||||
} catch (_) {}
|
||||
ref.read(chatMessagesProvider.notifier).finishStreaming();
|
||||
_socketWatchdog.stop();
|
||||
socketWatchdog.stop();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -3118,9 +3112,7 @@ void _attachSocketStreamingHandlers({
|
||||
final exists =
|
||||
(msgs.isNotEmpty) &&
|
||||
RegExp(
|
||||
r'<details\s+type=\"tool_calls\"[^>]*\bname=\"' +
|
||||
RegExp.escape(name) +
|
||||
r'\"',
|
||||
'<details\\s+type="tool_calls"[^>]*\\bname="${RegExp.escape(name)}"',
|
||||
multiLine: true,
|
||||
).hasMatch(msgs.last.content);
|
||||
if (!exists) {
|
||||
@@ -3157,13 +3149,13 @@ void _attachSocketStreamingHandlers({
|
||||
}
|
||||
}
|
||||
} else if (line is Map) {
|
||||
_socketWatchdog.ping();
|
||||
socketWatchdog.ping();
|
||||
if (line['done'] == true) {
|
||||
try {
|
||||
socketService.offEvent(channel);
|
||||
} catch (_) {}
|
||||
ref.read(chatMessagesProvider.notifier).finishStreaming();
|
||||
_socketWatchdog.stop();
|
||||
socketWatchdog.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3172,7 +3164,7 @@ void _attachSocketStreamingHandlers({
|
||||
|
||||
socketService.onEvent(channel, handler);
|
||||
// Start activity watchdog now that handler is attached
|
||||
_socketWatchdog.ping();
|
||||
socketWatchdog.ping();
|
||||
}
|
||||
|
||||
void chatHandler(Map<String, dynamic> ev) {
|
||||
@@ -3198,9 +3190,7 @@ void _attachSocketStreamingHandlers({
|
||||
final exists =
|
||||
(msgs.isNotEmpty) &&
|
||||
RegExp(
|
||||
r'<details\s+type=\"tool_calls\"[^>]*\bname=\"' +
|
||||
RegExp.escape(name) +
|
||||
r'\"',
|
||||
'<details\\s+type="tool_calls"[^>]*\\bname="${RegExp.escape(name)}"',
|
||||
multiLine: true,
|
||||
).hasMatch(msgs.last.content);
|
||||
if (!exists) {
|
||||
@@ -3235,9 +3225,7 @@ void _attachSocketStreamingHandlers({
|
||||
final exists =
|
||||
(msgs.isNotEmpty) &&
|
||||
RegExp(
|
||||
r'<details\s+type=\"tool_calls\"[^>]*\bname=\"' +
|
||||
RegExp.escape(name) +
|
||||
r'\"',
|
||||
'<details\\s+type="tool_calls"[^>]*\\bname="${RegExp.escape(name)}"',
|
||||
multiLine: true,
|
||||
).hasMatch(msgs.last.content);
|
||||
if (!exists) {
|
||||
@@ -3267,7 +3255,7 @@ void _attachSocketStreamingHandlers({
|
||||
socketService.offChatEvents();
|
||||
} catch (_) {}
|
||||
try {
|
||||
_socketWatchdog.stop();
|
||||
socketWatchdog.stop();
|
||||
} catch (_) {}
|
||||
try {
|
||||
unawaited(
|
||||
@@ -3421,7 +3409,7 @@ void _attachSocketStreamingHandlers({
|
||||
socketService.onChatEvents(chatHandler);
|
||||
socketService.onChannelEvents(channelEventsHandler);
|
||||
// Start activity watchdog for chat/channel events
|
||||
_socketWatchdog.ping();
|
||||
socketWatchdog.ping();
|
||||
}
|
||||
|
||||
// ========== Tool Servers (OpenAPI) Helpers ==========
|
||||
@@ -3495,8 +3483,9 @@ Map<String, dynamic>? _resolveRef(
|
||||
final section = components?[type];
|
||||
if (section is Map<String, dynamic>) {
|
||||
final schema = section[name];
|
||||
if (schema is Map<String, dynamic>)
|
||||
if (schema is Map<String, dynamic>) {
|
||||
return Map<String, dynamic>.from(schema);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -3515,12 +3504,14 @@ Map<String, dynamic> _resolveSchemaSimple(
|
||||
final out = <String, dynamic>{};
|
||||
if (type is String) {
|
||||
out['type'] = type;
|
||||
if (schema['description'] != null)
|
||||
if (schema['description'] != null) {
|
||||
out['description'] = schema['description'];
|
||||
}
|
||||
if (type == 'object') {
|
||||
out['properties'] = <String, dynamic>{};
|
||||
if (schema['required'] is List)
|
||||
if (schema['required'] is List) {
|
||||
out['required'] = List.from(schema['required']);
|
||||
}
|
||||
final props = schema['properties'];
|
||||
if (props is Map<String, dynamic>) {
|
||||
props.forEach((k, v) {
|
||||
|
||||
@@ -557,15 +557,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Implement select all functionality when needed
|
||||
// void _selectAllMessages() {
|
||||
// final messages = ref.read(chatMessagesProvider);
|
||||
// setState(() {
|
||||
// _selectedMessageIds.clear();
|
||||
// _selectedMessageIds.addAll(messages.map((m) => m.id));
|
||||
// });
|
||||
// }
|
||||
|
||||
void _clearSelection() {
|
||||
setState(() {
|
||||
_selectedMessageIds.clear();
|
||||
@@ -752,8 +743,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
modelName: displayModelName,
|
||||
onCopy: () => _copyMessage(message.content),
|
||||
onRegenerate: () => _regenerateMessage(message),
|
||||
onLike: () => _likeMessage(message),
|
||||
onDislike: () => _dislikeMessage(message),
|
||||
);
|
||||
} else {
|
||||
messageWidget = assistant.AssistantMessageWidget(
|
||||
@@ -763,8 +752,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
modelName: displayModelName,
|
||||
onCopy: () => _copyMessage(message.content),
|
||||
onRegenerate: () => _regenerateMessage(message),
|
||||
onLike: () => _likeMessage(message),
|
||||
onDislike: () => _dislikeMessage(message),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -840,14 +827,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
|
||||
// Inline editing handled by UserMessageBubble. Dialog flow removed.
|
||||
|
||||
void _likeMessage(dynamic message) {
|
||||
// TODO: Implement message liking
|
||||
}
|
||||
|
||||
void _dislikeMessage(dynamic message) {
|
||||
// TODO: Implement message disliking
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(ThemeData theme) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final currentUserAsync = ref.watch(currentUserProvider);
|
||||
@@ -1575,13 +1554,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Implement chat options when needed
|
||||
// void _showChatOptions() {
|
||||
// ScaffoldMessenger.of(
|
||||
// context,
|
||||
// ).showSnackBar(const SnackBar(content: Text('Chat options coming soon!')));
|
||||
// }
|
||||
|
||||
void _deleteSelectedMessages() {
|
||||
final selectedMessages = _getSelectedMessages();
|
||||
if (selectedMessages.isEmpty) return;
|
||||
@@ -1594,7 +1566,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
isDestructive: true,
|
||||
).then((confirmed) async {
|
||||
if (confirmed == true) {
|
||||
// TODO: Implement message removal
|
||||
// for (final selectedMessage in selectedMessages) {
|
||||
// ref.read(chatMessagesProvider.notifier).removeMessage(selectedMessage.id);
|
||||
// }
|
||||
|
||||
@@ -181,10 +181,12 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
final isExpanded = _expandedToolIds.contains(tc.id);
|
||||
final theme = context.conduitTheme;
|
||||
|
||||
String _pretty(dynamic v, {int max = 1200}) {
|
||||
String pretty(dynamic v, {int max = 1200}) {
|
||||
try {
|
||||
final pretty = const JsonEncoder.withIndent(' ').convert(v);
|
||||
return pretty.length > max ? '${pretty.substring(0, max)}\n…' : pretty;
|
||||
final formatted = const JsonEncoder.withIndent(' ').convert(v);
|
||||
return formatted.length > max
|
||||
? '${formatted.substring(0, max)}\n…'
|
||||
: formatted;
|
||||
} catch (_) {
|
||||
final s = v?.toString() ?? '';
|
||||
return s.length > max ? '${s.substring(0, max)}…' : s;
|
||||
@@ -233,7 +235,9 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
),
|
||||
const SizedBox(width: Spacing.xs),
|
||||
Icon(
|
||||
tc.done ? Icons.build_circle_outlined : Icons.play_circle_outline,
|
||||
tc.done
|
||||
? Icons.build_circle_outlined
|
||||
: Icons.play_circle_outline,
|
||||
size: 14,
|
||||
color: theme.buttonPrimary,
|
||||
),
|
||||
@@ -281,7 +285,7 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
),
|
||||
const SizedBox(height: Spacing.xxs),
|
||||
SelectableText(
|
||||
_pretty(tc.arguments),
|
||||
pretty(tc.arguments),
|
||||
style: TextStyle(
|
||||
fontSize: AppTypography.bodySmall,
|
||||
color: theme.textSecondary,
|
||||
@@ -303,7 +307,7 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
),
|
||||
const SizedBox(height: Spacing.xxs),
|
||||
SelectableText(
|
||||
_pretty(tc.result),
|
||||
pretty(tc.result),
|
||||
style: TextStyle(
|
||||
fontSize: AppTypography.bodySmall,
|
||||
color: theme.textSecondary,
|
||||
@@ -315,8 +319,9 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
],
|
||||
),
|
||||
),
|
||||
crossFadeState:
|
||||
isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
|
||||
crossFadeState: isExpanded
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
),
|
||||
],
|
||||
@@ -331,7 +336,7 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
// Determine if media (attachments or generated images) is rendered above.
|
||||
final hasMediaAbove =
|
||||
(widget.message.attachmentIds?.isNotEmpty ?? false) ||
|
||||
(widget.message.files?.isNotEmpty ?? false);
|
||||
(widget.message.files?.isNotEmpty ?? false);
|
||||
bool firstToolSpacerAdded = false;
|
||||
int idx = 0;
|
||||
for (final seg in _segments) {
|
||||
@@ -363,7 +368,7 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
}
|
||||
|
||||
bool get _hasRenderableSegments {
|
||||
bool _textRenderable(String t) {
|
||||
bool textRenderable(String t) {
|
||||
String cleaned = t;
|
||||
// Hide tool_calls blocks entirely
|
||||
cleaned = cleaned.replaceAll(
|
||||
@@ -398,7 +403,7 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
if (seg.isTool && seg.toolCall != null) return true;
|
||||
if (seg.isReasoning && seg.reasoning != null) return true;
|
||||
final text = seg.text ?? '';
|
||||
if (_textRenderable(text)) return true;
|
||||
if (textRenderable(text)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -507,7 +512,8 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
),
|
||||
);
|
||||
},
|
||||
child: (widget.isStreaming &&
|
||||
child:
|
||||
(widget.isStreaming &&
|
||||
!_hasRenderableSegments &&
|
||||
_allowTypingIndicator)
|
||||
? KeyedSubtree(
|
||||
@@ -566,8 +572,18 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
);
|
||||
// Remove raw <think>...</think> or <reasoning>...</reasoning> tags in text
|
||||
cleaned = cleaned
|
||||
.replaceAll(RegExp(r'<think>[\s\S]*?<\/think>', multiLine: true, dotAll: true), '')
|
||||
.replaceAll(RegExp(r'<reasoning>[\s\S]*?<\/reasoning>', multiLine: true, dotAll: true), '');
|
||||
.replaceAll(
|
||||
RegExp(r'<think>[\s\S]*?<\/think>', multiLine: true, dotAll: true),
|
||||
'',
|
||||
)
|
||||
.replaceAll(
|
||||
RegExp(
|
||||
r'<reasoning>[\s\S]*?<\/reasoning>',
|
||||
multiLine: true,
|
||||
dotAll: true,
|
||||
),
|
||||
'',
|
||||
);
|
||||
|
||||
// If there's an unclosed <details>, drop the tail to avoid raw tags.
|
||||
final lastOpen = cleaned.lastIndexOf('<details');
|
||||
@@ -699,7 +715,8 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
maxWidth: 500,
|
||||
maxHeight: 400,
|
||||
),
|
||||
disableAnimation: false, // Keep animations enabled to prevent black display
|
||||
disableAnimation:
|
||||
false, // Keep animations enabled to prevent black display
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -722,7 +739,8 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
maxWidth: imageCount == 2 ? 245 : 160,
|
||||
maxHeight: imageCount == 2 ? 245 : 160,
|
||||
),
|
||||
disableAnimation: false, // Keep animations enabled to prevent black display
|
||||
disableAnimation:
|
||||
false, // Keep animations enabled to prevent black display
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
@@ -764,13 +782,10 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
|
||||
Widget dot(Duration delay) {
|
||||
return Container(
|
||||
width: dotSize,
|
||||
height: dotSize,
|
||||
decoration: BoxDecoration(
|
||||
color: dotColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
)
|
||||
width: dotSize,
|
||||
height: dotSize,
|
||||
decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle),
|
||||
)
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.then(delay: delay)
|
||||
.scale(
|
||||
@@ -816,13 +831,10 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
|
||||
Widget dot(Duration delay) {
|
||||
return Container(
|
||||
width: dotSize,
|
||||
height: dotSize,
|
||||
decoration: BoxDecoration(
|
||||
color: dotColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
)
|
||||
width: dotSize,
|
||||
height: dotSize,
|
||||
decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle),
|
||||
)
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.then(delay: delay)
|
||||
.scale(
|
||||
@@ -859,8 +871,6 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Widget _buildActionButtons() {
|
||||
final isErrorMessage =
|
||||
widget.message.content.contains('⚠️') ||
|
||||
@@ -914,7 +924,8 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
String headerText() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final hasSummary = rc.summary.isNotEmpty;
|
||||
final isThinkingSummary = rc.summary.trim().toLowerCase() == 'thinking…' ||
|
||||
final isThinkingSummary =
|
||||
rc.summary.trim().toLowerCase() == 'thinking…' ||
|
||||
rc.summary.trim().toLowerCase() == 'thinking...';
|
||||
if (widget.isStreaming) {
|
||||
return hasSummary ? rc.summary : l10n.thinking;
|
||||
@@ -1012,8 +1023,9 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
),
|
||||
),
|
||||
),
|
||||
crossFadeState:
|
||||
isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
|
||||
crossFadeState: isExpanded
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -131,7 +131,9 @@ class _EnhancedAttachmentState extends ConsumerState<EnhancedAttachment> {
|
||||
if (path == null) return;
|
||||
final filename = (_fileInfo?['filename'] ?? _fileInfo?['name'] ?? 'file')
|
||||
.toString();
|
||||
await Share.shareXFiles([XFile(path, name: filename)]);
|
||||
await SharePlus.instance.share(
|
||||
ShareParams(files: [XFile(path, name: filename)]),
|
||||
);
|
||||
}
|
||||
|
||||
String _fileIconFor(String filename) {
|
||||
|
||||
@@ -151,7 +151,6 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
bool _isRecording = false;
|
||||
bool _isExpanded = true; // Start expanded for better UX
|
||||
// TODO: Implement voice input functionality
|
||||
// final String _voiceInputText = '';
|
||||
bool _hasText = false; // track locally without rebuilding on each keystroke
|
||||
StreamSubscription<String>? _voiceStreamSubscription;
|
||||
@@ -414,8 +413,6 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
});
|
||||
}
|
||||
|
||||
final bool showPlaceholder =
|
||||
!_hasText && !_focusNode.hasFocus && !_isRecording;
|
||||
final Brightness brightness = Theme.of(context).brightness;
|
||||
final Color outlineColor = (_focusNode.hasFocus || _hasText)
|
||||
? context.conduitTheme.inputBorderFocused.withValues(alpha: 0.6)
|
||||
@@ -425,16 +422,6 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
);
|
||||
final Color composerSurface = context.conduitTheme.inputBackground;
|
||||
final Color placeholderColor = context.conduitTheme.inputPlaceholder;
|
||||
final Color badgeBackground = showPlaceholder
|
||||
? placeholderColor.withValues(alpha: 0.12)
|
||||
: composerSurface.withValues(alpha: 0.3);
|
||||
final Color badgeBorder = showPlaceholder
|
||||
? Colors.transparent
|
||||
: outlineColor.withValues(alpha: 0.35);
|
||||
final Color badgeIconColor = showPlaceholder
|
||||
? placeholderColor
|
||||
: context.conduitTheme.textPrimary.withValues(alpha: 0.75);
|
||||
|
||||
return Container(
|
||||
// Transparent wrapper so rounded corners are visible against page background
|
||||
color: Colors.transparent,
|
||||
@@ -679,21 +666,21 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
),
|
||||
),
|
||||
if (!_isExpanded) ...[
|
||||
const SizedBox(width: Spacing.sm),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (voiceAvailable) ...[
|
||||
_buildVoiceButton(voiceAvailable),
|
||||
const SizedBox(width: Spacing.xs),
|
||||
],
|
||||
_buildPrimaryButton(
|
||||
_hasText,
|
||||
isGenerating,
|
||||
stopGeneration,
|
||||
),
|
||||
const SizedBox(width: Spacing.sm),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (voiceAvailable) ...[
|
||||
_buildVoiceButton(voiceAvailable),
|
||||
const SizedBox(width: Spacing.xs),
|
||||
],
|
||||
),
|
||||
_buildPrimaryButton(
|
||||
_hasText,
|
||||
isGenerating,
|
||||
stopGeneration,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -1017,7 +1004,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
|
||||
// Append tools button at the end (always visible)
|
||||
|
||||
rowChildren..add(
|
||||
rowChildren.add(
|
||||
_buildIconButton(
|
||||
icon: Platform.isIOS
|
||||
? CupertinoIcons.wrench
|
||||
@@ -1605,6 +1592,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
if (!widget.enabled) return;
|
||||
try {
|
||||
final ok = await _voiceService.initialize();
|
||||
if (!mounted) return;
|
||||
if (!ok) {
|
||||
_showVoiceUnavailable(
|
||||
AppLocalizations.of(context)?.errorMessage ??
|
||||
@@ -1614,6 +1602,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
||||
}
|
||||
// Centralized permission + start
|
||||
final stream = await _voiceService.beginListening();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isRecording = true;
|
||||
_baseTextAtStart = _controller.text;
|
||||
|
||||
Reference in New Issue
Block a user