mirror of
https://github.com/foss42/apidash.git
synced 2025-12-01 10:17:47 +08:00
194 lines
6.9 KiB
Dart
194 lines
6.9 KiB
Dart
import 'package:apidash/dashbot/features/chat/view/widgets/dashbot_task_buttons.dart';
|
|
import 'package:apidash_design_system/apidash_design_system.dart';
|
|
|
|
import '../../../../core/constants/constants.dart';
|
|
import '../widgets/chat_bubble.dart';
|
|
import '../../viewmodel/chat_viewmodel.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
class ChatScreen extends ConsumerStatefulWidget {
|
|
final ChatMessageType? initialTask;
|
|
const ChatScreen({super.key, this.initialTask});
|
|
|
|
@override
|
|
ConsumerState<ChatScreen> createState() => _ChatScreenState();
|
|
}
|
|
|
|
class _ChatScreenState extends ConsumerState<ChatScreen> {
|
|
final TextEditingController _textController = TextEditingController();
|
|
final ScrollController _scrollController = ScrollController();
|
|
bool _showTaskSuggestions = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Kick off task-specific prompt after first frame
|
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
|
if (!mounted) return;
|
|
final task = widget.initialTask;
|
|
if (task != null) {
|
|
final vm = ref.read(chatViewmodelProvider.notifier);
|
|
vm.sendTaskMessage(task);
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_textController.dispose();
|
|
_scrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _scrollToBottom() {
|
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
|
if (_scrollController.hasClients) {
|
|
_scrollController.animateTo(
|
|
_scrollController.position.maxScrollExtent,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeOut,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
ref.listen(chatViewmodelProvider, (prev, next) {
|
|
if (next.isGenerating) {
|
|
_showTaskSuggestions = false;
|
|
}
|
|
// Scroll to bottom when new message is added or streaming updates
|
|
if (prev?.currentStreamingResponse != next.currentStreamingResponse ||
|
|
(prev != null && prev.isGenerating && !next.isGenerating)) {
|
|
_scrollToBottom();
|
|
}
|
|
});
|
|
|
|
return Scaffold(
|
|
body: Column(
|
|
children: [
|
|
Expanded(
|
|
child: Consumer(
|
|
builder: (context, ref, _) {
|
|
final state = ref.watch(chatViewmodelProvider);
|
|
final vm = ref.read(chatViewmodelProvider.notifier);
|
|
final msgs = vm.currentMessages;
|
|
if (msgs.isEmpty && !state.isGenerating) {
|
|
return const Center(child: Text('Ask me anything!'));
|
|
}
|
|
return Scrollbar(
|
|
controller: _scrollController,
|
|
thumbVisibility: true,
|
|
child: ListView.builder(
|
|
controller: _scrollController,
|
|
itemCount: msgs.length + (state.isGenerating ? 1 : 0),
|
|
padding: const EdgeInsets.all(16.0),
|
|
itemBuilder: (context, index) {
|
|
if (state.isGenerating && index == msgs.length) {
|
|
return ChatBubble(
|
|
message: state.currentStreamingResponse,
|
|
role: MessageRole.system,
|
|
);
|
|
}
|
|
final message = msgs[index];
|
|
return ChatBubble(
|
|
message: message.content,
|
|
role: message.role,
|
|
actions: message.actions,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
Divider(
|
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
|
height: 5,
|
|
thickness: 6,
|
|
),
|
|
if (_showTaskSuggestions)
|
|
DashbotTaskButtons(
|
|
onTaskSelected: _scrollToBottom,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Row(
|
|
children: [
|
|
ADIconButton(
|
|
icon: Icons.help_outline_rounded,
|
|
tooltip: 'Show tasks',
|
|
onPressed: ref.watch(chatViewmodelProvider).isGenerating
|
|
? null
|
|
: () => setState(
|
|
() => _showTaskSuggestions = !_showTaskSuggestions),
|
|
),
|
|
ADIconButton(
|
|
icon: Icons.clear_all_rounded,
|
|
tooltip: 'Clear chat',
|
|
onPressed: ref.watch(chatViewmodelProvider).isGenerating
|
|
? null
|
|
: () {
|
|
ref
|
|
.read(chatViewmodelProvider.notifier)
|
|
.clearCurrentChat();
|
|
},
|
|
),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _textController,
|
|
decoration: InputDecoration(
|
|
hintText: ref.watch(chatViewmodelProvider).isGenerating
|
|
? 'Generating...'
|
|
: 'Ask anything',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
filled: true,
|
|
fillColor: Theme.of(context).colorScheme.surface,
|
|
),
|
|
enabled: !ref.watch(chatViewmodelProvider).isGenerating,
|
|
onSubmitted: (_) {
|
|
final vm = ref.read(chatViewmodelProvider.notifier);
|
|
if (!ref.read(chatViewmodelProvider).isGenerating) {
|
|
final text = _textController.text;
|
|
_textController.clear();
|
|
vm.sendMessage(
|
|
text: text,
|
|
type: ChatMessageType.general,
|
|
);
|
|
_scrollToBottom();
|
|
}
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
IconButton(
|
|
icon: const Icon(Icons.send_rounded),
|
|
onPressed: ref.watch(chatViewmodelProvider).isGenerating
|
|
? null
|
|
: () {
|
|
final vm = ref.read(chatViewmodelProvider.notifier);
|
|
final text = _textController.text;
|
|
_textController.clear();
|
|
vm.sendMessage(
|
|
text: text,
|
|
type: ChatMessageType.general,
|
|
);
|
|
_scrollToBottom();
|
|
},
|
|
tooltip: 'Send message',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|