Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion lib/modules/agent_network/network_execution_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'package:vide_cli/modules/commands/command_provider.dart';
import 'package:vide_core/vide_core.dart';
import 'package:vide_cli/modules/permissions/permission_service.dart';
import 'package:vide_cli/theme/theme.dart';
import 'package:vide_cli/theme/theme_provider.dart';
import '../permissions/permission_scope.dart';
import '../../components/typing_text.dart';

Expand All @@ -42,6 +43,7 @@ class NetworkExecutionPage extends StatefulComponent {
class _NetworkExecutionPageState extends State<NetworkExecutionPage> {
DateTime? _lastCtrlCPress;
bool _showQuitWarning = false;
String? _thinkingToggleMessage;
static const _quitTimeWindow = Duration(seconds: 2);

int selectedAgentIndex = 0;
Expand Down Expand Up @@ -119,6 +121,24 @@ class _NetworkExecutionPageState extends State<NetworkExecutionPage> {
}
}

void _handleCtrlO() {
// Toggle thinking tokens display
toggleShowThinking(context.container);
final newValue = context.read(showThinkingProvider);
setState(() {
_thinkingToggleMessage = newValue ? 'Thinking tokens: visible' : 'Thinking tokens: hidden';
});

// Hide message after 2 seconds
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
setState(() {
_thinkingToggleMessage = null;
});
}
});
}

@override
Component build(BuildContext context) {
final networkState = context.watch(agentNetworkManagerProvider);
Expand All @@ -145,6 +165,12 @@ class _NetworkExecutionPageState extends State<NetworkExecutionPage> {
return true;
}

// Ctrl+O: Toggle thinking tokens display
if (event.logicalKey == LogicalKey.keyO && event.isControlPressed) {
_handleCtrlO();
return true;
}

return false;
},
child: MouseRegion(
Expand All @@ -160,6 +186,15 @@ class _NetworkExecutionPageState extends State<NetworkExecutionPage> {
),
Divider(),
RunningAgentsBar(agents: networkState.agents, selectedIndex: selectedAgentIndex),
// Show thinking toggle message if active
if (_thinkingToggleMessage != null)
Container(
padding: EdgeInsets.symmetric(horizontal: 1),
child: Text(
_thinkingToggleMessage!,
style: TextStyle(color: VideTheme.of(context).base.primary),
),
),
if (networkState.agentIds.isEmpty)
Center(child: Text('No agents'))
else
Expand Down Expand Up @@ -580,8 +615,41 @@ class _AgentChatState extends State<_AgentChat> {
final widgets = <Component>[];
final renderedToolResults = <String>{};

// Check if we should show thinking content
final showThinking = context.watch(showThinkingProvider);

for (final response in message.responses) {
if (response is TextResponse) {
if (response is ThinkingResponse) {
// Only render thinking content if showThinking is enabled
if (showThinking && response.content.isNotEmpty) {
widgets.add(
Container(
padding: EdgeInsets.only(bottom: 1),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Thinking:',
style: TextStyle(
color: theme.base.onSurface.withOpacity(TextOpacity.secondary),
fontStyle: FontStyle.italic,
),
),
Container(
padding: EdgeInsets.only(left: 2),
child: Text(
response.content,
style: TextStyle(
color: theme.base.onSurface.withOpacity(TextOpacity.secondary),
),
),
),
],
),
),
);
}
} else if (response is TextResponse) {
if (response.content.isEmpty && message.isStreaming) {
widgets.add(EnhancedLoadingIndicator());
} else {
Expand Down
16 changes: 16 additions & 0 deletions lib/theme/theme_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,19 @@ void setTheme(ProviderContainer container, String? themeId) {
configManager.setTheme(themeId);
container.read(themeSettingProvider.notifier).state = themeId;
}

/// Provider for controlling whether thinking tokens are displayed.
/// Toggle with Ctrl+O shortcut.
final showThinkingProvider = StateProvider<bool>((ref) {
final configManager = ref.read(videConfigManagerProvider);
return configManager.getShowThinking();
});

/// Toggles the showThinking setting and persists it to config.
void toggleShowThinking(ProviderContainer container) {
final configManager = container.read(videConfigManagerProvider);
final current = container.read(showThinkingProvider);
final newValue = !current;
configManager.setShowThinking(newValue);
container.read(showThinkingProvider.notifier).state = newValue;
}
15 changes: 14 additions & 1 deletion packages/claude_sdk/lib/src/client/conversation_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,20 @@ class ConversationLoader {
if (block is Map<String, dynamic>) {
final blockType = block['type'] as String?;

if (blockType == 'text') {
if (blockType == 'thinking') {
final thinking = block['thinking'] as String? ?? '';
if (thinking.isNotEmpty) {
responses.add(
ThinkingResponse(
id:
block['id'] as String? ??
DateTime.now().millisecondsSinceEpoch.toString(),
timestamp: DateTime.now(),
content: HtmlEntityDecoder.decode(thinking),
),
);
}
} else if (blockType == 'text') {
final text = block['text'] as String? ?? '';
if (text.isNotEmpty) {
responses.add(
Expand Down
5 changes: 3 additions & 2 deletions packages/claude_sdk/lib/src/client/response_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ResponseProcessor {
responses = [response];
}

if (response is TextResponse) {
if (response is TextResponse || response is ThinkingResponse) {
return _processTextResponse(
response,
currentConversation,
Expand Down Expand Up @@ -92,13 +92,14 @@ class ResponseProcessor {
}

ProcessResult _processTextResponse(
TextResponse response,
ClaudeResponse response,
Conversation currentConversation,
String assistantId,
ConversationMessage? existingMessage,
bool isAssistantMessage,
List<ClaudeResponse> responses,
) {
// Works for both TextResponse and ThinkingResponse
// Extract usage if available
final usage = _extractUsageFromRawData(response.rawData);

Expand Down
24 changes: 24 additions & 0 deletions packages/claude_sdk/lib/src/models/response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,30 @@ sealed class ClaudeResponse {
}
}

/// Response containing thinking/reasoning content from extended thinking.
@JsonSerializable()
class ThinkingResponse extends ClaudeResponse {
final String content;

const ThinkingResponse({
required super.id,
required super.timestamp,
required this.content,
super.rawData,
});

factory ThinkingResponse.fromJson(Map<String, dynamic> json) {
return ThinkingResponse(
id: json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString(),
timestamp: DateTime.now(),
content: json['thinking'] ?? json['content'] ?? json['text'] ?? '',
rawData: json,
);
}

Map<String, dynamic> toJson() => _$ThinkingResponseToJson(this);
}

@JsonSerializable()
class TextResponse extends ClaudeResponse {
final String content;
Expand Down
16 changes: 16 additions & 0 deletions packages/claude_sdk/lib/src/models/response.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions packages/vide_core/lib/models/vide_global_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ class VideGlobalSettings {
@JsonKey(includeIfNull: false)
final String? theme;

/// Whether to show thinking/reasoning tokens in the UI.
/// Defaults to false (hidden).
@JsonKey(defaultValue: false)
final bool showThinking;

const VideGlobalSettings({
this.firstRunComplete = false,
this.theme,
this.showThinking = false,
});

factory VideGlobalSettings.defaults() => const VideGlobalSettings();
Expand All @@ -32,10 +38,12 @@ class VideGlobalSettings {
VideGlobalSettings copyWith({
bool? firstRunComplete,
String? Function()? theme,
bool? showThinking,
}) {
return VideGlobalSettings(
firstRunComplete: firstRunComplete ?? this.firstRunComplete,
theme: theme != null ? theme() : this.theme,
showThinking: showThinking ?? this.showThinking,
);
}
}
2 changes: 2 additions & 0 deletions packages/vide_core/lib/models/vide_global_settings.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions packages/vide_core/lib/services/vide_config_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ class VideConfigManager {
final settings = readGlobalSettings();
writeGlobalSettings(settings.copyWith(theme: () => themeName));
}

/// Get whether to show thinking tokens in the UI.
bool getShowThinking() {
return readGlobalSettings().showThinking;
}

/// Set whether to show thinking tokens in the UI.
void setShowThinking(bool show) {
final settings = readGlobalSettings();
writeGlobalSettings(settings.copyWith(showThinking: show));
}
}

/// Riverpod provider for VideConfigManager
Expand Down
Loading