| "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'; |
| 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([ |
| |
| |
| |
| |
| |
| |
| |
| |
| ]); |
| 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 () => { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| 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(); |
| }; |
|
|
| |
| 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(() => { |
| |
| 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; |
|
|