Tudo a funcionar, apenas falta a base de dados
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
const ChatScreen({super.key});
|
const ChatScreen({super.key});
|
||||||
@@ -44,7 +45,12 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.body);
|
||||||
print("Modelos ok");
|
print("--- MODELOS DISPONÍVEIS ---");
|
||||||
|
if (data['models'] != null) {
|
||||||
|
for (var m in data['models']) {
|
||||||
|
print("- ${m['name']}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Erro ao listar modelos: $e");
|
print("Erro ao listar modelos: $e");
|
||||||
@@ -59,7 +65,6 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Função para limpar o chat
|
|
||||||
void _clearChat() {
|
void _clearChat() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_messages.clear();
|
_messages.clear();
|
||||||
@@ -68,51 +73,49 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleSubmitted(String text) async {
|
Future<void> _handleSubmitted(String text) async {
|
||||||
// BLOQUEIO: Se já estiver a escrever (isTyping), ignora o clique
|
|
||||||
if (text.trim().isEmpty || _isTyping) return;
|
if (text.trim().isEmpty || _isTyping) return;
|
||||||
|
|
||||||
_textController.clear();
|
_textController.clear();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_messages.insert(0, ChatMessage(text: text));
|
_messages.insert(0, ChatMessage(text: text));
|
||||||
_isTyping = true; // Ativa o bloqueio
|
_isTyping = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final url = Uri.parse('http://89.114.196.110:11434/v1/chat/completions');
|
final url = Uri.parse('http://89.114.196.110:11434/api/chat');
|
||||||
|
|
||||||
final response = await http
|
final response = await http
|
||||||
.post(
|
.post(
|
||||||
url,
|
url,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
},
|
||||||
body: jsonEncode({
|
body: jsonEncode({
|
||||||
'model': 'tinyllama',
|
'model': 'qwen3:4b',
|
||||||
'messages': [{'role': 'user', 'content': text}],
|
'messages': [{'role': 'user', 'content': text}],
|
||||||
'stream': false,
|
'stream': false,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.timeout(const Duration(seconds: 60));
|
.timeout(const Duration(seconds: 60));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.body);
|
||||||
final reply = data['choices'][0]['message']['content'] ?? 'Sem resposta.';
|
final reply = data['message']?['content'] ?? 'Sem resposta.';
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isTyping = false; // Liberta o bloqueio
|
_isTyping = false;
|
||||||
_messages.insert(0, ChatMessage(text: reply, isAssistant: true));
|
_messages.insert(0, ChatMessage(text: reply, isAssistant: true));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Erro ${response.statusCode}');
|
throw Exception('Erro HTTP ${response.statusCode}: ${response.body}');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isTyping = false; // Liberta o bloqueio mesmo em caso de erro
|
_isTyping = false;
|
||||||
_messages.insert(
|
_messages.insert(
|
||||||
0,
|
0,
|
||||||
ChatMessage(text: "Erro: $e", isAssistant: true),
|
ChatMessage(text: "Erro: $e", isAssistant: true),
|
||||||
@@ -122,7 +125,6 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... (Mantenha _buildMessage, _buildAvatar, _buildTypingIndicator e _buildAnimatedDot iguais)
|
|
||||||
Widget _buildMessage(ChatMessage message) {
|
Widget _buildMessage(ChatMessage message) {
|
||||||
bool isAssistant = message.isAssistant;
|
bool isAssistant = message.isAssistant;
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -134,7 +136,7 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
if (isAssistant) _buildAvatar(Icons.auto_awesome),
|
if (isAssistant) _buildAvatar(Icons.auto_awesome),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 14.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: isAssistant
|
gradient: isAssistant
|
||||||
? const LinearGradient(colors: [Color(0xFFF1F5F9), Color(0xFFE2E8F0)])
|
? const LinearGradient(colors: [Color(0xFFF1F5F9), Color(0xFFE2E8F0)])
|
||||||
@@ -146,10 +148,19 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
bottomRight: Radius.circular(isAssistant ? 20 : 4),
|
bottomRight: Radius.circular(isAssistant ? 20 : 4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: isAssistant
|
||||||
message.text,
|
? MarkdownBody(
|
||||||
style: TextStyle(color: isAssistant ? Colors.black87 : Colors.white),
|
data: message.text,
|
||||||
),
|
styleSheet: MarkdownStyleSheet(
|
||||||
|
p: const TextStyle(color: Colors.black87, fontSize: 15, height: 1.4),
|
||||||
|
strong: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
|
||||||
|
listBullet: const TextStyle(color: Colors.black87),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
message.text,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 15, height: 1.4),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!isAssistant) _buildAvatar(Icons.person),
|
if (!isAssistant) _buildAvatar(Icons.person),
|
||||||
@@ -219,10 +230,9 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
// BOTÃO APAGAR CHAT
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.delete_sweep_rounded, color: Colors.redAccent),
|
icon: const Icon(Icons.delete_sweep_rounded, color: Colors.redAccent),
|
||||||
onPressed: _isTyping ? null : _clearChat, // Desativa enquanto digita
|
onPressed: _isTyping ? null : _clearChat,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -233,7 +243,7 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _textController,
|
controller: _textController,
|
||||||
enabled: !_isTyping, // Bloqueia o campo de texto
|
enabled: !_isTyping,
|
||||||
onSubmitted: _handleSubmitted,
|
onSubmitted: _handleSubmitted,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: _isTyping ? "Aguarde a resposta..." : "Mensagem EPVChat...",
|
hintText: _isTyping ? "Aguarde a resposta..." : "Mensagem EPVChat...",
|
||||||
@@ -248,7 +258,7 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: _isTyping
|
colors: _isTyping
|
||||||
? [Colors.grey, Colors.grey] // Cor de desativado
|
? [Colors.grey, Colors.grey]
|
||||||
: [const Color(0xFF8ad5c9), const Color(0xFF57a7ed)],
|
: [const Color(0xFF8ad5c9), const Color(0xFF57a7ed)],
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
@@ -279,20 +289,15 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
reverse: true, // Mantém a lógica de mensagens novas em baixo
|
reverse: true,
|
||||||
children: [
|
children: [
|
||||||
// As mensagens vêm primeiro (no reverse: true, o topo da lista é o fundo do ecrã)
|
|
||||||
...(_isTyping ? [_buildTypingIndicator()] : []),
|
...(_isTyping ? [_buildTypingIndicator()] : []),
|
||||||
..._messages.map((msg) => _buildMessage(msg)),
|
..._messages.map((msg) => _buildMessage(msg)),
|
||||||
|
|
||||||
// O LOGO E O SOMBREADO AGORA SÃO O ÚLTIMO ITEM DO LISTVIEW
|
|
||||||
// Quando o utilizador sobe o chat, eles sobem junto
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 50, top: 20),
|
padding: const EdgeInsets.only(bottom: 50, top: 20),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
// O Sombreado Verde
|
|
||||||
Container(
|
Container(
|
||||||
height: screenWidth * 0.8,
|
height: screenWidth * 0.8,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -305,7 +310,6 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// O Logo
|
|
||||||
Image.asset(
|
Image.asset(
|
||||||
'assets/logo.png',
|
'assets/logo.png',
|
||||||
height: 170,
|
height: 170,
|
||||||
@@ -322,4 +326,4 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
pubspec.lock
26
pubspec.lock
@@ -1,6 +1,14 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -70,6 +78,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_markdown:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_markdown
|
||||||
|
sha256: "08fb8315236099ff8e90cb87bb2b935e0a724a3af1623000a9cec930468e0f27"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.7+1"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -123,6 +139,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.0"
|
version: "6.1.0"
|
||||||
|
markdown:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: markdown
|
||||||
|
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -242,4 +266,4 @@ packages:
|
|||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.11.1 <4.0.0"
|
dart: ">=3.11.1 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.22.0"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
http: ^1.6.0
|
http: ^1.6.0
|
||||||
|
flutter_markdown: ^0.7.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user