人工智能课程设计—文心一言聊天室 发表于 2024-05-26 更新于 2024-05-26
字数总计: 2.4k 阅读时长: 12分钟 阅读量: 广东
人工智能课程设计—文心一言聊天室 前言 花了两天的时间配合 Ai 完成了一个聊天室的功能,主要是前端太菜了,这么点样式几乎花了我一天时间,后端的话半天不到就搞定了。本来是想全部都用异步,然后再加个登录界面的(主要是为了设置头像和名字),但是这些可能要设计到数据库那方面,而且因为是异步,代码复杂度高了,再加上小组的成员要理解代码以做 ppt 和 word 文档,时间成本有点高了,所以先放弃了。
用到的技术栈:Flask、websocket、H5C3、js
用到的 python 模块:Flask、flask_socketio
目录树 ├─static ├───client.png ├───wxyy.png ├─templates ├───index.html ├─app.py ├─service.py
后端部分 import jsonfrom flask import Flask, render_template, requestfrom flask_socketio import SocketIOfrom service import WenXinYiYanapp = Flask(__name__) socketio = SocketIO(app, cors_allowed_origins='*' ) connected_ws = dict () @socketio.on('connect' ) def on_connect (): """ 连接成功触发 """ wxyy_class = WenXinYiYan() print (request.sid + " 已连接" ) connected_ws[request.sid] = wxyy_class @socketio.on('disconnect' ) def handle_disconnect (): """ 断开连接触发 """ del connected_ws[request.sid] print (f"{request.sid} 断开连接" ) @socketio.on('message' ) def handle_message (message ): """ 收消息 """ print (f'{request.sid} ask: {message} ' ) wxyy_reply = (connected_ws[request.sid]).send(message) print (f'文心一言 reply: {wxyy_reply} ' ) socketio.emit('reply_message' , wxyy_reply) @app.route('/' ) def index (): return render_template("index.html" ) if __name__ == '__main__' : app.debug = True app.run(port=80 )
import requestsimport json""" auther: Admsec<admsec223@gmail.com> description: 封装了一个开箱即用的文心一言4.0Api(ERNIE-4.0-8K-Preview),需要提供 client_id 和 secret version: 1.0.0 """ class WenXinYiYan (): def __init__ (self ): self.client_id = "Djf***************" self.client_secret = "qa********************j" self.access_token_url = f'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={self.client_id} &client_secret={self.client_secret} ' self.headers = { 'Content-Type' : 'application/json' , 'Accept' : 'application/json' } self.access_token = None self.messages = {"messages" : []} def get_access_token (self ): try : response = requests.get(self.access_token_url, headers=self.headers) if response.status_code != 200 : print (f"[error] 状态码不是200:{response.text} " ) return False r_to_json = response.json() return r_to_json['access_token' ] except Exception as e: print (e) return False def send (self, msg ): self.messages['messages' ].append({"role" : "user" , "content" : f"{msg} " }) if not self.access_token: get_access_token = self.get_access_token() if get_access_token: self.access_token = self.get_access_token() url = f"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-4.0-8k-preview?access_token={self.access_token} " try : response = requests.request("POST" , url, headers=self.headers, data=json.dumps(self.messages)) if response.status_code != 200 : return False r_to_json = response.json() self.messages['messages' ].append({"role" : "assistant" , "content" : f"{r_to_json['result' ]} " }) return r_to_json['result' ] except Exception as e: print (e) return False
前端部分 <!DOCTYPE html > <html lang ="zh" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 聊天室</title > <style > .container { width : 50% ; margin : 0 auto; padding : 0 ; text-align : center; border : 1px solid #ccc ; height : 98vh ; border-radius : 5px ; } .nav { display : flex; justify-content : space-between; align-items : center; border-bottom : #ccc solid 1px ; height : 5vh ; } .nav-title { font-size : 24px ; font-weight : bold; color : #333 ; margin : 0 ; padding : 0 20px ; } .nav-right { display : flex; align-items : center; margin : 0 ; padding : 0 20px ; } .nav-right button { background-color : #ccc ; border : none; padding : 8px 16px ; border-radius : 5px ; cursor : pointer; } .nav-right button :hover { background-color : #aaa ; } .content { height : 64vh ; overflow-y : scroll; padding : 20px ; } .footer { background-color : #ccc ; height : 22vh ; display : flex; flex-direction : column; justify-content : center; align-items : center; margin-top : 1rem ; } .input-group { width : 100% ; height : 100% ; display : flex; align-items : center; } .footer button { position : absolute; right : 27% ; bottom : 5% ; width : 70px ; height : 40px ; border-radius : 10% ; border : none; background-color : #ccc ; cursor : pointer; } .footer button :hover { background-color : #aaa ; } textarea { width : 100% ; height : 98% ; border : none; padding : 1rem ; border-radius : 5px ; resize : none; border-top : #ccc solid 1px ; border : #ccc solid 1px ; } textarea :hover { outline : rgb (230 , 225 , 253 ) solid 2px ; } textarea :focus { outline : rgb (230 , 225 , 253 ) solid 2px ; } .msg-reply { display : flex; margin-bottom : 1rem ; } .replay-msg-avatar { width : 30px ; height : 30px ; border-radius : 50% ; margin-top : 1rem ; overflow : hidden; } .replay-msg-avatar img { width : 100% ; height : 100% ; object-fit : cover; } .replay-msg-content { margin-left : 1rem ; word-break : break-all; text-align : left; } .replay-msg-content p { margin : 0 ; padding : 5px ; } .replay-msg-time { font-size : 12px ; color : #999 ; margin-left : 1rem ; } .msg-sender { display : flex; justify-content : flex-end; margin-bottom : 1rem ; } .reply-msg-content-box { background-color : rgb (236 , 237 , 239 ); border-radius : 10px ; padding : 1rem ; } .reply-msg-content-box { max-width : 40rem ; min-width : 20rem ; } .sender-msg-avatar { width : 30px ; height : 30px ; border-radius : 50% ; margin-top : 1rem ; overflow : hidden; } .sender-msg-avatar img { width : 100% ; height : 100% ; object-fit : cover; } .sender-msg-content { margin-right : 1rem ; word-break : break-all; width : 20rem ; text-align : left; } .sender-msg-content p { margin : 0 ; padding : 5px ; } .sender-msg-time { font-size : 12px ; color : #999 ; margin-right : 1rem ; text-align : right; } .send-msg-content-box { background-color : rgb (230 , 225 , 253 ); border-radius : 10px ; padding : 1rem ; } </style > </head > <body > <div class ="container" > <div class ="nav" > <div class ="nav-title" > <span > 聊天室</span > </div > <div class ="nav-right" > <button id ="clear-btn" > 清空对话</button > </div > </div > <div class ="content" > <div class ="msg-reply" > <div class ="replay-msg-avatar" > <img src ="static/wxyy.png" alt ="" > </div > <div class ="replay-msg-content" > <div class ="reply-msg-content-box" > <p > 你好,欢迎来到聊天室</p > <p > 你可以在这里与大家一起聊天,也可以输入文字进行聊天</p > </div > <p class ="replay-msg-time" > 2024-05-26 8:00:00</p > </div > </div > </div > <div class ="footer" > <div class ="input-group" > <textarea name ="" id ="msg-box" placeholder ="请输入内容" > </textarea > </div > <button id ="send-btn" > 发送</button > </body > <script src ="https://cdn.staticfile.org/socket.io/4.5.2/socket.io.min.js" > </script > <script > let socket = io (); const clearBtn = document .getElementById ('clear-btn' ); clearBtn.addEventListener ('click' , () => { const content = document .querySelector ('.content' ); content.innerHTML = '' ; }); const sendBtn = document .getElementById ('send-btn' ); const msgBox = document .getElementById ('msg-box' ); sendBtn.addEventListener ('click' , send_msg); msgBox.addEventListener ("keydown" , function (e ) { if (e.key === "Enter" ) { e.preventDefault (); sendBtn.click (); } }); async function send_msg ( ) { const msg = msgBox.value ; if (msg) { socket.send (msg); const msgHtml = ` <div class="msg-sender"> <div class="sender-msg-content"> <div class="send-msg-content-box"> <p>${optimize(msg)} </p> </div> <p class="sender-msg-time">${new Date ().toLocaleString()} </p> </div> <div class="sender-msg-avatar"> <img src="static/client.png" alt=""> </div> </div> ` ; const content = document .querySelector ('.content' ); content.innerHTML += msgHtml; msgBox.value = '' ; const replyMsgWaitingHtml = ` <div class="msg-reply"> <div class="replay-msg-avatar"> <img src="static/wxyy.png" alt=""> </div> <div class="replay-msg-content"> <div class="reply-msg-content-box"> <p>正在等待服务端响应...</p> </div> </div> </div> ` ; content.innerHTML += replyMsgWaitingHtml; const messageContainer = document .querySelector ('.content' ); messageContainer.scrollTop = messageContainer.scrollHeight ; socket.on ('reply_message' , (msg ) => { const result = optimize (msg) console .log (result); const replyMsg = ` <div class="msg-reply"> <div class="replay-msg-avatar"> <img src="static/wxyy.png" alt=""> </div> <div class="replay-msg-content"> <div class="reply-msg-content-box"> ${result} </div> <p class="replay-msg-time">${new Date ().toLocaleString()} </p> </div> </div> ` ; content.innerHTML = content.innerHTML .replace (replyMsgWaitingHtml, replyMsg); const messageContainer = document .querySelector ('.content' ); messageContainer.scrollTop = messageContainer.scrollHeight ; }) } } function optimize (msg ){ msg = msg.replace (/</g , "<" ).replace (/>/g , ">" ).replace (/\n/g , "<br>" ).replace (/ /g , " " ); return msg; } </script > </html >
剩下的 static 文件夹的那两张图片就不必放了,随便找的
总结 实话说这次写代码让我意思到了配合 Ai 之后工作效率确实有被提高了很多(归根结底还是自己菜,得多学点)。这次本来是想用 vue 配合 flask 做个前后端分离的项目了,后来看了看 vue,感觉还是用原生实现的感觉爽一点,毕竟代码量不多,我也会写一点点。还有就是这次用的是新技术 websocket 进行前后端通信,有一说一 websocket 真的香,原本刚开始我是想用 http 带参数的形式实现后端的,代码都写一半了,忽然回头想了想,这样的话后端代码会很多,而且逻辑客观来说比 websocket 的逻辑复杂一点点,然后就开始拥抱新技术 websocket 了,真的香。