mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
chatgpt is taking shape, convo persists, layout mimics original
This commit is contained in:
parent
3a199757ae
commit
f889f23792
9 changed files with 161 additions and 35 deletions
|
|
@ -1,11 +1,23 @@
|
|||
require('dotenv').config();
|
||||
const Keyv = require('keyv');
|
||||
const messageStore = new Keyv(process.env.MONGODB_URI, { namespace: 'chatgpt' });
|
||||
|
||||
const ask = async (question) => {
|
||||
const ask = async (question, progressCallback, convo) => {
|
||||
const { ChatGPTAPI } = await import('chatgpt');
|
||||
const api = new ChatGPTAPI({ apiKey: process.env.OPENAI_KEY });
|
||||
const res = await api.sendMessage(question, {
|
||||
onProgress: (partialRes) => console.log(partialRes.text)
|
||||
});
|
||||
const api = new ChatGPTAPI({ apiKey: process.env.OPENAI_KEY, messageStore });
|
||||
let options = {
|
||||
onProgress: (partialRes) => {
|
||||
if (partialRes.text.length > 0) {
|
||||
progressCallback(partialRes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!!convo.parentMessageId && !!convo.conversationId) {
|
||||
options = { ...options, ...convo };
|
||||
}
|
||||
|
||||
const res = await api.sendMessage(question, options);
|
||||
return res;
|
||||
};
|
||||
|
||||
|
|
|
|||
10
index.html
10
index.html
|
|
@ -1,10 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./src/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
38
package-lock.json
generated
38
package-lock.json
generated
|
|
@ -9,11 +9,13 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@keyv/mongo": "^2.1.8",
|
||||
"chatgpt": "^4.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"eventsource": "^2.0.2",
|
||||
"keyv": "^4.5.2",
|
||||
"mongoose": "^6.9.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
@ -3511,6 +3513,26 @@
|
|||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@keyv/mongo": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@keyv/mongo/-/mongo-2.1.8.tgz",
|
||||
"integrity": "sha512-IOFKS9Y10c42NCaoD/6OKmqz7FMCm/VbMbrip7ma8tBvdWcPhDkkPV3ZpLgGsGw39RePzzKO6FQ89xs0+BFCKg==",
|
||||
"dependencies": {
|
||||
"mongodb": "^4.5.0",
|
||||
"pify": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@keyv/mongo/node_modules/pify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
|
||||
"integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@leichtgewicht/ip-codec": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
|
||||
|
|
@ -14615,6 +14637,22 @@
|
|||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
}
|
||||
},
|
||||
"@keyv/mongo": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@keyv/mongo/-/mongo-2.1.8.tgz",
|
||||
"integrity": "sha512-IOFKS9Y10c42NCaoD/6OKmqz7FMCm/VbMbrip7ma8tBvdWcPhDkkPV3ZpLgGsGw39RePzzKO6FQ89xs0+BFCKg==",
|
||||
"requires": {
|
||||
"mongodb": "^4.5.0",
|
||||
"pify": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
|
||||
"integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@leichtgewicht/ip-codec": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
|
||||
|
|
|
|||
|
|
@ -21,11 +21,13 @@
|
|||
},
|
||||
"homepage": "https://github.com/danny-avila/rpp2210-mvp#readme",
|
||||
"dependencies": {
|
||||
"@keyv/mongo": "^2.1.8",
|
||||
"chatgpt": "^4.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"eventsource": "^2.0.2",
|
||||
"keyv": "^4.5.2",
|
||||
"mongoose": "^6.9.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
|||
|
|
@ -15,22 +15,32 @@ app.get('/', function (req, res) {
|
|||
res.sendFile(path.join(projectPath, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
app.post('/ask', (req, res) => {
|
||||
console.log(req.body, 'we in here');
|
||||
app.post('/ask', async (req, res) => {
|
||||
console.log(req.body);
|
||||
const { text, parentMessageId, conversationId } = req.body;
|
||||
|
||||
res.writeHead(200, {
|
||||
Connection: 'keep-alive',
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache, no-transform',
|
||||
'Access-Control-Allow-Origin':'*',
|
||||
'X-Accel-Buffering':'no'
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'X-Accel-Buffering': 'no'
|
||||
});
|
||||
res.write('event: message\ndata: This is chunk 1\n\n');
|
||||
res.write('event: message\ndata: This is chunk 2\n\n');
|
||||
setTimeout(() => {
|
||||
res.write('event: message\ndata: This is chunk 3\n\n');
|
||||
res.end();
|
||||
}, 3500);
|
||||
|
||||
let i = 0;
|
||||
const progressCallback = (partial) => {
|
||||
// console.log('partial', partial);
|
||||
if (i === 0) {
|
||||
res.write(`event: message\ndata: ${JSON.stringify({ ...partial, initial: true })}\n\n`);
|
||||
i++;
|
||||
}
|
||||
const data = JSON.stringify({...partial, message: true });
|
||||
res.write(`event: message\ndata: ${data}\n\n`);
|
||||
};
|
||||
|
||||
const gptResponse = await ask(text, progressCallback, { parentMessageId, conversationId });
|
||||
res.write(`event: message\ndata: ${JSON.stringify(gptResponse)}\n\n`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import Messages from './components/Messages';
|
||||
import TextChat from './components/TextChat';
|
||||
|
||||
const App = () => {
|
||||
|
|
@ -8,9 +9,8 @@ const App = () => {
|
|||
<div className="flex h-screen">
|
||||
<div className="w-80 bg-slate-800"></div>
|
||||
<div className="flex h-full w-full flex-col bg-gray-50 ">
|
||||
<div className="flex-1 overflow-y-auto"></div>
|
||||
{/* <textarea className="m-10 h-16 p-4" onChange={(e) => console.log(e.target.value)}/> */}
|
||||
<TextChat />
|
||||
<Messages messages={messages} />
|
||||
<TextChat messages={messages} setMessages={setMessages}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
12
src/components/Message.jsx
Normal file
12
src/components/Message.jsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function Message({ sender, text }) {
|
||||
return (
|
||||
<div className="m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
|
||||
<strong className="relative flex w-[30px] flex-col items-end">{sender}:</strong>
|
||||
<div className="relative flex w-[calc(100%-50px)] flex-col gap-1 whitespace-pre-wrap md:gap-3 lg:w-[calc(100%-115px)]">
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
src/components/Messages.jsx
Normal file
28
src/components/Messages.jsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import Message from './Message';
|
||||
|
||||
export default function Messages({ messages }) {
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
// <div className="w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group dark:bg-gray-800">
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto ">
|
||||
{messages.map((message, i) => (
|
||||
<Message
|
||||
key={i}
|
||||
sender={message.sender}
|
||||
text={message.text}
|
||||
/>
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,18 +1,33 @@
|
|||
import React, { useState } from 'react';
|
||||
import { SSE } from '../../app/sse';
|
||||
|
||||
const handleSubmit = (payload) => {
|
||||
const handleSubmit = (text, messageHandler, convo, convoHandler) => {
|
||||
let payload = { text };
|
||||
if (convo.conversationId && convo.parentMessageId) {
|
||||
payload = {
|
||||
...payload,
|
||||
conversationId: convo.conversationId,
|
||||
parentMessageId: convo.parentMessageId
|
||||
};
|
||||
}
|
||||
|
||||
const events = new SSE('http://localhost:3050/ask', {
|
||||
payload: JSON.stringify({ text: payload }),
|
||||
payload: JSON.stringify(payload),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
console.log('we in handleSubmit');
|
||||
|
||||
events.onopen = function () {
|
||||
console.log('connection is opened');
|
||||
};
|
||||
|
||||
events.onmessage = function (e) {
|
||||
console.log(e);
|
||||
const data = JSON.parse(e.data);
|
||||
if (!!data.message) {
|
||||
messageHandler(data.text.replace(/^\n/, ''));
|
||||
} else {
|
||||
console.log(data);
|
||||
convoHandler(data);
|
||||
}
|
||||
};
|
||||
|
||||
events.onerror = function (e) {
|
||||
|
|
@ -23,8 +38,13 @@ const handleSubmit = (payload) => {
|
|||
events.stream();
|
||||
};
|
||||
|
||||
export default function TextChat() {
|
||||
export default function TextChat({ messages, setMessages, conversation = null }) {
|
||||
const [text, setText] = useState('');
|
||||
const [convo, setConvo] = useState({ conversationId: null, parentMessageId: null });
|
||||
|
||||
if (!!conversation) {
|
||||
setConvo(conversation);
|
||||
}
|
||||
|
||||
const handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter' && e.shiftKey) {
|
||||
|
|
@ -32,8 +52,21 @@ export default function TextChat() {
|
|||
}
|
||||
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
console.log('Submit Enter');
|
||||
handleSubmit(text);
|
||||
const payload = text.trim();
|
||||
const currentMsg = { sender: 'user', text: payload, current: true };
|
||||
setMessages([...messages, currentMsg]);
|
||||
setText('');
|
||||
const messageHandler = (data) => {
|
||||
setMessages([...messages, currentMsg, { sender: 'GPT', text: data }]);
|
||||
};
|
||||
const convoHandler = (data) => {
|
||||
if (convo.conversationId === null && convo.parentMessageId === null) {
|
||||
const { conversationId, parentMessageId } = data;
|
||||
setConvo({ conversationId, parentMessageId: data.id });
|
||||
}
|
||||
};
|
||||
console.log('User Input:', payload);
|
||||
handleSubmit(payload, messageHandler, convo, convoHandler);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -41,6 +74,7 @@ export default function TextChat() {
|
|||
<>
|
||||
<textarea
|
||||
className="m-10 h-16 p-4"
|
||||
value={text}
|
||||
onKeyUp={handleKeyPress}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue