import 'dart:async'; import 'dart:convert'; import 'package:Mzansi_Innovation_Hub/main.dart'; import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_button.dart'; import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_dropdown_input.dart'; import 'package:Mzansi_Innovation_Hub/mih_components/mih_inputs_and_buttons/mih_text_input.dart'; import 'package:Mzansi_Innovation_Hub/mih_components/mih_package/mih-app_tool_body.dart'; import 'package:Mzansi_Innovation_Hub/mih_components/mih_package/mih_app_window.dart'; import 'package:Mzansi_Innovation_Hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; import 'package:Mzansi_Innovation_Hub/mih_env/env.dart'; import 'package:Mzansi_Innovation_Hub/mih_objects/app_user.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:flutter/services.dart' show rootBundle; import 'package:ollama_dart/ollama_dart.dart' as ollama; import 'package:uuid/uuid.dart'; class AiChat extends StatefulWidget { final AppUser signedInUser; const AiChat({ super.key, required this.signedInUser, }); @override State createState() => _AiChatState(); } class _AiChatState extends State { final TextEditingController _modelCopntroller = TextEditingController(); final TextEditingController _fontSizeCopntroller = TextEditingController(); final ValueNotifier _showModelOptions = ValueNotifier(false); List _messages = []; late types.User _user; late types.User _mihAI; String systemPromt = "You are a helpful and friendly AI assistant. You are running on a system called MIH which was created by \"Mzansi Innovation Hub\" a South African based company."; bool _aiThinking = false; final client = ollama.OllamaClient( baseUrl: "${AppEnviroment.baseAiUrl}/api", ); List _chatHistory = []; double _chatFrontSize = 17; void _addMessage(types.Message message) { setState(() { _messages.insert(0, message); }); } void _handleSendPressed(types.PartialText message) { final textMessage = types.TextMessage( author: _user, createdAt: DateTime.now().millisecondsSinceEpoch, id: const Uuid().v4(), text: message.text, ); //Add user prompt to history setState(() { _chatHistory.add( ollama.Message( role: ollama.MessageRole.user, content: message.text, ), ); }); _addMessage(textMessage); _handleMessageBack(message.text); } void _handleMessageBack(String userMessage) async { showDialog( context: context, builder: (context) { return const Mihloadingcircle(); }, ); Stream aiChatStream = _generateChatCompletionWithHistoryStream(userMessage, client); Navigator.of(context).pop(); showDialog( context: context, barrierDismissible: false, builder: (context) { return responseWindow(aiChatStream); }, ); } Widget responseWindow( Stream aiChatStream, // StreamSubscription aiChatSubscription, ) { StreamSubscription aiChatSubscription = aiChatStream.listen((response) {}); types.TextMessage textMessage; return StreamBuilder( stream: _generateChatCompletionWithHistoryStream("", client), builder: (context, snapshot) { if (snapshot.hasData) { return MihAppWindow( fullscreen: false, windowTitle: 'Mzansi AI Thoughts', windowTools: const [], onWindowTapClose: () { if (_aiThinking) { aiChatSubscription.cancel(); } setState(() { _chatHistory.add( ollama.Message( role: ollama.MessageRole.assistant, content: snapshot.requireData, ), ); }); textMessage = types.TextMessage( author: _mihAI, createdAt: DateTime.now().millisecondsSinceEpoch, id: const Uuid().v4(), // metadata: { // "thoughts": snapshot.requireData // }, text: snapshot.requireData .replaceAll("\n\n", "Thinking:\n") .replaceAll("\n", "Thinking:\n") .replaceAll("\n\n", "Answer:\n"), //message.text, ); _addMessage(textMessage); Navigator.of(context).pop(); }, windowBody: [ Text( snapshot.requireData, textAlign: TextAlign.left, style: TextStyle( color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), fontSize: _chatFrontSize, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 15), Visibility( visible: _aiThinking == false, child: MIHButton( onTap: () { setState(() { _chatHistory.add( ollama.Message( role: ollama.MessageRole.assistant, content: snapshot.requireData, ), ); }); textMessage = types.TextMessage( author: _mihAI, createdAt: DateTime.now().millisecondsSinceEpoch, id: const Uuid().v4(), // metadata: { // "thoughts": snapshot.requireData // }, text: snapshot.requireData .replaceAll("\n\n", "Thinking:\n") .replaceAll("\n", "Thinking:\n") .replaceAll( "\n\n", "Answer:\n"), //message.text, ); _addMessage(textMessage); Navigator.of(context).pop(); }, buttonText: "Continue", buttonColor: MzanziInnovationHub.of(context)!.theme.successColor(), textColor: MzanziInnovationHub.of(context)!.theme.primaryColor(), ), ), ], ); } else { return MihAppWindow( fullscreen: false, windowTitle: 'Mzansi AI Thoughts', windowTools: [], onWindowTapClose: () { Navigator.of(context).pop(); }, windowBody: const [ Mihloadingcircle(), ], ); } }, ); } void _loadMessages() async { final response = await rootBundle.loadString('assets/messages.json'); final messages = (jsonDecode(response) as List) .map((e) => types.Message.fromJson(e as Map)) .toList(); setState(() { _messages = messages; }); } Stream _generateChatCompletionWithHistoryStream( String userMessage, final ollama.OllamaClient client, ) async* { final aiStream = client.generateChatCompletionStream( request: ollama.GenerateChatCompletionRequest( model: _modelCopntroller.text, messages: _chatHistory, ), ); String text = ''; setState(() { _aiThinking = true; }); await for (final res in aiStream) { text += (res.message.content); yield text; } setState(() { _aiThinking = false; }); // print(text); } void _resetChat() { setState(() { _messages = []; _chatHistory = []; _loadMessages(); }); // Navigator.of(context).popAndPushNamed( // '/mzansi-ai', // arguments: widget.signedInUser, // ); } ChatTheme getChatTheme() { return DarkChatTheme( backgroundColor: MzanziInnovationHub.of(context)!.theme.primaryColor(), inputBackgroundColor: MzanziInnovationHub.of(context)!.theme.secondaryColor(), inputTextColor: MzanziInnovationHub.of(context)!.theme.primaryColor(), inputTextCursorColor: MzanziInnovationHub.of(context)!.theme.primaryColor(), primaryColor: MzanziInnovationHub.of(context)!.theme.secondaryColor(), secondaryColor: MzanziInnovationHub.of(context)!.theme.successColor(), errorColor: MzanziInnovationHub.of(context)!.theme.errorColor(), sentMessageBodyTextStyle: TextStyle( color: MzanziInnovationHub.of(context)!.theme.primaryColor(), fontSize: _chatFrontSize, fontWeight: FontWeight.w500, fontFamily: 'Segoe UI', ), receivedMessageBodyTextStyle: TextStyle( color: MzanziInnovationHub.of(context)!.theme.primaryColor(), fontSize: _chatFrontSize, fontWeight: FontWeight.w500, fontFamily: 'Segoe UI', ), emptyChatPlaceholderTextStyle: TextStyle( color: MzanziInnovationHub.of(context)!.theme.messageTextColor(), fontSize: _chatFrontSize, fontWeight: FontWeight.w500, fontFamily: 'Segoe UI', ), ); } @override void dispose() { // TODO: implement dispose super.dispose(); _modelCopntroller.dispose(); client.endSession(); } @override void initState() { super.initState(); _user = types.User( firstName: widget.signedInUser.fname, id: widget.signedInUser.app_id, //'82091008-a484-4a89-ae75-a22bf8d6f3ac', ); _mihAI = types.User( firstName: "Mzansi AI", id: const Uuid().v4(), ); _modelCopntroller.text = 'deepseek-r1:1.5b'; _fontSizeCopntroller.text = _chatFrontSize.ceil().toString(); // _chatHistory.add( // ollama.Message( // role: ollama.MessageRole.system, // content: systemPromt, // ), // ); _loadMessages(); } @override Widget build(BuildContext context) { return MihAppToolBody( borderOn: false, bodyItem: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisSize: MainAxisSize.max, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: Container( alignment: Alignment.centerLeft, child: Row( children: [ Visibility( visible: _showModelOptions.value == false, child: IconButton( onPressed: () { if (_showModelOptions.value == true) { setState(() { _showModelOptions.value = false; }); } else { setState(() { _showModelOptions.value = true; }); } }, icon: const Icon(Icons.settings), ), ), Visibility( visible: _showModelOptions.value == true, child: IconButton.filled( onPressed: () { if (_showModelOptions.value == true) { setState(() { _showModelOptions.value = false; }); } else { setState(() { _showModelOptions.value = true; }); } }, icon: const Icon(Icons.settings), ), ), ], ), ), ), Text( "Mzansi AI", textAlign: TextAlign.center, style: TextStyle( fontSize: 25, fontWeight: FontWeight.bold, color: MzanziInnovationHub.of(context)!.theme.secondaryColor(), ), ), Expanded( child: Container( alignment: Alignment.centerRight, child: IconButton( onPressed: () { _resetChat(); }, icon: const Icon(Icons.refresh), ), ), ), ], ), ValueListenableBuilder( valueListenable: _showModelOptions, builder: (BuildContext context, bool value, Widget? child) { return Visibility( visible: value, child: Padding( padding: const EdgeInsets.only(top: 10.0), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 25), child: SizedBox( width: 300, child: MIHDropdownField( controller: _modelCopntroller, hintText: "AI Model", dropdownOptions: const ['deepseek-r1:1.5b'], required: true, editable: true, ), ), ) ], ), ), ); }, ), const SizedBox(height: 15), ValueListenableBuilder( valueListenable: _showModelOptions, builder: (BuildContext context, bool value, Widget? child) { return Visibility( visible: value, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 25), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ IconButton.filled( onPressed: () { setState(() { _chatFrontSize -= 1; _fontSizeCopntroller.text = _chatFrontSize.ceil().toString(); }); }, icon: const Icon( Icons.remove, ), ), const SizedBox(width: 10), SizedBox( width: 200, child: MIHTextField( controller: _fontSizeCopntroller, hintText: "Chat Font Size", editable: false, required: true, ), ), const SizedBox(width: 10), IconButton.filled( onPressed: () { setState(() { _chatFrontSize += 1; _fontSizeCopntroller.text = _chatFrontSize.ceil().toString(); }); }, icon: const Icon( Icons.add, ), ), ], ), ), ); }, ), const SizedBox(height: 5), Expanded( child: Chat( messages: _messages, // onAttachmentPressed: _handleAttachmentPressed, // onMessageTap: _handleMessageTap, // onPreviewDataFetched: _handlePreviewDataFetched, onSendPressed: _handleSendPressed, showUserAvatars: false, showUserNames: false, user: _user, theme: getChatTheme(), ), ) ], ), ); } }