AI / app /page.js
Niansuh's picture
Update app/page.js
d4abccb verified
"use client";
import "./chat.css";
import { useState, useEffect } from "react";
import ReactMarkdown from "react-markdown";
import axios from "axios";
import { RiDeleteBinLine } from "react-icons/ri";
import TextField from '@/app/components/TextField';
import { MdContentCopy } from "react-icons/md";
import { GrPowerReset } from "react-icons/gr";
import {TiTick} from "react-icons/ti";
import MessageOptions from "./components/MessageOptions";
function generateRandomId(length) {
const id = Math.random().toString(36).substring(2, length + 2);
console.log(`ID GENERATION: ${id}`);
return id;
}
const ChatPage = () => {
const apiKey = 'hf_freeall'; // Replace with your OpenAI API key
const maxTokens = 1000;
const [isGenerating, setIsGenerating] = useState(false);
const chatSessionsKey = 'chatSessions';
let chatHistory = [];
process.on("uncaughtException", (err) => {
console.log(err);
});
const [currentModel, setCurrentModel] = useState("gpt-3.5-turbo-16k-0613");
const [sendButtonDisabled, setSendButtonDisabled] = useState(false);
const [bingSession, setBingSession] = useState(null);
const [currentTab, setCurrentTab] = useState("chat");
const [stop, setStop] = useState(false);
const [messages, setMessages] = useState([
// {
// role: "assistant",
// who: "AI: ",
// content: "Hello! How can I assist you today? 😊",
// greet: true,
// timestamp: 0,
// id: generateRandomId(6),
// }
]);
const models = ["gpt-3.5-turbo", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-16k-0613", "gpt-4", "gpt-4-0613", "gpt-4-32k", "gpt-4-32k-0613"];
const [modelSelectionModalOpen, setModelSelectionModalOpen] = useState(false);
const [conversations, setConversations] = useState({});
const saveToLocalStorage = () => {
localStorage.setItem("messages-"+messages[0].content+"|"+messages[0].id, JSON.stringify(messages));
setConversations({
...localStorage
})
};
const getFromLocalStorage = () => {
if (localStorage.getItem("messages"+messages[0].content+"|"+messages[0].id)) {
setMessages(JSON.parse(localStorage.getItem("messages")));
}
};
useEffect(() => {
var localStorage = window.localStorage;
}, []);
const getAIResponse = async () => {
// setSendButtonDisabled(true);
// // ask for response buut also timeout if no response
// let resp = await axios.post("/api/v1/chat/completions", {
// messages: messages,
// model: currentModel,
// bingSession: bingSession
// }//,
// // {
// // timeout: 10000
// // }
// ).catch(err => {
// console.log(err);
// updateMessages({
// role: "assistant",
// who: "AI: ",
// content: "Error: " + err.message,
// timestamp: Date.now(),
// id: generateRandomId(6),
// })
// setSendButtonDisabled(false);
// });
// resp = resp.data;
// console.log(resp);
// if (resp.choices[resp.choices.length - 1].content) {
// if (resp.choices[resp.choices.length - 1].content.includes('BingProdUnAuthenticatedUsers')){
// let message = {
// role: "assistant",
// who: "AI: ",
// content: JSON.parse(resp.choices[resp.choices.length - 1].content).resultant,
// timestamp: Date.now(),
// id: generateRandomId(6),
// }
// setBingSession((prev) => JSON.parse(resp.choices[resp.choices.length - 1].content).session);
// console.log(bingSession);
// console.log(JSON.parse(resp.choices[resp.choices.length - 1].content));
// updateMessages(
// message
// )
// }
// else{
// updateMessages({
// ...resp.choices[resp.choices.length - 1],
// timestamp: Date.now(),
// });
// }
// }
// else{
// updateMessages({
// role: "assistant",
// who: "AI: ",
// content: "Error: No response. Try different AI Model or try again later.",
// timestamp: Date.now(),
// id: generateRandomId(6),
// })
// }
// setSendButtonDisabled(false);
// chatHistory.push({ role: 'user', content: messages[messages.length - 1].content });
for (let i = 0; i < messages.length; i++) {
chatHistory.push({ role: messages[i].role, content: messages[i].content });
}
let controller = new AbortController();
const signal = controller.signal;
const response = await fetch('https://jf7k5cw4jx8mwrlo.us-east-1.aws.endpoints.huggingface.cloud/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: chatHistory,
max_tokens: maxTokens,
stream: true
}),
signal
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let finalMessage = '';
while (true) {
setIsGenerating(true);
console.log(stop);
const { done, value } = await reader.read();
if (done || stop) {
setIsGenerating(false);
break;
};
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n').filter(line => line.startsWith('data:'));
for (const line of lines) {
const json = line.replace('data:', '');
try {
const parsed = JSON.parse(json);
if (parsed.choices && parsed.choices.length > 0) {
finalMessage += parsed.choices[0].delta?.content || '';
updateMessages({
role: 'assistant',
who: 'AI: ',
content: finalMessage,
timestamp: Date.now(),
id: generateRandomId(6),
});
}
} catch (error) {
console.error('Error parsing JSON:', error);
}
}
};
setStop(false);
}
const regenerateMessage = (message) => {
updateMessages({
role: "user",
who: "You: ",
content: message.content,
timestamp: Date.now(),
id: generateRandomId(6),
});
}
const AIProcess = () => {
getAIResponse();
};
// message-container scroll to bottom on new message
useEffect(() => {
if (!messages?.length) return;
const messageContainer = document.querySelector(".message-container");
messageContainer.scrollTop = messageContainer.scrollHeight;
if (messages[messages?.length - 1]?.role === "user") {
AIProcess();
}
if (messages?.length > 1) {
saveToLocalStorage();
}
if (messages?.length === 1) {
setConversations({
...localStorage
})
}
}, [messages]);
useEffect(() => {
setConversations({
...localStorage
})
}, [])
useEffect(() => {
// scroll to bottom
const messageContainer = document.querySelector(".message-container");
messageContainer.scrollTop = messageContainer.scrollHeight;
}, [sendButtonDisabled])
const updateMessages = (message) => {
setMessages([...messages||[], message]);
};
return (
<div className="fullpage" style={{ display: "flex", flexDirection: "column", width: "100%", alignItems: "center", justifyContent: "center" }}>
<div className="tabsForMobile chat-container" style={{
flexDirection: "row",
justifyContent: "center",
width: "max-content",
gap: "10px",
padding: "10px",
alignItems: "center",
maxWidth: "600px",
height: "fit-content",
marginBottom: "0px",}
}>
<button
className={`btn4 ${currentTab === "history" ? "active" : ""}`}
onClick={()=>{
setCurrentTab("history");
}}
>
История
</button>
<button
className={`btn4 ${currentTab === "chat" ? "active" : ""}`}
onClick={()=>{
setCurrentTab("chat");
}}
>
Чат
</button>
{/* <button
className={`btn4 ${currentTab === "settings" ? "active" : ""}`}
onClick={()=>{
setCurrentTab("settings");
}}
>
Settings
</button> */}
</div>
<div className="whole-container" style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
gap: "5px",
alignItems: "center",
width: "100%",
padding: "0px 0px 0px 0px"
}}>
{/* Here goes available chats or conversation history */}
<div
className="history chat-container"
style={{
maxWidth: "400px",
display: currentTab === "history" ? "flex" : undefined,
flexDirection: "column",
alignItems: "center",
justifyContent: "flex-start",
padding: "20px",
}}
>
<h1
className="gradient-text"
style={{ textAlign: "center", fontSize: "30px", fontWeight: "bold", marginBottom: "20px" }}
>
История
</h1>
<div className="line"></div>
<main style={{width: '100%', overflowY: 'auto'}}>
<div className="goToConv" style={{width: '100%'}} onClick={()=>{
setMessages([]);
setCurrentTab("chat");
}}>
<div className="titler">Новый разговор</div>
<p>Начните новый разговор, просто щелкнув здесь!</p>
</div>
{typeof window !== 'undefined' &&
Object.keys(conversations).map((key,index)=>{
if (!key.includes('messages') && !key.includes('|')) return
let name = key.split('|')[0].replace('messages-', '')
// take only first 10 characters from name
name = name.slice(0, 20) + '...';
return <div key={index} style={{marginTop: '10px',width: '100%'}} className="goToConv" onClick={()=>{
setMessages(JSON.parse(localStorage.getItem(key)));
setCurrentTab("chat");
}}>
<div className="titler" style={{fontSize: '18px', display: 'flex', justifyContent: 'space-between'}}><span>{name}</span> {/*Delete button*/}
<button className="btn3" onClick={
()=>{
setMessages({})
localStorage.removeItem(key);
setConversations({
...localStorage
})
}
} style={{
color: '#0000ff',
}}>
<RiDeleteBinLine/>
</button></div>
<div className="row">
<p>{JSON.parse(localStorage.getItem(key)).length} messages</p>
<p>{Date(JSON.parse(localStorage.getItem(key))[0].timestamp)}</p>
</div>
</div>
})
}
</main>
</div>
{/* Here goes main chat UI */}
<div
className="chat"
style={{
width: "100%",
maxHeight: "100dvh",
minHeight: "100%",
display: currentTab === "chat" ? "flex" : undefined,
flexDirection: "column",
alignItems: "center",
justifyContent: "space-between",
}}
>
{modelSelectionModalOpen && (
<div
style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: "rgba(0, 0, 0, 0.5)",
zIndex: 9999,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div
style={{
backgroundColor: "rgba(90, 90, 90, 0.2)",
backdropFilter: "blur(5px)",
padding: 20,
borderRadius: 10,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<select
value={currentModel}
onChange={(e) => setCurrentModel(e.target.value)}
>
{models.map((model) => (
<option key={model} value={model}>
{model}
</option>
))}
</select>
<button
onClick={() => setModelSelectionModalOpen(false)}
style={{
marginTop: 10,
}}
>
Закрыть
</button>
</div>
</div>
)}
<div className="chat-container">
<div className="message-container">
{/* Welcome message if the messages array is empty */}
{(messages?.length === 0 || !messages) && (
<div
className="message-holder"
style={{
alignSelf: "center",
justifySelf: "center",
alignItems: "center",
justifyContent: "flex-start",
height: "100%",
}}
>
<div className="gradient-text" style={{fontSize: "30px", textAlign: "center", fontWeight: "bold" }}>Добро пожаловать на GPT-CHATBOT.ru</div>
<div className="small" style={{
color: "gray",
}}>Начните, задав мне вопрос!</div>
{/* Some random AI message and when clicked will be set as input value and be sent */}
<div
className="message-holder"
style={{
display: "flex",
flexDirection: "column",
width: "100%",
alignItems: "center",
justifyContent: "center",
}}
>
<button
className="btn3 small"
style={{
width: "90%",
marginTop: "10px",
marginLeft: "20px"
}}
onClick={(e) => {
document.querySelector('textarea').value = e.target.innerText
}}
>
Как мне сделать красивые кексы с шоколадным вкусом? Расскажите шаг за шагом!
</button>
<button
className="btn3 small"
style={{
width: "90%",
marginTop: "10px",
marginLeft: "20px"
}}
onClick={(e) => {
document.querySelector('textarea').value = e.target.innerText
}}
>
Мне скучно! Можешь рассказать мне что-то особенное? Можем сыграть в игры вместе или в викторину?
</button>
<button
className="btn3 small"
style={{
marginTop: "10px",
width: "90%",
marginLeft: "20px"
}}
onClick={(e) => {
document.querySelector('textarea').value = e.target.innerText
}}
>
Ах, мир прекрасен! Ты, как искусственный интеллект, можешь рассказать мне что-то о мире! Ты можешь выразить свои мысли и идеи о мире и человеке! Я с нетерпением жду твоего ответа!
</button>
</div>
</div>
)}
{messages?.map((message, index) => (
<div
className="message-holder"
key={index}
style={{
alignSelf: message.role === "user" ? "flex-end" : "flex-start",
}}
>
<div
className="small"
style={{
alignSelf:
message.role === "user" ? "flex-end" : "flex-start",
}}
>
{message.role.charAt(0).toUpperCase() + message.role.slice(1)}
{!message.greet &&
" - " + new Date(message.timestamp).toLocaleTimeString()}
</div>
<ReactMarkdown
key={index}
className={`message ${
message.role === "user" ? "user-message" : "ai-message"
}`}
>
{message.content}
</ReactMarkdown>
{message.role === "assistant" && ( // Only show options for assistant messages
<MessageOptions
message={message}
regenerateMessage={regenerateMessage}
/>
)}
</div>
))}
{/* Loading Message */}
<div
className="message-holder"
style={{
alignSelf: "flex-start",
}}
>
<div
className="small"
style={{
alignSelf: "flex-start",
}}
>
{sendButtonDisabled && <div className="" style={{display:"flex", alignItems:"center", justifyContent:"center",}}>
<div className="ld-ripple">
<div></div>
<div></div>
</div> Генерация ответа ⚙️
</div>}
</div>
</div>
</div>
<div
className="inpsection"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
margin: "0px 0px 10px 0px",
}}
>
<button className="btn2" onClick={() => setModelSelectionModalOpen(true)}>
<i className="animation"></i>AI<i className="animation"></i>
</button>
<TextField
type="text"
placeholder="Aa"
className="inp"
multiline
maxRows={10}
onKeyDown={(e) => {
// check if on mobile
function checkMobile() {
let check = false;
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
return check;
}
if (e.key === "Enter" && !e.shiftKey && !checkMobile()) {
updateMessages({
role: "user",
content: e.target.value,
timestamp: Date.now(),
who: "You: ",
id: generateRandomId(),
});
document.querySelector(".inp").value = null;
}
}}
/>
<button
disabled={sendButtonDisabled}
className="btn2"
onClick={() => {
if (isGenerating) {
setStop(true);
console.log("stop");
return;
}
let newMessage = {
role: "user",
who: "You: ",
id: generateRandomId(),
content: document.querySelector(".inp").value,
timestamp: Date.now(),
};
updateMessages(newMessage);
document.querySelector(".inp").value = null;
}}
>
<i className="animation"></i>{isGenerating? "Остановить" : "Отправить"}<i className="animation"></i>
</button>
</div>
</div>
</div>
{/* Here goes chat info */}
</div>
</div>
);
};
export default ChatPage;