mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 17:30:16 +01:00
Merge pull request #45 from danny-avila/submit-state
fixes: allow simultaneous convos & minor improvements
This commit is contained in:
commit
a705e907ff
19 changed files with 290 additions and 128 deletions
16
README.md
16
README.md
|
|
@ -8,6 +8,18 @@ https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5
|
||||||
|
|
||||||
## Updates
|
## Updates
|
||||||
<details open>
|
<details open>
|
||||||
|
<summary><strong>2023-03-12</strong></summary>
|
||||||
|
Really thankful for all the issues reported and contributions made, the project's features and improvements have accelerated as result. Honorable mention is [wtlyu](https://github.com/wtlyu) for contributing a lot of mindful code, namely hostname configuration and mobile styling. I will upload images on next release for faster docker setup, and starting updating them simultaneously with this repo.
|
||||||
|
|
||||||
|
|
||||||
|
Many improvements across the board, the biggest is being able to start conversations simultaneously (again thanks to [wtlyu](https://github.com/wtlyu) for bringing it to my attention), as you can switch conversations or start a new chat without any response streaming from a prior one, as the backend will still process/save client responses. Just watch out for any rate limiting from OpenAI/Microsoft if this is done excessively.
|
||||||
|
|
||||||
|
|
||||||
|
Adding support for conversation search is next! Thank you [mysticaltech](https://github.com/mysticaltech) for bringing up a method I can use for this.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<details>
|
||||||
<summary><strong>2023-03-09</strong></summary>
|
<summary><strong>2023-03-09</strong></summary>
|
||||||
Released v.0.0.2
|
Released v.0.0.2
|
||||||
|
|
||||||
|
|
@ -16,8 +28,6 @@ Adds Sydney (jailbroken Bing AI) to the model menu. Thank you [DavesDevFails](ht
|
||||||
|
|
||||||
I've re-enabled the ChatGPT browser client (free version) since it might be working for most people, it no longer works for me. Sydney is the best free route anyway.
|
I've re-enabled the ChatGPT browser client (free version) since it might be working for most people, it no longer works for me. Sydney is the best free route anyway.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong>2023-03-07</strong></summary>
|
<summary><strong>2023-03-07</strong></summary>
|
||||||
Due to increased interest in the repo, I've dockerized the app as of this update for quick setup! See setup instructions below. I realize this still takes some time with installing docker dependencies, so it's on the roadmap to have a deployed demo. Besides this, I've made major improvements for a lot of the existing features across the board, mainly UI/UX.
|
Due to increased interest in the repo, I've dockerized the app as of this update for quick setup! See setup instructions below. I realize this still takes some time with installing docker dependencies, so it's on the roadmap to have a deployed demo. Besides this, I've made major improvements for a lot of the existing features across the board, mainly UI/UX.
|
||||||
|
|
@ -95,6 +105,7 @@ Here are my recently completed and planned features:
|
||||||
- [x] Customize prompt prefix/label (custom ChatGPT using official API)
|
- [x] Customize prompt prefix/label (custom ChatGPT using official API)
|
||||||
- [x] Server convo pagination (limit fetch and load more with 'show more' button)
|
- [x] Server convo pagination (limit fetch and load more with 'show more' button)
|
||||||
- [x] Config file for easy startup (docker compose)
|
- [x] Config file for easy startup (docker compose)
|
||||||
|
- [x] Mobile styling (thanks to [wtlyu](https://github.com/wtlyu))
|
||||||
- [ ] Bing AI Styling (for suggested responses, convo end, etc.) - **In progress**
|
- [ ] Bing AI Styling (for suggested responses, convo end, etc.) - **In progress**
|
||||||
- [ ] Add warning before clearing convos
|
- [ ] Add warning before clearing convos
|
||||||
- [ ] Build test suite for CI/CD
|
- [ ] Build test suite for CI/CD
|
||||||
|
|
@ -104,7 +115,6 @@ Here are my recently completed and planned features:
|
||||||
- [ ] Prompt Templates/Search
|
- [ ] Prompt Templates/Search
|
||||||
- [ ] Refactor/clean up code (tech debt)
|
- [ ] Refactor/clean up code (tech debt)
|
||||||
- [ ] Optional use of local storage for credentials
|
- [ ] Optional use of local storage for credentials
|
||||||
- [ ] Mobile styling (half-finished)
|
|
||||||
- [ ] Deploy demo
|
- [ ] Deploy demo
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ const { KeyvFile } = require('keyv-file');
|
||||||
|
|
||||||
const clientOptions = {
|
const clientOptions = {
|
||||||
// Warning: This will expose your access token to a third party. Consider the risks before using this.
|
// Warning: This will expose your access token to a third party. Consider the risks before using this.
|
||||||
reverseProxyUrl: 'https://chatgpt.duti.tech/api/conversation',
|
reverseProxyUrl: 'https://bypass.duti.tech/api/conversation',
|
||||||
// Access token from https://chat.openai.com/api/auth/session
|
// Access token from https://chat.openai.com/api/auth/session
|
||||||
accessToken: process.env.CHATGPT_TOKEN,
|
accessToken: process.env.CHATGPT_TOKEN,
|
||||||
// debug: true
|
// debug: true
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const { ModelOperations } = require('@vscode/vscode-languagedetection');
|
||||||
const languages = require('../utils/languages.js');
|
const languages = require('../utils/languages.js');
|
||||||
const codeRegex = /(```[\s\S]*?```)/g;
|
const codeRegex = /(```[\s\S]*?```)/g;
|
||||||
// const languageMatch = /```(\w+)/;
|
// const languageMatch = /```(\w+)/;
|
||||||
const replaceRegex = /```\w+/g;
|
const replaceRegex = /```\w+\n/g;
|
||||||
|
|
||||||
const detectCode = async (input) => {
|
const detectCode = async (input) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -22,7 +22,7 @@ const detectCode = async (input) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[detectCode.js] replacing', match, 'with', '```shell');
|
console.log('[detectCode.js] replacing', match, 'with', '```shell');
|
||||||
text = text.replace(match, '```shell');
|
text = text.replace(match, '```shell\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,10 @@ router.post('/', async (req, res) => {
|
||||||
sendMessage(res, { ...partial, message: true });
|
sendMessage(res, { ...partial, message: true });
|
||||||
} else {
|
} else {
|
||||||
tokens += partial === text ? '' : partial;
|
tokens += partial === text ? '' : partial;
|
||||||
|
if (tokens.match(/^\n/)) {
|
||||||
|
tokens = tokens.replace(/^\n/, '');
|
||||||
|
}
|
||||||
|
|
||||||
if (tokens.includes('[DONE]')) {
|
if (tokens.includes('[DONE]')) {
|
||||||
tokens = tokens.replace('[DONE]', '');
|
tokens = tokens.replace('[DONE]', '');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
api/server/routes/stopStream.js
Normal file
11
api/server/routes/stopStream.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = (req, res, next) => {
|
||||||
|
let { stopStream } = req.body;
|
||||||
|
if (stopStream) {
|
||||||
|
console.log('stopStream');
|
||||||
|
res.write('event: stop\ndata:\n\n');
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -3,8 +3,8 @@ import RenameButton from './RenameButton';
|
||||||
import DeleteButton from './DeleteButton';
|
import DeleteButton from './DeleteButton';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { setConversation } from '~/store/convoSlice';
|
import { setConversation } from '~/store/convoSlice';
|
||||||
import { setCustomGpt, setModel, setCustomModel } from '~/store/submitSlice';
|
import { setSubmission, setStopStream, setCustomGpt, setModel, setCustomModel } from '~/store/submitSlice';
|
||||||
import { setMessages } from '~/store/messageSlice';
|
import { setMessages, setEmptyMessage } from '~/store/messageSlice';
|
||||||
import { setText } from '~/store/textSlice';
|
import { setText } from '~/store/textSlice';
|
||||||
import manualSWR from '~/utils/fetchers';
|
import manualSWR from '~/utils/fetchers';
|
||||||
import ConvoIcon from '../svg/ConvoIcon';
|
import ConvoIcon from '../svg/ConvoIcon';
|
||||||
|
|
@ -14,13 +14,15 @@ export default function Conversation({
|
||||||
parentMessageId,
|
parentMessageId,
|
||||||
conversationId,
|
conversationId,
|
||||||
title = 'New conversation',
|
title = 'New conversation',
|
||||||
bingData,
|
|
||||||
chatGptLabel = null,
|
chatGptLabel = null,
|
||||||
promptPrefix = null
|
promptPrefix = null,
|
||||||
|
bingData,
|
||||||
|
retainView,
|
||||||
}) {
|
}) {
|
||||||
const [renaming, setRenaming] = useState(false);
|
const [renaming, setRenaming] = useState(false);
|
||||||
const [titleInput, setTitleInput] = useState(title);
|
const [titleInput, setTitleInput] = useState(title);
|
||||||
const { modelMap } = useSelector((state) => state.models);
|
const { modelMap } = useSelector((state) => state.models);
|
||||||
|
const { stopStream } = useSelector((state) => state.submit);
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { trigger } = manualSWR(`/api/messages/${id}`, 'get');
|
const { trigger } = manualSWR(`/api/messages/${id}`, 'get');
|
||||||
|
|
@ -31,6 +33,12 @@ export default function Conversation({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!stopStream) {
|
||||||
|
dispatch(setStopStream(true));
|
||||||
|
dispatch(setSubmission({}));
|
||||||
|
}
|
||||||
|
dispatch(setEmptyMessage());
|
||||||
|
|
||||||
const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix };
|
const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix };
|
||||||
|
|
||||||
if (bingData) {
|
if (bingData) {
|
||||||
|
|
@ -81,6 +89,7 @@ export default function Conversation({
|
||||||
dispatch(setMessages(data));
|
dispatch(setMessages(data));
|
||||||
dispatch(setCustomGpt(convo));
|
dispatch(setCustomGpt(convo));
|
||||||
dispatch(setText(''));
|
dispatch(setText(''));
|
||||||
|
dispatch(setStopStream(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const renameHandler = (e) => {
|
const renameHandler = (e) => {
|
||||||
|
|
@ -154,6 +163,7 @@ export default function Conversation({
|
||||||
conversationId={id}
|
conversationId={id}
|
||||||
renaming={renaming}
|
renaming={renaming}
|
||||||
cancelHandler={cancelHandler}
|
cancelHandler={cancelHandler}
|
||||||
|
retainView={retainView}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ import manualSWR from '~/utils/fetchers';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setNewConvo, removeConvo } from '~/store/convoSlice';
|
import { setNewConvo, removeConvo } from '~/store/convoSlice';
|
||||||
import { setMessages } from '~/store/messageSlice';
|
import { setMessages } from '~/store/messageSlice';
|
||||||
|
import { setSubmission } from '~/store/submitSlice';
|
||||||
|
|
||||||
export default function DeleteButton({ conversationId, renaming, cancelHandler }) {
|
export default function DeleteButton({ conversationId, renaming, cancelHandler, retainView }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { trigger } = manualSWR(
|
const { trigger } = manualSWR(
|
||||||
`/api/convos/clear`,
|
`/api/convos/clear`,
|
||||||
|
|
@ -15,6 +16,8 @@ export default function DeleteButton({ conversationId, renaming, cancelHandler }
|
||||||
dispatch(setMessages([]));
|
dispatch(setMessages([]));
|
||||||
dispatch(removeConvo(conversationId));
|
dispatch(removeConvo(conversationId));
|
||||||
dispatch(setNewConvo());
|
dispatch(setNewConvo());
|
||||||
|
dispatch(setSubmission({}));
|
||||||
|
retainView();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,11 @@ export default function Conversations({ conversations, conversationId, showMore
|
||||||
chatGptLabel={convo.chatGptLabel}
|
chatGptLabel={convo.chatGptLabel}
|
||||||
promptPrefix={convo.promptPrefix}
|
promptPrefix={convo.promptPrefix}
|
||||||
bingData={bingData}
|
bingData={bingData}
|
||||||
|
retainView={showMore.bind(null, false)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{conversations && conversations.length >= 12 && conversations.length % 12 === 0 && (
|
{conversations?.length >= 12 && (
|
||||||
<button
|
<button
|
||||||
onClick={clickHandler}
|
onClick={clickHandler}
|
||||||
className="btn btn-dark btn-small m-auto mb-2 flex justify-center gap-2"
|
className="btn btn-dark btn-small m-auto mb-2 flex justify-center gap-2"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { SSE } from '~/utils/sse';
|
||||||
import SubmitButton from './SubmitButton';
|
import SubmitButton from './SubmitButton';
|
||||||
import Regenerate from './Regenerate';
|
import Regenerate from './Regenerate';
|
||||||
import ModelMenu from '../Models/ModelMenu';
|
import ModelMenu from '../Models/ModelMenu';
|
||||||
|
|
@ -8,7 +9,7 @@ import handleSubmit from '~/utils/handleSubmit';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { setConversation, setError } from '~/store/convoSlice';
|
import { setConversation, setError } from '~/store/convoSlice';
|
||||||
import { setMessages } from '~/store/messageSlice';
|
import { setMessages } from '~/store/messageSlice';
|
||||||
import { setSubmitState } from '~/store/submitSlice';
|
import { setSubmitState, setSubmission } from '~/store/submitSlice';
|
||||||
import { setText } from '~/store/textSlice';
|
import { setText } from '~/store/textSlice';
|
||||||
|
|
||||||
export default function TextChat({ messages }) {
|
export default function TextChat({ messages }) {
|
||||||
|
|
@ -16,43 +17,26 @@ export default function TextChat({ messages }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const convo = useSelector((state) => state.convo);
|
const convo = useSelector((state) => state.convo);
|
||||||
const { initial } = useSelector((state) => state.models);
|
const { initial } = useSelector((state) => state.models);
|
||||||
const { isSubmitting, disabled, model, chatGptLabel, promptPrefix } = useSelector(
|
const { isSubmitting, stopStream, submission, disabled, model, chatGptLabel, promptPrefix } =
|
||||||
(state) => state.submit
|
useSelector((state) => state.submit);
|
||||||
);
|
|
||||||
const { text } = useSelector((state) => state.text);
|
const { text } = useSelector((state) => state.text);
|
||||||
const { error } = convo;
|
const { error } = convo;
|
||||||
const isCustomModel = model === 'chatgptCustom' || !initial[model];
|
|
||||||
|
|
||||||
const submitMessage = () => {
|
const messageHandler = (data, currentState) => {
|
||||||
if (error) {
|
const { messages, currentMsg, sender } = currentState;
|
||||||
dispatch(setError(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!!isSubmitting || text.trim() === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(setSubmitState(true));
|
|
||||||
const message = text.trim();
|
|
||||||
const currentMsg = { sender: 'User', text: message, current: true };
|
|
||||||
const sender = model === 'chatgptCustom' ? chatGptLabel : model;
|
|
||||||
const initialResponse = { sender, text: '' };
|
|
||||||
dispatch(setMessages([...messages, currentMsg, initialResponse]));
|
|
||||||
dispatch(setText(''));
|
|
||||||
const messageHandler = (data) => {
|
|
||||||
dispatch(setMessages([...messages, currentMsg, { sender, text: data }]));
|
dispatch(setMessages([...messages, currentMsg, { sender, text: data }]));
|
||||||
};
|
};
|
||||||
const convoHandler = (data) => {
|
|
||||||
|
const convoHandler = (data, currentState) => {
|
||||||
|
const { messages, currentMsg, sender, isCustomModel, model, chatGptLabel, promptPrefix } =
|
||||||
|
currentState;
|
||||||
dispatch(
|
dispatch(
|
||||||
setMessages([...messages, currentMsg, { sender, text: data.text || data.response }])
|
setMessages([...messages, currentMsg, { sender, text: data.text || data.response }])
|
||||||
);
|
);
|
||||||
|
|
||||||
const isBing = model === 'bingai' || model === 'sydney';
|
const isBing = model === 'bingai' || model === 'sydney';
|
||||||
|
|
||||||
if (
|
if (!isBing && convo.conversationId === null && convo.parentMessageId === null) {
|
||||||
!isBing &&
|
|
||||||
convo.conversationId === null &&
|
|
||||||
convo.parentMessageId === null
|
|
||||||
) {
|
|
||||||
const { title, conversationId, id } = data;
|
const { title, conversationId, id } = data;
|
||||||
dispatch(
|
dispatch(
|
||||||
setConversation({
|
setConversation({
|
||||||
|
|
@ -72,14 +56,8 @@ export default function TextChat({ messages }) {
|
||||||
convo.conversationId === null &&
|
convo.conversationId === null &&
|
||||||
convo.invocationId === null
|
convo.invocationId === null
|
||||||
) {
|
) {
|
||||||
console.log('Bing data:', data)
|
console.log('Bing data:', data);
|
||||||
const {
|
const { title, conversationSignature, clientId, conversationId, invocationId } = data;
|
||||||
title,
|
|
||||||
conversationSignature,
|
|
||||||
clientId,
|
|
||||||
conversationId,
|
|
||||||
invocationId
|
|
||||||
} = data;
|
|
||||||
dispatch(
|
dispatch(
|
||||||
setConversation({
|
setConversation({
|
||||||
title,
|
title,
|
||||||
|
|
@ -87,7 +65,7 @@ export default function TextChat({ messages }) {
|
||||||
conversationSignature,
|
conversationSignature,
|
||||||
clientId,
|
clientId,
|
||||||
conversationId,
|
conversationId,
|
||||||
invocationId,
|
invocationId
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else if (model === 'sydney') {
|
} else if (model === 'sydney') {
|
||||||
|
|
@ -108,7 +86,7 @@ export default function TextChat({ messages }) {
|
||||||
conversationSignature,
|
conversationSignature,
|
||||||
clientId,
|
clientId,
|
||||||
conversationId,
|
conversationId,
|
||||||
invocationId,
|
invocationId
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +94,8 @@ export default function TextChat({ messages }) {
|
||||||
dispatch(setSubmitState(false));
|
dispatch(setSubmitState(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorHandler = (event) => {
|
const errorHandler = (event, currentState) => {
|
||||||
|
const { initialResponse, messages, currentMsg, message } = currentState;
|
||||||
console.log('Error:', event);
|
console.log('Error:', event);
|
||||||
const errorResponse = {
|
const errorResponse = {
|
||||||
...initialResponse,
|
...initialResponse,
|
||||||
|
|
@ -130,20 +109,124 @@ export default function TextChat({ messages }) {
|
||||||
dispatch(setError(true));
|
dispatch(setError(true));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const submitMessage = () => {
|
||||||
|
if (error) {
|
||||||
|
dispatch(setError(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!isSubmitting || text.trim() === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCustomModel = model === 'chatgptCustom' || !initial[model];
|
||||||
|
const message = text.trim();
|
||||||
|
const currentMsg = { sender: 'User', text: message, current: true };
|
||||||
|
const sender = model === 'chatgptCustom' ? chatGptLabel : model;
|
||||||
|
const initialResponse = { sender, text: '' };
|
||||||
|
|
||||||
|
dispatch(setSubmitState(true));
|
||||||
|
dispatch(setMessages([...messages, currentMsg, initialResponse]));
|
||||||
|
dispatch(setText(''));
|
||||||
|
|
||||||
const submission = {
|
const submission = {
|
||||||
model,
|
model,
|
||||||
text: message,
|
text: message,
|
||||||
convo,
|
convo,
|
||||||
messageHandler,
|
|
||||||
convoHandler,
|
|
||||||
errorHandler,
|
|
||||||
chatGptLabel,
|
chatGptLabel,
|
||||||
promptPrefix
|
promptPrefix,
|
||||||
|
isCustomModel,
|
||||||
|
message,
|
||||||
|
messages,
|
||||||
|
currentMsg,
|
||||||
|
sender,
|
||||||
|
initialResponse
|
||||||
};
|
};
|
||||||
console.log('User Input:', message);
|
console.log('User Input:', message);
|
||||||
handleSubmit(submission);
|
// handleSubmit(submission);
|
||||||
|
dispatch(setSubmission(submission));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createPayload = ({ model, text, convo, chatGptLabel, promptPrefix }) => {
|
||||||
|
const endpoint = `/api/ask`;
|
||||||
|
let payload = { model, text, chatGptLabel, promptPrefix };
|
||||||
|
if (convo.conversationId && convo.parentMessageId) {
|
||||||
|
payload = {
|
||||||
|
...payload,
|
||||||
|
conversationId: convo.conversationId,
|
||||||
|
parentMessageId: convo.parentMessageId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBing = model === 'bingai' || model === 'sydney';
|
||||||
|
if (isBing && convo.conversationId) {
|
||||||
|
payload = {
|
||||||
|
...payload,
|
||||||
|
jailbreakConversationId: convo.jailbreakConversationId,
|
||||||
|
conversationId: convo.conversationId,
|
||||||
|
conversationSignature: convo.conversationSignature,
|
||||||
|
clientId: convo.clientId,
|
||||||
|
invocationId: convo.invocationId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let server = endpoint;
|
||||||
|
server = model === 'bingai' ? server + '/bing' : server;
|
||||||
|
server = model === 'sydney' ? server + '/sydney' : server;
|
||||||
|
return { server, payload };
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (Object.keys(submission).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentState = submission;
|
||||||
|
const { server, payload } = createPayload(submission);
|
||||||
|
const onMessage = (e) => {
|
||||||
|
if (stopStream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = JSON.parse(e.data);
|
||||||
|
let text = data.text || data.response;
|
||||||
|
if (data.message) {
|
||||||
|
messageHandler(text, currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.final) {
|
||||||
|
convoHandler(data, currentState);
|
||||||
|
console.log('final', data);
|
||||||
|
} else {
|
||||||
|
// console.log('dataStream', data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const events = new SSE(server, {
|
||||||
|
payload: JSON.stringify(payload),
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
events.onopen = function () {
|
||||||
|
console.log('connection is opened');
|
||||||
|
};
|
||||||
|
|
||||||
|
events.onmessage = onMessage;
|
||||||
|
|
||||||
|
events.onerror = function (e) {
|
||||||
|
console.log('error in opening conn.');
|
||||||
|
events.close();
|
||||||
|
errorHandler(e, currentState);
|
||||||
|
};
|
||||||
|
|
||||||
|
events.stream();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
events.removeEventListener('message', onMessage);
|
||||||
|
events.close();
|
||||||
|
};
|
||||||
|
}, [submission]);
|
||||||
|
|
||||||
const handleKeyDown = (e) => {
|
const handleKeyDown = (e) => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useSelector } from 'react-redux';
|
||||||
import GPTIcon from '../svg/GPTIcon';
|
import GPTIcon from '../svg/GPTIcon';
|
||||||
import BingIcon from '../svg/BingIcon';
|
import BingIcon from '../svg/BingIcon';
|
||||||
import HoverButtons from './HoverButtons';
|
import HoverButtons from './HoverButtons';
|
||||||
|
import Spinner from '../svg/Spinner';
|
||||||
|
|
||||||
export default function Message({
|
export default function Message({
|
||||||
sender,
|
sender,
|
||||||
|
|
@ -23,6 +24,10 @@ export default function Message({
|
||||||
}
|
}
|
||||||
}, [isSubmitting, text, blinker, scrollToBottom, abortScroll]);
|
}, [isSubmitting, text, blinker, scrollToBottom, abortScroll]);
|
||||||
|
|
||||||
|
if (sender === '') {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
|
||||||
const handleWheel = () => {
|
const handleWheel = () => {
|
||||||
if (blinker) {
|
if (blinker) {
|
||||||
setAbort(true);
|
setAbort(true);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { setModel, setCustomGpt } from '~/store/submitSlice';
|
import { setSubmission, setModel, setCustomGpt } from '~/store/submitSlice';
|
||||||
import { setNewConvo } from '~/store/convoSlice';
|
import { setNewConvo } from '~/store/convoSlice';
|
||||||
import manualSWR from '~/utils/fetchers';
|
import manualSWR from '~/utils/fetchers';
|
||||||
import { Button } from '../ui/Button.tsx';
|
import { Button } from '../ui/Button.tsx';
|
||||||
|
|
@ -39,6 +39,7 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
|
||||||
handleSaveState(chatGptLabel.toLowerCase());
|
handleSaveState(chatGptLabel.toLowerCase());
|
||||||
// Set new conversation
|
// Set new conversation
|
||||||
dispatch(setNewConvo());
|
dispatch(setNewConvo());
|
||||||
|
dispatch(setSubmission({}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveHandler = (e) => {
|
const saveHandler = (e) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { setModel, setDisabled, setCustomGpt, setCustomModel } from '~/store/submitSlice';
|
import { setSubmission, setModel, setDisabled, setCustomGpt, setCustomModel } from '~/store/submitSlice';
|
||||||
import { setNewConvo } from '~/store/convoSlice';
|
import { setNewConvo } from '~/store/convoSlice';
|
||||||
import ModelDialog from './ModelDialog';
|
import ModelDialog from './ModelDialog';
|
||||||
import MenuItems from './MenuItems';
|
import MenuItems from './MenuItems';
|
||||||
|
|
@ -77,6 +77,7 @@ export default function ModelMenu() {
|
||||||
|
|
||||||
// Set new conversation
|
// Set new conversation
|
||||||
dispatch(setNewConvo());
|
dispatch(setNewConvo());
|
||||||
|
dispatch(setSubmission({}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOpenChange = (open) => {
|
const onOpenChange = (open) => {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ export default function ClearConvos() {
|
||||||
const { trigger } = manualSWR(`/api/convos/clear`, 'post', () => {
|
const { trigger } = manualSWR(`/api/convos/clear`, 'post', () => {
|
||||||
dispatch(setMessages([]));
|
dispatch(setMessages([]));
|
||||||
dispatch(setNewConvo());
|
dispatch(setNewConvo());
|
||||||
|
dispatch(setSubmission({}));
|
||||||
mutate(`/api/convos`);
|
mutate(`/api/convos`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export default function MobileNav({ setNavVisible }) {
|
||||||
dispatch(setText(''));
|
dispatch(setText(''));
|
||||||
dispatch(setMessages([]));
|
dispatch(setMessages([]));
|
||||||
dispatch(setNewConvo());
|
dispatch(setNewConvo());
|
||||||
|
dispatch(setSubmission({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = convos?.find(element => element?.conversationId == conversationId)?.title || 'New Chat';
|
const title = convos?.find(element => element?.conversationId == conversationId)?.title || 'New Chat';
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { setNewConvo } from '~/store/convoSlice';
|
import { setNewConvo } from '~/store/convoSlice';
|
||||||
import { setMessages } from '~/store/messageSlice';
|
import { setMessages } from '~/store/messageSlice';
|
||||||
|
import { setSubmission } from '~/store/submitSlice';
|
||||||
import { setText } from '~/store/textSlice';
|
import { setText } from '~/store/textSlice';
|
||||||
|
|
||||||
export default function NewChat() {
|
export default function NewChat() {
|
||||||
|
|
@ -11,6 +12,7 @@ export default function NewChat() {
|
||||||
dispatch(setText(''));
|
dispatch(setText(''));
|
||||||
dispatch(setMessages([]));
|
dispatch(setMessages([]));
|
||||||
dispatch(setNewConvo());
|
dispatch(setNewConvo());
|
||||||
|
dispatch(setSubmission({}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,15 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||||
const scrollPositionRef = useRef(null);
|
const scrollPositionRef = useRef(null);
|
||||||
|
|
||||||
const showMore = async (increment = true) => {
|
const showMore = async (increment = true) => {
|
||||||
if (increment) {
|
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (container) {
|
if (container) {
|
||||||
scrollPositionRef.current = container.scrollTop;
|
scrollPositionRef.current = container.scrollTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (increment) {
|
||||||
dispatch(incrementPage());
|
dispatch(incrementPage());
|
||||||
}
|
|
||||||
await mutate();
|
await mutate();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useDidMountEffect(() => mutate(), [conversationId]);
|
useDidMountEffect(() => mutate(), [conversationId]);
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,20 @@ const currentSlice = createSlice({
|
||||||
setMessages: (state, action) => {
|
setMessages: (state, action) => {
|
||||||
state.messages = action.payload;
|
state.messages = action.payload;
|
||||||
},
|
},
|
||||||
|
setEmptyMessage: (state) => {
|
||||||
|
state.messages = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
conversationId: '1',
|
||||||
|
parentMessageId: '1',
|
||||||
|
sender: '',
|
||||||
|
text: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setMessages, setSubmitState } = currentSlice.actions;
|
export const { setMessages, setEmptyMessage } = currentSlice.actions;
|
||||||
|
|
||||||
export default currentSlice.reducer;
|
export default currentSlice.reducer;
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
|
submission: {},
|
||||||
|
stopStream: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
model: 'chatgpt',
|
model: 'chatgpt',
|
||||||
promptPrefix: '',
|
promptPrefix: '',
|
||||||
chatGptLabel: '',
|
chatGptLabel: '',
|
||||||
customModel: null
|
customModel: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentSlice = createSlice({
|
const currentSlice = createSlice({
|
||||||
|
|
@ -16,6 +18,15 @@ const currentSlice = createSlice({
|
||||||
setSubmitState: (state, action) => {
|
setSubmitState: (state, action) => {
|
||||||
state.isSubmitting = action.payload;
|
state.isSubmitting = action.payload;
|
||||||
},
|
},
|
||||||
|
setSubmission: (state, action) => {
|
||||||
|
state.submission = action.payload;
|
||||||
|
if (Object.keys(action.payload).length === 0) {
|
||||||
|
state.isSubmitting = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setStopStream: (state, action) => {
|
||||||
|
state.stopStream = action.payload;
|
||||||
|
},
|
||||||
setDisabled: (state, action) => {
|
setDisabled: (state, action) => {
|
||||||
state.disabled = action.payload;
|
state.disabled = action.payload;
|
||||||
},
|
},
|
||||||
|
|
@ -32,7 +43,7 @@ const currentSlice = createSlice({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setSubmitState, setDisabled, setModel, setCustomGpt, setCustomModel } =
|
export const { setSubmitState, setSubmission, setStopStream, setDisabled, setModel, setCustomGpt, setCustomModel } =
|
||||||
currentSlice.actions;
|
currentSlice.actions;
|
||||||
|
|
||||||
export default currentSlice.reducer;
|
export default currentSlice.reducer;
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export default function handleSubmit({
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
let text = data.text || data.response;
|
let text = data.text || data.response;
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
messageHandler(text);
|
messageHandler(text, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.final) {
|
if (data.final) {
|
||||||
|
|
@ -68,5 +68,11 @@ export default function handleSubmit({
|
||||||
errorHandler(e);
|
errorHandler(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
events.addEventListener('stop', () => {
|
||||||
|
// Close the SSE stream
|
||||||
|
console.log('stop event received');
|
||||||
|
events.close();
|
||||||
|
});
|
||||||
|
|
||||||
events.stream();
|
events.stream();
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue