QOL: Mzansi AI Enhancement pt1
This commit is contained in:
@@ -98,131 +98,166 @@ class _MihDropdownFieldState extends State<MihDropdownField> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
selectionColor: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark")
|
||||
.withValues(alpha: 0.3),
|
||||
selectionHandleColor: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
selectionColor: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!
|
||||
.theme
|
||||
.mode ==
|
||||
"Dark")
|
||||
.withValues(alpha: 0.3),
|
||||
selectionHandleColor: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
),
|
||||
),
|
||||
child: DropdownMenu(
|
||||
controller: widget.controller,
|
||||
dropdownMenuEntries: menu,
|
||||
enableSearch: widget.enableSearch,
|
||||
enableFilter: widget.enableSearch,
|
||||
enabled: widget.editable,
|
||||
textInputAction: widget.enableSearch
|
||||
? TextInputAction.search
|
||||
: TextInputAction.none,
|
||||
requestFocusOnTap: widget.enableSearch,
|
||||
menuHeight: 400,
|
||||
expandedInsets: EdgeInsets.zero,
|
||||
textStyle: TextStyle(
|
||||
color: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
trailingIcon: Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
),
|
||||
selectedTrailingIcon: Icon(
|
||||
Icons.arrow_drop_up,
|
||||
color: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
),
|
||||
// leadingIcon:
|
||||
// IconButton(
|
||||
// onPressed: () {
|
||||
// widget.controller.clear();
|
||||
// field.didChange('');
|
||||
// },
|
||||
// icon: Icon(
|
||||
// Icons.delete_outline_rounded,
|
||||
// color: MihColors.getPrimaryColor(
|
||||
// MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
// "Dark"),
|
||||
// ),
|
||||
// ),
|
||||
onSelected: (String? selectedValue) {
|
||||
field.didChange(selectedValue);
|
||||
widget.onSelected?.call(selectedValue);
|
||||
},
|
||||
menuStyle: MenuStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
MihColors.getSecondaryColor(
|
||||
MzansiInnovationHub.of(context)!
|
||||
.theme
|
||||
.mode ==
|
||||
"Dark")),
|
||||
side: WidgetStatePropertyAll(
|
||||
BorderSide(
|
||||
color: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!
|
||||
.theme
|
||||
.mode ==
|
||||
"Dark"),
|
||||
width: 1.0),
|
||||
),
|
||||
shape: WidgetStatePropertyAll(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
10), // Increase for more roundness
|
||||
),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
errorStyle: const TextStyle(height: 0, fontSize: 0),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 10.0, vertical: 8.0),
|
||||
filled: true,
|
||||
fillColor: MihColors.getSecondaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: field.hasError
|
||||
? MihColors.getRedColor(
|
||||
MzansiInnovationHub.of(context)!
|
||||
.theme
|
||||
.mode ==
|
||||
"Dark")
|
||||
: MihColors.getSecondaryColor(
|
||||
MzansiInnovationHub.of(context)!
|
||||
.theme
|
||||
.mode ==
|
||||
"Dark"),
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: MihColors.getRedColor(
|
||||
MzansiInnovationHub.of(context)!
|
||||
.theme
|
||||
.mode ==
|
||||
"Dark"),
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: MihColors.getRedColor(
|
||||
MzansiInnovationHub.of(context)!
|
||||
.theme
|
||||
.mode ==
|
||||
"Dark"),
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: DropdownMenu(
|
||||
controller: widget.controller,
|
||||
dropdownMenuEntries: menu,
|
||||
enableSearch: widget.enableSearch,
|
||||
enableFilter: widget.enableSearch,
|
||||
enabled: widget.editable,
|
||||
textInputAction: widget.enableSearch
|
||||
? TextInputAction.search
|
||||
: TextInputAction.none,
|
||||
requestFocusOnTap: widget.enableSearch,
|
||||
menuHeight: 400,
|
||||
expandedInsets: EdgeInsets.zero,
|
||||
textStyle: TextStyle(
|
||||
color: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
trailingIcon: Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
),
|
||||
selectedTrailingIcon: Icon(
|
||||
Icons.arrow_drop_up,
|
||||
color: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
),
|
||||
leadingIcon: IconButton(
|
||||
onPressed: () {
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
widget.controller.clear();
|
||||
field.didChange('');
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.delete_outline_rounded,
|
||||
color: MihColors.getPrimaryColor(
|
||||
child: Icon(
|
||||
size: 35,
|
||||
Icons.delete_rounded,
|
||||
color: MihColors.getSecondaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
),
|
||||
),
|
||||
onSelected: (String? selectedValue) {
|
||||
field.didChange(selectedValue);
|
||||
widget.onSelected?.call(selectedValue);
|
||||
},
|
||||
menuStyle: MenuStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
MihColors.getSecondaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark")),
|
||||
side: WidgetStatePropertyAll(
|
||||
BorderSide(
|
||||
color: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
width: 1.0),
|
||||
),
|
||||
shape: WidgetStatePropertyAll(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
10), // Increase for more roundness
|
||||
),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
errorStyle: const TextStyle(height: 0, fontSize: 0),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 10.0, vertical: 8.0),
|
||||
filled: true,
|
||||
fillColor: MihColors.getSecondaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: field.hasError
|
||||
? MihColors.getRedColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark")
|
||||
: MihColors.getSecondaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: MihColors.getRedColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: MihColors.getRedColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode ==
|
||||
"Dark"),
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (field.hasError)
|
||||
Padding(
|
||||
|
||||
@@ -228,10 +228,11 @@ class _MihAiChatState extends State<MihAiChat> with WidgetsBindingObserver {
|
||||
LlmChatView(
|
||||
provider: aiProvider.ollamaProvider,
|
||||
messageSender: aiProvider.ollamaProvider.sendMessageStream,
|
||||
speechToText: aiProvider.ollamaProvider.speechToText,
|
||||
// welcomeMessage:
|
||||
// "Mzansi AI is here to help. Send us a messahe and we'll try our best to assist you.",
|
||||
autofocus: false,
|
||||
enableAttachments: false,
|
||||
enableAttachments: true,
|
||||
enableVoiceNotes: false,
|
||||
style: aiProvider.getChatStyle(context),
|
||||
suggestions: [
|
||||
|
||||
@@ -19,7 +19,10 @@ class MzansiAiProvider extends ChangeNotifier {
|
||||
}) {
|
||||
ollamaProvider = OllamaProvider(
|
||||
baseUrl: "${AppEnviroment.baseAiUrl}/api",
|
||||
model: AppEnviroment.getEnv() == "Prod" ? 'gemma3n:e4b' : "gemma3:1b",
|
||||
model: AppEnviroment.getEnv() == "Prod"
|
||||
? 'gemma3n:e4b'
|
||||
: "qwen3-vl:2b-instruct",
|
||||
think: false,
|
||||
systemPrompt: "You are Mzansi AI, a helpful and friendly AI assistant running on the 'MIH App'.\n" +
|
||||
"The MIH App was created by 'Mzansi Innovation Hub', a South African-based startup company." +
|
||||
"Your primary purpose is to assist users by answering general questions and helping with creative writing tasks or any other task a user might have for you.\n" +
|
||||
@@ -147,9 +150,6 @@ class MzansiAiProvider extends ChangeNotifier {
|
||||
MzansiInnovationHub.of(context)!.theme.mode == "Dark"),
|
||||
progressIndicatorColor: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode == "Dark"),
|
||||
menuColor: Colors.black,
|
||||
// MihColors.getGreenColor(
|
||||
// MzansiInnovationHub.of(context)!.theme.mode == "Dark"),
|
||||
disabledButtonStyle: ActionButtonStyle(
|
||||
icon: MihIcons.mzansiAi,
|
||||
iconColor: MihColors.getSecondaryColor(
|
||||
@@ -323,6 +323,21 @@ class MzansiAiProvider extends ChangeNotifier {
|
||||
MzansiInnovationHub.of(context)!.theme.mode == "Dark"),
|
||||
),
|
||||
),
|
||||
addButtonStyle: ActionButtonStyle(
|
||||
iconDecoration: BoxDecoration(
|
||||
color: MihColors.getSecondaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode == "Dark"),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
),
|
||||
iconColor: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode == "Dark"),
|
||||
textStyle: TextStyle(
|
||||
color: MihColors.getPrimaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode == "Dark"),
|
||||
),
|
||||
),
|
||||
menuColor: MihColors.getSecondaryColor(
|
||||
MzansiInnovationHub.of(context)!.theme.mode == "Dark"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_ai_toolkit/flutter_ai_toolkit.dart';
|
||||
import 'package:ken_logger/ken_logger.dart';
|
||||
import 'package:ollama_dart/ollama_dart.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
|
||||
class OllamaProvider extends LlmProvider with ChangeNotifier {
|
||||
OllamaProvider({
|
||||
String? baseUrl,
|
||||
Map<String, String>? headers,
|
||||
Map<String, String>? queryParams,
|
||||
Map<String, dynamic>? queryParams,
|
||||
required String model,
|
||||
String? systemPrompt,
|
||||
bool? think,
|
||||
}) : _client = OllamaClient(
|
||||
baseUrl: baseUrl,
|
||||
headers: headers,
|
||||
@@ -19,11 +21,13 @@ class OllamaProvider extends LlmProvider with ChangeNotifier {
|
||||
),
|
||||
_model = model,
|
||||
_systemPrompt = systemPrompt,
|
||||
_think = think,
|
||||
_history = [];
|
||||
final OllamaClient _client;
|
||||
final String _model;
|
||||
final List<ChatMessage> _history;
|
||||
final String? _systemPrompt;
|
||||
final bool? _think;
|
||||
|
||||
@override
|
||||
Stream<String> generateStream(
|
||||
@@ -36,6 +40,29 @@ class OllamaProvider extends LlmProvider with ChangeNotifier {
|
||||
yield* _generateStream(messages);
|
||||
}
|
||||
|
||||
Stream<String> speechToText(XFile audioFile) async* {
|
||||
KenLogger.success("Inside Custom speechToText funtion");
|
||||
// 1. Convert the XFile to the attachment format needed for the LLM.
|
||||
final attachments = [await FileAttachment.fromFile(audioFile)];
|
||||
KenLogger.success("added attachment for audio file");
|
||||
|
||||
// 2. Define the transcription prompt, mirroring the logic from LlmChatView.
|
||||
const prompt =
|
||||
'translate the attached audio to text; provide the result of that '
|
||||
'translation as just the text of the translation itself. be careful to '
|
||||
'separate the background audio from the foreground audio and only '
|
||||
'provide the result of translating the foreground audio.';
|
||||
|
||||
KenLogger.success("Created Prompt");
|
||||
// 3. Use your existing Ollama API call to process the prompt and attachment.
|
||||
// We are essentially running a new, one-off chat session for transcription.
|
||||
yield* generateStream(
|
||||
prompt,
|
||||
attachments: attachments,
|
||||
);
|
||||
KenLogger.success("done");
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<String> sendMessageStream(
|
||||
String prompt, {
|
||||
@@ -76,6 +103,7 @@ class OllamaProvider extends LlmProvider with ChangeNotifier {
|
||||
Stream<String> _generateStream(List<Message> messages) async* {
|
||||
final allMessages = <Message>[];
|
||||
if (_systemPrompt != null && _systemPrompt.isNotEmpty) {
|
||||
KenLogger.success("Adding system prompt to the conversation");
|
||||
allMessages.add(Message(
|
||||
role: MessageRole.system,
|
||||
content: _systemPrompt,
|
||||
@@ -87,6 +115,7 @@ class OllamaProvider extends LlmProvider with ChangeNotifier {
|
||||
request: GenerateChatCompletionRequest(
|
||||
model: _model,
|
||||
messages: allMessages,
|
||||
think: _think,
|
||||
),
|
||||
);
|
||||
// final stream = _client.generateChatCompletionStream(
|
||||
@@ -109,19 +138,32 @@ class OllamaProvider extends LlmProvider with ChangeNotifier {
|
||||
content: message.text ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
final imageAttachments = <String>[];
|
||||
final docAttachments = <String>[];
|
||||
if (message.text != null && message.text!.isNotEmpty) {
|
||||
docAttachments.add(message.text!);
|
||||
}
|
||||
for (final attachment in message.attachments) {
|
||||
if (attachment is FileAttachment) {
|
||||
final mimeType = attachment.mimeType.toLowerCase();
|
||||
if (mimeType.startsWith('image/')) {
|
||||
imageAttachments.add(base64Encode(attachment.bytes));
|
||||
} else if (mimeType == 'application/pdf' ||
|
||||
mimeType.startsWith('text/')) {
|
||||
throw LlmFailureException(
|
||||
"\n\nAww, that file is a little too advanced for us right now ($mimeType)! We're still learning, but we'll get there! Please try sending us a different file type.\n\nHint: We can handle images quite well!",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw LlmFailureException(
|
||||
'Unsupported attachment type: $attachment',
|
||||
);
|
||||
}
|
||||
}
|
||||
return Message(
|
||||
role: MessageRole.user,
|
||||
content: message.text ?? '',
|
||||
images: [
|
||||
for (final attachment in message.attachments)
|
||||
if (attachment is ImageFileAttachment)
|
||||
base64Encode(attachment.bytes)
|
||||
else
|
||||
throw LlmFailureException(
|
||||
'Unsupported attachment type: $attachment',
|
||||
),
|
||||
],
|
||||
content: docAttachments.join(' '),
|
||||
images: imageAttachments,
|
||||
);
|
||||
|
||||
case MessageOrigin.llm:
|
||||
|
||||
Reference in New Issue
Block a user