前言
前段时间比较闲,正好chatGpt风头很大,想着瞅瞅做个App玩玩儿,找遍全网都没看到类似Android的实现,没办法只能自己去看文档了,还能写篇文章啥的
直到我点开文档才知道。。。好家伙,这不就是接口拿数据?这做客户端的xdm可太懂了呀,这文章写出来不是侮辱人嘛
本来想就做个App玩玩儿算了,直到后来,竟然有好几个朋友都向我要!!??我才知道,好家伙你们不是不会啊,是真懒啊
思来想去仍是把这个写出来吧,一来记载下自己、二来给xdm一点思路,写的不好xdm别见笑,话不多说,走起!
一、准备工作
首先仍是得有ChatGpt的账号以及魔法能力(调用API),账号怎么注册各大博主写的太清楚了,我就不再赘述了,不清楚的能够搜一下,至于魔法能力。。。OK,准备就绪,我们直接开始。
直接翻开OpenAi的API官网:platform.openai.com/overview
登录之后,去生成一个KEY,保存一下,这个KEY一瞬间要用
然后点击API,找到Chat
这儿信息很齐全,做一个简略的恳求我们只需求供给必选的信息即可,其他的可选参数大家感兴趣能够自己看看
简略归类一下这儿的信息:
恳求类型为POST
恳求API为:api.openai.com/v1/chat/com…
model(模型)选择 :gpt-3.5-turbo(更多模型能够查看:platform.openai.com/docs/models)
恳求头需求增加两个字段,Content-Type(类型)和Authorization(KEY)
恳求参数最少为model(模型)和messages , 这儿留意下流式输出要增加stream = true参数(非必选)
呼应格局为
ok,信息很全,拿出吃饭的家伙
二、完好代码
先撸个简略的ui
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.ChatFragment"
android:background="@color/c_f7f8fa"
android:id="@+id/frag_add">
<!-- TODO: Update blank fragment layout -->
<LinearLayout
android:layout_height="match_parent"
android:id="@+id/linear_add"
android:layout_gravity="center_vertical"
android:layout_width="match_parent"
android:orientation="vertical">
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="6"
>
<LinearLayout
android:id="@+id/linear_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
</LinearLayout>
</ScrollView>
<RelativeLayout
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/edittext_rounded_background">
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@null"
android:hint="请输入音讯..."
android:maxLines="5"
android:padding="10dp"
android:textColor="@android:color/black" />
<Button
android:id="@+id/send_button"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:background="@drawable/button_rounded_background"
android:text="发送"
android:textColor="@android:color/white" />
</RelativeLayout>
</LinearLayout>
</FrameLayout>
ScrollView里做谈天框,动态增加用户输入文本和GPT呼应的文本
public void setChatInf(String text , int img , int color){
LinearLayout mLinearLayoutAdd = LayoutInflater.from(getContext()).inflate(R.layout.chat_textview , null).findViewById(R.id.chat_layout);
mLinearLayoutAdd.setBackground(ContextCompat.getDrawable(getContext(), color));
imageView = mLinearLayoutAdd.findViewById(R.id.chat_iv);
imageView.setBackground(getResources().getDrawable(img));
textView = mLinearLayoutAdd.findViewById(R.id.chat_tv);
textView.setText(text);
if (mLinearLayoutAdd.getParent() != null) {
((ViewGroup)mLinearLayoutAdd.getParent()).removeView(mLinearLayoutAdd);
}
mLinearLayout.addView(mLinearLayoutAdd);
}
chat_textview:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/chat_layout"
android:gravity="center_vertical"
android:fillViewport="true"
android:paddingTop="@dimen/dp_6"
android:paddingBottom="@dimen/dp_6"
android:paddingStart="@dimen/dp_11"
android:paddingEnd="@dimen/dp_11"
>
<ImageView
android:id="@+id/chat_iv"
android:scaleType="fitCenter"
android:layout_marginRight="@dimen/dp_14"
android:layout_width="@dimen/dp_30"
android:layout_height="@dimen/dp_30"/>
<TextView
android:id="@+id/chat_tv"
android:textColor="@color/black"
android:textIsSelectable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
ok,大概长这样
增加Gson、okHttp依靠——>写一个简略的恳求方法
public void chatCreate(boolean usedP , String url , String urlProxy , String port , String key , String text ,String json, Callback callback) {
//usedP,是否运用代理
//url,恳求Api地址
//urlProxy,代理地址
//port,代理端口号
//Key,chatGpt的Key
//text,用户输入问题
//json,恳求json(后续要动态增加上下文,所以这儿加了个json参数不定死)
Proxy proxy;
if (usedP){
//这儿只写了SOCK5代理
proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(
urlProxy, Integer.parseInt(port)));
}else {
proxy = null;
}
RequestBody requestBodyJson =
RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
Request request = new Request.Builder()
.url(url)
.post(requestBodyJson)
.addHeader("content-type", "application/json")
.addHeader("Authorization", "Bearer " + key)//留意key的格局,官网给的是sk最初的
.build();
client = new OkHttpClient.Builder()
.connectTimeout(10 , TimeUnit.SECONDS)
.readTimeout(20 , TimeUnit.SECONDS)//假如不采纳流式输出要把超时设置长一点
.proxy(proxy)//增加代理
.build();
client.newCall(request).enqueue(callback);
}
ok,然后写个实体类用于接纳回来的数据
public class ChatCompletionChunk2 {
private String id;
private String object;
private long created;
private String model;
private List<Choice> choices;
public String getId() {
return id;
}
public String getObject() {
return object;
}
public long getCreated() {
return created;
}
public String getModel() {
return model;
}
public List<Choice> getChoices() {
return choices;
}
public static class Choice {
private Delta delta;
private int index;
private String finish_reason;
public Delta getDelta() {
return delta;
}
public int getIndex() {
return index;
}
public String getFinishReason() {
return finish_reason;
}
}
public static class Delta {
private String content;
public String getContent() {
return content;
}
}
}
最终对呼应的数据进行解析,完好代码:
public class ChatFragment extends Fragment implements ScreenShotable, View.OnClickListener {
@Inject
Api api;
ChatCompletion chatCompletion;
ChatCompletionChunk2 chatCompletionChunk2;
private OkHttpClient client;
private EditText editText;
private Button button;
private LinearLayout mLinearLayout;
private LinearLayout mLinearLayoutAdd;
private FrameLayout frameLayout;
StringBuffer buffer;
String json;
private TextView textView;
private ScrollView scrollView;
private ImageView imageView;
@SuppressLint("HandlerLeak")
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//button按钮文字及状态
button.setEnabled(true);
button.setText((String)msg.obj);
//滑动底部
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
break;
case 2:
//response回来拼接
textView.setText(msg.obj.toString());
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
break;
}
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate( R.layout.fragment_chat, null);
return view;
}
@Override
public void onActivityCreated(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
editText = getView().findViewById(R.id.edit_text);
button = getView().findViewById(R.id.send_button);
mLinearLayout = getView().findViewById(R.id.linear_layout);
scrollView = getView().findViewById(R.id.scrollView);
frameLayout = getView().findViewById(R.id.frag_add);
mLinearLayoutAdd = LayoutInflater.from(getContext()).inflate(R.layout.chat_textview , null).findViewById(R.id.chat_layout);
setChatInf("你好,我是Ai小助手,需求帮助吗?" , R.mipmap.chat_img , R.color.c_f2f3f5);
//流式输出形式,messages由于要增加上下文坚持连接,所以选用动态增加,假如不增加上下文这儿mesage能够直接写死
json = "{"model": "gpt-3.5-turbo", " +
""messages": [] , " +
""stream" : true}";
button = getView().findViewById(R.id.send_button);
button.setOnClickListener(this);
}
@SuppressLint("ResourceAsColor")
@Override
public void onClick(View v) {
String message = editText.getText().toString().trim();
if (!message.isEmpty()) {
//收起软键盘
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
View view = getActivity().getCurrentFocus();
if (view != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
chatResp(message);
}else {
Toast.makeText(getContext(), "请输入内容", Toast.LENGTH_SHORT).show();
}
}
@SuppressLint({"ResourceAsColor", "MissingInflatedId"})
public void sendMsg(final String text) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (!text.isEmpty()) {
editText.getText().clear();
setChatInf(text , R.mipmap.user_img , R.color.c_f7f8fa);
setToken(1 , text);
button.setEnabled(false);
button.setText("别急");
}
}
});
}
public void chatResp(final String text){
sendMsg(text);
new Thread(new Runnable() {
@Override
public void run() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
setChatInf("正在输入中..." , R.mipmap.chat_img , R.color.c_f2f3f5);
}
});
Api api = new Api();
SharedPreferences sharedPreferences = getActivity().getSharedPreferences("user", Context.MODE_PRIVATE);
//这儿能够直接传参的
api.chatCreate(sharedPreferences.getBoolean("radioGroupUsedP" , Constants.usedP) ,
Constants.URL_CREATE,
sharedPreferences.getString("editTextIp" , Constants.PROXY),
sharedPreferences.getString("editTextPort" , String.valueOf(Constants.PORT)),
sharedPreferences.getString("Key" , Constants.KEY),
text,
json,
new Callback() {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
//流式输出
buffer = new StringBuffer();
Gson gson = new Gson();
// 获取response输入流
InputStream inputStream = response.body().byteStream();
// 读取呼应数据
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
// 处理每一行数据
Log.d("line", "onResponse: " + line);
//判断是否回来了数据,去除response前data关键字,否则解析不了
if (line.length() > 6) {
Log.d("line.substring", "onResponse: " + line.substring(6));
try {
chatCompletionChunk2 = gson.fromJson(line.substring(5) , ChatCompletionChunk2.class);
Log.d("getContent", "onResponse: " + chatCompletionChunk2.getChoices().get(0).getDelta().getContent());
if (chatCompletionChunk2.getChoices().get(0).getDelta().getContent() != null){
addNewlineAfterPeriod(chatCompletionChunk2.getChoices().get(0).getDelta().getContent());
buffer.append(chatCompletionChunk2.getChoices().get(0).getDelta().getContent());
setMessage(2 , buffer);
}
if (chatCompletionChunk2.getChoices().get(0).getFinishReason() != null) {
break;
}
}catch (Exception e){
e.printStackTrace();
buffer.append("恳求有误,请检查key或稍后重试,当然,假如你上下文满足长也会出现,请自行找寻原因,嗯,我懒得写 /doge");
setMessage(2 , buffer);
setToken(2 , buffer.toString());
break;
}
}
}
setMessage(1 , "发送");
Log.d("buffer", "onResponse: " + buffer.toString());
}
}
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.d("onFailure", "onFailure: 恳求失利" );
setMessage(2 , "恳求超时,请检查网络并重试");
setMessage(1 , "发送");
}
});
}
}).start();
}
/**
* @author liaoqg
* @date ${YEAR}-${MONTH}-${DAY}
* 描绘
* 对字符串进行处理
*/
public String addNewlineAfterPeriod(String str) {
StringBuilder sb = new StringBuilder();
boolean periodFound = false;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '.' || c == '。') {
periodFound = true;
sb.append(c);
sb.append('\n');
} else if (c == '\n') {
continue;
} else {
sb.append(c);
}
}
if (!periodFound) {
return str;
}
return sb.toString();
}
/**
* @author liaoqg
* @date ${YEAR}-${MONTH}-${DAY}
* 描绘
* 设置头像和谈天框款式
*/
public void setChatInf(String text , int img , int color){
LinearLayout mLinearLayoutAdd = LayoutInflater.from(getContext()).inflate(R.layout.chat_textview , null).findViewById(R.id.chat_layout);
mLinearLayoutAdd.setBackground(ContextCompat.getDrawable(getContext(), color));
imageView = mLinearLayoutAdd.findViewById(R.id.chat_iv);
imageView.setBackground(getResources().getDrawable(img));
textView = mLinearLayoutAdd.findViewById(R.id.chat_tv);
textView.setText(text);
if (mLinearLayoutAdd.getParent() != null) {
((ViewGroup)mLinearLayoutAdd.getParent()).removeView(mLinearLayoutAdd);
}
mLinearLayout.addView(mLinearLayoutAdd);
}
/**
* @author liaoqg
* @date ${YEAR}-${MONTH}-${DAY}
* 描绘
* handle
*/
public void setMessage(int what , Object object){
Message message = Message.obtain();
message.what = what;
message.obj = object;
handler.sendMessage(message);
}
/**
* @author liaoqg
* @date ${YEAR}-${MONTH}-${DAY}
* 描绘
* 将上下文信息保存
* 测试出,只需求用户信息,不需求将chat的回答增加到恳求中,也能够坚持gpt上下文连接
*/
public void setToken(int status , String text){
try {
if (Constants.IsTOKEN){
// 将json字符串转化为json目标
JSONObject jsonObject = new JSONObject(json);
// 获取messages字段的json数组目标
JSONArray messagesArray = jsonObject.getJSONArray("messages");
if (status == 1){
Constants.newMessage = new JSONObject();
Constants.newMessage.put("role", "user");
Constants.newMessage.put("content", text);
}
// else if (status == 2){
// Constants.newMessage = new JSONObject();
// Constants.newMessage.put("role", "assistant");
// Constants.newMessage.put("content", text);
// }
// 将新的音讯目标增加到messages数组中
messagesArray.put(Constants.newMessage);
// 更新json目标中的messages字段
jsonObject.put("messages", messagesArray);
// 将json目标转化为字符串
json = jsonObject.toString();
}else {
json = "{"model": "gpt-3.5-turbo", " +
""messages": [{"role": "user", "content": "" + text + ""}] , " +
""stream" : true}";
}
} catch (Exception e) {
e.printStackTrace();
}
Log.d("TAG", "setToken: " + json);
}
}
功德圆满,没有什么难度,接纳数据的坑很多,有兴趣的能够打印出来好好看看,假如想生成图片也是相同的步骤,官方API文档里面写的很清楚,我就不罗嗦了,ok,上个制品图
ps:假如有自己的代理服务器ip能够在proxy中设置,没有的只能放魔法访问了。现在ip封锁严峻,仍是主张大家尽量不要选亚洲节点,尤其是港服。
弥补
应xdm的要求,特此附上源码链接: github.com/mingzhennan…
这儿包括了谈天(chat)和图片(img)的接口实现,Down下来之后需求设置一下key
Ui运用的是Yalantis的侧边菜单栏:github.com/Yalantis/Si…,有兴趣的xdm能够看看