实现了如下功能(使用react实现,原创)
实现功能:
1、对接gpt35模型问答,并实现了流式传输(在java端)
2、在实际使用中,我们的问答历史会经常分享给他人,所以下图的 copy all 按钮,可以copy成一个json然后通过社交软件发送给别人,别人就可以轻松应用你的问答历史。
3、选择文件,在我们预想当中,我们可能会经常遇到向文档提问(当时还不知道embedding的知识哈哈哈),通过拆分文档,一段段跟gpt提问,当前段落是否和用户内容相关,相关就回答段落问题(段落拆分通过java实现)
4、我们需要经常保存我们的聊天记录,特别是在调试我们的prompt的时候,所以加了一个缓存功能,可以随时提取缓存记录来提问。
5、利用这个分享的时候,设计了很多便利我去学习英语的prompt,避免老是手打提示词
role.js
export default {"专业的英语翻译家": (text = "示例") => {return "现在你充当我专业的翻译家。当我输入中文,你就翻译成英文。当我输入英文,你就翻译成中文。请翻译:" + text},"文章截断翻译": (text = "示例") => {return "因为我是中国的软件开发工程师,我要面试美国的软件开发岗位,所以我要学习英语,我要你充当我的翻译家," +"所以我给一些中文的软件知识给你帮我翻译,但是你不能直译,因为中文说出来的知识,英语的表达有不一样,所" +"以请你理解我的中文知识,按照自己的理解用英语表达出来,所以我给你一段文字,首先你要将文章拆分成一句一句,理解每" +"一句的意思,然后用英语将你理解的意思输出。输出格式为一句中文,输出一个回车符,下一行输出你的英文理解。并且每一句末尾都" +"给生僻词单独翻译。文章内容为:“" + text + "”"},"给出5个英语句子对应的词汇": (text = "示例") => {return "我给你一个英文单词,请你用这个英文单词造出5个英文句子,句子要求是计算机互联网相关知识" +"(包括但不限于前端专业细节知识,react专业细节知识,vue专业细节知识,js专业细节知识,管理系统的功能专业细节知识," +"http网络相关专业细节知识),并附带中文翻译。最后还要给出他的衍生词汇," +"给出他的发音以及词汇类型。单词为:" + text},"给你一个中文词汇,你给我说出英语一般用什么句式去表达": (text = "示例") => {return "我给你一个中文词汇,你给我说出英语一般用什么句式去表达。" +"例如:中文意思:确保一些东西是有效的,英语一般表达为:ensure that somethings is valid。" +"这个(ensure that ... is valid)就是英语的常规表达句式。" +"例如:允许轻松自定义表单验证,,英语一般表达为:ensure that somethings is valid。" +"这个(allows for ... 。" +"中文词汇为:" + text},"面试中怎么表达这个中文意思": (text = "示例") => {return "在美国的it开发工程师英语面试当中,怎么表达:" + text + ", 请用三种或者三种以上不同的句式表达"},"在英语中有多少英文表达这个中文意思": (text = "示例") => {return "在英语中有多少英文表达这个中文意思,请列举出来,中文为:" + text},"假设你是一个从小就在美国长大的人": (text = "示例") => {return "假设你是一个从小就在美国长大的人,你已经30岁,在互联网公司工作8年,请你使用简洁的口语帮我将中文翻译成英文,重点是简洁,简洁,你自己听得懂就好。中文为:" + text}
}
index.js
import React, { useState, useCallback, useRef } from 'react';
import './index.css';
import { useEffect } from 'react';
import axios from 'axios';
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
import { vscDarkPlus, coyWithoutShadows, darcula } from 'react-syntax-highlighter/dist/esm/styles/prism';
// 设置高亮的语言
import { jsx, javascript } from "react-syntax-highlighter/dist/esm/languages/prism";
import ReactMarkdown from 'react-markdown';
import ClipboardJS from 'clipboard';
import { Drawer, Input, message, Select } from 'antd';
import roles from "./roles";const { Search } = Input;
const { TextArea } = Input;
const { Option } = Select;function clearLocalStorage() {localStorage.setItem("LOCALDATA", "[]");
}// 封装localStorage的get方法
function getLocalStorage() {let arrStr = localStorage.getItem("LOCALDATA");if (arrStr) {let arr = JSON.parse(arrStr);return arr;} else {return [];}
}const them = {dark: vscDarkPlus,light: coyWithoutShadows
};
const ENDTEXT = "__END__";let comments = [];
let streaming = falseexport default function App1() {const [question, setQuestion] = useState("");const [roleType, setRoleType] = useState("");const [frontPrompts, setFrontPrompts] = useState("");const list_container_id = useRef(null);const currentTexts = useRef("");const [count, setCount] = useState(0);const [messageApi, contextHolder] = message.useMessage();const [open, setOpen] = useState(false);const [openMoreFunction, setOpenMoreFunction] = useState(false);const [jsonData, setJsonData] = useState("{}");const key = 'copy';const postStreamList = async (callback) => {let requestList = [];comments.map((item) => {if (item.type === "chatgpt-url") {if (item.contents[0]) {requestList.push({ "role": "user", "content": item.contents[0].hiddenQuestion });requestList.push({ "role": "assistant", "content": item.contents[0].hiddenContent });}} else {requestList.push({ "role": "user", "content": item.name });if (item.contents[0] && item.contents[0].text) {requestList.push({ "role": "assistant", "content": item.contents[0].text });}}})const requestOptions = {method: 'POST',headers: {'Content-Type': 'application/json',"Authorization": "Bearer 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"},body: JSON.stringify({"model": "gpt-3.5-turbo","messages": requestList})};let count = 0;const streamResponse = await fetch('/chat', requestOptions);// const streamResponse = await fetch('/search/api/dev/stream', requestOptions);const reader = streamResponse.body.getReader();let errText = "";const read = () => {return reader.read().then(({ done, value }) => {count++;if (done) {console.log("victor react reviced: end");callback(ENDTEXT);return;}const textDecoder = new TextDecoder();// console.log("返回的数据:", textDecoder.decode(value));let text = "";const strArr = (errText + textDecoder.decode(value)).split("data: ");console.log("解析字符", textDecoder.decode(value))if (strArr) {for (let i = 0; i < strArr.length; i++) {let json = {};if (strArr[i] && strArr[i] !== "[DONE]") {try {json = JSON.parse(strArr[i]);if (json.choices.length && json.choices[0].delta.content) {text = text + json.choices[0].delta.content;}errText = "";} catch (e) {console.log("出错", strArr[i])errText = strArr[i];}}}callback(text);}return read();});}read();}const postStreamListAudio = async (erjinzhi) => {const requestOptions = {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({"model": "gpt-3.5-turbo","messages": [{ "role": "assistant", "content": erjinzhi }]})};let count = 0;const streamResponse = await fetch('/chat', requestOptions);// const streamResponse = await fetch('/search/api/dev/stream', requestOptions);const reader = streamResponse.body.getReader();let errText = "";const read = () => {return reader.read().then(({ done, value }) => {count++;if (done) {console.log("victor react reviced: end");return;}const textDecoder = new TextDecoder();// console.log("返回的数据:", textDecoder.decode(value));let text = "";const strArr = (errText + textDecoder.decode(value)).split("data: ");console.log("解析字符", textDecoder.decode(value))if (strArr) {for (let i = 0; i < strArr.length; i++) {let json = {};if (strArr[i] && strArr[i] !== "[DONE]") {try {json = JSON.parse(strArr[i]);if (json.choices.length && json.choices[0].delta.content) {text = text + json.choices[0].delta.content;}errText = "";} catch (e) {console.log("出错", strArr[i])errText = strArr[i];}}}console.log(text);}return read();});}read();}const addLocalStorage = (dataArr) => {var now = new Date();var year = now.getFullYear(); //获取完整的年份(4位,1970-????)var month = now.getMonth() + 1; //获取当前月份(0-11,0代表1月)var date = now.getDate(); //获取当前日(1-31)var hour = now.getHours(); //获取当前小时数(0-23)var minute = now.getMinutes(); //获取当前分钟数(0-59)var second = now.getSeconds(); //获取当前秒数(0-59)var timestamp = year + "-" + (month < 10 ? "0" + month : month) + "-" + (date < 10 ? "0" + date : date) + " " + (hour < 10 ? "0" + hour : hour) + ":" + (minute < 10 ? "0" + minute : minute) + ":" + (second < 10 ? "0" + second : second);try {let arrStr = localStorage.getItem("LOCALDATA");if (arrStr) {let arr = JSON.parse(arrStr);arr.push({time: timestamp,dataArr: dataArr});localStorage.setItem("LOCALDATA", JSON.stringify(arr));} else {let arr = [];arr.push({time: timestamp,dataArr: dataArr});localStorage.setItem("LOCALDATA", JSON.stringify(arr));}messageApi.open({key,type: 'success',content: '缓存成功',duration: 1});} catch (err) {console.error('localStorage set error: ', err);}}const addComment = async (e) => {if (question.trim() === '') {alert('请输入问题');return;}setQuestion('');let index = comments.length;comments.push({id: Math.random(),role: 'user',type: "chatgpt",name: question,contents: []});setCount(count + 1);setTimeout(async () => {let responseList = await getList();if (responseList[0].type === "chatgpt-url") {comments[index].type = "chatgpt-url";}comments[index].contents = responseList;setQuestion('');setCount(0);}, 0);}const getList = (question) => {let requestList = [];comments.map((item) => {if (item.type === "chatgpt-url") {if (item.contents[0]) {requestList.push({ "role": "user", "content": item.contents[0].hiddenQuestion });requestList.push({ "role": "assistant", "content": item.contents[0].hiddenContent });}} else {requestList.push({ "role": "user", "content": item.name });if (item.contents[0]) {requestList.push({ "role": "assistant", "content": item.contents[0].text });}}})return new Promise((resolve) => {axios.post('/search/send', {frequency_penalty: 0,max_tokens: 2048,model: "text-davinci-003",presence_penalty: 0,message: requestList,temperature: 0.5,top_p: 1}).then((response) => {if (Array.isArray(response.data.choices)) {// console.log('请求成功', response);let arr = response.data.choices.map((item) => {if (item.message.type === "chatgpt-url") {return {type: item.message.type,index: item.index,text: "我已经对这个链接学习完成,你可以向我提问关于这个链接的内容",hiddenQuestion: item.message.question,hiddenContent: item.message.content}} else {return {type: item.type,index: item.index,text: item.message.content}}})resolve(arr);} else {alert('程序错误');}// 请求成功}).catch((error) => {// 请求失败,console.log(error);});})}const scrollBottom = () => {if (!list_container_id.current) {return;}setTimeout(() => {list_container_id.current.scrollTop = list_container_id.current.scrollHeight}, 0);}const updateScroll = useCallback(() => {scrollBottom()})const addStreamComment = async ({question1 = "",isCreate = false,isContinue = false}) => {if (question.trim() === '' && !question1 && isContinue === false) {alert('请输入问题');return;}streaming = true;setQuestion('');let index = 0;// 修改不需要新数据, 创建就需要push新itemif (isCreate || comments.length === 0) {console.log("走创建")index = comments.length;let questionText = question1 || question;if (roles[roleType]) {questionText = roles[roleType](question1 || question)}comments.push({id: Math.random(),role: 'user',type: "chatgpt",name: questionText,edit: false,contents: [{ index: Math.random(), text: "", edit: false }]});} else if (isContinue === true) {console.log("走继续")index = comments.length - 1;comments[index] = {...comments[index],id: Math.random(),role: 'user',type: "chatgpt",edit: false};} else {console.log("走编辑")index = comments.length - 1;comments[index] = {id: Math.random(),role: 'user',type: "chatgpt",name: question1 || question,edit: false,contents: [{ index: Math.random(), text: "", edit: false }]};}setCount(count + 1);let str = comments[index].contents[0].text;const callback = (text) => {if (text === ENDTEXT) {streaming = false;setCount(1);return;}str = str + text;comments[index].contents[0].text = str;setQuestion('');setCount((count) => count + 1);}postStreamList(callback);}const copy = (index) => {const clipboard = new ClipboardJS("#copyBtn" + index);clipboard.on('success', () => {messageApi.open({key,type: 'success',content: '复制成功',duration: 1});});}useEffect(() => {const clipboard = new ClipboardJS("#copyBtnAll");clipboard.on('success', () => {messageApi.open({key,type: 'success',content: '复制成功',duration: 1});});comments.map((item, index) => {const clipboard = new ClipboardJS("#copyBtn" + index);clipboard.on('success', () => {messageApi.open({key,type: 'success',content: '复制成功',duration: 2});});})})console.log("comments", comments)const renderList = () => {return comments.length === 0 ?(<div style={{ flex: 1 }}><div className='no-comment'>暂无问题,快去提问吧~</div></div>): (<divref={(el) => {list_container_id.current = el;}}style={{ flex: 1 }}className="list_container"><ul style={{ color: 'white' }}>{comments.map((item, index) => (<li key={item.id} style={{ color: 'white' }}>{item.name ? (<div className='quiz'><div className='response' style={{ marginLeft: 8 }}><div className='action_btn'><div>提问:</div><div className="copy_button" id={"copyBtn" + index} data-clipboard-text={item.name} onClick={(e) => copy(index)}>copy</div>{comments.length === index + 1 ? (<divclassName="copy_button"onClick={() => {if (item.edit === false) {item.edit = true;setCount(count + 1);} else {addStreamComment({question1: item.name,isCreate: false,isContinue: false});}}}>{!item.edit ? "edit" : "submit"}</div>) : null}<divclassName="copy_button"onClick={() => {comments.splice(index, 1);setCount(count + 1);}}>delete</div></div>{!item.edit ? <p>{item.name}</p> : (<div className=""><TextArearows={4}defaultValue={item.name}onChange={(e) => {item.name = e.target.value;}}/></div>)}</div></div>) : null}{item.contents.length ? (<><divclassName='answer'><div style={{ marginLeft: 8, marginBottom: 10 }} ><div className='action_btn'><div>回答:</div><div className="copy_button" id={"copyBtn" + index} data-clipboard-text={item.contents[0].text} onClick={(e) => copy(index)}>copy</div></div><pre style={{ width: "100%" }}><OmsSyntaxHighlight textContent={item.contents[0].text} language={"javascript"} darkMode /></pre></div></div><div>{currentTexts.current}</div></>) : <div><div style={{ display: 'flex', justifyContent: 'center', backgroundColor: 'black' }}><div className='heike' >chatgpt</div></div><div className='answer2'>思考中...</div></div>}</li>))}</ul ></div >)}const handleForm = (e) => {setQuestion(e.target.value)}const handleSelectChange = (value) => {setFrontPrompts(value);setRoleType(value);};useEffect(() => {scrollBottom()})const overWriteData = (jsonData) => {let jsonData1 = JSON.parse(jsonData);// console.log("jsonData1", jsonData1)comments = [];jsonData1.map((item, index) => {if (index % 2 === 0) {comments.push({id: Math.random(),role: 'user',type: "chatgpt",name: item.content,edit: false,contents: [{index: Math.random(),edit: false,text: jsonData1[index + 1].content}]})// console.log(comments)setCount(count + 1)}})}const handleLocalDataChange = (value) => {overWriteData(value);};useEffect(() => {const mp3File = document.getElementById('mp3-file');mp3File.addEventListener('change', () => {const file = mp3File.files[0];const reader = new FileReader();reader.addEventListener('loadend', () => {const byteArray = new Uint8Array(reader.result);// 将byteArray上传至服务器console.log(byteArray)postStreamListAudio(byteArray);});reader.readAsArrayBuffer(file);});}, [])const renderHeader = () => {return (<div className='header_button'><divclassName="copy_all_button"style={{ color: "white" }}onClick={() => {let tmp = [];comments.map((item) => {tmp.push({role: 'user',content: item.name,})tmp.push({role: 'assistant',content: item.contents[0].text})})setJsonData(JSON.stringify(tmp));setOpen(true);}}>copy all</div><input type="file" id="mp3-file"></input><divclassName="copy_all_button"onClick={() => {setOpenMoreFunction(true);}}style={{ color: "white" }}>更多功能</div></div>)}const renderDrawerCopyBtnAll = () => {return (<Drawertitle={<div style={{ display: 'flex' }}><divclassName='copy_button'id={"copyBtnAll"}data-clipboard-text={jsonData}onClick={(e) => {const clipboard = new ClipboardJS("#copyBtnAll");clipboard.on('success', () => {messageApi.open({key,type: 'success',content: '复制成功',duration: 2});});}}>copy</div><div className='copy_button' onClick={() => {try {overWriteData(jsonData);setOpen(false);} catch (e) {messageApi.open({key,type: 'error',content: 'json格式出错',duration: 2});}}}>执行json</div><div className='copy_button' onClick={() => {try {addLocalStorage(jsonData);} catch (e) {messageApi.open({key,type: 'error',content: 'json格式出错',duration: 2});}}}>缓存</div></div>}placement={"bottom"}open={open}size='small'onClose={() => {setOpen(false)}}><TextArearows={4}value={jsonData}onChange={(e) => {setJsonData(e.target.value);}}/></Drawer>)}const renderDrawerMoreFunction = () => {return (<Drawertitle={"更多功能"}placement={"bottom"}open={openMoreFunction}size='small'onClose={() => {setOpenMoreFunction(false)}}><div>{!streaming ? (<buttonclassName="copy_all_button"onClick={() => {comments = [];setCount(0);}}>clear</button>) : null}{<buttonclassName="copy_all_button"onClick={() => {clearLocalStorage();setCount(10);}}>clearStorage</button>}<div><span>角色:</span><Selectstyle={{ width: '100%' }}defaultValue="origin"onChange={handleSelectChange}options={[{ value: 'origin', label: 'origin' },...Object.keys(roles).map((role) => ({ value: role, label: role }))]}/></div><div><span>缓存:</span><Selectstyle={{ width: '100%' }}onChange={handleLocalDataChange}>{getLocalStorage().length ? getLocalStorage().map((item) => {return <Option value={item.dataArr} key={Math.random()}>{item.time}</Option>}) : <Option value={"0"} key="无">无</Option>}</Select></div></div></Drawer>)}const renderFrontPrompts = () => {if (frontPrompts && roles[frontPrompts]) {return <div className='frontPrompts'>前置指令:{roles[frontPrompts]()}</div>;} else {return null;}}const renderQuestion = () => {return (<div className='input_style'><TextAreaclassName='input_quertion'type="text"placeholder="请输入问题"value={question}name="question"onChange={handleForm}autoSize={{ minRows: 1, maxRows: 5 }}/><div style={{ width: '1vw' }}></div><button onClick={() => {addStreamComment({isContinue: true,isCreate: false,question1: ""});}} className="confirm_button" >继续</button><div style={{ width: '1vw' }}></div><button onClick={() => {const pattern = /(http|https):\/\/([\w.]+\/?)\S*/;addStreamComment({ isCreate: true, isContinue: false, question1: "" });}} className="confirm_button" >提问</button></div>)}return (<div className='app_container'>{renderHeader()}{renderFrontPrompts()}{renderList()}{contextHolder}{renderQuestion()}{renderDrawerCopyBtnAll()}{renderDrawerMoreFunction()}</div>)}const OmsSyntaxHighlight = (props) => {const { textContent, darkMode, language = 'txt' } = props;const [value, setValue] = useState(textContent);if (typeof darkMode === 'undefined') {them.light = darcula;}if (typeof darkMode === 'boolean') {them.light = coyWithoutShadows;}useEffect(() => {SyntaxHighlighter.registerLanguage("jsx", jsx);SyntaxHighlighter.registerLanguage("javascript", javascript);SyntaxHighlighter.registerLanguage("js", javascript);}, []);return (<ReactMarkdown source={value} escapeHtml={false} language={language}>{textContent}</ReactMarkdown>);
};
css文件
body,
html {margin: 0;
}ul,
li,
p {padding: 0;margin: 0;list-style: none
}h3 {margin-bottom: 0;
}.input_quertion {width: 50vw;height: 50px;border-radius: 10px;border: 1px solid black;
}pre {white-space: pre-wrap;white-space: -moz-pre-wrap;white-space: -pre-wrap;white-space: -o-pre-wrap;word-wrap: break-word;
}.copy_button {line-height: 35px;margin-right: 4px;border: 1px solid royalblue;
}.copy_all_button {line-height: 44px;margin-right: 4px;border: 1px solid royalblue;
}.content {width: 280px;margin: 5px;border: 1px solid black;
}.quickButton {width: 70px;border-radius: 10px;background-color: #03b96b;border: 0;height: 30px;color: white;position: absolute;right: 10px;
}.no-comment {text-align: center;padding: 20px;color: white;background-color: rgb(53, 54, 65);
}.frontPrompts {text-align: left;padding: 8px 46px;color: white;font-size: 12px;background-color: rgb(53, 54, 65);border-bottom: 1px solid black;
}.app_container {width: 100%;height: 100%;display: flex;flex-direction: column;background-color: rgb(53, 54, 65);
}.confirm_button {width: 26vw;border-radius: 10px;background-color: #03b96b;border: 0;height: 50px;color: white;box-shadow: 7px 6px 28px 1px rgba(0, 0, 0, 0.24);cursor: pointer;outline: none;transition: 0.2s all;
}.list_container {overflow: auto;flex: 1;
}.qiu {width: 15%;height: 15%;
}.chatGPTImg {position: fixed;top: 0;right: 0;bottom: 0;left: 0;margin: auto;width: 300px;height: 300px;z-index: 999;
}.response {overflow-wrap: break-word;word-break: normal;white-space: normal;flex: 1;
}.header_button {width: 100%;height: 67px;display: flex;align-items: center;bottom: 0;padding: 10px 40px 10px;border-bottom: 1px solid;background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0) 100%);box-shadow: 0px -5px 10px rgba(0, 0, 0, 0.3);
}.input_style {width: 100%;display: flex;bottom: 0;padding: 1%;align-items: end;
}.action_btn {display: flex;align-items: flex-start;color: white;
}.quiz {display: flex;align-items: flex-start;padding: 10px 40px 10px;color: white;line-height: 41px;background-color: rgb(53, 54, 65);
}.quiz_avatar {width: 40px;height: 40px;
}.answer {display: flex;background-color: #3b3d53;color: white;height: auto;line-height: 35px;padding: 20px 40px;overflow: auto;white-space: normal;word-break: break-all;
}.answer2 {text-align: center;padding-top: 40px;
}.confirm_button:active {transform: scale(0.98);background-color: blue;box-shadow: 3px 2px 22px 1px rgba(0, 0, 0, 0.24);
}