diff --git a/lib/controller/chat.dart b/lib/controller/chat.dart index 0b563b6..2c280ad 100644 --- a/lib/controller/chat.dart +++ b/lib/controller/chat.dart @@ -20,9 +20,15 @@ class ChatPageController extends GetxController { currHistoryMessage = Get.find().histories.last; update(); } - void setHistory(HistoryMessage history) { currHistoryMessage = history; update(); } + + @override + void onClose() { + Get.find().saveAll(); + super.onClose(); + + } } diff --git a/lib/controller/setting.dart b/lib/controller/setting.dart index 9bc03ce..fc41735 100644 --- a/lib/controller/setting.dart +++ b/lib/controller/setting.dart @@ -16,12 +16,13 @@ class SettingPageController extends GetxController { final key = "".obs; // 对话配置 final chatModel = "gpt-3.5-turbo".obs; - final drawModel = "dall-e-2".obs; + final imageModel = "dall-e-2".obs; final temperature = 0.5.obs; final presencePenalty = 0.0.obs; final frequencyPenalty = 0.0.obs; final historyLength = 16.obs; - final disabledSystemPrompt = true.obs; + final enabledSystemPrompt = true.obs; + final enabledImageChat = false.obs; final topP = 0.5.obs; final db = SettingDatabase(); @@ -33,11 +34,13 @@ class SettingPageController extends GetxController { api.value = db.getApi()??api.value; key.value = db.getKey()??key.value; chatModel.value = db.getChatModel()??chatModel.value; + imageModel.value = db.getImageModel()??imageModel.value; temperature.value = db.getTemperature()??temperature.value; presencePenalty.value = db.getPresencePenalty()??presencePenalty.value; frequencyPenalty.value = db.getFrequencyPenalty()??frequencyPenalty.value; historyLength.value = db.getHistoryLength()??historyLength.value; - disabledSystemPrompt.value = db.getDisabledSystemPrompt()??disabledSystemPrompt.value; + enabledSystemPrompt.value = db.getDisabledSystemPrompt()??enabledSystemPrompt.value; + enabledImageChat.value = db.getEnabledImageChat()??enabledImageChat.value; topP.value = db.getTopP()??topP.value; setLanguage(language.value!); super.onInit(); @@ -54,7 +57,8 @@ class SettingPageController extends GetxController { presencePenalty.value = 0.0; frequencyPenalty.value = 0.0; historyLength.value = 16; - disabledSystemPrompt(true); + enabledSystemPrompt(true); + enabledImageChat(false); topP.value = 0.5; update(); } @@ -71,11 +75,13 @@ class SettingPageController extends GetxController { db.saveApi(api.value); db.saveKey(key.value); db.saveChatModel(chatModel.value); + db.saveImageModel(imageModel.value); db.saveTemperature(temperature.value); db.savePresencePenalty(presencePenalty.value); db.saveFrequencyPenalty(frequencyPenalty.value); db.saveHistoryLength(historyLength.value); - db.saveDisabledSystemPrompt(disabledSystemPrompt.value); + db.saveDisabledSystemPrompt(enabledSystemPrompt.value); + db.saveEnabledImageChat(enabledImageChat.value); db.saveTopP(topP.value); log("配置信息存储完成"); } diff --git a/lib/controller/sidebar.dart b/lib/controller/sidebar.dart index 8bcbe55..2030106 100644 --- a/lib/controller/sidebar.dart +++ b/lib/controller/sidebar.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:chat_all/db/message_database.dart'; import 'package:chat_all/model/message.dart'; import 'package:get/get.dart'; @@ -77,11 +79,12 @@ class SidebarPageController extends GetxController { @override void onClose() { - db.saveHistory(histories); + saveAll(); super.onClose(); } void saveAll() { + log("聊天数据保存成功"); db.saveHistory(histories); } diff --git a/lib/db/message_database.dart b/lib/db/message_database.dart index 701a23a..4006ff9 100644 --- a/lib/db/message_database.dart +++ b/lib/db/message_database.dart @@ -1,4 +1,6 @@ +import 'dart:developer'; + import 'package:chat_all/db/boxes.dart'; import 'package:chat_all/model/message.dart'; @@ -30,13 +32,12 @@ class MessageDatabase { for (var element in values) { histories.add(HistoryMessage(id: element.id, title: element.title, messages: element.messages, createTime: element.createTime)); } - print(histories); return histories; } - + // // 添加HistoryMessage - void addHistoryMessage(HistoryMessage message){ + void addHistoryMessage(HistoryMessage message){ // var histories = readAll(); histories.add(message); historyBox.put("key_histories", histories); diff --git a/lib/db/setting_database.dart b/lib/db/setting_database.dart index a8ab763..e2a9ac1 100644 --- a/lib/db/setting_database.dart +++ b/lib/db/setting_database.dart @@ -75,6 +75,13 @@ class SettingDatabase { return settingBox.get("key_setting_chatModel"); } + void saveImageModel(String imageModel){ + settingBox.put("key_setting_imageModel", imageModel); + } + String? getImageModel(){ + return settingBox.get("key_setting_imageModel"); + } + void saveTemperature(double temperature){ settingBox.put("key_setting_temperature", temperature); } @@ -115,6 +122,14 @@ class SettingDatabase { return settingBox.get("key_setting_disabledSystemPrompt"); } + void saveEnabledImageChat(bool enabledImageChat){ + settingBox.put("key_setting_enabledImageChat", enabledImageChat); + } + + bool? getEnabledImageChat() { + return settingBox.get("key_setting_enabledImageChat"); + } + void saveTopP(double topP){ settingBox.put("key_setting_topP", topP); } diff --git a/lib/i18n/record.dart b/lib/i18n/record.dart index 8673e72..5a5f0aa 100644 --- a/lib/i18n/record.dart +++ b/lib/i18n/record.dart @@ -15,13 +15,15 @@ class MessageRecord extends Translations { "basic_setting_language": "语言", "api_setting": "接口设置", "chat_setting": "对话设置", - "chat_setting_model": "模型", + "chat_setting_chat_model": "对话模型", + "chat_setting_image_model": "图片生成模型", "chat_setting_temperature": "随机性", "chat_setting_top_p": "核采样", "chat_setting_presence_penalty": "话题新鲜度", "chat_setting_frequency_penalty": "频率惩罚", "chat_setting_history_length": "携带的消息长度", - "chat_setting_disabled_system_prompt": "开启系统词注入", + "chat_setting_enabled_system_prompt": "开启系统词注入", + "chat_setting_enabled_image_chat": "开启图片绘制", "chat_other_reset_setting": "重置配置", "chat_other_clear_history": "清空对话", "chat_page_title": "聊天对话", @@ -33,7 +35,6 @@ class MessageRecord extends Translations { "chat_page_home_tip_tour_content": "请给我一些关于拉萨旅行的计划", "chat_page_home_tip_social_title": "学习社交技巧", "chat_page_home_tip_social_content": "如何结交到真心朋友?", - "default_history_chat_title": "开启新对话", "default_history_chat_overview": "新的聊天", "default_dialog_delete_title": "确定删除", @@ -52,13 +53,15 @@ class MessageRecord extends Translations { "basic_setting_language": "Language", "api_setting": "API Settings", "chat_setting": "Chat Settings", - "chat_setting_model": "Model", + "chat_setting_chat_model": "Chat Model", + "chat_setting_image_model": "Image Model", "chat_setting_temperature": "temperature", "chat_setting_top_p": "top_p", "chat_setting_presence_penalty": "Presence Penalty", "chat_setting_frequency_penalty": "Frequency Penalty", "chat_setting_history_length": "History Length", - "chat_setting_disabled_system_prompt": "Enable System Prompt", + "chat_setting_enabled_system_prompt": "Enabled System Prompt", + "chat_setting_enabled_image_chat": "Enabled Image Chat", "chat_other_reset_setting": "Reset Settings", "chat_other_clear_history": "Clear History", "chat_page_title": "Chat Conversation", diff --git a/lib/page/chat.dart b/lib/page/chat.dart index a7a2d34..2d2a273 100644 --- a/lib/page/chat.dart +++ b/lib/page/chat.dart @@ -14,8 +14,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; - - import '../controller/sidebar.dart'; class ChatPage extends StatefulWidget { @@ -32,13 +30,21 @@ class _ChatPageState extends State { final _chatService = OpenAIService(); final _textEditingController = TextEditingController(); final ScrollController _scrollController = ScrollController(); - final isWaiting = false.obs; // 给我一张小狗狗的照片 + final isWaiting = false.obs; @override void initState() { super.initState(); } + @override + void dispose() { + _sidebarController.saveAll(); + super.dispose(); + } + + + @override Widget build(BuildContext context) { return Scaffold( @@ -142,6 +148,7 @@ class _ChatPageState extends State { child: currMessage.content.isEmpty ? const CircularProgressIndicator( backgroundColor: Colors.blue, + ) : MdCodeMath(currMessage.content), ), @@ -210,13 +217,13 @@ class _ChatPageState extends State { // 获取对话Message final chatMessage = getChatMessageByLen(); - log("$chatMessage------**********"); // 发起对话 await _chatService.chat( - drawModel: _settingController.drawModel.value, + imageModel: _settingController.imageModel.value, messages: chatMessage, - model: _settingController.chatModel.value, + isImageChat: _settingController.enabledImageChat.value, + chatModel: _settingController.chatModel.value, temperature: _settingController.temperature.value, topP: _settingController.topP.value, presencePenalty: _settingController.presencePenalty.value, @@ -228,7 +235,8 @@ class _ChatPageState extends State { _chatController.updateMessageContent( _chatController.currHistoryMessage.messages.length - 1, event); updateToBottom(); - }); // + }); + _sidebarController.saveAll(); } void updateToBottom() { diff --git a/lib/page/setting.dart b/lib/page/setting.dart index 8cf2b77..30450ea 100644 --- a/lib/page/setting.dart +++ b/lib/page/setting.dart @@ -173,13 +173,9 @@ class _SettingPageState extends State { children: [ Row( children: [ - SvgPicture.asset( - AssetsManage.apiIcon, - width: 30, - height: 30, - color: AdaptiveTheme.of(context).mode.isLight - ? Colors.black - : Colors.white, + const Icon( + Icons.api, + size: 30, ), const SizedBox( width: 10, @@ -269,7 +265,7 @@ class _SettingPageState extends State { const Divider(), /// - /// 对话设置:模型、随机性、核采样、话题新鲜度、频率惩罚、是否注入系统提示词、附带的历史消息总数 + /// 对话设置:对话模型、图片模型、随机性、核采样、话题新鲜度、频率惩罚、是否注入系统提示词、附带的历史消息总数 Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -282,19 +278,11 @@ class _SettingPageState extends State { children: [ Row( children: [ - SvgPicture.asset( - AssetsManage.modelIcon, - width: 30, - height: 30, - color: AdaptiveTheme.of(context).mode.isLight - ? Colors.black - : Colors.white, - ), const SizedBox( - width: 10, + width: 40, ), Text( - "chat_setting_model".tr, + "chat_setting_chat_model".tr, style: AdaptiveTheme.of(context) .theme .textTheme @@ -317,6 +305,42 @@ class _SettingPageState extends State { }) ], ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const SizedBox( + width: 40, + ), + Text( + "chat_setting_image_model".tr, + style: AdaptiveTheme.of(context) + .theme + .textTheme + .titleMedium, + ) + ], + ), + DropdownButton( + padding: const EdgeInsets.all(5), + borderRadius: BorderRadius.circular(10), + value: "dall-e-2", + items: const [ + DropdownMenuItem( + value: "dall-e-2", + child: Text("dall-e-2"), + ), + DropdownMenuItem( + value: "dall-e-3", + child: Text("dall-e-3"), + ) + ], + onChanged: (String? value) { + _settingController.imageModel.value = value!; + }) + ], + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -520,7 +544,34 @@ class _SettingPageState extends State { width: 40, ), Text( - "chat_setting_disabled_system_prompt".tr, + "chat_setting_enabled_system_prompt".tr, + style: AdaptiveTheme.of(context) + .theme + .textTheme + .titleMedium, + ) + ], + ), + Obx(() => Switch( + activeColor: Colors.blue, + inactiveThumbColor: Colors.grey, + value: _settingController.enabledSystemPrompt.value, + onChanged: (value) { + _settingController.enabledSystemPrompt.value = + !_settingController.enabledSystemPrompt.value; + })) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const SizedBox( + width: 40, + ), + Text( + "chat_setting_enabled_image_chat".tr, style: AdaptiveTheme.of(context) .theme .textTheme @@ -531,10 +582,10 @@ class _SettingPageState extends State { Obx(() => Switch( activeColor: Colors.blue, inactiveThumbColor: Colors.grey, - value: _settingController.disabledSystemPrompt.value, + value: _settingController.enabledImageChat.value, onChanged: (value) { - _settingController.disabledSystemPrompt.value = - !_settingController.disabledSystemPrompt.value; + _settingController.enabledImageChat.value = + !_settingController.enabledImageChat.value; })) ], ) diff --git a/lib/service/openai.dart b/lib/service/openai.dart index fe5eb49..d07b256 100644 --- a/lib/service/openai.dart +++ b/lib/service/openai.dart @@ -7,17 +7,20 @@ import 'package:flutter/cupertino.dart'; import '../model/message.dart'; class OpenAIService { - void init({required String api, required String key}) { OpenAI.apiKey = key; OpenAI.baseUrl = api; + OpenAI.showLogs = true; + OpenAI.showResponsesLogs = true; + OpenAI.requestsTimeOut = const Duration(seconds: 60); } // 综合绘图+对话 Future chat( {required List messages, - required String model, - required String drawModel, + required String chatModel, + required String imageModel, + required bool isImageChat, required double temperature, required double topP, required double presencePenalty, @@ -25,74 +28,87 @@ class OpenAIService { required Function onDone, Function? onError, required ValueChanged resultBack}) async { - - + // 拷贝一份 List requestMessages = List.from(messages); - - // 获取用户问题 - int len = requestMessages.length-1; - - var prompt = ""; - while(len >= 0){ - if(requestMessages[len].role == OpenAIChatMessageRole.user){ - prompt = requestMessages[len].content; - break; - } - len--; - } - // 判断结果 - bool judgeResult = await judgeDraw(model, prompt); - log("画图判断:$prompt,判断结果:$judgeResult",); - - if (judgeResult) { - resultBack("图片生成中..."); - var imageUrl = await generateImage(drawModel, prompt); - if(!imageUrl.startsWith("http")){ - resultBack(imageUrl); - return; - } - log(imageUrl);// 小狗狗照片 - String response = "![]($imageUrl)"; - resultBack(response); - // var promptTemplate = - // """ - // 我已经为你提供了图片信息: - // --- - // 图片描述:$prompt - // 图片地址:$imageUrl - // --- - // 你可以使用MarkDown格式回复图片的URL,例如:![]($imageUrl)。 - // 注意,你的回复内容需要表现得天生就知道这些图片信息一样并且你需要保证图片能够根据Markdown语法正常渲染,例如你在给出图片时,需要换行。 - // 以下是你回复时的参考案例(图片描述:请给我一张小狗狗的图片,图片地址:https://img.example.com/dog.png): - // ----- - // 小狗狗是人类的好朋友,并且非常可爱,我现在为你提供小狗狗的图片: - // - // ![请给我一张小狗狗的图片](https://img.example.com/dog.png) - // ----- - // 你在给出图片前,需要对图片描述进行一些扩展说明并且一定不要把图片地址弄错,必须提供的地址保持一致。 - // """; - // print("图片地址:$imageUrl"); - // requestMessages.insert(requestMessages.length-2,Message(content: promptTemplate, role: OpenAIChatMessageRole.system, historyId: "")); - // // 金发女郎的图片 - // await streamChat( - // messages: requestMessages, - // model: model, - // temperature: temperature, - // topP: topP, - // presencePenalty: presencePenalty, - // frequencyPenalty: frequencyPenalty, - // onDone: onDone, - // resultBack: resultBack); - } else { + // 根据是否开启了图片生成对话来执行 + if (!isImageChat) { await streamChat( messages: requestMessages, - model: model, + model: chatModel, temperature: temperature, topP: topP, presencePenalty: presencePenalty, frequencyPenalty: frequencyPenalty, onDone: onDone, resultBack: resultBack); + } else { + // 获取用户问题 + int len = requestMessages.length - 1; + var prompt = ""; + while (len >= 0) { + if (requestMessages[len].role == OpenAIChatMessageRole.user) { + prompt = requestMessages[len].content; + break; + } + len--; + } + // 判断结果 + bool judgeResult = await judgeDraw(chatModel, prompt); + log( + "画图判断:$prompt,判断结果:$judgeResult", + ); + + if (judgeResult) { + resultBack("图片生成中..."); + var imageBase64 = await generateImage(imageModel, prompt); + if (imageBase64.startsWith("ERR")) { + resultBack(imageBase64); + return; + } + log(imageBase64); + String response = + "### 图片生成成功\n\n![$prompt](data:image/png;base64,$imageBase64)\n"; + resultBack(response); + // var promptTemplate =// + // """ + // 我已经为你提供了图片信息: + // --- + // 图片描述:$prompt + // 图片地址:$imageUrl + // --- + // 你可以使用MarkDown格式回复图片的URL,例如:![]($imageUrl)。 + // 注意,你的回复内容需要表现得天生就知道这些图片信息一样并且你需要保证图片能够根据Markdown语法正常渲染,例如你在给出图片时,需要换行。 + // 以下是你回复时的参考案例(图片描述:请给我一张小狗狗的图片,图片地址:https://img.example.com/dog.png): + // ----- + // 小狗狗是人类的好朋友,并且非常可爱,我现在为你提供小狗狗的图片: + // + // ![请给我一张小狗狗的图片](https://img.example.com/dog.png) + // ----- + // 你在给出图片前,需要对图片描述进行一些扩展说明并且一定不要把图片地址弄错,必须提供的地址保持一致。 + // """; + // print("图片地址:$imageUrl"); + // requestMessages.insert(requestMessages.length-2,Message(content: promptTemplate, role: OpenAIChatMessageRole.system, historyId: "")); + // + // await streamChat( + // messages: requestMessages, + // model: model, + // temperature: temperature, + // topP: topP, + // presencePenalty: presencePenalty, + // frequencyPenalty: frequencyPenalty, + // onDone: onDone, + // resultBack: resultBack); + } else { + await streamChat( + messages: requestMessages, + model: chatModel, + temperature: temperature, + topP: topP, + presencePenalty: presencePenalty, + frequencyPenalty: frequencyPenalty, + onDone: onDone, + resultBack: resultBack); + } } } @@ -108,56 +124,67 @@ class OpenAIService { Function? onError, required ValueChanged resultBack}) async { var chatMessages = transformMessage(messages); - final chatStream = OpenAI.instance.chat.createStream( - model: model, - messages: chatMessages, - temperature: temperature, - topP: topP, - presencePenalty: presencePenalty, - frequencyPenalty: frequencyPenalty); - final completer = Completer(); + try { + final chatStream = OpenAI.instance.chat.createStream( + model: model, + messages: chatMessages, + temperature: temperature, + topP: topP, + presencePenalty: presencePenalty, + frequencyPenalty: frequencyPenalty); + final completer = Completer(); - var assistantMessage = ""; - chatStream.listen((event) { - var content = event.choices.first.delta.content; - if (content != null && !completer.isCompleted) { - String subText = content.first!.text!; - assistantMessage = assistantMessage + subText; - resultBack(assistantMessage); - } else { + var assistantMessage = ""; + chatStream.listen((event) { + var content = event.choices.first.delta.content; + if (content != null && !completer.isCompleted) { + String subText = content.first!.text!; + assistantMessage = assistantMessage + subText; + resultBack(assistantMessage); + } else { + if (!completer.isCompleted) { + completer.complete(true); + assistantMessage = ""; + } + } + }, onDone: () { + onDone(); if (!completer.isCompleted) { completer.complete(true); assistantMessage = ""; } - } - }, onDone: () { - onDone(); - if (!completer.isCompleted) { - completer.complete(true); - assistantMessage = ""; - } - }, onError: (err) { - resultBack(err.toString()); - if (!completer.isCompleted) { - completer.complete(true); - assistantMessage = ""; - } - }); - await completer.future; + }, onError: (err) { + resultBack(err.toString()); + if (!completer.isCompleted) { + completer.complete(true); + assistantMessage = ""; + } + }); + await completer.future; + } catch (e) { + resultBack(e.toString()); + return; + } } // 绘图 Future generateImage(String model, String prompt) async { + log(model); try { - var imageResponse = - await OpenAI.instance.image.create(prompt: prompt, model: model,n: 1,size: OpenAIImageSize.size1024,responseFormat: OpenAIImageResponseFormat.url); - var imageUrl = imageResponse.data.first.url; - if (imageUrl != null) { - return imageUrl; + var imageResponse = await OpenAI.instance.image.create( + prompt: prompt, + model: model, + n: 1, + size: OpenAIImageSize.size1024, + responseFormat: OpenAIImageResponseFormat.b64Json); + log(imageResponse.toString()); + var imageBase64 = imageResponse.data.first.b64Json; + if (imageBase64 != null) { + return imageBase64; } - return imageResponse.toString(); + return "ERR:${imageResponse.toString()}"; } catch (e) { - return e.toString(); + return "ERR:${e.toString()}"; } } @@ -165,8 +192,14 @@ class OpenAIService { Future judgeDraw(String model, String prompt) async { final judgeDrawPrompt = """Does this message want to generate an AI picture, image, art or anything similar? $prompt . Simply answer with a yes or no."""; - var message = OpenAIChatCompletionChoiceMessageModel(role: OpenAIChatMessageRole.user, content: [OpenAIChatCompletionChoiceMessageContentItemModel.text(judgeDrawPrompt)]); - var judgeResponse = await OpenAI.instance.chat.create(model: model, messages: [message]); + var message = OpenAIChatCompletionChoiceMessageModel( + role: OpenAIChatMessageRole.user, + content: [ + OpenAIChatCompletionChoiceMessageContentItemModel.text( + judgeDrawPrompt) + ]); + var judgeResponse = + await OpenAI.instance.chat.create(model: model, messages: [message]); var judgeResult = judgeResponse.choices.first.message.content?.first.text; switch (judgeResult?.toLowerCase()) { case "yes": @@ -177,7 +210,7 @@ class OpenAIService { return false; } - // 将Message List转换为 + // 将Message List转换 List transformMessage( List messages) { List chatMessages = [];