Skip to content

Commit

Permalink
新增GPT联网支持;新增对0613版本其他GPT模型的支持;设置界面增加选项说明;优化流式输出时渲染频率过高问题;修正无法选中文本问题;版…
Browse files Browse the repository at this point in the history
…本号修改为1.6.0
  • Loading branch information
Skythinker616 committed Nov 5, 2023
1 parent 117468c commit 6beb742
Show file tree
Hide file tree
Showing 16 changed files with 747 additions and 94 deletions.
1 change: 1 addition & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ GPT Assistant 是一个基于ChatGPT的安卓端语音助手,允许用户通

### 项目特性

- 支持用户预设**问题模板**,支持**连续对话**,支持`gpt-3.5-turbo``gpt-4`模型
- 支持用户预设**问题模板**,支持**连续对话**,支持`gpt-3.5-turbo``gpt-4`等模型
- **支持联网**,允许GPT获取在线网页
- 通过无障碍功能捕获音量键事件,实现在**任意界面唤起**
- 支持从**全局上下文菜单**(选中文本后弹出的系统菜单)中直接唤起
- 支持通过状态栏**快捷按钮**唤起
Expand Down Expand Up @@ -78,6 +79,23 @@ GPT Assistant 是一个基于ChatGPT的安卓端语音助手,允许用户通
<img src="readme_img/multi_chat.gif" height="400px">
</div>

**五、支持GPT联网**

本程序实现了OpenAI的Function接口,允许GPT发起联网请求,程序会向GPT自动返回所需的网页数据,使GPT具有联网能力(需先在设置中开启联网选项)

<div align="center">
<img src="readme_img/web_time.png" height="120px">
<img src="readme_img/web_weather.png" height="120px">
</div>
<div align="center">
<img src="readme_img/web_zhihu.png" height="200px">
<img src="readme_img/web_exchange.png" height="200px">
</div>

> 注1:上图均为使用`gpt-3.5-turbo-0613`模型的测试结果,建议在提问前加入“百度搜索”、“在线获取”、“从xxx获取”等字样引导GPT,以获得更好的联网效果
> 注2:由于需要将网页内容发送给GPT,联网时会产生大量Token消耗,`gpt-4`模型请谨慎使用
---

## 使用方法
Expand Down Expand Up @@ -144,13 +162,17 @@ A: 软件调用的是系统自带TTS(Text To Speech)服务,可以通过软件

A: 经测试,所使用的华为接口(实时语音识别)识别准确度较高,尤其是在中英混说的场景下,但其断句能力则不如百度,仅适合单句识别

**Q: GPT返回的Markdown中表格和图片无法正常显示?**
A: 所使用的Markdown渲染器无法在测试中产生稳定的结果,因此暂不支持表格和图片

---

## 主要功能更新日志

- **2023.09.10** 发布第一个版本,支持基础对话、百度语音输入、TTS输出、Markdown渲染等功能
- **2023.09.13** 支持连续对话、GPT-4、百度长语音识别,上下文菜单唤起
- **2023.10.06** 添加华为HMS语音识别
- **2023.11.06** 添加联网功能

---

Expand All @@ -160,12 +182,12 @@ A: 经测试,所使用的华为接口(实时语音识别)识别准确度

| 机型 | 系统版本 | Android 版本 | 本程序版本 |
| :--: | :-----: | :----------: | :-------: |
| 荣耀 7C | EMUI 8.0.0 | Android 8 | 1.5.1 |
| 荣耀 20 | HarmonyOS 3.0.0 | Android 10 | 1.5.1 |
| 荣耀 7C | EMUI 8.0.0 | Android 8 | 1.6.0 |
| 荣耀 20 | HarmonyOS 3.0.0 | Android 10 | 1.6.0 |
| 华为 Mate 30 | HarmonyOS 3.0.0 | Android 12 | 1.4.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.5.0 |
| 红米 K60 Pro | MIUI 14.0.23 | Android 13 | 1.6.0 |
| Pixel 2 (模拟器) | Android 12 | Android 12 | 1.2.0 |

---
Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ android {
minSdk 26
targetSdk 32
versionCode 1
versionName "1.5.1"
versionName "1.6.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
92 changes: 81 additions & 11 deletions app/src/main/java/com/skythinker/gptassistant/ChatApiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
import androidx.annotation.Nullable;

import com.unfbx.chatgpt.OpenAiStreamClient;
import com.unfbx.chatgpt.entity.chat.FunctionCall;
import com.unfbx.chatgpt.entity.chat.Functions;
import com.unfbx.chatgpt.entity.chat.Message;
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
import com.unfbx.chatgpt.entity.chat.Parameters;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import cn.hutool.core.lang.func.Func;
import cn.hutool.json.JSONObject;
import okhttp3.OkHttpClient;
import okhttp3.Response;
Expand All @@ -23,15 +27,17 @@

public class ChatApiClient {
public interface OnReceiveListener {
void onReceive(String message);
void onMsgReceive(String message);
void onError(String message);
void onFunctionCall(String name, String arg);
void onFinished();
}

public enum ChatRole {
SYSTEM,
USER,
ASSISTANT
ASSISTANT,
FUNCTION
}

String url = "";
Expand All @@ -42,6 +48,11 @@ public enum ChatRole {
OkHttpClient httpClient = null;
OpenAiStreamClient chatGPT = null;

List<Functions> functions = new ArrayList<>();

String callingFuncName = "";
String callingFuncArg = "";

public ChatApiClient(String url, String apiKey, String model, OnReceiveListener listener) {
this.listener = listener;
this.model = model;
Expand Down Expand Up @@ -84,14 +95,42 @@ public void sendPromptList(List<Pair<ChatRole, String>> promptList) {
} else if(prompt.first == ChatRole.USER) {
messages.add(Message.builder().role(Message.Role.USER).content(prompt.second).build());
} else if(prompt.first == ChatRole.ASSISTANT) {
messages.add(Message.builder().role(Message.Role.ASSISTANT).content(prompt.second).build());
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());
}
} 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 = ChatCompletion.builder()
.messages(messages)
.model(model)
.build();
ChatCompletion chatCompletion;
if(!functions.isEmpty()) {
chatCompletion = ChatCompletion.builder()
.messages(messages)
.model(model)
.functions(functions)
.functionCall("auto")
.build();
} else {
chatCompletion = ChatCompletion.builder()
.messages(messages)
.model(model)
.build();
}

callingFuncName = callingFuncArg = "";

chatGPT.streamChatCompletion(chatCompletion, new EventSourceListener() {
@Override
Expand All @@ -103,11 +142,23 @@ public void onOpen(EventSource eventSource, Response response) {
public void onEvent(EventSource eventSource, @Nullable String id, @Nullable String type, String data) {
if(data.equals("[DONE]")){
Log.d("ChatApiClient", "onEvent: DONE");
listener.onFinished();
if(callingFuncName.isEmpty()) {
listener.onFinished();
} else {
listener.onFunctionCall(callingFuncName, callingFuncArg);
}
} else {
String msg = ((JSONObject) (new JSONObject(data)).getJSONArray("choices").get(0)).getJSONObject("delta").getStr("content");
if(msg != null)
listener.onReceive(msg);
JSONObject delta = ((JSONObject) (new JSONObject(data)).getJSONArray("choices").get(0)).getJSONObject("delta");
if (delta.containsKey("function_call")) {
JSONObject functionCall = delta.getJSONObject("function_call");
if(functionCall.containsKey("name"))
callingFuncName = functionCall.getStr("name");
callingFuncArg += functionCall.getStr("arguments");
} else if(delta.containsKey("content")) {
String msg = delta.getStr("content");
if(msg != null)
listener.onMsgReceive(msg);
}
// Log.d("ChatApiClient", "onEvent: " + data);
}
}
Expand Down Expand Up @@ -169,4 +220,23 @@ public void stop() {
public void setModel(String model) {
this.model = model;
}

public void addFunction(String name, String desc, String params) {
Parameters parameters = Parameters.builder()
.type("object")
.properties(new JSONObject(params))
.build();

Functions functions = Functions.builder()
.name(name)
.description(desc)
.parameters(parameters)
.build();

this.functions.add(functions);
}

public void clearAllFunctions() {
this.functions.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ public class GlobalDataHolder {
private static boolean asrUseRealTime;
private static String gptApiHost;
private static String gptApiKey;
private static boolean gpt4Enable;
private static String gptModel;
private static boolean checkAccessOnStart;
private static boolean defaultEnableTts;
private static boolean defaultEnableMultiChat;
private static int selectedTab;
private static boolean enableInternetAccess;
private static int webMaxCharCount;
private static boolean onlyLatestWebResult;
private static SharedPreferences sp = null;

public static void init(Context context) {
Expand All @@ -42,6 +45,7 @@ public static void init(Context context) {
loadTtsSetting();
loadMultiChatSetting();
loadSelectedTab();
loadFunctionSetting();
}

public static List<PromptTabData> getTabDataList() {
Expand Down Expand Up @@ -104,17 +108,17 @@ public static void saveAsrInfo(boolean useBaidu, String appId, String apiKey, St
public static void loadGptApiInfo() {
gptApiHost = sp.getString("gpt_api_host", "");
gptApiKey = sp.getString("gpt_api_key", "");
gpt4Enable = sp.getBoolean("gpt4_enable", false);
gptModel = sp.getString("gpt_model", "gpt-3.5-turbo-0613");
}

public static void saveGptApiInfo(String host, String key, boolean gpt4) {
public static void saveGptApiInfo(String host, String key, String model) {
gptApiHost = host;
gptApiKey = key;
gpt4Enable = gpt4;
gptModel = model;
SharedPreferences.Editor editor = sp.edit();
editor.putString("gpt_api_host", gptApiHost);
editor.putString("gpt_api_key", gptApiKey);
editor.putBoolean("gpt4_enable", gpt4Enable);
editor.putString("gpt_model", gptModel);
editor.apply();
}

Expand Down Expand Up @@ -162,6 +166,23 @@ public static void saveSelectedTab(int tab) {
editor.apply();
}

public static void loadFunctionSetting() {
enableInternetAccess = sp.getBoolean("enable_internet", false);
webMaxCharCount = sp.getInt("web_max_char_count", 2000);
onlyLatestWebResult = sp.getBoolean("only_latest_web_result", true);
}

public static void saveFunctionSetting(boolean enableInternet, int maxCharCount, boolean onlyLatest) {
enableInternetAccess = enableInternet;
webMaxCharCount = maxCharCount;
onlyLatestWebResult = onlyLatest;
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("enable_internet", enableInternetAccess);
editor.putInt("web_max_char_count", webMaxCharCount);
editor.putBoolean("only_latest_web_result", onlyLatestWebResult);
editor.apply();
}

public static boolean getAsrUseBaidu() { return asrUseBaidu; }

public static String getAsrAppId() { return asrAppId; }
Expand All @@ -176,7 +197,7 @@ public static void saveSelectedTab(int tab) {

public static String getGptApiKey() { return gptApiKey; }

public static boolean getGpt4Enable() { return gpt4Enable; }
public static String getGptModel() { return gptModel; }

public static boolean getCheckAccessOnStart() { return checkAccessOnStart; }

Expand All @@ -185,4 +206,10 @@ public static void saveSelectedTab(int tab) {
public static boolean getDefaultEnableMultiChat() { return defaultEnableMultiChat; }

public static int getSelectedTab() { return selectedTab; }

public static boolean getEnableInternetAccess() { return enableInternetAccess; }

public static int getWebMaxCharCount() { return webMaxCharCount; }

public static boolean getOnlyLatestWebResult() { return onlyLatestWebResult; }
}
Loading

0 comments on commit 6beb742

Please sign in to comment.