实现了如下功能(使用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 = false
export 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新item
if (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>)
: (
<div
ref={(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 ? (
<div
className="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}
<div
className="copy_button"
onClick={() => {
comments.splice(index, 1);
setCount(count + 1);
}}>delete</div>
</div>
{
!item.edit ? <p>{item.name}</p> : (
<div className="">
<TextArea
rows={4}
defaultValue={item.name}
onChange={(e) => {
item.name = e.target.value;
}}
/>
</div>
)
}
</div>
</div>
) : null
}
{
item.contents.length ? (
<>
<div
className='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'>
<div
className="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>
<div
className="copy_all_button"
onClick={() => {
setOpenMoreFunction(true);
}}
style={{ color: "white" }}
>
更多功能
</div>
</div>
)
}
const renderDrawerCopyBtnAll = () => {
return (
<Drawer
title={
<div style={{ display: 'flex' }}>
<div
className='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)
}}
>
<TextArea
rows={4}
value={jsonData}
onChange={(e) => {
setJsonData(e.target.value);
}}
/>
</Drawer>
)
}
const renderDrawerMoreFunction = () => {
return (
<Drawer
title={"更多功能"}
placement={"bottom"}
open={openMoreFunction}
size='small'
onClose={() => {
setOpenMoreFunction(false)
}}
>
<div>
{
!streaming ? (
<button
className="copy_all_button"
onClick={() => {
comments = [];
setCount(0);
}}>clear</button>
) : null
}
{
<button
className="copy_all_button"
onClick={() => {
clearLocalStorage();
setCount(10);
}}>clearStorage</button>
}
<div>
<span>角色:</span>
<Select
style={{ width: '100%' }}
defaultValue="origin"
onChange={handleSelectChange}
options={[
{ value: 'origin', label: 'origin' },
...Object.keys(roles).map((role) => ({ value: role, label: role }))
]}
/>
</div>
<div>
<span>缓存:</span>
<Select
style={{ 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'>
<TextArea
className='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);
}