gemini/sample-apps/photo-discovery/app/lib/ui/screens/chat.dart (154 lines of code) (raw):

import 'dart:async'; import 'dart:convert' as convert; import 'package:app/config.dart'; import '../components/core_components.dart'; import 'package:flutter/material.dart'; import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart' as types; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:uuid/uuid.dart'; import 'package:http/http.dart' as http; import '../../functionality/state.dart'; class ChatPage extends StatefulWidget { const ChatPage({this.onExit, super.key}); final VoidCallback? onExit; @override State<ChatPage> createState() => _ChatPageState(); } class _ChatPageState extends State<ChatPage> { bool loading = false; final List<types.Message> _messages = []; final types.User _user = const types.User(id: 'user', firstName: 'You'); final types.User _agent = const types.User( firstName: 'Khanh', id: 'agent', imageUrl: 'https://services.google.com/fh/files/misc/bunny.jpeg'); @override void initState() { super.initState(); _messages.add(types.TextMessage( id: const Uuid().v4(), author: _agent, text: 'Hey there! My name is Khanh. I\'m your assistant, let me know how I can help.', )); } void _addMessage(types.Message message) { setState(() { _messages.insert(0, message); }); } void _handleSendPressed(types.PartialText message) { final textMessage = types.TextMessage( author: _user, id: const Uuid().v4(), text: message.text, ); _addMessage(textMessage); _sendMessageToAgent(message); } Future<String> askAgent( String name, String description, String question) async { var query = 'The photo is $name. $description. $question.'; var endpoint = Uri.https(cloudRunHost, '/ask_gemini', {'query': query}); var response = await http.get(endpoint); if (response.statusCode == 200) { var responseText = convert.utf8.decode(response.bodyBytes); return responseText.replaceAll(RegExp(r'\*'), ''); } return 'Sorry I can\'t answer that.'; } void _sendMessageToAgent(types.PartialText message) async { setState(() { loading = true; }); var text = await askAgent( context.read<AppState>().metadata!.name, context.read<AppState>().metadata!.description, message.text, ); final textMessage = types.TextMessage( author: _agent, id: const Uuid().v4(), text: text, ); _addMessage(textMessage); setState(() { loading = false; }); } void _pickSuggestedQuestion(String question) { var message = types.PartialText(text: question); _handleSendPressed(message); } @override Widget build(BuildContext context) { var metadata = context.watch<AppState>().metadata; Widget? suggestionsWidget; if (metadata != null) { if (_messages.length == 1) { suggestionsWidget = Padding( padding: const EdgeInsets.fromLTRB(16, 24, 16, 8), child: TagCapsule( onTap: _pickSuggestedQuestion, title: 'Suggested Questions', tags: metadata.suggestedQuestions, ), ); } } List<types.User> typingUsers = []; if (loading) typingUsers.add(_agent); return Column( children: [ AppBar( title: const Text('Chat with AI'), actions: (widget.onExit != null) ? [ Padding( padding: const EdgeInsets.all(8), child: IconButton( color: Theme.of(context).colorScheme.secondary, onPressed: widget.onExit, icon: const Icon( size: 28, FontAwesomeIcons.circleXmark, ), ), ) ] : [], ), Expanded( child: Chat( typingIndicatorOptions: TypingIndicatorOptions( typingUsers: typingUsers, ), listBottomWidget: suggestionsWidget, messages: _messages, onSendPressed: _handleSendPressed, showUserAvatars: true, showUserNames: true, user: _user, theme: DefaultChatTheme( receivedMessageBodyTextStyle: TextStyle( color: Theme.of(context).colorScheme.onSurface, ), sentMessageBodyTextStyle: TextStyle( color: Theme.of(context).colorScheme.onSecondary, ), userAvatarNameColors: [ Theme.of(context).colorScheme.primary, ], backgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh, primaryColor: Theme.of(context).colorScheme.primary, secondaryColor: Theme.of(context).colorScheme.surface, ), ), ), ], ); } }