From 9e2ca319de07f90553e7b55f5ac98a23f5c257e5 Mon Sep 17 00:00:00 2001
From: skythinker
Date: Mon, 4 Dec 2023 18:00:48 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9EGPT=20Vision=E6=94=AF?=
=?UTF-8?q?=E6=8C=81=EF=BC=8C=E6=94=AF=E6=8C=81=E6=8B=8D=E7=85=A7=E3=80=81?=
=?UTF-8?q?=E5=9B=BE=E5=BA=93=E3=80=81=E5=88=86=E4=BA=AB=EF=BC=9B=E4=B8=BB?=
=?UTF-8?q?=E7=95=8C=E9=9D=A2=E6=B7=BB=E5=8A=A0=E6=A8=A1=E5=9E=8B=E9=80=89?=
=?UTF-8?q?=E6=A1=86=EF=BC=9B=E5=85=81=E8=AE=B8=E8=87=AA=E5=AE=9A=E4=B9=89?=
=?UTF-8?q?=E5=A4=9A=E4=B8=AA=E6=A8=A1=E5=9E=8B=EF=BC=9B=E9=87=8D=E6=9E=84?=
=?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=B6=88=E6=81=AF=E7=9B=B8=E5=85=B3=E9=80=BB?=
=?UTF-8?q?=E8=BE=91=EF=BC=9B=E4=BC=98=E5=8C=96=E9=83=A8=E5=88=86=E7=95=8C?=
=?UTF-8?q?=E9=9D=A2=EF=BC=9B=E7=89=88=E6=9C=AC=E5=8F=B7=E4=BF=AE=E6=94=B9?=
=?UTF-8?q?=E4=B8=BA1.7.0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/deploymentTargetDropDown.xml | 17 ++
.idea/misc.xml | 5 +
README.md | 45 ++-
app/build.gradle | 4 +-
app/src/main/AndroidManifest.xml | 19 +-
.../gptassistant/ChatApiClient.java | 150 +++++++---
.../gptassistant/GlobalDataHolder.java | 25 +-
.../skythinker/gptassistant/MainActivity.java | 278 +++++++++++++++---
.../gptassistant/TabConfActivity.java | 58 +++-
app/src/main/res/drawable/image.png | Bin 0 -> 4827 bytes
app/src/main/res/drawable/image_enabled.png | Bin 0 -> 4542 bytes
app/src/main/res/layout/activity_main.xml | 75 +++--
app/src/main/res/layout/activity_tab_conf.xml | 74 ++++-
.../res/layout/ask_accessibility_dialog.xml | 4 +-
.../main/res/layout/image_method_dialog.xml | 55 ++++
.../main/res/layout/image_preview_dialog.xml | 98 ++++++
.../res/layout/main_model_spinner_item.xml | 11 +
.../layout/model_spinner_dropdown_item.xml | 9 +
app/src/main/res/values/strings.xml | 1 -
app/src/main/res/xml/file_provider_paths.xml | 5 +
readme_img/vision.gif | Bin 0 -> 8770418 bytes
21 files changed, 794 insertions(+), 139 deletions(-)
create mode 100644 .idea/deploymentTargetDropDown.xml
create mode 100644 app/src/main/res/drawable/image.png
create mode 100644 app/src/main/res/drawable/image_enabled.png
create mode 100644 app/src/main/res/layout/image_method_dialog.xml
create mode 100644 app/src/main/res/layout/image_preview_dialog.xml
create mode 100644 app/src/main/res/layout/main_model_spinner_item.xml
create mode 100644 app/src/main/res/layout/model_spinner_dropdown_item.xml
create mode 100644 app/src/main/res/xml/file_provider_paths.xml
create mode 100644 readme_img/vision.gif
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..e016282
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index c9463f4..0bc5797 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -9,6 +9,7 @@
+
@@ -16,6 +17,10 @@
+
+
+
+
diff --git a/README.md b/README.md
index 61b5821..331e436 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
- 国内可用 · 免费使用 · 语音交互 · 支持联网
+ 免费聊天 · 语音交互 · 支持联网 · 支持识图
@@ -27,8 +27,6 @@
-
-
---
## 介绍
@@ -37,6 +35,7 @@
- 支持用户预设**问题模板**,支持**连续对话**,支持`gpt-3.5-turbo`、`gpt-4`等模型
- **支持联网**,允许GPT获取在线网页
+- 支持拍照或从相册中**上传图片**到GPT Vision模型
- 通过无障碍功能捕获音量键事件,实现在**任意界面唤起**
- 支持从**全局上下文菜单**(选中文本后弹出的系统菜单)中直接唤起
- 支持通过状态栏**快捷按钮**唤起
@@ -100,13 +99,25 @@
**四、支持连续对话**
-激活上方的对话图标,即可保留当前会话,进行连续对话
+激活上方的对话图标,即可保留当前会话,进行连续对话(点击左侧的头像图标可以对单条对话进行删除、重试等操作)
-**五、支持GPT联网**
+**五、支持上传图片到Vision**
+
+当选择的模型中含有`vision`时(如`gpt-4-vision-preview`),输入框左侧会出现图片按钮,点击后可以拍照或从相册中选择图片
+
+从其他应用中分享图片时,也可以选择本程序,将图片添加到输入框
+
+
+
![](readme_img/vision.gif)
+
+
+> 注:Vision模型一般无法免费使用(如Chatanywhere),有需要的用户可以考虑付费服务
+
+**六、支持GPT联网**
本程序实现了OpenAI的Function接口,允许GPT发起联网请求,程序会向GPT自动返回所需的网页数据,使GPT具有联网能力(需先在设置中开启联网选项)
@@ -120,8 +131,10 @@
> 注1:上图均为使用`gpt-3.5-turbo`模型的测试结果,建议在提问前加入“百度搜索”、“在线获取”、“从xxx获取”等字样引导GPT,以获得更好的联网效果
-
+>
> 注2:由于需要将网页内容发送给GPT,联网时会产生大量Token消耗,`gpt-4`模型请谨慎使用
+>
+> 注3:`gpt-4-vision-preview`模型暂不支持联网
---
@@ -211,6 +224,10 @@ A: 网页加载超时(15s)、需要登录、需要验证等原因都可能导致
### 其他问题
+**Q: 为什么列表中没有我需要的模型?**
+
+A: 软件仅内置了少数常用模型,你可以在设置中添加自定义模型(以英文分号分隔),添加后即会出现在列表中
+
**Q: GPT返回的内容中表格和图片无法正常显示?**
A: 所使用的Markdown渲染器无法在测试中产生稳定的结果,因此暂不支持表格和图片
@@ -227,12 +244,13 @@ A: 排除网络因素,该错误一般由OpenAI接口产生,可能由于其
- **2023.09.13** 支持连续对话、GPT-4、百度长语音识别,上下文菜单唤起
- **2023.10.06** 添加华为HMS语音识别
- **2023.11.06** 添加联网功能
+- **2023.12.04** 添加Vision识图功能
---
## TODO
-- 对话保存功能、删除部分对话功能
+- 保存对话功能
- 支持渲染Markdown表格、图片等
- 允许GPT控制手机其他功能
@@ -244,13 +262,14 @@ A: 排除网络因素,该错误一般由OpenAI接口产生,可能由于其
| 机型 | 系统版本 | Android 版本 | 本程序版本 |
| :--: | :-----: | :----------: | :-------: |
-| 荣耀 7C | EMUI 8.0.0 | Android 8 | 1.6.1 |
-| 荣耀 20 | HarmonyOS 3.0.0 | Android 10 | 1.6.1 |
-| 华为 Mate 30 | HarmonyOS 3.0.0 | Android 12 | 1.4.0 |
+| 荣耀 7C | EMUI 8.0.0 | Android 8 | 1.7.0 |
+| 荣耀 20 | HarmonyOS 3.0.0 | Android 10 | 1.7.0 |
+| 华为 Mate 30 | HarmonyOS 3.0.0 | Android 12 | 1.6.0 |
+| 华为 Mate 30 | HarmonyOS 4.0 | Android 12 | 1.7.0 |
| 荣耀 Magic 4 | MagicOS 7.0 | Android 13 | 1.2.0 |
| 红米 K20 Pro | MIUI 12.5.6 | Android 11 | 1.5.0 |
-| 红米 K60 Pro | MIUI 14.0.23 | Android 13 | 1.6.0 |
-| Pixel 2 (模拟器) | Android 12 | Android 12 | 1.2.0 |
+| 红米 K60 Pro | MIUI 14.0.23 | Android 13 | 1.7.0 |
+| Pixel 2 (模拟器) | Android 12 | Android 12 | 1.7.0 |
---
@@ -262,7 +281,7 @@ A: 排除网络因素,该错误一般由OpenAI接口产生,可能由于其
## 隐私说明
-本程序不会以任何方式收集用户的个人信息,语音输入会直接发送给华为或百度API,提问会直接发送给OpenAI API,不会经过任何中间服务器
+本程序不会以任何方式收集用户的个人信息,语音输入会直接发送给华为或百度API,提问会直接发送给OpenAI API,不会经过其他中间服务器
---
diff --git a/app/build.gradle b/app/build.gradle
index 52df536..f0f6d55 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -11,7 +11,7 @@ android {
minSdk 26
targetSdk 32
versionCode 1
- versionName "1.6.2"
+ versionName "1.7.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -57,6 +57,6 @@ dependencies {
implementation"io.noties.markwon:linkify:$markwon_version"
implementation"io.noties.markwon:syntax-highlight:$markwon_version"
annotationProcessor 'io.noties:prism4j-bundler:2.0.0'
- implementation group: 'com.unfbx', name: 'chatgpt-java', version: '1.1.0'
+ implementation group: 'com.unfbx', name: 'chatgpt-java', version: '1.1.3'
implementation 'com.huawei.hms:ml-computer-voice-asr:3.12.0.301'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 72f4069..47b32a6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -70,12 +70,29 @@
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/skythinker/gptassistant/ChatApiClient.java b/app/src/main/java/com/skythinker/gptassistant/ChatApiClient.java
index 97e30f3..5610ac0 100644
--- a/app/src/main/java/com/skythinker/gptassistant/ChatApiClient.java
+++ b/app/src/main/java/com/skythinker/gptassistant/ChatApiClient.java
@@ -6,10 +6,15 @@
import androidx.annotation.Nullable;
import com.unfbx.chatgpt.OpenAiStreamClient;
+import com.unfbx.chatgpt.entity.chat.BaseChatCompletion;
+import com.unfbx.chatgpt.entity.chat.ChatCompletionWithPicture;
+import com.unfbx.chatgpt.entity.chat.Content;
import com.unfbx.chatgpt.entity.chat.FunctionCall;
import com.unfbx.chatgpt.entity.chat.Functions;
+import com.unfbx.chatgpt.entity.chat.ImageUrl;
import com.unfbx.chatgpt.entity.chat.Message;
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
+import com.unfbx.chatgpt.entity.chat.MessagePicture;
import com.unfbx.chatgpt.entity.chat.Parameters;
import java.io.IOException;
@@ -39,6 +44,28 @@ public enum ChatRole {
FUNCTION
}
+ public static class ChatMessage {
+ public ChatRole role;
+ public String contentText;
+ public String contentImageBase64;
+ public String functionName;
+ public ChatMessage(ChatRole role) {
+ this.role = role;
+ }
+ public ChatMessage setText(String text) {
+ this.contentText = text;
+ return this;
+ }
+ public ChatMessage setImage(String base64) {
+ this.contentImageBase64 = base64;
+ return this;
+ }
+ public ChatMessage setFunction(String name) {
+ this.functionName = name;
+ return this;
+ }
+ }
+
String url = "";
String apiKey = "";
String model = "";
@@ -63,19 +90,19 @@ public ChatApiClient(String url, String apiKey, String model, OnReceiveListener
setApiInfo(url, apiKey);
}
- public void sendPrompt(String systemPrompt, String userPrompt) {
- if(systemPrompt == null && userPrompt == null) {
- listener.onError("模板和问题内容均为空");
- return;
- }
-
- sendPromptList(Arrays.asList(
- Pair.create(ChatRole.SYSTEM, systemPrompt),
- Pair.create(ChatRole.USER, userPrompt)
- ));
- }
+// public void sendPrompt(String systemPrompt, String userPrompt) {
+// if(systemPrompt == null && userPrompt == null) {
+// listener.onError("模板和问题内容均为空");
+// return;
+// }
+//
+// sendPromptList(Arrays.asList(
+// Pair.create(ChatRole.SYSTEM, systemPrompt),
+// Pair.create(ChatRole.USER, userPrompt)
+// ));
+// }
- public void sendPromptList(List> promptList) {
+ public void sendPromptList(List promptList) {
if(url.isEmpty()) {
listener.onError("请在设置中填写服务器地址");
return;
@@ -87,44 +114,75 @@ public void sendPromptList(List> promptList) {
return;
}
- ArrayList messages = new ArrayList<>();
- for(Pair prompt : promptList) {
- if(prompt.first == ChatRole.SYSTEM) {
- messages.add(Message.builder().role(Message.Role.SYSTEM).content(prompt.second).build());
- } else if(prompt.first == ChatRole.USER) {
- messages.add(Message.builder().role(Message.Role.USER).content(prompt.second).build());
- } else if(prompt.first == ChatRole.ASSISTANT) {
- if(prompt.second.startsWith("[Function]")) {
- int sepIndex = prompt.second.indexOf("\n");
- String funcName = prompt.second.substring("[Function]".length(), sepIndex);
- String arguments = prompt.second.substring(sepIndex + 1);
- FunctionCall functionCall = FunctionCall.builder()
- .name(funcName)
- .arguments(arguments)
- .build();
- messages.add(Message.builder().role(Message.Role.ASSISTANT).functionCall(functionCall).build());
- } else {
- messages.add(Message.builder().role(Message.Role.ASSISTANT).content(prompt.second).build());
+ BaseChatCompletion chatCompletion = null;
+
+ if(!model.contains("vision")) {
+ ArrayList messageList = new ArrayList<>();
+ for (ChatMessage message : promptList) {
+ if (message.role == ChatRole.SYSTEM) {
+ messageList.add(Message.builder().role(Message.Role.SYSTEM).content(message.contentText).build());
+ } else if (message.role == ChatRole.USER) {
+ messageList.add(Message.builder().role(Message.Role.USER).content(message.contentText).build());
+ } else if (message.role == ChatRole.ASSISTANT) {
+ if (message.functionName != null) {
+ FunctionCall functionCall = FunctionCall.builder()
+ .name(message.functionName)
+ .arguments(message.contentText)
+ .build();
+ messageList.add(Message.builder().role(Message.Role.ASSISTANT).functionCall(functionCall).build());
+ } else {
+ messageList.add(Message.builder().role(Message.Role.ASSISTANT).content(message.contentText).build());
+ }
+ } else if (message.role == ChatRole.FUNCTION) {
+ messageList.add(Message.builder().role(Message.Role.FUNCTION).name(message.functionName).content(message.contentText).build());
}
- } else if(prompt.first == ChatRole.FUNCTION) {
- int sepIndex = prompt.second.indexOf("\n");
- String funcName = prompt.second.substring(0, sepIndex);
- String reply = prompt.second.substring(sepIndex + 1);
- messages.add(Message.builder().role(Message.Role.FUNCTION).name(funcName).content(reply).build());
}
- }
- ChatCompletion chatCompletion;
- if(!functions.isEmpty()) {
- chatCompletion = ChatCompletion.builder()
- .messages(messages)
- .model(model)
- .functions(functions)
- .functionCall("auto")
- .build();
+ if (!functions.isEmpty()) {
+ chatCompletion = ChatCompletion.builder()
+ .messages(messageList)
+ .model(model)
+ .functions(functions)
+ .functionCall("auto")
+ .build();
+ } else {
+ chatCompletion = ChatCompletion.builder()
+ .messages(messageList)
+ .model(model)
+ .build();
+ }
} else {
- chatCompletion = ChatCompletion.builder()
- .messages(messages)
+ ArrayList messageList = new ArrayList<>();
+ for (ChatMessage message : promptList) {
+ List contentList = new ArrayList<>();
+ if (message.contentText != null) {
+ contentList.add(Content.builder().type(Content.Type.TEXT.getName()).text(message.contentText).build());
+ }
+ if(message.contentImageBase64 != null) {
+ ImageUrl imageUrl = ImageUrl.builder().url("data:image/jpeg;base64," + message.contentImageBase64).build();
+ contentList.add(Content.builder().type(Content.Type.IMAGE_URL.getName()).imageUrl(imageUrl).build());
+ }
+ if (message.role == ChatRole.SYSTEM) {
+ messageList.add(MessagePicture.builder().role(Message.Role.SYSTEM).content(contentList).build());
+ } else if (message.role == ChatRole.USER) {
+ messageList.add(MessagePicture.builder().role(Message.Role.USER).content(contentList).build());
+ } else if (message.role == ChatRole.ASSISTANT) {
+ if (message.functionName != null) {
+ FunctionCall functionCall = FunctionCall.builder()
+ .name(message.functionName)
+ .arguments(message.contentText)
+ .build();
+ messageList.add(MessagePicture.builder().role(Message.Role.ASSISTANT).functionCall(functionCall).build());
+ } else {
+ messageList.add(MessagePicture.builder().role(Message.Role.ASSISTANT).content(contentList).build());
+ }
+ } else if (message.role == ChatRole.FUNCTION) {
+ messageList.add(MessagePicture.builder().role(Message.Role.FUNCTION).name(message.functionName).content(contentList).build());
+ }
+ }
+
+ chatCompletion = ChatCompletionWithPicture.builder()
+ .messages(messageList)
.model(model)
.build();
}
diff --git a/app/src/main/java/com/skythinker/gptassistant/GlobalDataHolder.java b/app/src/main/java/com/skythinker/gptassistant/GlobalDataHolder.java
index fd2e148..8a8deda 100644
--- a/app/src/main/java/com/skythinker/gptassistant/GlobalDataHolder.java
+++ b/app/src/main/java/com/skythinker/gptassistant/GlobalDataHolder.java
@@ -11,6 +11,7 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
public class GlobalDataHolder {
@@ -23,6 +24,7 @@ public class GlobalDataHolder {
private static String gptApiHost;
private static String gptApiKey;
private static String gptModel;
+ private static List customModels = null;
private static boolean checkAccessOnStart;
private static boolean defaultEnableTts;
private static boolean defaultEnableMultiChat;
@@ -30,6 +32,7 @@ public class GlobalDataHolder {
private static boolean enableInternetAccess;
private static int webMaxCharCount;
private static boolean onlyLatestWebResult;
+ private static boolean limitVisionSize;
private static SharedPreferences sp = null;
public static void init(Context context) {
@@ -46,6 +49,7 @@ public static void init(Context context) {
loadMultiChatSetting();
loadSelectedTab();
loadFunctionSetting();
+ loadVisionSetting();
}
public static List getTabDataList() {
@@ -109,16 +113,20 @@ public static void loadGptApiInfo() {
gptApiHost = sp.getString("gpt_api_host", "");
gptApiKey = sp.getString("gpt_api_key", "");
gptModel = sp.getString("gpt_model", "gpt-3.5-turbo");
+ customModels = new ArrayList<>(Arrays.asList(sp.getString("custom_models", "gpt-4-1106-preview;gpt-4-vision-preview").split(";")));
+ customModels.removeIf(String::isEmpty);
}
- public static void saveGptApiInfo(String host, String key, String model) {
+ public static void saveGptApiInfo(String host, String key, String model, List customModelList) {
gptApiHost = host;
gptApiKey = key;
gptModel = model;
+ customModels = customModelList;
SharedPreferences.Editor editor = sp.edit();
editor.putString("gpt_api_host", gptApiHost);
editor.putString("gpt_api_key", gptApiKey);
editor.putString("gpt_model", gptModel);
+ editor.putString("custom_models", String.join(";", customModels));
editor.apply();
}
@@ -183,6 +191,17 @@ public static void saveFunctionSetting(boolean enableInternet, int maxCharCount,
editor.apply();
}
+ public static void loadVisionSetting() {
+ limitVisionSize = sp.getBoolean("limit_vision_size", false);
+ }
+
+ public static void saveVisionSetting(boolean limitSize) {
+ limitVisionSize = limitSize;
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean("limit_vision_size", limitVisionSize);
+ editor.apply();
+ }
+
public static boolean getAsrUseBaidu() { return asrUseBaidu; }
public static String getAsrAppId() { return asrAppId; }
@@ -199,6 +218,8 @@ public static void saveFunctionSetting(boolean enableInternet, int maxCharCount,
public static String getGptModel() { return gptModel; }
+ public static List getCustomModels() { return customModels; }
+
public static boolean getCheckAccessOnStart() { return checkAccessOnStart; }
public static boolean getDefaultEnableTts() { return defaultEnableTts; }
@@ -212,4 +233,6 @@ public static void saveFunctionSetting(boolean enableInternet, int maxCharCount,
public static int getWebMaxCharCount() { return webMaxCharCount; }
public static boolean getOnlyLatestWebResult() { return onlyLatestWebResult; }
+
+ public static boolean getLimitVisionSize() { return limitVisionSize; }
}
diff --git a/app/src/main/java/com/skythinker/gptassistant/MainActivity.java b/app/src/main/java/com/skythinker/gptassistant/MainActivity.java
index 128e386..580493e 100644
--- a/app/src/main/java/com/skythinker/gptassistant/MainActivity.java
+++ b/app/src/main/java/com/skythinker/gptassistant/MainActivity.java
@@ -12,22 +12,30 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.PaintDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.provider.MediaStore;
import android.speech.tts.TextToSpeech;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
+import android.text.style.ImageSpan;
+import android.util.Base64;
import android.util.Log;
-import android.util.Pair;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
@@ -35,16 +43,24 @@
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.ScrollView;
+import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
+import androidx.core.content.FileProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
import java.net.URLDecoder;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@@ -61,6 +77,7 @@
import io.noties.prism4j.annotations.PrismBundle;
import com.skythinker.gptassistant.ChatApiClient.ChatRole;
+import com.skythinker.gptassistant.ChatApiClient.ChatMessage;
@SuppressLint({"UseCompatLoadingForDrawables", "JavascriptInterface", "SetTextI18n"})
@PrismBundle(includeAll = true)
@@ -69,7 +86,8 @@ public class MainActivity extends Activity {
private int selectedTab = 0;
private TextView tvGptReply;
private EditText etUserInput;
- private ImageButton btSend;
+ private ImageButton btSend, btImage;
+ private Spinner spModels;
private ScrollView svChatArea;
private LinearLayout llChatList;
private Handler handler = new Handler();
@@ -95,13 +113,16 @@ public class MainActivity extends Activity {
private int ttsSentenceEndIndex = 0;
private boolean multiChat = false;
- private List> multiChatList = new ArrayList<>();
+ private List multiChatList = new ArrayList<>();
AsrClientBase asrClient = null;
AsrClientBase.IAsrCallback asrCallback = null;
WebScraper webScraper = null;
+ Bitmap selectedImageBitmap = null;
+ Uri photoUri = null;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -143,21 +164,44 @@ protected void onCreate(Bundle savedInstanceState) {
tvGptReply.setMovementMethod(LinkMovementMethod.getInstance());
etUserInput = findViewById(R.id.et_user_input);
+ btSend = findViewById(R.id.bt_send);
+ btImage = findViewById(R.id.bt_image);
+ svChatArea = findViewById(R.id.sv_chat_list);
+ llChatList = findViewById(R.id.ll_chat_list);
+
Intent activityIntent = getIntent();
if(activityIntent != null){
String action = activityIntent.getAction();
- if(action != null && action.equals("android.intent.action.PROCESS_TEXT")){
+ if(Intent.ACTION_PROCESS_TEXT.equals(action)) {
String text = activityIntent.getStringExtra(Intent.EXTRA_PROCESS_TEXT);
if(text != null){
etUserInput.setText(text);
}
+ } else if(Intent.ACTION_SEND.equals(action)) {
+ String type = activityIntent.getType();
+ if(type != null && type.startsWith("image/")) {
+ Uri imageUri = activityIntent.getParcelableExtra(Intent.EXTRA_STREAM);
+ if(imageUri != null) {
+ try {
+ Bitmap bitmap = (Bitmap) BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
+ selectedImageBitmap = bitmap;
+ if(GlobalDataHolder.getLimitVisionSize()) {
+ if (bitmap.getWidth() < bitmap.getHeight())
+ selectedImageBitmap = resizeBitmap(bitmap, 512, 2048);
+ else
+ selectedImageBitmap = resizeBitmap(bitmap, 2048, 512);
+ }
+ btImage.setImageResource(R.drawable.image_enabled);
+ if(!GlobalDataHolder.getGptModel().contains("vision"))
+ Toast.makeText(this, "请选择支持vision的模型以发送图片", Toast.LENGTH_LONG).show();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+ }
}
}
- btSend = findViewById(R.id.bt_send);
- svChatArea = findViewById(R.id.sv_chat_list);
- llChatList = findViewById(R.id.ll_chat_list);
-
if(GlobalDataHolder.getSelectedTab() != -1 && GlobalDataHolder.getSelectedTab() < GlobalDataHolder.getTabDataList().size())
selectedTab = GlobalDataHolder.getSelectedTab();
updateTabListView();
@@ -217,25 +261,24 @@ public void onFinished(boolean completed) {
int referenceCount = 0;
if(completed) {
int questionIndex = multiChatList.size() - 1;
- while(questionIndex >= 0 && multiChatList.get(questionIndex).first != ChatRole.USER) {
+ while(questionIndex >= 0 && multiChatList.get(questionIndex).role != ChatRole.USER) {
questionIndex--;
}
for(int i = questionIndex + 1; i < multiChatList.size(); i++) {
- if(multiChatList.get(i).first == ChatRole.FUNCTION
- && multiChatList.get(i-1).first == ChatRole.ASSISTANT
- && multiChatList.get(i-1).second.startsWith("[Function]")) {
- String funcRequest = multiChatList.get(i-1).second;
- int sepIndex = funcRequest.indexOf("\n");
- String funcName = funcRequest.substring("[Function]".length(), sepIndex);
+ if(multiChatList.get(i).role == ChatRole.FUNCTION
+ && multiChatList.get(i-1).role == ChatRole.ASSISTANT
+ && multiChatList.get(i-1).functionName != null) {
+ String funcName = multiChatList.get(i-1).functionName;
+ String funcArgs = multiChatList.get(i-1).contentText;
if(funcName.equals("get_html_text")) {
- String url = new JSONObject(funcRequest.substring(sepIndex + 1)).getStr("url");
+ String url = new JSONObject(funcArgs).getStr("url");
referenceStr += String.format("[[%s]](%s) ", ++referenceCount, url);
}
}
}
}
try {
- multiChatList.add(new Pair<>(ChatRole.ASSISTANT, chatApiBuffer));
+ multiChatList.add(new ChatMessage(ChatRole.ASSISTANT).setText(chatApiBuffer));
((LinearLayout) tvGptReply.getParent()).setTag(multiChatList.get(multiChatList.size() - 1));
btSend.setImageResource(R.drawable.send_btn);
markwon.setMarkdown(tvGptReply, chatApiBuffer);
@@ -269,8 +312,7 @@ public void onError(String message) {
@Override
public void onFunctionCall(String name, String arg) {
Log.d("FunctionCall", String.format("%s: %s", name, arg));
- multiChatList.add(new Pair<>(ChatRole.ASSISTANT,
- String.format("[Function]%s\n%s", name, arg)));
+ multiChatList.add(new ChatMessage(ChatRole.ASSISTANT).setFunction(name).setText(arg));
if (name.equals("get_html_text")) {
try {
JSONObject argJson = new JSONObject(arg);
@@ -344,6 +386,51 @@ public void onLoadFail(String message) {
}
});
+ btImage.setOnClickListener(view -> {
+ if (selectedImageBitmap != null) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ LayoutInflater inflater = LayoutInflater.from(this);
+ View dialogView = inflater.inflate(R.layout.image_preview_dialog, null);
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ dialog.getWindow().setContentView(dialogView);
+ ((ImageView) dialogView.findViewById(R.id.iv_image_preview)).setImageBitmap(selectedImageBitmap);
+ ((TextView) dialogView.findViewById(R.id.tv_image_preview_size)).setText(String.format("%s x %s", selectedImageBitmap.getWidth(), selectedImageBitmap.getHeight()));
+ dialogView.findViewById(R.id.bt_image_preview_cancel).setOnClickListener(view1 -> dialog.dismiss());
+ dialogView.findViewById(R.id.bt_image_preview_del).setOnClickListener(view1 -> {
+ dialog.dismiss();
+ selectedImageBitmap = null;
+ btImage.setImageResource(R.drawable.image);
+ });
+ dialogView.findViewById(R.id.bt_image_preview_reselect).setOnClickListener(view1 -> {
+ dialogView.findViewById(R.id.bt_image_preview_del).performClick();
+ btImage.performClick();
+ });
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ LayoutInflater inflater = LayoutInflater.from(this);
+ View dialogView = inflater.inflate(R.layout.image_method_dialog, null);
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ dialog.getWindow().setContentView(dialogView);
+ dialogView.findViewById(R.id.bt_take_photo).setOnClickListener(view1 -> {
+ dialog.dismiss();
+ photoUri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider", new File(getCacheDir(), "photo.jpg"));
+ Intent intent=new Intent();
+ intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
+ startActivityForResult(intent, 1);
+ });
+ dialogView.findViewById(R.id.bt_select_from_album).setOnClickListener(view1 -> {
+ dialog.dismiss();
+ Intent intent = new Intent(Intent.ACTION_PICK);
+ intent.setType("image/*");
+ startActivityForResult(intent, 2);
+ });
+ dialogView.findViewById(R.id.bt_image_cancel).setOnClickListener(view1 -> dialog.dismiss());
+ }
+ });
+
etUserInput.setOnLongClickListener(view -> {
etUserInput.setText("");
return true;
@@ -406,6 +493,9 @@ public void onLoadFail(String message) {
((CardView) findViewById(R.id.cv_tts_off)).setForeground(getDrawable(R.drawable.tts_off_enable));
}
+ updateModelSpinner();
+ updateImageButtonVisible();
+
isAlive = true;
requestPermission();
@@ -520,6 +610,8 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
GlobalDataHolder.saveSelectedTab(selectedTab);
updateTabListView();
+ updateModelSpinner();
+ updateImageButtonVisible();
chatApiClient.setApiInfo(GlobalDataHolder.getGptApiHost(), GlobalDataHolder.getGptApiKey());
chatApiClient.setModel(GlobalDataHolder.getGptModel());
@@ -531,6 +623,21 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}
setFunctions();
+ } else if((requestCode == 1 || requestCode == 2) && resultCode == RESULT_OK) {
+ Uri uri = requestCode == 1 ? photoUri : data.getData();
+ try {
+ Bitmap bitmap = (Bitmap) BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
+ selectedImageBitmap = bitmap;
+ if(GlobalDataHolder.getLimitVisionSize()) {
+ if (bitmap.getWidth() < bitmap.getHeight())
+ selectedImageBitmap = resizeBitmap(bitmap, 512, 2048);
+ else
+ selectedImageBitmap = resizeBitmap(bitmap, 2048, 512);
+ }
+ btImage.setImageResource(R.drawable.image_enabled);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
}
}
@@ -543,6 +650,53 @@ private void scrollChatAreaToBottom() {
});
}
+ private void updateImageButtonVisible() {
+ if(GlobalDataHolder.getGptModel().contains("vision"))
+ btImage.setVisibility(View.VISIBLE);
+ else
+ btImage.setVisibility(View.GONE);
+ }
+
+ private void updateModelSpinner() {
+ Spinner spModels = findViewById(R.id.sp_main_model);
+ List models = new ArrayList<>(Arrays.asList(getResources().getStringArray(R.array.models)));
+ models.addAll(GlobalDataHolder.getCustomModels());
+ ArrayAdapter modelsAdapter = new ArrayAdapter(this, R.layout.main_model_spinner_item, models) {
+ @Override
+ public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ TextView tv = (TextView) super.getDropDownView(position, convertView, parent);
+ if(spModels.getSelectedItemPosition() == position) {
+ tv.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
+ tv.setBackgroundColor(ContextCompat.getColor(MainActivity.this, R.color.tag_background_unselected));
+ } else {
+ tv.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
+ tv.setBackgroundColor(Color.WHITE);
+ }
+ return tv;
+ }
+ };
+ modelsAdapter.setDropDownViewResource(R.layout.model_spinner_dropdown_item);
+ spModels.setAdapter(modelsAdapter);
+ spModels.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
+ GlobalDataHolder.saveGptApiInfo(GlobalDataHolder.getGptApiHost(), GlobalDataHolder.getGptApiKey(), adapterView.getItemAtPosition(i).toString(), GlobalDataHolder.getCustomModels());
+ chatApiClient.setModel(GlobalDataHolder.getGptModel());
+ updateImageButtonVisible();
+ modelsAdapter.notifyDataSetChanged();
+ }
+ public void onNothingSelected(AdapterView> adapterView) { }
+ });
+ for(int i = 0; i < modelsAdapter.getCount(); i++) {
+ if(modelsAdapter.getItem(i).equals(GlobalDataHolder.getGptModel())) {
+ spModels.setSelection(i);
+ break;
+ }
+ if(i == modelsAdapter.getCount() - 1) {
+ spModels.setSelection(0);
+ }
+ }
+ }
+
private void updateTabListView() {
LinearLayout tabList = findViewById(R.id.tabs_layout);
tabList.removeAllViews();
@@ -574,7 +728,7 @@ private void updateTabListView() {
}
}
- private LinearLayout addChatView(ChatRole role, String content) {
+ private LinearLayout addChatView(ChatRole role, String content, String imageBase64) {
ViewGroup.MarginLayoutParams iconParams = new ViewGroup.MarginLayoutParams(dpToPx(30), dpToPx(30));
iconParams.setMargins(dpToPx(4), dpToPx(12), dpToPx(4), dpToPx(12));
@@ -597,7 +751,18 @@ private LinearLayout addChatView(ChatRole role, String content) {
ivIcon.setLayoutParams(iconParams);
TextView tvContent = new TextView(this);
- tvContent.setText(content);
+ SpannableString spannableString = null;
+ if(imageBase64 != null) {
+ spannableString = new SpannableString(content + "\n ");
+ Bitmap bitmap = base64ToBitmap(imageBase64);
+ int maxSize = dpToPx(100);
+ bitmap = resizeBitmap(bitmap, maxSize, maxSize);
+ ImageSpan imageSpan = new ImageSpan(this, bitmap);
+ spannableString.setSpan(imageSpan, content.length()+1, content.length() + 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ spannableString = new SpannableString(content);
+ }
+ tvContent.setText(spannableString);
tvContent.setTextSize(16);
tvContent.setTextColor(Color.BLACK);
tvContent.setLayoutParams(contentParams);
@@ -618,12 +783,12 @@ private LinearLayout addChatView(ChatRole role, String content) {
cvDelete.setForeground(getDrawable(R.drawable.clear_btn));
cvDelete.setOnClickListener(view -> {
popupWindow.dismiss();
- Pair chat = (Pair) llOuter.getTag();
+ ChatMessage chat = (ChatMessage) llOuter.getTag();
if(chat != null) {
int index = multiChatList.indexOf(chat);
multiChatList.remove(chat);
- while(--index > 0 && (multiChatList.get(index).first == ChatRole.FUNCTION
- || multiChatList.get(index).second.startsWith("[Function]get_html_text")))
+ while(--index > 0 && (multiChatList.get(index).role == ChatRole.FUNCTION
+ || multiChatList.get(index).functionName != null && multiChatList.get(index).functionName.equals("get_html_text")))
multiChatList.remove(index);
}
if(tvContent == tvGptReply) {
@@ -654,7 +819,18 @@ private LinearLayout addChatView(ChatRole role, String content) {
cvEdit.setForeground(getDrawable(R.drawable.edit_btn));
cvEdit.setOnClickListener(view -> {
popupWindow.dismiss();
- etUserInput.setText(tvContent.getText().toString());
+ ChatMessage chat = (ChatMessage) llOuter.getTag();
+ String text = chat.contentText;
+ if(chat.contentImageBase64 != null) {
+ if(text.endsWith("\n "))
+ text = text.substring(0, text.length() - 2);
+ selectedImageBitmap = base64ToBitmap(chat.contentImageBase64);
+ btImage.setImageResource(R.drawable.image_enabled);
+ } else {
+ selectedImageBitmap = null;
+ btImage.setImageResource(R.drawable.image);
+ }
+ etUserInput.setText(text);
cvDelBelow.performClick();
});
llPopup.addView(cvEdit);
@@ -663,7 +839,15 @@ private LinearLayout addChatView(ChatRole role, String content) {
cvRetry.setForeground(getDrawable(R.drawable.retry_btn));
cvRetry.setOnClickListener(view -> {
popupWindow.dismiss();
- String text = tvContent.getText().toString();
+ ChatMessage chat = (ChatMessage) llOuter.getTag();
+ String text = chat.contentText;
+ if(chat.contentImageBase64 != null) {
+ if(text.endsWith("\n "))
+ text = text.substring(0, text.length() - 2);
+ selectedImageBitmap = base64ToBitmap(chat.contentImageBase64);
+ } else {
+ selectedImageBitmap = null;
+ }
cvDelBelow.performClick();
sendQuestion(text);
});
@@ -712,9 +896,17 @@ private void sendQuestion(String input){
if(!template.contains("%input%"))
template += "%input%";
String question = template.replace("%input%", userInput);
- multiChatList.add(new Pair<>(ChatRole.USER, question));
+ multiChatList.add(new ChatMessage(ChatRole.USER).setText(question));
}else {
- multiChatList.add(new Pair<>(ChatRole.USER, userInput));
+ multiChatList.add(new ChatMessage(ChatRole.USER).setText(userInput));
+ }
+
+ if(selectedImageBitmap != null) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ selectedImageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
+ byte[] bytes = baos.toByteArray();
+ String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
+ multiChatList.get(multiChatList.size() - 1).setImage(base64);
}
if(llChatList.getChildCount() > 0 && llChatList.getChildAt(0) instanceof TextView){
@@ -722,17 +914,17 @@ private void sendQuestion(String input){
}
if(multiChat && multiChatList.size() > 0 && llChatList.getChildCount() > 0){
- String firstUserInput = multiChatList.get(0).second;
+ String firstUserInput = multiChatList.get(0).contentText;
((TextView) ((LinearLayout) llChatList.getChildAt(0)).getChildAt(1)).setText(firstUserInput);
}
if(GlobalDataHolder.getOnlyLatestWebResult()) {
for (int i = 0; i < multiChatList.size(); i++) {
- Pair chatItem = multiChatList.get(i);
- if (chatItem.first == ChatRole.FUNCTION) {
+ ChatMessage chatItem = multiChatList.get(i);
+ if (chatItem.role == ChatRole.FUNCTION) {
multiChatList.remove(i);
i--;
- if(i > 0 && multiChatList.get(i).first == ChatRole.ASSISTANT) {
+ if(i > 0 && multiChatList.get(i).role == ChatRole.ASSISTANT) {
multiChatList.remove(i);
i--;
}
@@ -740,8 +932,8 @@ private void sendQuestion(String input){
}
}
- LinearLayout llInput = addChatView(ChatRole.USER, multiChat ? multiChatList.get(multiChatList.size() - 1).second : userInput);
- LinearLayout llReply = addChatView(ChatRole.ASSISTANT, "正在等待回复...");
+ LinearLayout llInput = addChatView(ChatRole.USER, multiChat ? multiChatList.get(multiChatList.size() - 1).contentText : userInput, multiChatList.get(multiChatList.size() - 1).contentImageBase64);
+ LinearLayout llReply = addChatView(ChatRole.ASSISTANT, "正在等待回复...", null);
llInput.setTag(multiChatList.get(multiChatList.size() - 1));
@@ -752,13 +944,15 @@ private void sendQuestion(String input){
chatApiBuffer = "";
ttsSentenceEndIndex = 0;
chatApiClient.sendPromptList(multiChatList);
+ btImage.setImageResource(R.drawable.image);
+ selectedImageBitmap = null;
btSend.setImageResource(R.drawable.cancel_btn);
}
private void postSendFunctionReply(String funcName, String reply) {
handler.post(() -> {
Log.d("FunctionCall", "postSendFunctionReply: " + funcName);
- multiChatList.add(new Pair<>(ChatRole.FUNCTION, String.format("%s\n%s", funcName, reply)));
+ multiChatList.add(new ChatMessage(ChatRole.FUNCTION).setFunction(funcName).setText(reply));
chatApiClient.sendPromptList(multiChatList);
});
}
@@ -767,6 +961,20 @@ private int dpToPx(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
+ private Bitmap resizeBitmap(Bitmap bitmap, int maxWidth, int maxHeight) {
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+ float scale = 1;
+ if(width > maxWidth || height > maxHeight)
+ scale = Math.min((float)maxWidth / width, (float)maxHeight / height);
+ return Bitmap.createScaledBitmap(bitmap, (int)(width * scale), (int)(height * scale), true);
+ }
+
+ private Bitmap base64ToBitmap(String base64) {
+ byte[] bytes = Base64.decode(base64, Base64.NO_WRAP);
+ return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+ }
+
public static boolean isAlive() {
return isAlive;
}
diff --git a/app/src/main/java/com/skythinker/gptassistant/TabConfActivity.java b/app/src/main/java/com/skythinker/gptassistant/TabConfActivity.java
index 635957b..6a19c77 100644
--- a/app/src/main/java/com/skythinker/gptassistant/TabConfActivity.java
+++ b/app/src/main/java/com/skythinker/gptassistant/TabConfActivity.java
@@ -1,5 +1,8 @@
package com.skythinker.gptassistant;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -13,6 +16,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
+import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -21,6 +25,7 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@@ -36,6 +41,7 @@
import org.json.JSONObject;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -116,7 +122,7 @@ public void afterTextChanged(Editable editable) {
host += "/";
}
}
- GlobalDataHolder.saveGptApiInfo(host, GlobalDataHolder.getGptApiKey(), GlobalDataHolder.getGptModel());
+ GlobalDataHolder.saveGptApiInfo(host, GlobalDataHolder.getGptApiKey(), GlobalDataHolder.getGptModel(), GlobalDataHolder.getCustomModels());
}
});
@@ -125,38 +131,57 @@ public void afterTextChanged(Editable editable) {
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
public void afterTextChanged(Editable editable) {
- GlobalDataHolder.saveGptApiInfo(GlobalDataHolder.getGptApiHost(), editable.toString().trim(), GlobalDataHolder.getGptModel());
+ GlobalDataHolder.saveGptApiInfo(GlobalDataHolder.getGptApiHost(), editable.toString().trim(), GlobalDataHolder.getGptModel(), GlobalDataHolder.getCustomModels());
}
});
- ArrayAdapter modelsAdapter = ArrayAdapter.createFromResource(this, R.array.models, R.layout.model_spinner_item);
- modelsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ List models = new ArrayList<>(Arrays.asList(getResources().getStringArray(R.array.models)));
+ models.addAll(GlobalDataHolder.getCustomModels());
+ ArrayAdapter modelsAdapter = new ArrayAdapter(this, R.layout.model_spinner_item, models) {
+ @Override
+ public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ TextView tv = (TextView) super.getDropDownView(position, convertView, parent);
+ if(((Spinner) findViewById(R.id.sp_model_conf)).getSelectedItemPosition() == position) {
+ tv.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
+ tv.setBackgroundColor(ContextCompat.getColor(TabConfActivity.this, R.color.tag_background_unselected));
+ } else {
+ tv.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
+ tv.setBackgroundColor(Color.WHITE);
+ }
+ return tv;
+ }
+ };
+ modelsAdapter.setDropDownViewResource(R.layout.model_spinner_dropdown_item);
((Spinner) findViewById(R.id.sp_model_conf)).setAdapter(modelsAdapter);
((Spinner) findViewById(R.id.sp_model_conf)).setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
- String model = adapterView.getItemAtPosition(i).toString();
- if(model.equals("自定义")) {
- ((LinearLayout) findViewById(R.id.et_custom_model_conf).getParent()).setVisibility(View.VISIBLE);
- ((EditText) findViewById(R.id.et_custom_model_conf)).setText(GlobalDataHolder.getGptModel());
- } else {
- ((LinearLayout) findViewById(R.id.et_custom_model_conf).getParent()).setVisibility(View.GONE);
- GlobalDataHolder.saveGptApiInfo(GlobalDataHolder.getGptApiHost(), GlobalDataHolder.getGptApiKey(), adapterView.getItemAtPosition(i).toString());
- }
+ GlobalDataHolder.saveGptApiInfo(GlobalDataHolder.getGptApiHost(), GlobalDataHolder.getGptApiKey(), adapterView.getItemAtPosition(i).toString(), GlobalDataHolder.getCustomModels());
+ modelsAdapter.notifyDataSetChanged();
}
public void onNothingSelected(AdapterView> adapterView) { }
});
for(int i = 0; i < modelsAdapter.getCount(); i++) {
- if(modelsAdapter.getItem(i).toString().equals(GlobalDataHolder.getGptModel()) || i == modelsAdapter.getCount() - 1) {
+ if(modelsAdapter.getItem(i).equals(GlobalDataHolder.getGptModel())) {
((Spinner) findViewById(R.id.sp_model_conf)).setSelection(i);
break;
}
+ if(i == modelsAdapter.getCount() - 1) {
+ ((Spinner) findViewById(R.id.sp_model_conf)).setSelection(0);
+ }
}
+ ((EditText) findViewById(R.id.et_custom_model_conf)).setText(String.join(";", GlobalDataHolder.getCustomModels()));
((EditText) findViewById(R.id.et_custom_model_conf)).addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
public void afterTextChanged(Editable editable) {
- GlobalDataHolder.saveGptApiInfo(GlobalDataHolder.getGptApiHost(), GlobalDataHolder.getGptApiKey(), editable.toString().trim());
+ List modelList = new ArrayList<>(Arrays.asList(editable.toString().trim().split(";")));
+ modelList.removeIf(String::isEmpty);
+ GlobalDataHolder.saveGptApiInfo(GlobalDataHolder.getGptApiHost(), GlobalDataHolder.getGptApiKey(), GlobalDataHolder.getGptModel(), modelList);
+ models.clear();
+ models.addAll(Arrays.asList(getResources().getStringArray(R.array.models)));
+ models.addAll(modelList);
+ modelsAdapter.notifyDataSetChanged();
}
});
@@ -223,6 +248,11 @@ public void afterTextChanged(Editable editable) {
}
});
+ ((Switch) findViewById(R.id.sw_limit_vision_size_conf)).setChecked(GlobalDataHolder.getLimitVisionSize());
+ ((Switch) findViewById(R.id.sw_limit_vision_size_conf)).setOnCheckedChangeListener((compoundButton, checked) -> {
+ GlobalDataHolder.saveVisionSetting(checked);
+ });
+
(findViewById(R.id.tv_set_tts_conf)).setOnClickListener(view -> {
Intent intent = new Intent("com.android.settings.TTS_SETTINGS");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/app/src/main/res/drawable/image.png b/app/src/main/res/drawable/image.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2328157591b85673b44dfb71777be44645a5066
GIT binary patch
literal 4827
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
literal 0
HcmV?d00001
diff --git a/app/src/main/res/drawable/image_enabled.png b/app/src/main/res/drawable/image_enabled.png
new file mode 100644
index 0000000000000000000000000000000000000000..30b03c3b7ed594b246874f43fb1cede774889930
GIT binary patch
literal 4542
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
W{J<5faQjO`24HyCR1dA|68Aq>9ZY@z
literal 0
HcmV?d00001
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 8ab7d23..685d8f1 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -32,12 +32,34 @@
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp"
android:gravity="right"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:paddingLeft="10dp">
+
+
+
+
+
+ app:contentPadding="0dp">
-
+ android:orientation="horizontal"
+ android:paddingHorizontal="5dp">
+
+
+
+
+
+
+
+ android:textAlignment="textEnd"
+ tools:ignore="RtlCompat" />
-
+ android:layout_marginRight="20dp"
+ android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/image_method_dialog.xml b/app/src/main/res/layout/image_method_dialog.xml
new file mode 100644
index 0000000..6290120
--- /dev/null
+++ b/app/src/main/res/layout/image_method_dialog.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/image_preview_dialog.xml b/app/src/main/res/layout/image_preview_dialog.xml
new file mode 100644
index 0000000..198d6e8
--- /dev/null
+++ b/app/src/main/res/layout/image_preview_dialog.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/main_model_spinner_item.xml b/app/src/main/res/layout/main_model_spinner_item.xml
new file mode 100644
index 0000000..0f32fad
--- /dev/null
+++ b/app/src/main/res/layout/main_model_spinner_item.xml
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/model_spinner_dropdown_item.xml b/app/src/main/res/layout/model_spinner_dropdown_item.xml
new file mode 100644
index 0000000..2397cf3
--- /dev/null
+++ b/app/src/main/res/layout/model_spinner_dropdown_item.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5318d13..0926d23 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -15,7 +15,6 @@
- gpt-3.5-turbo-16k
- gpt-4
- gpt-4-32k
- - 自定义
DAEDADuGjMLeSU+tLR7t9Z3XEtc5TH45ztgfk9MyzlFy5ogpHIYiX1wL0fWyL9tS/Y7MzU9QK4vtZUvoo97OT4YcpG9BmxtgtQpWpw==
\ No newline at end of file
diff --git a/app/src/main/res/xml/file_provider_paths.xml b/app/src/main/res/xml/file_provider_paths.xml
new file mode 100644
index 0000000..c733e18
--- /dev/null
+++ b/app/src/main/res/xml/file_provider_paths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/readme_img/vision.gif b/readme_img/vision.gif
new file mode 100644
index 0000000000000000000000000000000000000000..49ca38f605244fb3d11b4395d8692d6f84c2caac
GIT binary patch
literal 8770418
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