import 'package:flutter/material.dart'; import 'dart:ui'; import '../g4f/client.dart'; import '../g4f/models.dart'; import '../services/settings_repository.dart'; class OracleChatScreen extends StatefulWidget { const OracleChatScreen({Key? key}) : super(key: key); @override State createState() => _OracleChatScreenState(); } class ChatMessage { final String text; final bool isUser; ChatMessage({required this.text, required this.isUser}); } class _OracleChatScreenState extends State { final TextEditingController _controller = TextEditingController(); final ScrollController _scrollController = ScrollController(); final G4FClient _client = G4FClient(); late final List _messages; bool _isLoading = false; @override void initState() { super.initState(); _messages = [ ChatMessage( text: SettingsRepository().persona.chatGreeting, isUser: false, ), ]; } void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } }); } Future _sendMessage() async { final text = _controller.text.trim(); if (text.isEmpty || _isLoading) return; setState(() { _messages.add(ChatMessage(text: text, isUser: true)); _messages.add(ChatMessage(text: '', isUser: false)); _isLoading = true; }); _controller.clear(); _scrollToBottom(); final apiMessages = [ Message( role: Role.system, content: '${SettingsRepository().persona.systemPrompt}\n' 'Отвечай на языке вопрошающего. Не упоминай модели, провайдеров и технические сбои.', ), for (final m in _messages) if (m.text.trim().isNotEmpty) Message( role: m.isUser ? Role.user : Role.assistant, content: m.text, ), ]; try { final reply = await _client.chat.completions.create( model: G4FModels.gpt4o, messages: apiMessages, ); if (!mounted) return; _setLastOracleText( reply.trim().isEmpty ? 'Мой взор затуманен. Знаки молчат. Спроси ещё раз.' : reply.trim(), ); } catch (_) { if (!mounted) return; _setLastOracleText('Мой взор затуманен. Связь прервалась.'); } finally { if (mounted) { setState(() => _isLoading = false); _scrollToBottom(); } } } void _setLastOracleText(String text) { setState(() { _messages[_messages.length - 1] = ChatMessage(text: text, isUser: false); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF060B19), appBar: AppBar( title: const Text("Оракул", style: TextStyle(color: Color(0xFFFFD700), fontWeight: FontWeight.w300, letterSpacing: 2.0)), backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, iconTheme: const IconThemeData(color: Color(0xFFFFD700)), ), extendBodyBehindAppBar: true, body: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xFF0D0415), Color(0xFF060B19)], ), ), child: SafeArea( child: Column( children: [ Expanded( child: ListView.builder( controller: _scrollController, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), itemCount: _messages.length, itemBuilder: (context, index) { final msg = _messages[index]; return _buildMessageBubble(msg); }, ), ), if (_isLoading) const Padding( padding: EdgeInsets.all(8.0), child: SizedBox( width: 20, height: 20, child: CircularProgressIndicator( color: Color(0xFFFFD700), strokeWidth: 2, ), ), ), _buildInputArea(), ], ), ), ), ); } Widget _buildMessageBubble(ChatMessage message) { return Align( alignment: message.isUser ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(bottom: 16.0), constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.8), child: ClipRRect( borderRadius: BorderRadius.only( topLeft: const Radius.circular(20), topRight: const Radius.circular(20), bottomLeft: Radius.circular(message.isUser ? 20 : 5), bottomRight: Radius.circular(message.isUser ? 5 : 20), ), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14), decoration: BoxDecoration( color: message.isUser ? const Color(0xFF1E293B).withOpacity(0.6) : const Color(0xFF2E1A47).withOpacity(0.4), border: Border.all( color: message.isUser ? Colors.white.withOpacity(0.1) : const Color(0xFFFFD700).withOpacity(0.2), width: 1, ), ), child: SelectableText( message.text, style: TextStyle( color: message.isUser ? Colors.white : const Color(0xFFE5E7EB), fontSize: 16, height: 1.4, ), ), ), ), ), ), ); } Widget _buildInputArea() { return Container( padding: const EdgeInsets.only(left: 16, right: 16, top: 12, bottom: 24), decoration: BoxDecoration( color: const Color(0xFF0D0415).withOpacity(0.8), border: Border( top: BorderSide( color: const Color(0xFFFFD700).withOpacity(0.2), width: 1, ), ), ), child: Row( children: [ Expanded( child: Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.05), borderRadius: BorderRadius.circular(30), border: Border.all(color: Colors.white.withOpacity(0.1)), ), child: TextField( controller: _controller, style: const TextStyle(color: Colors.white), decoration: const InputDecoration( hintText: "Спроси Оракула...", hintStyle: TextStyle(color: Colors.white38), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 14), ), textInputAction: TextInputAction.send, onSubmitted: (_) => _sendMessage(), ), ), ), const SizedBox(width: 12), GestureDetector( onTap: _isLoading ? null : _sendMessage, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( shape: BoxShape.circle, gradient: const RadialGradient( colors: [Color(0xFFFFD700), Color(0xFFB8860B)], center: Alignment.topLeft, radius: 1.0, ), boxShadow: [ BoxShadow( color: const Color(0xFFFFD700).withOpacity(0.3), blurRadius: 12, spreadRadius: 2, ), ], ), child: const Icon( Icons.auto_awesome, color: Color(0xFF2E1A47), size: 24, ), ), ), ], ), ); } }