前言

前段时间比较闲,正好chatGpt风头很大,想着瞅瞅做个App玩玩儿,找遍全网都没看到类似Android的实现,没办法只能自己去看文档了,还能写篇文章啥的

直到我点开文档才知道。。。好家伙,这不就是接口拿数据?这做客户端的xdm可太懂了呀,这文章写出来不是侮辱人嘛

做个ChatGPT的App玩玩儿

本来想就做个App玩玩儿算了,直到后来,竟然有好几个朋友都向我要!!??我才知道,好家伙你们不是不会啊,是真懒啊

做个ChatGPT的App玩玩儿

思来想去仍是把这个写出来吧,一来记载下自己、二来给xdm一点思路,写的不好xdm别见笑,话不多说,走起!

一、准备工作

首先仍是得有ChatGpt的账号以及魔法能力(调用API),账号怎么注册各大博主写的太清楚了,我就不再赘述了,不清楚的能够搜一下,至于魔法能力。。。OK,准备就绪,我们直接开始。

直接翻开OpenAi的API官网:platform.openai.com/overview

登录之后,去生成一个KEY,保存一下,这个KEY一瞬间要用

做个ChatGPT的App玩玩儿

做个ChatGPT的App玩玩儿

然后点击API,找到Chat

做个ChatGPT的App玩玩儿

这儿信息很齐全,做一个简略的恳求我们只需求供给必选的信息即可,其他的可选参数大家感兴趣能够自己看看

简略归类一下这儿的信息:

恳求类型为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参数(非必选)

呼应格局为

做个ChatGPT的App玩玩儿

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,大概长这样

做个ChatGPT的App玩玩儿

增加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,上个制品图

做个ChatGPT的App玩玩儿

ps:假如有自己的代理服务器ip能够在proxy中设置,没有的只能放魔法访问了。现在ip封锁严峻,仍是主张大家尽量不要选亚洲节点,尤其是港服。

弥补

应xdm的要求,特此附上源码链接: github.com/mingzhennan…

这儿包括了谈天(chat)和图片(img)的接口实现,Down下来之后需求设置一下key

Ui运用的是Yalantis的侧边菜单栏:github.com/Yalantis/Si…,有兴趣的xdm能够看看