Merge pull request #45 from danny-avila/submit-state

fixes: allow simultaneous convos & minor improvements
This commit is contained in:
Danny Avila 2023-03-11 23:28:33 -05:00 committed by GitHub
commit a705e907ff
19 changed files with 290 additions and 128 deletions

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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]', '');
} }

View 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();
}
};

View file

@ -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>
) : ( ) : (

View file

@ -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();
} }
); );

View file

@ -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"

View file

@ -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();

View file

@ -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);

View file

@ -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) => {

View file

@ -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) => {

View file

@ -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`);
}); });

View file

@ -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';

View file

@ -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 (

View file

@ -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]);

View file

@ -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;

View file

@ -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;

View file

@ -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();
} }