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
75 changes: 74 additions & 1 deletion lib/modules/agent_network/pages/networks_overview_page.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:nocterm/nocterm.dart';
import 'package:nocterm_riverpod/nocterm_riverpod.dart';
Expand All @@ -14,6 +15,9 @@ import 'package:vide_cli/theme/theme.dart';
import 'package:vide_cli/constants/text_opacity.dart';
import 'package:vide_cli/modules/commands/command_provider.dart';
import 'package:vide_cli/modules/commands/command.dart';
import 'package:vide_cli/modules/haiku/haiku_providers.dart';
import 'package:vide_cli/modules/haiku/message_enhancement_service.dart';
import 'package:vide_cli/utils/project_detector.dart';

class NetworksOverviewPage extends StatefulComponent {
const NetworksOverviewPage({super.key});
Expand All @@ -27,10 +31,48 @@ class _NetworksOverviewPageState extends State<NetworksOverviewPage> {
String? _commandResult;
bool _commandResultIsError = false;

// Placeholder animation state
Timer? _placeholderTimer;
bool _isLoadingPlaceholder = true;
bool _isTypingPlaceholder = false;
String _fullPlaceholder = '';
String _displayedPlaceholder = '';
int _typingIndex = 0;

@override
void initState() {
super.initState();
_loadProjectInfo();
_generateStartupContent();
}

void _startTypingAnimation(String text) {
_placeholderTimer?.cancel();
_fullPlaceholder = text;
_typingIndex = 0;
_displayedPlaceholder = '';
_isTypingPlaceholder = true;

_placeholderTimer = Timer.periodic(const Duration(milliseconds: 30), (_) {
if (mounted && _typingIndex < _fullPlaceholder.length) {
setState(() {
_typingIndex++;
_displayedPlaceholder = _fullPlaceholder.substring(0, _typingIndex);
});
} else {
_placeholderTimer?.cancel();
setState(() {
_isTypingPlaceholder = false;
_displayedPlaceholder = _fullPlaceholder;
});
}
});
}

@override
void dispose() {
_placeholderTimer?.cancel();
super.dispose();
}

Future<void> _loadProjectInfo() async {
Expand All @@ -54,6 +96,25 @@ class _NetworksOverviewPageState extends State<NetworksOverviewPage> {
return fullPath;
}

/// Generate startup content using Haiku
void _generateStartupContent() {
final now = DateTime.now();

// Generate dynamic placeholder text
MessageEnhancementService.generatePlaceholderText(
now,
(placeholder) {
if (mounted) {
setState(() {
_isLoadingPlaceholder = false;
});
context.read(placeholderTextProvider.notifier).state = placeholder;
_startTypingAnimation(placeholder);
}
},
);
}

void _handleSubmit(Message message) async {
// Start a new agent network with the full message (preserves attachments)
// This returns immediately - client creation happens in background
Expand Down Expand Up @@ -137,6 +198,18 @@ class _NetworksOverviewPageState extends State<NetworksOverviewPage> {
// Watch sidebar focus state from app-level provider
final sidebarFocused = context.watch(sidebarFocusProvider);

// Get placeholder - empty while loading, then type in the text when ready
final String placeholder;
if (_isLoadingPlaceholder) {
placeholder = '';
} else if (_isTypingPlaceholder) {
placeholder = _displayedPlaceholder;
} else {
placeholder = _displayedPlaceholder.isNotEmpty
? _displayedPlaceholder
: (context.watch(placeholderTextProvider) ?? 'Describe your goal (you can attach images)');
}

return Focusable(
focused: !sidebarFocused,
onKeyEvent: (event) {
Expand Down Expand Up @@ -201,7 +274,7 @@ class _NetworksOverviewPageState extends State<NetworksOverviewPage> {
Container(
child: AttachmentTextField(
focused: !sidebarFocused,
placeholder: 'Describe your goal (you can attach images)',
placeholder: placeholder ?? 'Describe your goal (you can attach images)',
onSubmit: _handleSubmit,
onCommand: _handleCommand,
commandSuggestions: _getCommandSuggestions,
Expand Down
23 changes: 23 additions & 0 deletions lib/modules/haiku/prompts/placeholder_prompt.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// Prompt builder for dynamic input field placeholder text
class PlaceholderPrompt {
static String build(DateTime now) {
final randomSeed = now.millisecondsSinceEpoch % 100;

return '''
Generate witty placeholder text for an AI coding assistant input field.

Seed: $randomSeed

RULES:
- 3-5 words only
- Playful, slightly cheeky tone
- Like a friend asking what you want to work on
- BANNED: "What's on your mind", "Describe your", "Enter your", "Type here"
- Be creative! Think: "What are we breaking today?", "Got bugs?", "Your wish, my code..."

CRITICAL: Output ONLY the placeholder text itself.
NO explanations, NO options, NO numbering, NO markdown.
Just the 3-5 word phrase, nothing else.
''';
}
}