diff --git a/lib/chat_screen.dart b/lib/chat_screen.dart index dcc7dcc..b61c426 100644 --- a/lib/chat_screen.dart +++ b/lib/chat_screen.dart @@ -1,5 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; class ChatScreen extends StatefulWidget { const ChatScreen({super.key}); @@ -21,9 +23,9 @@ class _ChatScreenState extends State with TickerProviderStateMixin { final ScrollController _scrollController = ScrollController(); bool _isTyping = false; - void _handleSubmitted(String text) { + Future _handleSubmitted(String text) async { if (text.trim().isEmpty) return; - + _textController.clear(); setState(() { @@ -31,18 +33,54 @@ class _ChatScreenState extends State with TickerProviderStateMixin { _isTyping = true; }); - // Simulate EPVChat! typing delay - Future.delayed(const Duration(milliseconds: 1500), () { + try { + // Faz o pedido para o IP usando o formato compatível com OpenAI + final response = await http + .post( + Uri.parse('http://192.168.60.134:11434/v1/chat/completions'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'model': 'qwen3:4b', // Um modelo disponível no servidor + 'messages': [ + {'role': 'user', 'content': text}, + ], + 'stream': false, + }), + ) + .timeout(const Duration(seconds: 30)); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + final reply = + data['choices'][0]['message']['content'] ?? + 'Sem resposta do modelo.'; + + if (mounted) { + setState(() { + _isTyping = false; + _messages.insert(0, ChatMessage(text: reply, isAssistant: true)); + }); + } + } else { + throw Exception( + 'Erro no servidor: HTTP ${response.statusCode} - ${response.body}', + ); + } + } catch (e) { if (mounted) { setState(() { _isTyping = false; - _messages.insert(0, ChatMessage( - text: "This is a simulated modern response to: \"$text\"", - isAssistant: true, - )); + _messages.insert( + 0, + ChatMessage( + text: + "Não foi possível comunicar com o modelo.\nVerifique se o IP está acessível.\nDetalhes: $e", + isAssistant: true, + ), + ); }); } - }); + } } Widget _buildMessage(ChatMessage message) { @@ -50,7 +88,9 @@ class _ChatScreenState extends State with TickerProviderStateMixin { return Padding( padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), child: Row( - mainAxisAlignment: isAssistant ? MainAxisAlignment.start : MainAxisAlignment.end, + mainAxisAlignment: isAssistant + ? MainAxisAlignment.start + : MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ if (isAssistant) @@ -79,19 +119,28 @@ class _ChatScreenState extends State with TickerProviderStateMixin { ), Flexible( child: Container( - padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 14.0), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 14.0, + ), decoration: BoxDecoration( - gradient: isAssistant - ? const LinearGradient( - colors: [Color(0xFFF1F5F9), Color(0xFFE2E8F0)], // Light bubbles - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ) - : const LinearGradient( - colors: [Color(0xFF8ad5c9), Color(0xFF57a7ed)], // Mint & Blue bubbles - begin: Alignment.centerLeft, - end: Alignment.centerRight, - ), + gradient: isAssistant + ? const LinearGradient( + colors: [ + Color(0xFFF1F5F9), + Color(0xFFE2E8F0), + ], // Light bubbles + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ) + : const LinearGradient( + colors: [ + Color(0xFF8ad5c9), + Color(0xFF57a7ed), + ], // Mint & Blue bubbles + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), borderRadius: BorderRadius.only( topLeft: const Radius.circular(20), topRight: const Radius.circular(20), @@ -106,9 +155,11 @@ class _ChatScreenState extends State with TickerProviderStateMixin { ), ], border: Border.all( - color: isAssistant ? Colors.black.withOpacity(0.05) : Colors.transparent, + color: isAssistant + ? Colors.black.withOpacity(0.05) + : Colors.transparent, width: 1, - ) + ), ), child: Text( message.text, @@ -126,7 +177,10 @@ class _ChatScreenState extends State with TickerProviderStateMixin { decoration: BoxDecoration( shape: BoxShape.circle, gradient: const LinearGradient( - colors: [Color(0xFF57a7ed), Color(0xFF3A8BD1)], // User avatar gradient + colors: [ + Color(0xFF57a7ed), + Color(0xFF3A8BD1), + ], // User avatar gradient begin: Alignment.topLeft, end: Alignment.bottomRight, ), @@ -172,9 +226,14 @@ class _ChatScreenState extends State with TickerProviderStateMixin { ), ), Container( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), decoration: BoxDecoration( - color: const Color(0xFFF1F5F9), // Typing bubble matching assistant light bubble + color: const Color( + 0xFFF1F5F9, + ), // Typing bubble matching assistant light bubble borderRadius: const BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), @@ -240,7 +299,9 @@ class _ChatScreenState extends State with TickerProviderStateMixin { Expanded( child: Container( decoration: BoxDecoration( - color: const Color(0xFFF1F5F9), // Light input box background + color: const Color( + 0xFFF1F5F9, + ), // Light input box background borderRadius: BorderRadius.circular(30.0), border: Border.all(color: Colors.black.withOpacity(0.05)), boxShadow: [ @@ -257,9 +318,14 @@ class _ChatScreenState extends State with TickerProviderStateMixin { style: const TextStyle(color: Colors.black87), decoration: InputDecoration( hintText: "Message EPVChat!...", - hintStyle: TextStyle(color: Colors.black.withOpacity(0.4)), + hintStyle: TextStyle( + color: Colors.black.withOpacity(0.4), + ), border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 14.0), + contentPadding: const EdgeInsets.symmetric( + horizontal: 24.0, + vertical: 14.0, + ), ), ), ), @@ -268,7 +334,10 @@ class _ChatScreenState extends State with TickerProviderStateMixin { Container( decoration: BoxDecoration( gradient: const LinearGradient( - colors: [Color(0xFF8ad5c9), Color(0xFF57a7ed)], // Mint & Blue send button + colors: [ + Color(0xFF8ad5c9), + Color(0xFF57a7ed), + ], // Mint & Blue send button begin: Alignment.topLeft, end: Alignment.bottomRight, ), @@ -282,7 +351,11 @@ class _ChatScreenState extends State with TickerProviderStateMixin { ], ), child: IconButton( - icon: const Icon(Icons.send_rounded, color: Colors.white, size: 20), + icon: const Icon( + Icons.send_rounded, + color: Colors.white, + size: 20, + ), onPressed: () => _handleSubmitted(_textController.text), ), ), @@ -306,7 +379,10 @@ class _ChatScreenState extends State with TickerProviderStateMixin { child: AppBar( title: ShaderMask( shaderCallback: (bounds) => const LinearGradient( - colors: [Color(0xFF8ad5c9), Color(0xFF57a7ed)], // Text gradient header + colors: [ + Color(0xFF8ad5c9), + Color(0xFF57a7ed), + ], // Text gradient header begin: Alignment.topLeft, end: Alignment.bottomRight, ).createShader(bounds), @@ -319,7 +395,9 @@ class _ChatScreenState extends State with TickerProviderStateMixin { ), ), ), - backgroundColor: Colors.white.withOpacity(0.7), // Light Header backgrop + backgroundColor: Colors.white.withOpacity( + 0.7, + ), // Light Header backgrop elevation: 0, centerTitle: true, bottom: PreferredSize( @@ -342,7 +420,7 @@ class _ChatScreenState extends State with TickerProviderStateMixin { Colors.white, Color(0xFFF8FAFC), // Slate 50 ], - ) + ), ), child: SafeArea( bottom: false, diff --git a/pubspec.lock b/pubspec.lock index 3cf1fef..61e67ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -75,6 +75,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -192,6 +208,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.10" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -208,6 +232,14 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" sdks: dart: ">=3.11.1 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index d06ab17..4b40485 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + http: ^1.6.0 dev_dependencies: flutter_test: