mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
refactor: basic message and send message. as well as model
THIS IS NOT FINISHED. DONT USE THIS
This commit is contained in:
parent
de8f519742
commit
c7c30d8bb5
24 changed files with 1057 additions and 1035 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
|
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
|
||||||
import Root from './routes/Root';
|
import Root from './routes/Root';
|
||||||
// import Chat from './routes/Chat';
|
import Chat from './routes/Chat';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
import userAuth from './utils/userAuth';
|
import userAuth from './utils/userAuth';
|
||||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||||
|
|
@ -24,7 +24,7 @@ const router = createBrowserRouter([
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'chat/:conversationId',
|
path: 'chat/:conversationId',
|
||||||
element: null //<Chat />
|
element: <Chat />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -33,14 +33,45 @@ const router = createBrowserRouter([
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [user, setUser] = useRecoilState(store.user);
|
const [user, setUser] = useRecoilState(store.user);
|
||||||
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);
|
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);
|
||||||
|
const setModelsFilter = useSetRecoilState(store.modelsFilter);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios.get('/api/search/enable').then(res => {
|
// fetch if seatch enabled
|
||||||
setIsSearchEnabled(res.data);
|
axios
|
||||||
});
|
.get('/api/search/enable', {
|
||||||
|
timeout: 1000,
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
setIsSearchEnabled(res.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// fetch user
|
||||||
userAuth()
|
userAuth()
|
||||||
.then(user => setUser(user))
|
.then(user => setUser(user))
|
||||||
.catch(err => console.log(err));
|
.catch(err => console.log(err));
|
||||||
|
|
||||||
|
// fetch models
|
||||||
|
axios
|
||||||
|
.get('/api/models', {
|
||||||
|
timeout: 1000,
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
const filter = {
|
||||||
|
chatgpt: data?.hasOpenAI,
|
||||||
|
chatgptCustom: data?.hasOpenAI,
|
||||||
|
bingai: data?.hasBing,
|
||||||
|
sydney: data?.hasBing,
|
||||||
|
chatgptBrowser: data?.hasChatGpt
|
||||||
|
};
|
||||||
|
setModelsFilter(filter);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
console.log('Not login!');
|
||||||
|
window.location.href = '/auth/login';
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (user)
|
if (user)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export default function Conversation({ conversation, retainView }) {
|
||||||
const resetLatestMessage = useResetRecoilState(store.latestMessage);
|
const resetLatestMessage = useResetRecoilState(store.latestMessage);
|
||||||
|
|
||||||
const { refreshConversations } = store.useConversations();
|
const { refreshConversations } = store.useConversations();
|
||||||
|
const { switchToConversation } = store.useConversation();
|
||||||
|
|
||||||
const [renaming, setRenaming] = useState(false);
|
const [renaming, setRenaming] = useState(false);
|
||||||
const [titleInput, setTitleInput] = useState(title);
|
const [titleInput, setTitleInput] = useState(title);
|
||||||
|
|
@ -56,9 +57,7 @@ export default function Conversation({ conversation, retainView }) {
|
||||||
setSubmission(null);
|
setSubmission(null);
|
||||||
|
|
||||||
// set conversation to the new conversation
|
// set conversation to the new conversation
|
||||||
setCurrentConversation(conversation);
|
switchToConversation(conversation);
|
||||||
setMessages(null);
|
|
||||||
resetLatestMessage();
|
|
||||||
|
|
||||||
// if (!stopStream) {
|
// if (!stopStream) {
|
||||||
// dispatch(setStopStream(true));
|
// dispatch(setStopStream(true));
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,12 @@ import ModelItem from './ModelItem';
|
||||||
export default function MenuItems({ models, onSelect }) {
|
export default function MenuItems({ models, onSelect }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{models.map((modelItem) => (
|
{models.map(modelItem => (
|
||||||
<ModelItem
|
<ModelItem
|
||||||
key={modelItem._id}
|
key={modelItem._id}
|
||||||
id={modelItem._id}
|
|
||||||
modelName={modelItem.name}
|
|
||||||
value={modelItem.value}
|
value={modelItem.value}
|
||||||
model={modelItem.model || 'chatgptCustom'}
|
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
chatGptLabel={modelItem.chatGptLabel}
|
model={modelItem}
|
||||||
promptPrefix={modelItem.promptPrefix}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
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 { setSubmission, setModel, setCustomGpt } from '~/store/submitSlice';
|
|
||||||
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';
|
||||||
import { Input } from '../ui/Input.tsx';
|
import { Input } from '../../ui/Input.tsx';
|
||||||
import { Label } from '../ui/Label.tsx';
|
import { Label } from '../../ui/Label.tsx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DialogClose,
|
DialogClose,
|
||||||
|
|
@ -15,11 +12,13 @@ import {
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle
|
DialogTitle
|
||||||
} from '../ui/Dialog.tsx';
|
} from '../../ui/Dialog.tsx';
|
||||||
|
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
|
export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
|
||||||
const dispatch = useDispatch();
|
const { newConversation } = store.useConversation();
|
||||||
const { modelMap, initial } = useSelector((state) => state.models);
|
|
||||||
const [chatGptLabel, setChatGptLabel] = useState('');
|
const [chatGptLabel, setChatGptLabel] = useState('');
|
||||||
const [promptPrefix, setPromptPrefix] = useState('');
|
const [promptPrefix, setPromptPrefix] = useState('');
|
||||||
const [saveText, setSaveText] = useState('Save');
|
const [saveText, setSaveText] = useState('Save');
|
||||||
|
|
@ -27,22 +26,25 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const updateCustomGpt = manualSWR(`/api/customGpts/`, 'post');
|
const updateCustomGpt = manualSWR(`/api/customGpts/`, 'post');
|
||||||
|
|
||||||
const selectHandler = (e) => {
|
const selectHandler = e => {
|
||||||
if (chatGptLabel.length === 0) {
|
if (chatGptLabel.length === 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setRequired(true);
|
setRequired(true);
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
|
|
||||||
dispatch(setModel('chatgptCustom'));
|
|
||||||
handleSaveState(chatGptLabel.toLowerCase());
|
handleSaveState(chatGptLabel.toLowerCase());
|
||||||
|
|
||||||
// Set new conversation
|
// Set new conversation
|
||||||
dispatch(setNewConvo());
|
newConversation({
|
||||||
dispatch(setSubmission({}));
|
model: 'chatgptCustom',
|
||||||
|
chatGptLabel,
|
||||||
|
promptPrefix
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveHandler = (e) => {
|
const saveHandler = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setModelSave(true);
|
setModelSave(true);
|
||||||
const value = chatGptLabel.toLowerCase();
|
const value = chatGptLabel.toLowerCase();
|
||||||
|
|
@ -56,26 +58,30 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
|
||||||
updateCustomGpt.trigger({ value, chatGptLabel, promptPrefix });
|
updateCustomGpt.trigger({ value, chatGptLabel, promptPrefix });
|
||||||
|
|
||||||
mutate();
|
mutate();
|
||||||
setSaveText((prev) => prev + 'd!');
|
setSaveText(prev => prev + 'd!');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setSaveText('Save');
|
setSaveText('Save');
|
||||||
}, 2500);
|
}, 2500);
|
||||||
|
|
||||||
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
|
// dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
|
||||||
dispatch(setModel('chatgptCustom'));
|
newConversation({
|
||||||
// dispatch(setDisabled(false));
|
model: 'chatgptCustom',
|
||||||
|
chatGptLabel,
|
||||||
|
promptPrefix
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
// Commented by wtlyu
|
||||||
chatGptLabel !== 'chatgptCustom' &&
|
// if (
|
||||||
modelMap[chatGptLabel.toLowerCase()] &&
|
// chatGptLabel !== 'chatgptCustom' &&
|
||||||
!initial[chatGptLabel.toLowerCase()] &&
|
// modelMap[chatGptLabel.toLowerCase()] &&
|
||||||
saveText === 'Save'
|
// !initial[chatGptLabel.toLowerCase()] &&
|
||||||
) {
|
// saveText === 'Save'
|
||||||
setSaveText('Update');
|
// ) {
|
||||||
} else if (!modelMap[chatGptLabel.toLowerCase()] && saveText === 'Update') {
|
// setSaveText('Update');
|
||||||
setSaveText('Save');
|
// } else if (!modelMap[chatGptLabel.toLowerCase()] && saveText === 'Update') {
|
||||||
}
|
// setSaveText('Save');
|
||||||
|
// }
|
||||||
|
|
||||||
const requiredProp = required ? { required: true } : {};
|
const requiredProp = required ? { required: true } : {};
|
||||||
|
|
||||||
|
|
@ -84,8 +90,7 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-gray-800 dark:text-white">Customize ChatGPT</DialogTitle>
|
<DialogTitle className="text-gray-800 dark:text-white">Customize ChatGPT</DialogTitle>
|
||||||
<DialogDescription className="text-gray-600 dark:text-gray-300">
|
<DialogDescription className="text-gray-600 dark:text-gray-300">
|
||||||
Note: important instructions are often better placed in your message rather than the
|
Note: important instructions are often better placed in your message rather than the prefix.{' '}
|
||||||
prefix.{' '}
|
|
||||||
<a
|
<a
|
||||||
href="https://platform.openai.com/docs/guides/chat/instructing-chat-models"
|
href="https://platform.openai.com/docs/guides/chat/instructing-chat-models"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
@ -107,7 +112,7 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
|
||||||
id="chatGptLabel"
|
id="chatGptLabel"
|
||||||
value={chatGptLabel}
|
value={chatGptLabel}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
onChange={(e) => setChatGptLabel(e.target.value)}
|
onChange={e => setChatGptLabel(e.target.value)}
|
||||||
placeholder="Set a custom name for ChatGPT"
|
placeholder="Set a custom name for ChatGPT"
|
||||||
className=" col-span-3 shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 invalid:border-red-400 invalid:text-red-600 invalid:placeholder-red-600 invalid:placeholder-opacity-70 invalid:ring-opacity-10 focus:ring-0 focus:invalid:border-red-400 focus:invalid:ring-red-300 dark:border-none dark:bg-gray-700
|
className=" col-span-3 shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 invalid:border-red-400 invalid:text-red-600 invalid:placeholder-red-600 invalid:placeholder-opacity-70 invalid:ring-opacity-10 focus:ring-0 focus:invalid:border-red-400 focus:invalid:ring-red-300 dark:border-none dark:bg-gray-700
|
||||||
dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:invalid:border-red-600 dark:invalid:text-red-300 dark:invalid:placeholder-opacity-80 dark:focus:border-none dark:focus:border-transparent dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0 dark:focus:invalid:ring-red-600 dark:focus:invalid:ring-opacity-50"
|
dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:invalid:border-red-600 dark:invalid:text-red-300 dark:invalid:placeholder-opacity-80 dark:focus:border-none dark:focus:border-transparent dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0 dark:focus:invalid:ring-red-600 dark:focus:invalid:ring-opacity-50"
|
||||||
|
|
@ -124,7 +129,7 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) {
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
id="promptPrefix"
|
id="promptPrefix"
|
||||||
value={promptPrefix}
|
value={promptPrefix}
|
||||||
onChange={(e) => setPromptPrefix(e.target.value)}
|
onChange={e => setPromptPrefix(e.target.value)}
|
||||||
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"
|
placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'"
|
||||||
className="col-span-3 flex h-20 w-full resize-none rounded-md border border-gray-300 bg-transparent py-2 px-3 text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-none dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-none dark:focus:border-transparent dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0"
|
className="col-span-3 flex h-20 w-full resize-none rounded-md border border-gray-300 bg-transparent py-2 px-3 text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-none dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-none dark:focus:border-transparent dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0"
|
||||||
/>
|
/>
|
||||||
|
|
@ -1,36 +1,61 @@
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { DropdownMenuRadioItem } from '../ui/DropdownMenu.tsx';
|
import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx';
|
||||||
import { setModels } from '~/store/modelSlice';
|
|
||||||
import { Circle } from 'lucide-react';
|
import { Circle } from 'lucide-react';
|
||||||
import { DialogTrigger } from '../ui/Dialog.tsx';
|
import { DialogTrigger } from '../../ui/Dialog.tsx';
|
||||||
import RenameButton from '../Conversations/RenameButton';
|
import RenameButton from '../../Conversations/RenameButton';
|
||||||
import TrashIcon from '../svg/TrashIcon';
|
import TrashIcon from '../../svg/TrashIcon';
|
||||||
import manualSWR from '~/utils/fetchers';
|
import manualSWR from '~/utils/fetchers';
|
||||||
import { getIconOfModel } from '../../utils';
|
import { getIconOfModel } from '~/utils';
|
||||||
|
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
export default function ModelItem({ model: _model, value, onSelect }) {
|
||||||
|
const { name, model, _id: id, chatGptLabel = null, promptPrefix = null } = _model;
|
||||||
|
const setCustomGPTModels = useSetRecoilState(store.customGPTModels);
|
||||||
|
const currentConversation = useRecoilValue(store.conversation) || {};
|
||||||
|
|
||||||
export default function ModelItem({ modelName, value, model, onSelect, id, chatGptLabel, promptPrefix }) {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { customModel } = useSelector((state) => state.submit);
|
|
||||||
const { initial } = useSelector((state) => state.models);
|
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
const [renaming, setRenaming] = useState(false);
|
const [renaming, setRenaming] = useState(false);
|
||||||
const [currentName, setCurrentName] = useState(modelName);
|
const [currentName, setCurrentName] = useState(name);
|
||||||
const [modelInput, setModelInput] = useState(modelName);
|
const [modelInput, setModelInput] = useState(name);
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const rename = manualSWR(`/api/customGpts`, 'post');
|
const rename = manualSWR(`/api/customGpts`, 'post', res => {});
|
||||||
const deleteCustom = manualSWR(`/api/customGpts/delete`, 'post', (res) => {
|
const deleteCustom = manualSWR(`/api/customGpts/delete`, 'post', res => {
|
||||||
const fetchedModels = res.data.map((modelItem) => ({
|
const fetchedModels = res.data.map(modelItem => ({
|
||||||
...modelItem,
|
...modelItem,
|
||||||
name: modelItem.chatGptLabel
|
name: modelItem.chatGptLabel,
|
||||||
|
model: 'chatgptCustom'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dispatch(setModels(fetchedModels));
|
setCustomGPTModels(fetchedModels);
|
||||||
});
|
});
|
||||||
|
|
||||||
const icon = getIconOfModel({ size: 20, sender: modelName, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, className: "mr-2" });
|
const icon = getIconOfModel({
|
||||||
|
size: 20,
|
||||||
|
sender: chatGptLabel || model,
|
||||||
|
isCreatedByUser: false,
|
||||||
|
model,
|
||||||
|
chatGptLabel,
|
||||||
|
promptPrefix,
|
||||||
|
error: false,
|
||||||
|
className: 'mr-2'
|
||||||
|
});
|
||||||
|
|
||||||
if (value === 'chatgptCustom') {
|
if (model !== 'chatgptCustom')
|
||||||
|
// regular model
|
||||||
|
return (
|
||||||
|
<DropdownMenuRadioItem
|
||||||
|
value={value}
|
||||||
|
className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{name}
|
||||||
|
{model === 'chatgpt' && <sup>$</sup>}
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
);
|
||||||
|
else if (model === 'chatgptCustom' && chatGptLabel === null && promptPrefix === null)
|
||||||
|
// base chatgptCustom model, click to add new chatgptCustom.
|
||||||
return (
|
return (
|
||||||
<DialogTrigger className="w-full">
|
<DialogTrigger className="w-full">
|
||||||
<DropdownMenuRadioItem
|
<DropdownMenuRadioItem
|
||||||
|
|
@ -38,26 +63,13 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
|
||||||
className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
{modelName}
|
{name}
|
||||||
<sup>$</sup>
|
<sup>$</sup>
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (initial[value])
|
// else: a chatgptCustom model
|
||||||
return (
|
|
||||||
<DropdownMenuRadioItem
|
|
||||||
value={value}
|
|
||||||
className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
{modelName}
|
|
||||||
{value === 'chatgpt' && <sup>$</sup>}
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
const handleMouseOver = () => {
|
const handleMouseOver = () => {
|
||||||
setIsHovering(true);
|
setIsHovering(true);
|
||||||
};
|
};
|
||||||
|
|
@ -66,7 +78,7 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
|
||||||
setIsHovering(false);
|
setIsHovering(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renameHandler = (e) => {
|
const renameHandler = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setRenaming(true);
|
setRenaming(true);
|
||||||
|
|
@ -75,10 +87,10 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
|
||||||
}, 25);
|
}, 25);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRename = (e) => {
|
const onRename = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setRenaming(false);
|
setRenaming(false);
|
||||||
if (modelInput === modelName) {
|
if (modelInput === name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rename.trigger({
|
rename.trigger({
|
||||||
|
|
@ -89,13 +101,13 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
|
||||||
setCurrentName(modelInput);
|
setCurrentName(modelInput);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDelete = async (e) => {
|
const onDelete = async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await deleteCustom.trigger({ _id: id });
|
await deleteCustom.trigger({ _id: id });
|
||||||
onSelect('chatgpt', true);
|
onSelect('chatgpt');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e) => {
|
const handleKeyDown = e => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
onRename(e);
|
onRename(e);
|
||||||
}
|
}
|
||||||
|
|
@ -115,14 +127,14 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
|
||||||
<span
|
<span
|
||||||
value={value}
|
value={value}
|
||||||
className={itemClass.className}
|
className={itemClass.className}
|
||||||
onClick={(e) => {
|
onClick={e => {
|
||||||
if (isHovering) {
|
if (isHovering) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onSelect(value, true);
|
onSelect('chatgptCustom', value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{customModel === value && (
|
{currentConversation?.chatGptLabel === value && (
|
||||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
<Circle className="h-2 w-2 fill-current" />
|
<Circle className="h-2 w-2 fill-current" />
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -137,8 +149,8 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG
|
||||||
type="text"
|
type="text"
|
||||||
className="pointer-events-auto z-50 m-0 mr-2 w-3/4 border border-blue-500 bg-transparent p-0 text-sm leading-tight outline-none"
|
className="pointer-events-auto z-50 m-0 mr-2 w-3/4 border border-blue-500 bg-transparent p-0 text-sm leading-tight outline-none"
|
||||||
value={modelInput}
|
value={modelInput}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
onChange={(e) => setModelInput(e.target.value)}
|
onChange={e => setModelInput(e.target.value)}
|
||||||
// onBlur={onRename}
|
// onBlur={onRename}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
205
client/src/components/Input/Models/ModelMenu.jsx
Normal file
205
client/src/components/Input/Models/ModelMenu.jsx
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
import axios from 'axios';
|
||||||
|
import ModelDialog from './ModelDialog';
|
||||||
|
import MenuItems from './MenuItems';
|
||||||
|
import { swr } from '~/utils/fetchers';
|
||||||
|
import { getIconOfModel } from '~/utils';
|
||||||
|
|
||||||
|
import { Button } from '../../ui/Button.tsx';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from '../../ui/DropdownMenu.tsx';
|
||||||
|
import { Dialog } from '../../ui/Dialog.tsx';
|
||||||
|
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
export default function ModelMenu() {
|
||||||
|
const [modelSave, setModelSave] = useState(false);
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
const models = useRecoilValue(store.models);
|
||||||
|
const availableModels = useRecoilValue(store.availableModels);
|
||||||
|
const setCustomGPTModels = useSetRecoilState(store.customGPTModels);
|
||||||
|
|
||||||
|
const conversation = useRecoilValue(store.conversation) || {};
|
||||||
|
const { model, promptPrefix, chatGptLabel, conversationId } = conversation;
|
||||||
|
const { newConversation } = store.useConversation();
|
||||||
|
|
||||||
|
// fetch the list of saved chatgptCustom
|
||||||
|
const { data, isLoading, mutate } = swr(`/api/customGpts`, res => {
|
||||||
|
const fetchedModels = res.map(modelItem => ({
|
||||||
|
...modelItem,
|
||||||
|
name: modelItem.chatGptLabel,
|
||||||
|
model: 'chatgptCustom'
|
||||||
|
}));
|
||||||
|
|
||||||
|
setCustomGPTModels(fetchedModels);
|
||||||
|
});
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// mutate();
|
||||||
|
// try {
|
||||||
|
// const lastSelected = JSON.parse(localStorage.getItem('model'));
|
||||||
|
|
||||||
|
// if (lastSelected === 'chatgptCustom') {
|
||||||
|
// return;
|
||||||
|
// } else if (initial[lastSelected]) {
|
||||||
|
// dispatch(setModel(lastSelected));
|
||||||
|
// }
|
||||||
|
// } catch (err) {
|
||||||
|
// console.log(err);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// update the default model when availableModels changes
|
||||||
|
// typically, availableModels changes => modelsFilter or customGPTModels changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (conversationId == 'new') {
|
||||||
|
newConversation();
|
||||||
|
}
|
||||||
|
}, [availableModels]);
|
||||||
|
|
||||||
|
// save selected model to localstoreage
|
||||||
|
useEffect(() => {
|
||||||
|
if (model) localStorage.setItem('model', JSON.stringify({ model, chatGptLabel, promptPrefix }));
|
||||||
|
}, [model]);
|
||||||
|
|
||||||
|
// set the current model
|
||||||
|
const onChange = (newModel, value = null) => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
|
||||||
|
if (!newModel) {
|
||||||
|
return;
|
||||||
|
} else if (newModel === model && value === chatGptLabel) {
|
||||||
|
// bypass if not changed
|
||||||
|
return;
|
||||||
|
} else if (newModel === 'chatgptCustom' && value === null) {
|
||||||
|
// return;
|
||||||
|
} else if (newModel !== 'chatgptCustom') {
|
||||||
|
newConversation({
|
||||||
|
model: newModel,
|
||||||
|
chatGptLabel: null,
|
||||||
|
promptPrefix: null
|
||||||
|
});
|
||||||
|
} else if (newModel === 'chatgptCustom') {
|
||||||
|
const targetModel = models.find(element => element.value == value);
|
||||||
|
if (targetModel) {
|
||||||
|
const chatGptLabel = targetModel?.chatGptLabel;
|
||||||
|
const promptPrefix = targetModel?.promptPrefix;
|
||||||
|
newConversation({
|
||||||
|
model: newModel,
|
||||||
|
chatGptLabel,
|
||||||
|
promptPrefix
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenChange = open => {
|
||||||
|
mutate();
|
||||||
|
if (!open) {
|
||||||
|
setModelSave(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveState = value => {
|
||||||
|
if (!modelSave) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCustomGPTModels(value);
|
||||||
|
setModelSave(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultColorProps = [
|
||||||
|
'text-gray-500',
|
||||||
|
'hover:bg-gray-100',
|
||||||
|
'hover:bg-opacity-20',
|
||||||
|
'disabled:hover:bg-transparent',
|
||||||
|
'dark:data-[state=open]:bg-gray-800',
|
||||||
|
'dark:hover:bg-opacity-20',
|
||||||
|
'dark:hover:bg-gray-900',
|
||||||
|
'dark:hover:text-gray-400',
|
||||||
|
'dark:disabled:hover:bg-transparent'
|
||||||
|
];
|
||||||
|
|
||||||
|
const chatgptColorProps = [
|
||||||
|
'text-green-700',
|
||||||
|
'data-[state=open]:bg-green-100',
|
||||||
|
'dark:text-emerald-300',
|
||||||
|
'hover:bg-green-100',
|
||||||
|
'disabled:hover:bg-transparent',
|
||||||
|
'dark:data-[state=open]:bg-green-900',
|
||||||
|
'dark:hover:bg-opacity-50',
|
||||||
|
'dark:hover:bg-green-900',
|
||||||
|
'dark:hover:text-gray-100',
|
||||||
|
'dark:disabled:hover:bg-transparent'
|
||||||
|
];
|
||||||
|
|
||||||
|
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
|
||||||
|
const icon = getIconOfModel({
|
||||||
|
size: 32,
|
||||||
|
sender: chatGptLabel || model,
|
||||||
|
isCreatedByUser: false,
|
||||||
|
model,
|
||||||
|
chatGptLabel,
|
||||||
|
promptPrefix,
|
||||||
|
error: false,
|
||||||
|
button: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog onOpenChange={onOpenChange}>
|
||||||
|
<DropdownMenu
|
||||||
|
open={menuOpen}
|
||||||
|
onOpenChange={setMenuOpen}
|
||||||
|
>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
|
||||||
|
className={`absolute top-[0.25px] mb-0 ml-1 items-center rounded-md border-0 p-1 outline-none md:ml-0 ${colorProps.join(
|
||||||
|
' '
|
||||||
|
)} focus:ring-0 focus:ring-offset-0 disabled:top-[0.25px] dark:data-[state=open]:bg-opacity-50 md:top-1 md:left-1 md:pl-1 md:disabled:top-1`}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-56 dark:bg-gray-700"
|
||||||
|
onCloseAutoFocus={event => event.preventDefault()}
|
||||||
|
>
|
||||||
|
<DropdownMenuLabel className="dark:text-gray-300">Select a Model</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
value={chatGptLabel || model}
|
||||||
|
onValueChange={onChange}
|
||||||
|
className="overflow-y-auto"
|
||||||
|
>
|
||||||
|
{availableModels.length ? (
|
||||||
|
<MenuItems
|
||||||
|
models={availableModels}
|
||||||
|
onSelect={onChange}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DropdownMenuLabel className="dark:text-gray-300">No model available.</DropdownMenuLabel>
|
||||||
|
)}
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<ModelDialog
|
||||||
|
mutate={mutate}
|
||||||
|
setModelSave={setModelSave}
|
||||||
|
handleSaveState={handleSaveState}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
export default function SubmitButton({ submitMessage, disabled }) {
|
export default function SubmitButton({ submitMessage, disabled, isSubmitting }) {
|
||||||
const { isSubmitting } = useSelector((state) => state.submit);
|
const clickHandler = e => {
|
||||||
const { error, latestMessage } = useSelector((state) => state.convo);
|
|
||||||
|
|
||||||
const clickHandler = (e) => {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitMessage();
|
submitMessage();
|
||||||
};
|
};
|
||||||
454
client/src/components/Input/index.jsx
Normal file
454
client/src/components/Input/index.jsx
Normal file
|
|
@ -0,0 +1,454 @@
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useRecoilValue, useRecoilState } from 'recoil';
|
||||||
|
// import { SSE } from '~/utils/sse';
|
||||||
|
import SubmitButton from './SubmitButton';
|
||||||
|
// import AdjustToneButton from './AdjustToneButton';
|
||||||
|
// import BingStyles from './BingStyles';
|
||||||
|
import ModelMenu from './Models/ModelMenu';
|
||||||
|
import Footer from './Footer';
|
||||||
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
|
import createPayload from '~/utils/createPayload';
|
||||||
|
import RegenerateIcon from '../svg/RegenerateIcon';
|
||||||
|
import StopGeneratingIcon from '../svg/StopGeneratingIcon';
|
||||||
|
// import { setConversation, setError, refreshConversation } from '~/store/convoSlice';
|
||||||
|
// import { setMessages } from '~/store/messageSlice';
|
||||||
|
// import { setSubmitState, toggleCursor } from '~/store/submitSlice';
|
||||||
|
// import { setText } from '~/store/textSlice';
|
||||||
|
import { useMessageHandler } from '../../utils/handleSubmit';
|
||||||
|
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
export default function TextChat() {
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
const isComposing = useRef(false);
|
||||||
|
|
||||||
|
const conversation = useRecoilValue(store.conversation);
|
||||||
|
const latestMessage = useRecoilValue(store.latestMessage);
|
||||||
|
const messages = useRecoilValue(store.messages);
|
||||||
|
|
||||||
|
const isSubmitting = useRecoilValue(store.isSubmitting);
|
||||||
|
|
||||||
|
// TODO: do we need this?
|
||||||
|
const disabled = false;
|
||||||
|
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
const { ask, regenerate, stopGenerating } = useMessageHandler();
|
||||||
|
|
||||||
|
const bingStylesRef = useRef(null);
|
||||||
|
const [showBingToneSetting, setShowBingToneSetting] = useState(false);
|
||||||
|
|
||||||
|
const isNotAppendable = latestMessage?.cancelled || latestMessage?.error;
|
||||||
|
|
||||||
|
// auto focus to input, when enter a conversation.
|
||||||
|
useEffect(() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
setText('');
|
||||||
|
}, [conversation?.conversationId]);
|
||||||
|
|
||||||
|
// controls the height of Bing tone style tabs
|
||||||
|
useEffect(() => {
|
||||||
|
if (!inputRef.current) {
|
||||||
|
return; // wait for the ref to be available
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
const newHeight = inputRef.current.clientHeight;
|
||||||
|
if (newHeight >= 24) {
|
||||||
|
// 24 is the default height of the input
|
||||||
|
bingStylesRef.current.style.bottom = 15 + newHeight + 'px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resizeObserver.observe(inputRef.current);
|
||||||
|
return () => resizeObserver.disconnect();
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
// const messageHandler = (data, currentState, currentMsg) => {
|
||||||
|
// const { messages, message, sender, isRegenerate } = currentState;
|
||||||
|
|
||||||
|
// if (isRegenerate)
|
||||||
|
// dispatch(
|
||||||
|
// setMessages([
|
||||||
|
// ...messages,
|
||||||
|
// {
|
||||||
|
// sender,
|
||||||
|
// text: data,
|
||||||
|
// parentMessageId: message?.overrideParentMessageId,
|
||||||
|
// messageId: message?.overrideParentMessageId + '_',
|
||||||
|
// submitting: true
|
||||||
|
// }
|
||||||
|
// ])
|
||||||
|
// );
|
||||||
|
// else
|
||||||
|
// dispatch(
|
||||||
|
// setMessages([
|
||||||
|
// ...messages,
|
||||||
|
// currentMsg,
|
||||||
|
// {
|
||||||
|
// sender,
|
||||||
|
// text: data,
|
||||||
|
// parentMessageId: currentMsg?.messageId,
|
||||||
|
// messageId: currentMsg?.messageId + '_',
|
||||||
|
// submitting: true
|
||||||
|
// }
|
||||||
|
// ])
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const cancelHandler = (data, currentState, currentMsg) => {
|
||||||
|
// const { messages, message, sender, isRegenerate } = currentState;
|
||||||
|
|
||||||
|
// if (isRegenerate)
|
||||||
|
// dispatch(
|
||||||
|
// setMessages([
|
||||||
|
// ...messages,
|
||||||
|
// {
|
||||||
|
// sender,
|
||||||
|
// text: data,
|
||||||
|
// parentMessageId: message?.overrideParentMessageId,
|
||||||
|
// messageId: message?.overrideParentMessageId + '_',
|
||||||
|
// cancelled: true
|
||||||
|
// }
|
||||||
|
// ])
|
||||||
|
// );
|
||||||
|
// else
|
||||||
|
// dispatch(
|
||||||
|
// setMessages([
|
||||||
|
// ...messages,
|
||||||
|
// currentMsg,
|
||||||
|
// {
|
||||||
|
// sender,
|
||||||
|
// text: data,
|
||||||
|
// parentMessageId: currentMsg?.messageId,
|
||||||
|
// messageId: currentMsg?.messageId + '_',
|
||||||
|
// cancelled: true
|
||||||
|
// }
|
||||||
|
// ])
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const createdHandler = (data, currentState, currentMsg) => {
|
||||||
|
// const { conversationId } = currentMsg;
|
||||||
|
// dispatch(
|
||||||
|
// setConversation({
|
||||||
|
// conversationId,
|
||||||
|
// latestMessage: null
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const convoHandler = (data, currentState) => {
|
||||||
|
// const { requestMessage, responseMessage } = data;
|
||||||
|
// const { messages, message, isCustomModel, isRegenerate } = currentState;
|
||||||
|
// const { model, chatGptLabel, promptPrefix } = message;
|
||||||
|
// if (isRegenerate) dispatch(setMessages([...messages, responseMessage]));
|
||||||
|
// else dispatch(setMessages([...messages, requestMessage, responseMessage]));
|
||||||
|
// dispatch(setSubmitState(false));
|
||||||
|
|
||||||
|
// const isBing = model === 'bingai' || model === 'sydney';
|
||||||
|
|
||||||
|
// // refresh title
|
||||||
|
// if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||||
|
// setTimeout(() => {
|
||||||
|
// dispatch(refreshConversation());
|
||||||
|
// }, 2000);
|
||||||
|
|
||||||
|
// // in case it takes too long.
|
||||||
|
// setTimeout(() => {
|
||||||
|
// dispatch(refreshConversation());
|
||||||
|
// }, 5000);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!isBing && convo.conversationId === null && convo.parentMessageId === null) {
|
||||||
|
// const { title } = data;
|
||||||
|
// const { conversationId, messageId } = responseMessage;
|
||||||
|
// dispatch(
|
||||||
|
// setConversation({
|
||||||
|
// title,
|
||||||
|
// conversationId,
|
||||||
|
// parentMessageId: messageId,
|
||||||
|
// jailbreakConversationId: null,
|
||||||
|
// conversationSignature: null,
|
||||||
|
// clientId: null,
|
||||||
|
// invocationId: null,
|
||||||
|
// chatGptLabel: model === isCustomModel ? chatGptLabel : null,
|
||||||
|
// promptPrefix: model === isCustomModel ? promptPrefix : null,
|
||||||
|
// latestMessage: null
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// } else if (model === 'bingai') {
|
||||||
|
// console.log('Bing data:', data);
|
||||||
|
// const { title } = data;
|
||||||
|
// const { conversationSignature, clientId, conversationId, invocationId, parentMessageId } =
|
||||||
|
// responseMessage;
|
||||||
|
// dispatch(
|
||||||
|
// setConversation({
|
||||||
|
// title,
|
||||||
|
// parentMessageId,
|
||||||
|
// conversationSignature,
|
||||||
|
// clientId,
|
||||||
|
// conversationId,
|
||||||
|
// invocationId,
|
||||||
|
// latestMessage: null
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// } else if (model === 'sydney') {
|
||||||
|
// const { title } = data;
|
||||||
|
// const {
|
||||||
|
// jailbreakConversationId,
|
||||||
|
// parentMessageId,
|
||||||
|
// conversationSignature,
|
||||||
|
// clientId,
|
||||||
|
// conversationId,
|
||||||
|
// invocationId
|
||||||
|
// } = responseMessage;
|
||||||
|
// dispatch(
|
||||||
|
// setConversation({
|
||||||
|
// title,
|
||||||
|
// jailbreakConversationId,
|
||||||
|
// parentMessageId,
|
||||||
|
// conversationSignature,
|
||||||
|
// clientId,
|
||||||
|
// conversationId,
|
||||||
|
// invocationId,
|
||||||
|
// latestMessage: null
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const errorHandler = (data, currentState, currentMsg) => {
|
||||||
|
// const { messages, message } = currentState;
|
||||||
|
// console.log('Error:', data);
|
||||||
|
// const errorResponse = {
|
||||||
|
// ...data,
|
||||||
|
// error: true,
|
||||||
|
// parentMessageId: currentMsg?.messageId
|
||||||
|
// };
|
||||||
|
// dispatch(setSubmitState(false));
|
||||||
|
// dispatch(setMessages([...messages, currentMsg, errorResponse]));
|
||||||
|
// dispatch(setText(message?.text));
|
||||||
|
// dispatch(setError(true));
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
const submitMessage = () => {
|
||||||
|
ask({ text });
|
||||||
|
setText('');
|
||||||
|
};
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// inputRef.current?.focus();
|
||||||
|
// if (Object.keys(submission).length === 0) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const currentState = submission;
|
||||||
|
|
||||||
|
// let currentMsg = { ...currentState.message };
|
||||||
|
// let latestResponseText = '';
|
||||||
|
|
||||||
|
// const { server, payload } = createPayload(submission);
|
||||||
|
// const onMessage = e => {
|
||||||
|
// if (stopStream) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const data = JSON.parse(e.data);
|
||||||
|
|
||||||
|
// if (data.final) {
|
||||||
|
// convoHandler(data, currentState);
|
||||||
|
// dispatch(toggleCursor());
|
||||||
|
// console.log('final', data);
|
||||||
|
// }
|
||||||
|
// if (data.created) {
|
||||||
|
// currentMsg = data.message;
|
||||||
|
// createdHandler(data, currentState, currentMsg);
|
||||||
|
// } else {
|
||||||
|
// let text = data.text || data.response;
|
||||||
|
// if (data.initial) {
|
||||||
|
// dispatch(toggleCursor());
|
||||||
|
// }
|
||||||
|
// if (data.message) {
|
||||||
|
// latestResponseText = text;
|
||||||
|
// messageHandler(text, currentState, currentMsg);
|
||||||
|
// }
|
||||||
|
// // 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.oncancel = () => {
|
||||||
|
// dispatch(toggleCursor(true));
|
||||||
|
// cancelHandler(latestResponseText, currentState, currentMsg);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// events.onerror = function (e) {
|
||||||
|
// console.log('error in opening conn.');
|
||||||
|
// events.close();
|
||||||
|
|
||||||
|
// const data = JSON.parse(e.data);
|
||||||
|
// dispatch(toggleCursor(true));
|
||||||
|
// errorHandler(data, currentState, currentMsg);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// events.stream();
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// events.removeEventListener('message', onMessage);
|
||||||
|
// dispatch(toggleCursor(true));
|
||||||
|
// const isCancelled = events.readyState <= 1;
|
||||||
|
// events.close();
|
||||||
|
// if (isCancelled) {
|
||||||
|
// const e = new Event('cancel');
|
||||||
|
// events.dispatchEvent(e);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }, [submission]);
|
||||||
|
|
||||||
|
const handleRegenerate = () => {
|
||||||
|
if (latestMessage && !latestMessage?.isCreatedByUser) regenerate(latestMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStopGenerating = () => {
|
||||||
|
stopGenerating();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = e => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
if (!isComposing?.current) submitMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyUp = e => {
|
||||||
|
if (e.keyCode === 8 && e.target.value.trim() === '') {
|
||||||
|
setText(e.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'Enter' && e.shiftKey) {
|
||||||
|
return console.log('Enter + Shift');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSubmitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCompositionStart = () => {
|
||||||
|
isComposing.current = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCompositionEnd = () => {
|
||||||
|
isComposing.current = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeHandler = e => {
|
||||||
|
const { value } = e.target;
|
||||||
|
|
||||||
|
setText(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSearchView = messages?.[0]?.searchResult === true;
|
||||||
|
const getPlaceholderText = () => {
|
||||||
|
if (isSearchView) {
|
||||||
|
return 'Click a message title to open its conversation.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
return 'Choose another model or customize GPT again';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotAppendable) {
|
||||||
|
return 'Edit your message or Regenerate.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBingToneSetting = () => {
|
||||||
|
setShowBingToneSetting(show => !show);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isSearchView) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="input-panel md:bg-vert-light-gradient dark:md:bg-vert-dark-gradient fixed bottom-0 left-0 w-full border-t bg-white py-2 dark:border-white/20 dark:bg-gray-800 md:absolute md:border-t-0 md:border-transparent md:bg-transparent md:dark:border-transparent md:dark:bg-transparent">
|
||||||
|
<form className="stretch mx-2 flex flex-row gap-3 last:mb-2 md:pt-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6">
|
||||||
|
<div className="relative flex h-full flex-1 md:flex-col">
|
||||||
|
<span className="order-last ml-1 flex justify-center gap-0 md:order-none md:m-auto md:mb-2 md:w-full md:gap-2">
|
||||||
|
{/* <BingStyles
|
||||||
|
ref={bingStylesRef}
|
||||||
|
show={showBingToneSetting}
|
||||||
|
/> */}
|
||||||
|
{isSubmitting ? (
|
||||||
|
<button
|
||||||
|
onClick={handleStopGenerating}
|
||||||
|
className="input-panel-button btn btn-neutral flex justify-center gap-2 border-0 md:border"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<StopGeneratingIcon />
|
||||||
|
<span className="hidden md:block">Stop generating</span>
|
||||||
|
</button>
|
||||||
|
) : latestMessage && !latestMessage?.isCreatedByUser ? (
|
||||||
|
<button
|
||||||
|
onClick={handleRegenerate}
|
||||||
|
className="input-panel-button btn btn-neutral flex justify-center gap-2 border-0 md:border"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<RegenerateIcon />
|
||||||
|
<span className="hidden md:block">Regenerate response</span>
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
className={`relative flex flex-grow flex-col rounded-md border border-black/10 ${
|
||||||
|
disabled ? 'bg-gray-100' : 'bg-white'
|
||||||
|
} py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
|
||||||
|
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
|
||||||
|
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
|
||||||
|
>
|
||||||
|
<ModelMenu />
|
||||||
|
<TextareaAutosize
|
||||||
|
tabIndex="0"
|
||||||
|
autoFocus
|
||||||
|
ref={inputRef}
|
||||||
|
// style={{maxHeight: '200px', height: '24px', overflowY: 'hidden'}}
|
||||||
|
rows="1"
|
||||||
|
value={disabled || isNotAppendable ? '' : text}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onChange={changeHandler}
|
||||||
|
onCompositionStart={handleCompositionStart}
|
||||||
|
onCompositionEnd={handleCompositionEnd}
|
||||||
|
placeholder={getPlaceholderText()}
|
||||||
|
disabled={disabled || isNotAppendable}
|
||||||
|
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-12 pr-8 leading-6 placeholder:text-sm placeholder:text-gray-600 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder:text-gray-500 md:pl-8"
|
||||||
|
/>
|
||||||
|
<SubmitButton
|
||||||
|
submitMessage={submitMessage}
|
||||||
|
disabled={disabled || isNotAppendable}
|
||||||
|
/>
|
||||||
|
{/* {messages?.length && model === 'sydney' ? (
|
||||||
|
<AdjustToneButton onClick={handleBingToneSetting} />
|
||||||
|
) : null} */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import RegenerateIcon from '../svg/RegenerateIcon';
|
|
||||||
|
|
||||||
export default function Regenerate({ submitMessage, tryAgain, errorMessage }) {
|
|
||||||
const clickHandler = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
submitMessage();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<span className="mb-2 block flex justify-center text-xs text-black dark:text-white/50 md:mb-2">
|
|
||||||
There was an error generating a response
|
|
||||||
</span>
|
|
||||||
<span className="m-auto flex justify-center">
|
|
||||||
{!errorMessage.includes('short') && (
|
|
||||||
<button
|
|
||||||
onClick={clickHandler}
|
|
||||||
className="btn btn-primary m-auto flex justify-center gap-2"
|
|
||||||
>
|
|
||||||
<RegenerateIcon />
|
|
||||||
Regenerate response
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={tryAgain}
|
|
||||||
className="btn btn-neutral flex justify-center gap-2 border-0 md:border"
|
|
||||||
>
|
|
||||||
<RegenerateIcon />
|
|
||||||
Try another message
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,446 +0,0 @@
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
|
||||||
import { SSE } from '~/utils/sse';
|
|
||||||
import SubmitButton from './SubmitButton';
|
|
||||||
// import Regenerate from './Regenerate'; // not used as of Wentao's update
|
|
||||||
import BingStyles from './BingStyles';
|
|
||||||
import ModelMenu from '../Models/ModelMenu';
|
|
||||||
import Footer from './Footer';
|
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
|
||||||
import createPayload from '~/utils/createPayload';
|
|
||||||
import RegenerateIcon from '../svg/RegenerateIcon';
|
|
||||||
import StopGeneratingIcon from '../svg/StopGeneratingIcon';
|
|
||||||
import { setConversation, setError, refreshConversation } from '~/store/convoSlice';
|
|
||||||
import { setMessages } from '~/store/messageSlice';
|
|
||||||
import { setSubmitState, toggleCursor } from '~/store/submitSlice';
|
|
||||||
import { setText } from '~/store/textSlice';
|
|
||||||
import { useMessageHandler } from '../../utils/handleSubmit';
|
|
||||||
import AdjustToneButton from './AdjustToneButton';
|
|
||||||
|
|
||||||
export default function TextChat({ messages }) {
|
|
||||||
const inputRef = useRef(null);
|
|
||||||
const bingStylesRef = useRef(null);
|
|
||||||
const [showBingToneSetting, setShowBingToneSetting] = useState(false);
|
|
||||||
const isComposing = useRef(false);
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const convo = useSelector(state => state.convo);
|
|
||||||
const { isSubmitting, stopStream, submission, disabled, model } = useSelector(state => state.submit);
|
|
||||||
const { text } = useSelector(state => state.text);
|
|
||||||
const { latestMessage } = convo;
|
|
||||||
const { ask, regenerate, stopGenerating } = useMessageHandler();
|
|
||||||
|
|
||||||
const isNotAppendable = latestMessage?.cancelled || latestMessage?.error;
|
|
||||||
|
|
||||||
// auto focus to input, when enter a conversation.
|
|
||||||
useEffect(() => {
|
|
||||||
inputRef.current?.focus();
|
|
||||||
}, [convo?.conversationId]);
|
|
||||||
|
|
||||||
// controls the height of Bing tone style tabs
|
|
||||||
useEffect(() => {
|
|
||||||
if (!inputRef.current) {
|
|
||||||
return; // wait for the ref to be available
|
|
||||||
}
|
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
|
||||||
const newHeight = inputRef.current.clientHeight;
|
|
||||||
if (newHeight >= 24) { // 24 is the default height of the input
|
|
||||||
bingStylesRef.current.style.bottom = 15 + newHeight + 'px';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
resizeObserver.observe(inputRef.current);
|
|
||||||
return () => resizeObserver.disconnect();
|
|
||||||
}, [inputRef]);
|
|
||||||
|
|
||||||
const messageHandler = (data, currentState, currentMsg) => {
|
|
||||||
const { messages, message, sender, isRegenerate } = currentState;
|
|
||||||
|
|
||||||
if (isRegenerate)
|
|
||||||
dispatch(
|
|
||||||
setMessages([
|
|
||||||
...messages,
|
|
||||||
{
|
|
||||||
sender,
|
|
||||||
text: data,
|
|
||||||
parentMessageId: message?.overrideParentMessageId,
|
|
||||||
messageId: message?.overrideParentMessageId + '_',
|
|
||||||
submitting: true
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
else
|
|
||||||
dispatch(
|
|
||||||
setMessages([
|
|
||||||
...messages,
|
|
||||||
currentMsg,
|
|
||||||
{
|
|
||||||
sender,
|
|
||||||
text: data,
|
|
||||||
parentMessageId: currentMsg?.messageId,
|
|
||||||
messageId: currentMsg?.messageId + '_',
|
|
||||||
submitting: true
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelHandler = (data, currentState, currentMsg) => {
|
|
||||||
const { messages, message, sender, isRegenerate } = currentState;
|
|
||||||
|
|
||||||
if (isRegenerate)
|
|
||||||
dispatch(
|
|
||||||
setMessages([
|
|
||||||
...messages,
|
|
||||||
{
|
|
||||||
sender,
|
|
||||||
text: data,
|
|
||||||
parentMessageId: message?.overrideParentMessageId,
|
|
||||||
messageId: message?.overrideParentMessageId + '_',
|
|
||||||
cancelled: true
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
else
|
|
||||||
dispatch(
|
|
||||||
setMessages([
|
|
||||||
...messages,
|
|
||||||
currentMsg,
|
|
||||||
{
|
|
||||||
sender,
|
|
||||||
text: data,
|
|
||||||
parentMessageId: currentMsg?.messageId,
|
|
||||||
messageId: currentMsg?.messageId + '_',
|
|
||||||
cancelled: true
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createdHandler = (data, currentState, currentMsg) => {
|
|
||||||
const { conversationId } = currentMsg;
|
|
||||||
dispatch(
|
|
||||||
setConversation({
|
|
||||||
conversationId,
|
|
||||||
latestMessage: null
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const convoHandler = (data, currentState) => {
|
|
||||||
const { requestMessage, responseMessage } = data;
|
|
||||||
const { messages, message, isCustomModel, isRegenerate } = currentState;
|
|
||||||
const { model, chatGptLabel, promptPrefix } = message;
|
|
||||||
if (isRegenerate) dispatch(setMessages([...messages, responseMessage]));
|
|
||||||
else dispatch(setMessages([...messages, requestMessage, responseMessage]));
|
|
||||||
dispatch(setSubmitState(false));
|
|
||||||
|
|
||||||
const isBing = model === 'bingai' || model === 'sydney';
|
|
||||||
|
|
||||||
// refresh title
|
|
||||||
if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') {
|
|
||||||
setTimeout(() => {
|
|
||||||
dispatch(refreshConversation());
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
// in case it takes too long.
|
|
||||||
setTimeout(() => {
|
|
||||||
dispatch(refreshConversation());
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isBing && convo.conversationId === null && convo.parentMessageId === null) {
|
|
||||||
const { title } = data;
|
|
||||||
const { conversationId, messageId } = responseMessage;
|
|
||||||
dispatch(
|
|
||||||
setConversation({
|
|
||||||
title,
|
|
||||||
conversationId,
|
|
||||||
parentMessageId: messageId,
|
|
||||||
jailbreakConversationId: null,
|
|
||||||
conversationSignature: null,
|
|
||||||
clientId: null,
|
|
||||||
invocationId: null,
|
|
||||||
chatGptLabel: model === isCustomModel ? chatGptLabel : null,
|
|
||||||
promptPrefix: model === isCustomModel ? promptPrefix : null,
|
|
||||||
latestMessage: null
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else if (model === 'bingai') {
|
|
||||||
console.log('Bing data:', data);
|
|
||||||
const { title } = data;
|
|
||||||
const { conversationSignature, clientId, conversationId, invocationId, parentMessageId } =
|
|
||||||
responseMessage;
|
|
||||||
dispatch(
|
|
||||||
setConversation({
|
|
||||||
title,
|
|
||||||
parentMessageId,
|
|
||||||
conversationSignature,
|
|
||||||
clientId,
|
|
||||||
conversationId,
|
|
||||||
invocationId,
|
|
||||||
latestMessage: null
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else if (model === 'sydney') {
|
|
||||||
const { title } = data;
|
|
||||||
const {
|
|
||||||
jailbreakConversationId,
|
|
||||||
parentMessageId,
|
|
||||||
conversationSignature,
|
|
||||||
clientId,
|
|
||||||
conversationId,
|
|
||||||
invocationId
|
|
||||||
} = responseMessage;
|
|
||||||
dispatch(
|
|
||||||
setConversation({
|
|
||||||
title,
|
|
||||||
jailbreakConversationId,
|
|
||||||
parentMessageId,
|
|
||||||
conversationSignature,
|
|
||||||
clientId,
|
|
||||||
conversationId,
|
|
||||||
invocationId,
|
|
||||||
latestMessage: null
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const errorHandler = (data, currentState, currentMsg) => {
|
|
||||||
const { messages, message } = currentState;
|
|
||||||
console.log('Error:', data);
|
|
||||||
const errorResponse = {
|
|
||||||
...data,
|
|
||||||
error: true,
|
|
||||||
parentMessageId: currentMsg?.messageId
|
|
||||||
};
|
|
||||||
dispatch(setSubmitState(false));
|
|
||||||
dispatch(setMessages([...messages, currentMsg, errorResponse]));
|
|
||||||
dispatch(setText(message?.text));
|
|
||||||
dispatch(setError(true));
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
const submitMessage = () => {
|
|
||||||
ask({ text });
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
inputRef.current?.focus();
|
|
||||||
if (Object.keys(submission).length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentState = submission;
|
|
||||||
|
|
||||||
let currentMsg = { ...currentState.message };
|
|
||||||
let latestResponseText = '';
|
|
||||||
|
|
||||||
const { server, payload } = createPayload(submission);
|
|
||||||
const onMessage = e => {
|
|
||||||
if (stopStream) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = JSON.parse(e.data);
|
|
||||||
|
|
||||||
if (data.final) {
|
|
||||||
convoHandler(data, currentState);
|
|
||||||
dispatch(toggleCursor());
|
|
||||||
console.log('final', data);
|
|
||||||
}
|
|
||||||
if (data.created) {
|
|
||||||
currentMsg = data.message;
|
|
||||||
createdHandler(data, currentState, currentMsg);
|
|
||||||
} else {
|
|
||||||
let text = data.text || data.response;
|
|
||||||
if (data.initial) {
|
|
||||||
dispatch(toggleCursor());
|
|
||||||
}
|
|
||||||
if (data.message) {
|
|
||||||
latestResponseText = text;
|
|
||||||
messageHandler(text, currentState, currentMsg);
|
|
||||||
}
|
|
||||||
// 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.oncancel = () => {
|
|
||||||
dispatch(toggleCursor(true));
|
|
||||||
cancelHandler(latestResponseText, currentState, currentMsg);
|
|
||||||
};
|
|
||||||
|
|
||||||
events.onerror = function (e) {
|
|
||||||
console.log('error in opening conn.');
|
|
||||||
events.close();
|
|
||||||
|
|
||||||
const data = JSON.parse(e.data);
|
|
||||||
dispatch(toggleCursor(true));
|
|
||||||
errorHandler(data, currentState, currentMsg);
|
|
||||||
};
|
|
||||||
|
|
||||||
events.stream();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
events.removeEventListener('message', onMessage);
|
|
||||||
dispatch(toggleCursor(true));
|
|
||||||
const isCancelled = events.readyState <= 1;
|
|
||||||
events.close();
|
|
||||||
if (isCancelled) {
|
|
||||||
const e = new Event('cancel');
|
|
||||||
events.dispatchEvent(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [submission]);
|
|
||||||
|
|
||||||
const handleRegenerate = () => {
|
|
||||||
if (latestMessage && !latestMessage?.isCreatedByUser) regenerate(latestMessage);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleStopGenerating = () => {
|
|
||||||
stopGenerating();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = e => {
|
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
|
||||||
if (!isComposing.current) submitMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyUp = e => {
|
|
||||||
if (e.keyCode === 8 && e.target.value.trim() === '') {
|
|
||||||
dispatch(setText(e.target.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === 'Enter' && e.shiftKey) {
|
|
||||||
return console.log('Enter + Shift');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSubmitting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCompositionStart = () => {
|
|
||||||
isComposing.current = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCompositionEnd = () => {
|
|
||||||
isComposing.current = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeHandler = e => {
|
|
||||||
const { value } = e.target;
|
|
||||||
|
|
||||||
// if (isSubmitting && (value === '' || value === '\n')) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
dispatch(setText(value));
|
|
||||||
};
|
|
||||||
|
|
||||||
// const tryAgain = (e) => {
|
|
||||||
// e.preventDefault();
|
|
||||||
// dispatch(setError(false));
|
|
||||||
// };
|
|
||||||
|
|
||||||
const isSearchView = messages?.[0]?.searchResult === true;
|
|
||||||
const getPlaceholderText = () => {
|
|
||||||
if (isSearchView) {
|
|
||||||
return 'Click a message title to open its conversation.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disabled) {
|
|
||||||
return 'Choose another model or customize GPT again';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNotAppendable) {
|
|
||||||
return 'Edit your message or Regenerate.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBingToneSetting = () => {
|
|
||||||
setShowBingToneSetting((show) => !show)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="input-panel md:bg-vert-light-gradient dark:md:bg-vert-dark-gradient fixed bottom-0 left-0 w-full border-t bg-white py-2 dark:border-white/20 dark:bg-gray-800 md:absolute md:border-t-0 md:border-transparent md:bg-transparent md:dark:border-transparent md:dark:bg-transparent">
|
|
||||||
<form className="stretch mx-2 flex flex-row gap-3 last:mb-2 md:pt-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6">
|
|
||||||
<div className="relative flex h-full flex-1 md:flex-col">
|
|
||||||
<span className="order-last ml-1 flex justify-center gap-0 md:order-none md:m-auto md:mb-2 md:w-full md:gap-2">
|
|
||||||
<BingStyles ref={bingStylesRef} show={showBingToneSetting}/>
|
|
||||||
{isSubmitting && !isSearchView ? (
|
|
||||||
<button
|
|
||||||
onClick={handleStopGenerating}
|
|
||||||
className="input-panel-button btn btn-neutral flex justify-center gap-2 border-0 md:border"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<StopGeneratingIcon />
|
|
||||||
<span className="hidden md:block">Stop generating</span>
|
|
||||||
</button>
|
|
||||||
) : latestMessage && !latestMessage?.isCreatedByUser && !isSearchView ? (
|
|
||||||
<button
|
|
||||||
onClick={handleRegenerate}
|
|
||||||
className="input-panel-button btn btn-neutral flex justify-center gap-2 border-0 md:border"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<RegenerateIcon />
|
|
||||||
<span className="hidden md:block">Regenerate response</span>
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
className={`relative flex flex-grow flex-col rounded-md border border-black/10 ${
|
|
||||||
disabled ? 'bg-gray-100' : 'bg-white'
|
|
||||||
} py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
|
|
||||||
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
|
|
||||||
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
|
|
||||||
>
|
|
||||||
<ModelMenu />
|
|
||||||
<TextareaAutosize
|
|
||||||
tabIndex="0"
|
|
||||||
autoFocus
|
|
||||||
ref={inputRef}
|
|
||||||
// style={{maxHeight: '200px', height: '24px', overflowY: 'hidden'}}
|
|
||||||
rows="1"
|
|
||||||
value={disabled || isNotAppendable ? '' : text}
|
|
||||||
onKeyUp={handleKeyUp}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
onChange={changeHandler}
|
|
||||||
onCompositionStart={handleCompositionStart}
|
|
||||||
onCompositionEnd={handleCompositionEnd}
|
|
||||||
placeholder={getPlaceholderText()}
|
|
||||||
disabled={disabled || isNotAppendable}
|
|
||||||
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-12 pr-8 leading-6 placeholder:text-sm placeholder:text-gray-600 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder:text-gray-500 md:pl-8"
|
|
||||||
/>
|
|
||||||
<SubmitButton
|
|
||||||
submitMessage={submitMessage}
|
|
||||||
disabled={disabled || isNotAppendable}
|
|
||||||
/>
|
|
||||||
{messages?.length && model === 'sydney' ?
|
|
||||||
<AdjustToneButton onClick={handleBingToneSetting} /> :
|
|
||||||
null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useRecoilState, useResetRecoilState, useSetRecoilState } from "recoil";
|
import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil';
|
||||||
import { SSE } from "~/utils/sse";
|
import { SSE } from '~/utils/sse';
|
||||||
import { useMessageHandler } from "../../utils/handleSubmit";
|
import { useMessageHandler } from '../../utils/handleSubmit';
|
||||||
import createPayload from "~/utils/createPayload";
|
import createPayload from '~/utils/createPayload';
|
||||||
|
|
||||||
import store from "~/store";
|
import store from '~/store';
|
||||||
|
|
||||||
export default function MessageHandler({ messages }) {
|
export default function MessageHandler({ messages }) {
|
||||||
const [submission, setSubmission] = useRecoilState(store.submission);
|
const [submission, setSubmission] = useRecoilState(store.submission);
|
||||||
|
|
@ -16,12 +16,7 @@ export default function MessageHandler({ messages }) {
|
||||||
const { refreshConversations } = store.useConversations();
|
const { refreshConversations } = store.useConversations();
|
||||||
|
|
||||||
const messageHandler = (data, submission) => {
|
const messageHandler = (data, submission) => {
|
||||||
const {
|
const { messages, message, initialResponse, isRegenerate = false } = submission;
|
||||||
messages,
|
|
||||||
message,
|
|
||||||
initialResponse,
|
|
||||||
isRegenerate = false,
|
|
||||||
} = submission;
|
|
||||||
|
|
||||||
if (isRegenerate)
|
if (isRegenerate)
|
||||||
setMessages([
|
setMessages([
|
||||||
|
|
@ -30,9 +25,9 @@ export default function MessageHandler({ messages }) {
|
||||||
...initialResponse,
|
...initialResponse,
|
||||||
text: data,
|
text: data,
|
||||||
parentMessageId: message?.overrideParentMessageId,
|
parentMessageId: message?.overrideParentMessageId,
|
||||||
messageId: message?.overrideParentMessageId + "_",
|
messageId: message?.overrideParentMessageId + '_',
|
||||||
submitting: true,
|
submitting: true
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
else
|
else
|
||||||
setMessages([
|
setMessages([
|
||||||
|
|
@ -42,19 +37,14 @@ export default function MessageHandler({ messages }) {
|
||||||
...initialResponse,
|
...initialResponse,
|
||||||
text: data,
|
text: data,
|
||||||
parentMessageId: message?.messageId,
|
parentMessageId: message?.messageId,
|
||||||
messageId: message?.messageId + "_",
|
messageId: message?.messageId + '_',
|
||||||
submitting: true,
|
submitting: true
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelHandler = (data, submission) => {
|
const cancelHandler = (data, submission) => {
|
||||||
const {
|
const { messages, message, initialResponse, isRegenerate = false } = submission;
|
||||||
messages,
|
|
||||||
message,
|
|
||||||
initialResponse,
|
|
||||||
isRegenerate = false,
|
|
||||||
} = submission;
|
|
||||||
|
|
||||||
if (isRegenerate)
|
if (isRegenerate)
|
||||||
setMessages([
|
setMessages([
|
||||||
|
|
@ -63,9 +53,9 @@ export default function MessageHandler({ messages }) {
|
||||||
...initialResponse,
|
...initialResponse,
|
||||||
text: data,
|
text: data,
|
||||||
parentMessageId: message?.overrideParentMessageId,
|
parentMessageId: message?.overrideParentMessageId,
|
||||||
messageId: message?.overrideParentMessageId + "_",
|
messageId: message?.overrideParentMessageId + '_',
|
||||||
cancelled: true,
|
cancelled: true
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
else
|
else
|
||||||
setMessages([
|
setMessages([
|
||||||
|
|
@ -75,19 +65,14 @@ export default function MessageHandler({ messages }) {
|
||||||
...initialResponse,
|
...initialResponse,
|
||||||
text: data,
|
text: data,
|
||||||
parentMessageId: message?.messageId,
|
parentMessageId: message?.messageId,
|
||||||
messageId: message?.messageId + "_",
|
messageId: message?.messageId + '_',
|
||||||
cancelled: true,
|
cancelled: true
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createdHandler = (data, submission) => {
|
const createdHandler = (data, submission) => {
|
||||||
const {
|
const { messages, message, initialResponse, isRegenerate = false } = submission;
|
||||||
messages,
|
|
||||||
message,
|
|
||||||
initialResponse,
|
|
||||||
isRegenerate = false,
|
|
||||||
} = submission;
|
|
||||||
|
|
||||||
if (isRegenerate)
|
if (isRegenerate)
|
||||||
setMessages([
|
setMessages([
|
||||||
|
|
@ -95,9 +80,9 @@ export default function MessageHandler({ messages }) {
|
||||||
{
|
{
|
||||||
...initialResponse,
|
...initialResponse,
|
||||||
parentMessageId: message?.overrideParentMessageId,
|
parentMessageId: message?.overrideParentMessageId,
|
||||||
messageId: message?.overrideParentMessageId + "_",
|
messageId: message?.overrideParentMessageId + '_',
|
||||||
submitting: true,
|
submitting: true
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
else
|
else
|
||||||
setMessages([
|
setMessages([
|
||||||
|
|
@ -106,27 +91,21 @@ export default function MessageHandler({ messages }) {
|
||||||
{
|
{
|
||||||
...initialResponse,
|
...initialResponse,
|
||||||
parentMessageId: message?.messageId,
|
parentMessageId: message?.messageId,
|
||||||
messageId: message?.messageId + "_",
|
messageId: message?.messageId + '_',
|
||||||
submitting: true,
|
submitting: true
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { conversationId } = message;
|
const { conversationId } = message;
|
||||||
setConversation((prevState) => ({
|
setConversation(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
conversationId,
|
conversationId
|
||||||
}));
|
}));
|
||||||
resetLatestMessage();
|
resetLatestMessage();
|
||||||
};
|
};
|
||||||
|
|
||||||
const finalHandler = (data, submission) => {
|
const finalHandler = (data, submission) => {
|
||||||
const {
|
const { conversation, messages, message, initialResponse, isRegenerate = false } = submission;
|
||||||
conversation,
|
|
||||||
messages,
|
|
||||||
message,
|
|
||||||
initialResponse,
|
|
||||||
isRegenerate = false,
|
|
||||||
} = submission;
|
|
||||||
|
|
||||||
const { requestMessage, responseMessage } = data;
|
const { requestMessage, responseMessage } = data;
|
||||||
const { conversationId } = requestMessage;
|
const { conversationId } = requestMessage;
|
||||||
|
|
@ -137,9 +116,7 @@ export default function MessageHandler({ messages }) {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|
||||||
// refresh title
|
// refresh title
|
||||||
if (
|
if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||||
requestMessage.parentMessageId == "00000000-0000-0000-0000-000000000000"
|
|
||||||
) {
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
refreshConversations();
|
refreshConversations();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
@ -151,12 +128,12 @@ export default function MessageHandler({ messages }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { model, chatGptLabel, promptPrefix } = conversation;
|
const { model, chatGptLabel, promptPrefix } = conversation;
|
||||||
const isBing = model === "bingai" || model === "sydney";
|
const isBing = model === 'bingai' || model === 'sydney';
|
||||||
|
|
||||||
if (!isBing) {
|
if (!isBing) {
|
||||||
const { title } = data;
|
const { title } = data;
|
||||||
const { conversationId } = responseMessage;
|
const { conversationId } = responseMessage;
|
||||||
setConversation((prevState) => ({
|
setConversation(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
title,
|
title,
|
||||||
conversationId,
|
conversationId,
|
||||||
|
|
@ -166,13 +143,12 @@ export default function MessageHandler({ messages }) {
|
||||||
invocationId: null,
|
invocationId: null,
|
||||||
chatGptLabel,
|
chatGptLabel,
|
||||||
promptPrefix,
|
promptPrefix,
|
||||||
latestMessage: null,
|
latestMessage: null
|
||||||
}));
|
}));
|
||||||
} else if (model === "bingai") {
|
} else if (model === 'bingai') {
|
||||||
const { title } = data;
|
const { title } = data;
|
||||||
const { conversationSignature, clientId, conversationId, invocationId } =
|
const { conversationSignature, clientId, conversationId, invocationId } = responseMessage;
|
||||||
responseMessage;
|
setConversation(prevState => ({
|
||||||
setConversation((prevState) => ({
|
|
||||||
...prevState,
|
...prevState,
|
||||||
title,
|
title,
|
||||||
conversationId,
|
conversationId,
|
||||||
|
|
@ -182,9 +158,9 @@ export default function MessageHandler({ messages }) {
|
||||||
invocationId,
|
invocationId,
|
||||||
chatGptLabel,
|
chatGptLabel,
|
||||||
promptPrefix,
|
promptPrefix,
|
||||||
latestMessage: null,
|
latestMessage: null
|
||||||
}));
|
}));
|
||||||
} else if (model === "sydney") {
|
} else if (model === 'sydney') {
|
||||||
const { title } = data;
|
const { title } = data;
|
||||||
const {
|
const {
|
||||||
jailbreakConversationId,
|
jailbreakConversationId,
|
||||||
|
|
@ -192,9 +168,9 @@ export default function MessageHandler({ messages }) {
|
||||||
conversationSignature,
|
conversationSignature,
|
||||||
clientId,
|
clientId,
|
||||||
conversationId,
|
conversationId,
|
||||||
invocationId,
|
invocationId
|
||||||
} = responseMessage;
|
} = responseMessage;
|
||||||
setConversation((prevState) => ({
|
setConversation(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
title,
|
title,
|
||||||
conversationId,
|
conversationId,
|
||||||
|
|
@ -204,25 +180,19 @@ export default function MessageHandler({ messages }) {
|
||||||
invocationId,
|
invocationId,
|
||||||
chatGptLabel,
|
chatGptLabel,
|
||||||
promptPrefix,
|
promptPrefix,
|
||||||
latestMessage: null,
|
latestMessage: null
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorHandler = (data, submission) => {
|
const errorHandler = (data, submission) => {
|
||||||
const {
|
const { conversation, messages, message, initialResponse, isRegenerate = false } = submission;
|
||||||
conversation,
|
|
||||||
messages,
|
|
||||||
message,
|
|
||||||
initialResponse,
|
|
||||||
isRegenerate = false,
|
|
||||||
} = submission;
|
|
||||||
|
|
||||||
console.log("Error:", data);
|
console.log('Error:', data);
|
||||||
const errorResponse = {
|
const errorResponse = {
|
||||||
...data,
|
...data,
|
||||||
error: true,
|
error: true,
|
||||||
parentMessageId: message?.messageId,
|
parentMessageId: message?.messageId
|
||||||
};
|
};
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
setMessages([...messages, message, errorResponse]);
|
setMessages([...messages, message, errorResponse]);
|
||||||
|
|
@ -240,16 +210,16 @@ export default function MessageHandler({ messages }) {
|
||||||
|
|
||||||
const events = new SSE(server, {
|
const events = new SSE(server, {
|
||||||
payload: JSON.stringify(payload),
|
payload: JSON.stringify(payload),
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
|
|
||||||
let latestResponseText = "";
|
let latestResponseText = '';
|
||||||
events.onmessage = (e) => {
|
events.onmessage = e => {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
|
||||||
if (data.final) {
|
if (data.final) {
|
||||||
finalHandler(data, { ...submission, message });
|
finalHandler(data, { ...submission, message });
|
||||||
console.log("final", data);
|
console.log('final', data);
|
||||||
}
|
}
|
||||||
if (data.created) {
|
if (data.created) {
|
||||||
message = {
|
message = {
|
||||||
|
|
@ -257,10 +227,10 @@ export default function MessageHandler({ messages }) {
|
||||||
model: message?.model,
|
model: message?.model,
|
||||||
chatGptLabel: message?.chatGptLabel,
|
chatGptLabel: message?.chatGptLabel,
|
||||||
promptPrefix: message?.promptPrefix,
|
promptPrefix: message?.promptPrefix,
|
||||||
overrideParentMessageId: message?.overrideParentMessageId,
|
overrideParentMessageId: message?.overrideParentMessageId
|
||||||
};
|
};
|
||||||
createdHandler(data, { ...submission, message });
|
createdHandler(data, { ...submission, message });
|
||||||
console.log("created", message);
|
console.log('created', message);
|
||||||
} else {
|
} else {
|
||||||
let text = data.text || data.response;
|
let text = data.text || data.response;
|
||||||
if (data.initial) console.log(data);
|
if (data.initial) console.log(data);
|
||||||
|
|
@ -273,13 +243,12 @@ export default function MessageHandler({ messages }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
events.onopen = () => console.log("connection is opened");
|
events.onopen = () => console.log('connection is opened');
|
||||||
|
|
||||||
events.oncancel = (e) =>
|
events.oncancel = e => cancelHandler(latestResponseText, { ...submission, message });
|
||||||
cancelHandler(latestResponseText, { ...submission, message });
|
|
||||||
|
|
||||||
events.onerror = function (e) {
|
events.onerror = function (e) {
|
||||||
console.log("error in opening conn.");
|
console.log('error in opening conn.');
|
||||||
events.close();
|
events.close();
|
||||||
|
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
|
@ -294,7 +263,7 @@ export default function MessageHandler({ messages }) {
|
||||||
const isCancelled = events.readyState <= 1;
|
const isCancelled = events.readyState <= 1;
|
||||||
events.close();
|
events.close();
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
const e = new Event("cancel");
|
const e = new Event('cancel');
|
||||||
events.dispatchEvent(e);
|
events.dispatchEvent(e);
|
||||||
}
|
}
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil';
|
||||||
import SubRow from './Content/SubRow';
|
import SubRow from './Content/SubRow';
|
||||||
import Content from './Content/Content';
|
import Content from './Content/Content';
|
||||||
import MultiMessage from './MultiMessage';
|
import MultiMessage from './MultiMessage';
|
||||||
import HoverButtons from './HoverButtons';
|
import HoverButtons from './HoverButtons';
|
||||||
import SiblingSwitch from './SiblingSwitch';
|
import SiblingSwitch from './SiblingSwitch';
|
||||||
import { setConversation, setLatestMessage } from '~/store/convoSlice';
|
|
||||||
import { setModel, setCustomModel, setCustomGpt, setDisabled } from '~/store/submitSlice';
|
|
||||||
import { setMessages } from '~/store/messageSlice';
|
|
||||||
import { fetchById } from '~/utils/fetchers';
|
import { fetchById } from '~/utils/fetchers';
|
||||||
import { getIconOfModel } from '~/utils';
|
import { getIconOfModel } from '~/utils';
|
||||||
import { useMessageHandler } from '~/utils/handleSubmit';
|
import { useMessageHandler } from '~/utils/handleSubmit';
|
||||||
|
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
export default function Message({
|
export default function Message({
|
||||||
|
conversation,
|
||||||
message,
|
message,
|
||||||
messages,
|
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
currentEditId,
|
currentEditId,
|
||||||
setCurrentEditId,
|
setCurrentEditId,
|
||||||
|
|
@ -22,30 +21,24 @@ export default function Message({
|
||||||
siblingCount,
|
siblingCount,
|
||||||
setSiblingIdx
|
setSiblingIdx
|
||||||
}) {
|
}) {
|
||||||
const { isSubmitting, model, chatGptLabel, cursor, promptPrefix } = useSelector(state => state.submit);
|
const isSubmitting = useRecoilValue(store.isSubmitting);
|
||||||
|
const setLatestMessage = useSetRecoilState(store.latestMessage);
|
||||||
|
const { model, chatGptLabel, promptPrefix } = conversation;
|
||||||
const [abortScroll, setAbort] = useState(false);
|
const [abortScroll, setAbort] = useState(false);
|
||||||
const { sender, text, searchResult, isCreatedByUser, error, submitting } = message;
|
const { sender, text, searchResult, isCreatedByUser, error, submitting } = message;
|
||||||
const textEditor = useRef(null);
|
const textEditor = useRef(null);
|
||||||
const last = !message?.children?.length;
|
const last = !message?.children?.length;
|
||||||
const edit = message.messageId == currentEditId;
|
const edit = message.messageId == currentEditId;
|
||||||
const { ask } = useMessageHandler();
|
const { ask } = useMessageHandler();
|
||||||
const dispatch = useDispatch();
|
const { switchToConversation } = store.useConversation();
|
||||||
// const currentConvo = convoMap[message.conversationId];
|
|
||||||
|
|
||||||
// const notUser = !isCreatedByUser; // sender.toLowerCase() !== 'user';
|
|
||||||
// const blinker = submitting && isSubmitting && last && !isCreatedByUser;
|
|
||||||
const blinker = submitting && isSubmitting;
|
const blinker = submitting && isSubmitting;
|
||||||
const generateCursor = useCallback(() => {
|
const generateCursor = useCallback(() => {
|
||||||
if (!blinker) {
|
if (!blinker) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cursor) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return <span className="result-streaming">█</span>;
|
return <span className="result-streaming">█</span>;
|
||||||
}, [blinker, cursor]);
|
}, [blinker]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (blinker && !abortScroll) {
|
if (blinker && !abortScroll) {
|
||||||
|
|
@ -55,9 +48,7 @@ export default function Message({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (last) {
|
if (last) {
|
||||||
// TODO: stop using conversation.parentMessageId and remove it.
|
setLatestMessage({ ...message });
|
||||||
dispatch(setConversation({ parentMessageId: message?.messageId }));
|
|
||||||
dispatch(setLatestMessage({ ...message }));
|
|
||||||
}
|
}
|
||||||
}, [last, message]);
|
}, [last, message]);
|
||||||
|
|
||||||
|
|
@ -110,22 +101,23 @@ export default function Message({
|
||||||
|
|
||||||
const clickSearchResult = async () => {
|
const clickSearchResult = async () => {
|
||||||
if (!searchResult) return;
|
if (!searchResult) return;
|
||||||
dispatch(setMessages([]));
|
// dispatch(setMessages([]));
|
||||||
const convoResponse = await fetchById('convos', message.conversationId);
|
const convoResponse = await fetchById('convos', message.conversationId);
|
||||||
const convo = convoResponse.data;
|
const convo = convoResponse.data;
|
||||||
if (convo?.chatGptLabel) {
|
// if (convo?.chatGptLabel) {
|
||||||
dispatch(setModel('chatgptCustom'));
|
// // dispatch(setModel('chatgptCustom'));
|
||||||
dispatch(setCustomModel(convo.chatGptLabel.toLowerCase()));
|
// // dispatch(setCustomModel(convo.chatGptLabel.toLowerCase()));
|
||||||
} else {
|
// } else {
|
||||||
dispatch(setModel(convo.model));
|
// // dispatch(setModel(convo.model));
|
||||||
dispatch(setCustomModel(null));
|
// // dispatch(setCustomModel(null));
|
||||||
}
|
// }
|
||||||
|
|
||||||
dispatch(setCustomGpt(convo));
|
// dispatch(setCustomGpt(convo));
|
||||||
dispatch(setConversation(convo));
|
switchToConversation(convo);
|
||||||
const { data } = await fetchById('messages', message.conversationId);
|
// dispatch(setConversation(convo));
|
||||||
dispatch(setMessages(data));
|
// const { data } = await fetchById('messages', message.conversationId);
|
||||||
dispatch(setDisabled(false));
|
// dispatch(setMessages(data));
|
||||||
|
// dispatch(setDisabled(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -133,7 +125,6 @@ export default function Message({
|
||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
onWheel={handleWheel}
|
onWheel={handleWheel}
|
||||||
// onClick={clickSearchResult}
|
|
||||||
>
|
>
|
||||||
<div className="relative 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">
|
<div className="relative 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">
|
||||||
<div className="relative flex h-[30px] w-[30px] flex-col items-end text-right text-xs md:text-sm">
|
<div className="relative flex h-[30px] w-[30px] flex-col items-end text-right text-xs md:text-sm">
|
||||||
|
|
@ -199,17 +190,13 @@ export default function Message({
|
||||||
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4 whitespace-pre-wrap">
|
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4 whitespace-pre-wrap">
|
||||||
{/* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */}
|
{/* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */}
|
||||||
<div className="markdown prose dark:prose-invert light w-full break-words">
|
<div className="markdown prose dark:prose-invert light w-full break-words">
|
||||||
{!isCreatedByUser ?
|
{!isCreatedByUser ? (
|
||||||
<>
|
<>
|
||||||
<Content
|
<Content content={text} />
|
||||||
content={text}
|
</>
|
||||||
/>
|
) : (
|
||||||
{generateCursor()}
|
<>{text}</>
|
||||||
</> :
|
)}
|
||||||
<>
|
|
||||||
{text}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -230,8 +217,8 @@ export default function Message({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MultiMessage
|
<MultiMessage
|
||||||
messageList={message.children}
|
conversation={conversation}
|
||||||
messages={messages}
|
messagesTree={message.children}
|
||||||
scrollToBottom={scrollToBottom}
|
scrollToBottom={scrollToBottom}
|
||||||
currentEditId={currentEditId}
|
currentEditId={currentEditId}
|
||||||
setCurrentEditId={setCurrentEditId}
|
setCurrentEditId={setCurrentEditId}
|
||||||
|
|
|
||||||
|
|
@ -2,44 +2,44 @@ import React, { useEffect, useState } from 'react';
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
|
|
||||||
export default function MultiMessage({
|
export default function MultiMessage({
|
||||||
messageList,
|
conversation,
|
||||||
messages,
|
messagesTree,
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
currentEditId,
|
currentEditId,
|
||||||
setCurrentEditId,
|
setCurrentEditId
|
||||||
}) {
|
}) {
|
||||||
const [siblingIdx, setSiblingIdx] = useState(0);
|
const [siblingIdx, setSiblingIdx] = useState(0);
|
||||||
|
|
||||||
const setSiblingIdxRev = (value) => {
|
const setSiblingIdxRev = value => {
|
||||||
setSiblingIdx(messageList?.length - value - 1);
|
setSiblingIdx(messagesTree?.length - value - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// reset siblingIdx when changes, mostly a new message is submitting.
|
// reset siblingIdx when changes, mostly a new message is submitting.
|
||||||
setSiblingIdx(0);
|
setSiblingIdx(0);
|
||||||
}, [messageList?.length])
|
}, [messagesTree?.length]);
|
||||||
|
|
||||||
// if (!messageList?.length) return null;
|
// if (!messageList?.length) return null;
|
||||||
if (!(messageList && messageList.length)) {
|
if (!(messagesTree && messagesTree.length)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (siblingIdx >= messageList?.length) {
|
if (siblingIdx >= messagesTree?.length) {
|
||||||
setSiblingIdx(0);
|
setSiblingIdx(0);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = messageList[messageList.length - siblingIdx - 1];
|
const message = messagesTree[messagesTree.length - siblingIdx - 1];
|
||||||
return (
|
return (
|
||||||
<Message
|
<Message
|
||||||
key={message.messageId}
|
key={message.messageId}
|
||||||
|
conversation={conversation}
|
||||||
message={message}
|
message={message}
|
||||||
messages={messages}
|
|
||||||
scrollToBottom={scrollToBottom}
|
scrollToBottom={scrollToBottom}
|
||||||
currentEditId={currentEditId}
|
currentEditId={currentEditId}
|
||||||
setCurrentEditId={setCurrentEditId}
|
setCurrentEditId={setCurrentEditId}
|
||||||
siblingIdx={messageList.length - siblingIdx - 1}
|
siblingIdx={messagesTree.length - siblingIdx - 1}
|
||||||
siblingCount={messageList.length}
|
siblingCount={messagesTree.length}
|
||||||
setSiblingIdx={setSiblingIdxRev}
|
setSiblingIdx={setSiblingIdxRev}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,30 @@
|
||||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||||
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import Spinner from '../svg/Spinner';
|
import Spinner from '../svg/Spinner';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import { CSSTransition } from 'react-transition-group';
|
import { CSSTransition } from 'react-transition-group';
|
||||||
import ScrollToBottom from './ScrollToBottom';
|
import ScrollToBottom from './ScrollToBottom';
|
||||||
import MultiMessage from './MultiMessage';
|
import MultiMessage from './MultiMessage';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
export default function Messages({ messages, messageTree }) {
|
import store from '~/store';
|
||||||
|
|
||||||
|
export default function Messages() {
|
||||||
const [currentEditId, setCurrentEditId] = useState(-1);
|
const [currentEditId, setCurrentEditId] = useState(-1);
|
||||||
const { conversationId } = useSelector((state) => state.convo);
|
const messagesTree = useRecoilValue(store.messagesTree);
|
||||||
const { model, customModel } = useSelector((state) => state.submit);
|
const conversation = useRecoilValue(store.conversation) || {};
|
||||||
const { models } = useSelector((state) => state.models);
|
const { conversationId, model, chatGptLabel } = conversation;
|
||||||
|
const models = useRecoilValue(store.models) || [];
|
||||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||||
const scrollableRef = useRef(null);
|
const scrollableRef = useRef(null);
|
||||||
const messagesEndRef = useRef(null);
|
const messagesEndRef = useRef(null);
|
||||||
|
|
||||||
const modelName = models.find((element) => element.model == model)?.name;
|
const modelName = models.find(element => element.model == model)?.name;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
|
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
|
||||||
const diff = Math.abs(scrollHeight - scrollTop);
|
const diff = Math.abs(scrollHeight - scrollTop);
|
||||||
const percent = Math.abs(clientHeight - diff ) / clientHeight;
|
const percent = Math.abs(clientHeight - diff) / clientHeight;
|
||||||
const hasScrollbar = scrollHeight > clientHeight && percent > 0.2;
|
const hasScrollbar = scrollHeight > clientHeight && percent > 0.2;
|
||||||
setShowScrollButton(hasScrollbar);
|
setShowScrollButton(hasScrollbar);
|
||||||
}, 650);
|
}, 650);
|
||||||
|
|
@ -33,17 +36,24 @@ export default function Messages({ messages, messageTree }) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
window.removeEventListener('scroll', handleScroll);
|
window.removeEventListener('scroll', handleScroll);
|
||||||
};
|
};
|
||||||
}, [messages]);
|
}, [messagesTree]);
|
||||||
|
|
||||||
const scrollToBottom = useCallback(throttle(() => {
|
const scrollToBottom = useCallback(
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
throttle(
|
||||||
setShowScrollButton(false);
|
() => {
|
||||||
}, 750, { leading: true }), [messagesEndRef]);
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
setShowScrollButton(false);
|
||||||
|
},
|
||||||
|
750,
|
||||||
|
{ leading: true }
|
||||||
|
),
|
||||||
|
[messagesEndRef]
|
||||||
|
);
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
|
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
|
||||||
const diff = Math.abs(scrollHeight - scrollTop);
|
const diff = Math.abs(scrollHeight - scrollTop);
|
||||||
const percent = Math.abs(clientHeight - diff ) / clientHeight;
|
const percent = Math.abs(clientHeight - diff) / clientHeight;
|
||||||
if (percent <= 0.2) {
|
if (percent <= 0.2) {
|
||||||
setShowScrollButton(false);
|
setShowScrollButton(false);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -57,7 +67,7 @@ export default function Messages({ messages, messageTree }) {
|
||||||
timeoutId = setTimeout(handleScroll, 100);
|
timeoutId = setTimeout(handleScroll, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollHandler = (e) => {
|
const scrollHandler = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
};
|
};
|
||||||
|
|
@ -68,20 +78,19 @@ export default function Messages({ messages, messageTree }) {
|
||||||
ref={scrollableRef}
|
ref={scrollableRef}
|
||||||
onScroll={debouncedHandleScroll}
|
onScroll={debouncedHandleScroll}
|
||||||
>
|
>
|
||||||
{/* <div className="flex-1 overflow-hidden"> */}
|
|
||||||
<div className="dark:gpt-dark-gray h-full">
|
<div className="dark:gpt-dark-gray h-full">
|
||||||
<div className="dark:gpt-dark-gray flex h-full flex-col items-center text-sm">
|
<div className="dark:gpt-dark-gray flex h-full flex-col items-center text-sm">
|
||||||
<div className="flex w-full items-center justify-center gap-1 border-b border-black/10 bg-gray-50 p-3 text-sm text-gray-500 dark:border-gray-900/50 dark:bg-gray-700 dark:text-gray-300">
|
<div className="flex w-full items-center justify-center gap-1 border-b border-black/10 bg-gray-50 p-3 text-sm text-gray-500 dark:border-gray-900/50 dark:bg-gray-700 dark:text-gray-300">
|
||||||
Model: {modelName} {customModel ? `(${customModel})` : null}
|
Model: {modelName} {chatGptLabel ? `(${chatGptLabel})` : null}
|
||||||
</div>
|
</div>
|
||||||
{(messageTree.length === 0 || !messages) ? (
|
{messagesTree === null ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<MultiMessage
|
<MultiMessage
|
||||||
key={conversationId} // avoid internal state mixture
|
key={conversationId} // avoid internal state mixture
|
||||||
messageList={messageTree}
|
conversation={conversation}
|
||||||
messages={messages}
|
messagesTree={messagesTree}
|
||||||
scrollToBottom={scrollToBottom}
|
scrollToBottom={scrollToBottom}
|
||||||
currentEditId={currentEditId}
|
currentEditId={currentEditId}
|
||||||
setCurrentEditId={setCurrentEditId}
|
setCurrentEditId={setCurrentEditId}
|
||||||
|
|
@ -103,7 +112,6 @@ export default function Messages({ messages, messageTree }) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* </div> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import axios from 'axios';
|
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
|
||||||
import {
|
|
||||||
setSubmission,
|
|
||||||
setModel,
|
|
||||||
setDisabled,
|
|
||||||
setCustomGpt,
|
|
||||||
setCustomModel
|
|
||||||
} from '~/store/submitSlice';
|
|
||||||
import { setNewConvo } from '~/store/convoSlice';
|
|
||||||
import ModelDialog from './ModelDialog';
|
|
||||||
import MenuItems from './MenuItems';
|
|
||||||
import { swr } from '~/utils/fetchers';
|
|
||||||
import { setModels, setInitial } from '~/store/modelSlice';
|
|
||||||
import { setMessages } from '~/store/messageSlice';
|
|
||||||
import { setText } from '~/store/textSlice';
|
|
||||||
import { Button } from '../ui/Button.tsx';
|
|
||||||
import { getIconOfModel } from '../../utils';
|
|
||||||
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger
|
|
||||||
} from '../ui/DropdownMenu.tsx';
|
|
||||||
|
|
||||||
import { Dialog } from '../ui/Dialog.tsx';
|
|
||||||
|
|
||||||
export default function ModelMenu() {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const [modelSave, setModelSave] = useState(false);
|
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
|
||||||
const { model, customModel, promptPrefix, chatGptLabel } = useSelector((state) => state.submit);
|
|
||||||
const { models, modelMap, initial } = useSelector((state) => state.models);
|
|
||||||
const { data, isLoading, mutate } = swr(`/api/customGpts`, (res) => {
|
|
||||||
const fetchedModels = res.map((modelItem) => ({
|
|
||||||
...modelItem,
|
|
||||||
name: modelItem.chatGptLabel,
|
|
||||||
model: 'chatgptCustom'
|
|
||||||
}));
|
|
||||||
|
|
||||||
dispatch(setModels(fetchedModels));
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
mutate();
|
|
||||||
try {
|
|
||||||
const lastSelected = JSON.parse(localStorage.getItem('model'));
|
|
||||||
|
|
||||||
if (lastSelected === 'chatgptCustom') {
|
|
||||||
return;
|
|
||||||
} else if (initial[lastSelected]) {
|
|
||||||
dispatch(setModel(lastSelected));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
axios.get('/api/models', {
|
|
||||||
timeout: 1000,
|
|
||||||
withCredentials: true
|
|
||||||
}).then((res) => {
|
|
||||||
return res.data
|
|
||||||
}).then((data) => {
|
|
||||||
const initial = {chatgpt: data?.hasOpenAI, chatgptCustom: data?.hasOpenAI, bingai: data?.hasBing, sydney: data?.hasBing, chatgptBrowser: data?.hasChatGpt}
|
|
||||||
dispatch(setInitial(initial))
|
|
||||||
// TODO, auto reset default model
|
|
||||||
if (data?.hasOpenAI) {
|
|
||||||
dispatch(setModel('chatgpt'));
|
|
||||||
dispatch(setDisabled(false));
|
|
||||||
dispatch(setCustomModel(null));
|
|
||||||
dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null }));
|
|
||||||
} else if (data?.hasBing) {
|
|
||||||
dispatch(setModel('bingai'));
|
|
||||||
dispatch(setDisabled(false));
|
|
||||||
dispatch(setCustomModel(null));
|
|
||||||
dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null }));
|
|
||||||
} else if (data?.hasChatGpt) {
|
|
||||||
dispatch(setModel('chatgptBrowser'));
|
|
||||||
dispatch(setDisabled(false));
|
|
||||||
dispatch(setCustomModel(null));
|
|
||||||
dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null }));
|
|
||||||
} else {
|
|
||||||
dispatch(setDisabled(true));
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
console.log('Not login!')
|
|
||||||
window.location.href = "/auth/login";
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem('model', JSON.stringify(model));
|
|
||||||
}, [model]);
|
|
||||||
|
|
||||||
const filteredModels = models.filter(({model, _id }) => initial[model] );
|
|
||||||
|
|
||||||
const onChange = (value) => {
|
|
||||||
if (!value) {
|
|
||||||
return;
|
|
||||||
} else if (value === model) {
|
|
||||||
return;
|
|
||||||
} else if (value === 'chatgptCustom') {
|
|
||||||
// return;
|
|
||||||
} else if (initial[value]) {
|
|
||||||
dispatch(setModel(value));
|
|
||||||
dispatch(setDisabled(false));
|
|
||||||
dispatch(setCustomModel(null));
|
|
||||||
dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null }));
|
|
||||||
} else if (!initial[value]) {
|
|
||||||
const chatGptLabel = modelMap[value]?.chatGptLabel;
|
|
||||||
const promptPrefix = modelMap[value]?.promptPrefix;
|
|
||||||
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
|
|
||||||
dispatch(setModel('chatgptCustom'));
|
|
||||||
dispatch(setCustomModel(value));
|
|
||||||
setMenuOpen(false);
|
|
||||||
} else if (!modelMap[value]) {
|
|
||||||
dispatch(setCustomModel(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set new conversation
|
|
||||||
dispatch(setText(''));
|
|
||||||
dispatch(setMessages([]));
|
|
||||||
dispatch(setNewConvo());
|
|
||||||
dispatch(setSubmission({}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const onOpenChange = (open) => {
|
|
||||||
mutate();
|
|
||||||
if (!open) {
|
|
||||||
setModelSave(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveState = (value) => {
|
|
||||||
if (!modelSave) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(setCustomModel(value));
|
|
||||||
setModelSave(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultColorProps = [
|
|
||||||
'text-gray-500',
|
|
||||||
'hover:bg-gray-100',
|
|
||||||
'hover:bg-opacity-20',
|
|
||||||
'disabled:hover:bg-transparent',
|
|
||||||
'dark:data-[state=open]:bg-gray-800',
|
|
||||||
'dark:hover:bg-opacity-20',
|
|
||||||
'dark:hover:bg-gray-900',
|
|
||||||
'dark:hover:text-gray-400',
|
|
||||||
'dark:disabled:hover:bg-transparent'
|
|
||||||
];
|
|
||||||
|
|
||||||
const chatgptColorProps = [
|
|
||||||
'text-green-700',
|
|
||||||
'data-[state=open]:bg-green-100',
|
|
||||||
'dark:text-emerald-300',
|
|
||||||
'hover:bg-green-100',
|
|
||||||
'disabled:hover:bg-transparent',
|
|
||||||
'dark:data-[state=open]:bg-green-900',
|
|
||||||
'dark:hover:bg-opacity-50',
|
|
||||||
'dark:hover:bg-green-900',
|
|
||||||
'dark:hover:text-gray-100',
|
|
||||||
'dark:disabled:hover:bg-transparent'
|
|
||||||
];
|
|
||||||
|
|
||||||
const isBing = model === 'bingai' || model === 'sydney';
|
|
||||||
const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps;
|
|
||||||
const icon = getIconOfModel({ size: 32, sender: chatGptLabel || model, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, button: true});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog onOpenChange={onOpenChange}>
|
|
||||||
<DropdownMenu
|
|
||||||
open={menuOpen}
|
|
||||||
onOpenChange={setMenuOpen}
|
|
||||||
>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
// style={{backgroundColor: 'rgb(16, 163, 127)'}}
|
|
||||||
className={`absolute top-[0.25px] items-center mb-0 rounded-md border-0 p-1 ml-1 md:ml-0 outline-none ${colorProps.join(
|
|
||||||
' '
|
|
||||||
)} focus:ring-0 focus:ring-offset-0 disabled:top-[0.25px] dark:data-[state=open]:bg-opacity-50 md:top-1 md:left-1 md:pl-1 md:disabled:top-1`}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-56 dark:bg-gray-700" onCloseAutoFocus={(event) => event.preventDefault()}>
|
|
||||||
<DropdownMenuLabel className="dark:text-gray-300">Select a Model</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={customModel ? customModel : model}
|
|
||||||
onValueChange={onChange}
|
|
||||||
className="overflow-y-auto"
|
|
||||||
>
|
|
||||||
{filteredModels.length?
|
|
||||||
<MenuItems
|
|
||||||
models={filteredModels}
|
|
||||||
onSelect={onChange}
|
|
||||||
/>:<DropdownMenuLabel className="dark:text-gray-300">No model available.</DropdownMenuLabel>
|
|
||||||
}
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<ModelDialog
|
|
||||||
mutate={mutate}
|
|
||||||
modelMap={modelMap}
|
|
||||||
setModelSave={setModelSave}
|
|
||||||
handleSaveState={handleSaveState}
|
|
||||||
/>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { setText } from '~/store/textSlice';
|
|
||||||
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
||||||
import Templates from '../Prompts/Templates';
|
import Templates from '../Prompts/Templates';
|
||||||
import SunIcon from '../svg/SunIcon';
|
import SunIcon from '../svg/SunIcon';
|
||||||
|
|
@ -8,27 +7,31 @@ import LightningIcon from '../svg/LightningIcon';
|
||||||
import CautionIcon from '../svg/CautionIcon';
|
import CautionIcon from '../svg/CautionIcon';
|
||||||
import ChatIcon from '../svg/ChatIcon';
|
import ChatIcon from '../svg/ChatIcon';
|
||||||
|
|
||||||
export default function Landing({ title }) {
|
import store from '~/store';
|
||||||
|
|
||||||
|
export default function Landing() {
|
||||||
const [showingTemplates, setShowingTemplates] = useState(false);
|
const [showingTemplates, setShowingTemplates] = useState(false);
|
||||||
const dispatch = useDispatch();
|
const conversation = useRecoilValue(store.conversation);
|
||||||
|
const { title = 'New Chat' } = conversation || {};
|
||||||
|
|
||||||
useDocumentTitle(title);
|
useDocumentTitle(title);
|
||||||
|
|
||||||
const clickHandler = (e) => {
|
const clickHandler = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { innerText } = e.target;
|
const { innerText } = e.target;
|
||||||
const quote = innerText.split('"')[1].trim();
|
const quote = innerText.split('"')[1].trim();
|
||||||
dispatch(setText(quote));
|
// dispatch(setText(quote));
|
||||||
};
|
};
|
||||||
|
|
||||||
const showTemplates = (e) => {
|
const showTemplates = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setShowingTemplates(!showingTemplates);
|
setShowingTemplates(!showingTemplates);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex pt-10 md:pt-0 h-full flex-col items-center overflow-y-auto text-sm dark:bg-gray-800">
|
<div className="flex h-full flex-col items-center overflow-y-auto pt-10 text-sm dark:bg-gray-800 md:pt-0">
|
||||||
<div className="w-full px-6 text-gray-800 dark:text-gray-100 md:flex md:max-w-2xl md:flex-col lg:max-w-3xl">
|
<div className="w-full px-6 text-gray-800 dark:text-gray-100 md:flex md:max-w-2xl md:flex-col lg:max-w-3xl">
|
||||||
<h1 className="mt-6 ml-auto mr-auto mb-10 flex items-center justify-center gap-2 text-center text-4xl font-semibold md:mt-[20vh] sm:mb-16">
|
<h1 className="mt-6 ml-auto mr-auto mb-10 flex items-center justify-center gap-2 text-center text-4xl font-semibold sm:mb-16 md:mt-[20vh]">
|
||||||
ChatGPT Clone
|
ChatGPT Clone
|
||||||
</h1>
|
</h1>
|
||||||
<div className="items-start gap-3.5 text-center md:flex">
|
<div className="items-start gap-3.5 text-center md:flex">
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from 'react';
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import Landing from "../components/ui/Landing";
|
import Landing from '../components/ui/Landing';
|
||||||
import Messages from "../components/Messages";
|
import Messages from '../components/Messages';
|
||||||
import TextChat from "../components/Input";
|
import TextChat from '../components/Input';
|
||||||
|
|
||||||
import store from "~/store";
|
import store from '~/store';
|
||||||
import manualSWR from "~/utils/fetchers";
|
import manualSWR from '~/utils/fetchers';
|
||||||
// import TextChat from './components/Main/TextChat';
|
// import TextChat from './components/Main/TextChat';
|
||||||
|
|
||||||
// {/* <TextChat messages={messages} /> */}
|
// {/* <TextChat messages={messages} /> */}
|
||||||
|
|
@ -20,28 +20,22 @@ export default function Chat() {
|
||||||
const { conversationId } = useParams();
|
const { conversationId } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { trigger: messagesTrigger } = manualSWR(
|
const { trigger: messagesTrigger } = manualSWR(`/api/messages/${conversation?.conversationId}`, 'get');
|
||||||
`/api/messages/${conversation?.conversationId}`,
|
|
||||||
"get"
|
|
||||||
);
|
|
||||||
|
|
||||||
const { trigger: conversationTrigger } = manualSWR(
|
const { trigger: conversationTrigger } = manualSWR(`/api/convos/${conversationId}`, 'get');
|
||||||
`/api/convos/${conversationId}`,
|
|
||||||
"get"
|
|
||||||
);
|
|
||||||
|
|
||||||
// when conversation changed or conversationId (in url) changed
|
// when conversation changed or conversationId (in url) changed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (conversation === null) {
|
if (conversation === null) {
|
||||||
// no current conversation, we need to do something
|
// no current conversation, we need to do something
|
||||||
if (conversationId == "new") {
|
if (conversationId == 'new') {
|
||||||
// create new
|
// create new
|
||||||
newConversation();
|
newConversation();
|
||||||
} else {
|
} else {
|
||||||
// fetch it from server
|
// fetch it from server
|
||||||
conversationTrigger().then(setConversation);
|
conversationTrigger().then(setConversation);
|
||||||
setMessages(null);
|
setMessages(null);
|
||||||
console.log("NEED TO FETCH DATA");
|
console.log('NEED TO FETCH DATA');
|
||||||
}
|
}
|
||||||
} else if (conversation?.conversationId !== conversationId)
|
} else if (conversation?.conversationId !== conversationId)
|
||||||
// conversationId (in url) should always follow conversation?.conversationId, unless conversation is null
|
// conversationId (in url) should always follow conversation?.conversationId, unless conversation is null
|
||||||
|
|
@ -60,7 +54,7 @@ export default function Chat() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{conversationId == "new" ? <Landing /> : <Messages />}
|
{conversationId == 'new' ? <Landing /> : <Messages />}
|
||||||
<TextChat />
|
<TextChat />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
import models from './models';
|
import models from './models';
|
||||||
import { atom, selector, useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil';
|
import {
|
||||||
|
atom,
|
||||||
|
selector,
|
||||||
|
useRecoilValue,
|
||||||
|
useSetRecoilState,
|
||||||
|
useResetRecoilState,
|
||||||
|
useRecoilCallback,
|
||||||
|
useRecoilState
|
||||||
|
} from 'recoil';
|
||||||
import buildTree from '~/utils/buildTree';
|
import buildTree from '~/utils/buildTree';
|
||||||
|
|
||||||
// current conversation, can be null (need to be fetched from server)
|
// current conversation, can be null (need to be fetched from server)
|
||||||
|
|
@ -44,19 +52,45 @@ const latestMessage = atom({
|
||||||
});
|
});
|
||||||
|
|
||||||
const useConversation = () => {
|
const useConversation = () => {
|
||||||
const modelsFilter = useRecoilValue(models.modelsFilter);
|
|
||||||
const setConversation = useSetRecoilState(conversation);
|
const setConversation = useSetRecoilState(conversation);
|
||||||
const setMessages = useSetRecoilState(messages);
|
const setMessages = useSetRecoilState(messages);
|
||||||
const resetLatestMessage = useResetRecoilState(latestMessage);
|
const resetLatestMessage = useResetRecoilState(latestMessage);
|
||||||
|
|
||||||
const newConversation = ({ model = null, chatGptLabel = null, promptPrefix = null } = {}) => {
|
const switchToConversation = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
async (_conversation, messages = null) => {
|
||||||
|
const prevConversation = await snapshot.getPromise(conversation);
|
||||||
|
const prevModelsFilter = await snapshot.getPromise(models.modelsFilter);
|
||||||
|
_switchToConversation(_conversation, messages, { prevModelsFilter, prevConversation });
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const _switchToConversation = (
|
||||||
|
conversation,
|
||||||
|
messages = null,
|
||||||
|
{ prevModelsFilter = {}, prev_conversation = {} }
|
||||||
|
) => {
|
||||||
|
let { model = null, chatGptLabel = null, promptPrefix = null } = conversation;
|
||||||
const getDefaultModel = () => {
|
const getDefaultModel = () => {
|
||||||
|
try {
|
||||||
|
// try to use current model
|
||||||
|
const { _model = null, _chatGptLabel = null, _promptPrefix = null } = prev_conversation || {};
|
||||||
|
console.log(_model, _chatGptLabel, _promptPrefix);
|
||||||
|
if (prevModelsFilter[_model]) {
|
||||||
|
model = _model;
|
||||||
|
chatGptLabel = _chatGptLabel;
|
||||||
|
promptPrefix = _promptPrefix;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// try to read latest selected model from local storage
|
// try to read latest selected model from local storage
|
||||||
const lastSelected = JSON.parse(localStorage.getItem('model'));
|
const lastSelected = JSON.parse(localStorage.getItem('model'));
|
||||||
const { model: _model, chatGptLabel: _chatGptLabel, promptPrefix: _promptPrefix } = lastSelected;
|
const { model: _model, chatGptLabel: _chatGptLabel, promptPrefix: _promptPrefix } = lastSelected;
|
||||||
|
|
||||||
if (modelsFilter[_model]) {
|
if (prevModelsFilter[_model]) {
|
||||||
model = _model;
|
model = _model;
|
||||||
chatGptLabel = _chatGptLabel;
|
chatGptLabel = _chatGptLabel;
|
||||||
promptPrefix = _promptPrefix;
|
promptPrefix = _promptPrefix;
|
||||||
|
|
@ -65,9 +99,9 @@ const useConversation = () => {
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|
||||||
// if anything happens, reset to default model
|
// if anything happens, reset to default model
|
||||||
if (modelsFilter?.chatgpt) model = 'chatgpt';
|
if (prevModelsFilter?.chatgpt) model = 'chatgpt';
|
||||||
else if (modelsFilter?.bingai) model = 'bingai';
|
else if (prevModelsFilter?.bingai) model = 'bingai';
|
||||||
else if (modelsFilter?.chatgptBrowser) model = 'chatgptBrowser';
|
else if (prevModelsFilter?.chatgptBrowser) model = 'chatgptBrowser';
|
||||||
chatGptLabel = null;
|
chatGptLabel = null;
|
||||||
promptPrefix = null;
|
promptPrefix = null;
|
||||||
};
|
};
|
||||||
|
|
@ -77,24 +111,36 @@ const useConversation = () => {
|
||||||
getDefaultModel();
|
getDefaultModel();
|
||||||
|
|
||||||
setConversation({
|
setConversation({
|
||||||
conversationId: 'new',
|
...conversation,
|
||||||
title: 'New Chat',
|
|
||||||
jailbreakConversationId: null,
|
|
||||||
conversationSignature: null,
|
|
||||||
clientId: null,
|
|
||||||
invocationId: null,
|
|
||||||
model: model,
|
model: model,
|
||||||
chatGptLabel: chatGptLabel,
|
chatGptLabel: chatGptLabel,
|
||||||
promptPrefix: promptPrefix,
|
promptPrefix: promptPrefix
|
||||||
user: null,
|
|
||||||
suggestions: [],
|
|
||||||
toneStyle: null
|
|
||||||
});
|
});
|
||||||
setMessages([]);
|
setMessages(messages);
|
||||||
resetLatestMessage();
|
resetLatestMessage();
|
||||||
};
|
};
|
||||||
|
|
||||||
return { newConversation };
|
const newConversation = ({ model = null, chatGptLabel = null, promptPrefix = null } = {}) => {
|
||||||
|
switchToConversation(
|
||||||
|
{
|
||||||
|
conversationId: 'new',
|
||||||
|
title: 'New Chat',
|
||||||
|
jailbreakConversationId: null,
|
||||||
|
conversationSignature: null,
|
||||||
|
clientId: null,
|
||||||
|
invocationId: null,
|
||||||
|
model: model,
|
||||||
|
chatGptLabel: chatGptLabel,
|
||||||
|
promptPrefix: promptPrefix,
|
||||||
|
user: null,
|
||||||
|
suggestions: [],
|
||||||
|
toneStyle: null
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { newConversation, switchToConversation };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ const odd =
|
||||||
'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654] hover:bg-gray-100/40 hover:text-gray-700 dark:hover:bg-[#3b3d49] dark:hover:text-gray-200';
|
'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654] hover:bg-gray-100/40 hover:text-gray-700 dark:hover:bg-[#3b3d49] dark:hover:text-gray-200';
|
||||||
|
|
||||||
export default function buildTree(messages, groupAll = false) {
|
export default function buildTree(messages, groupAll = false) {
|
||||||
|
if (messages === null) return null;
|
||||||
|
|
||||||
let messageMap = {};
|
let messageMap = {};
|
||||||
let rootMessages = [];
|
let rootMessages = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,50 @@
|
||||||
export default function createPayload({ convo, message }) {
|
export default function createPayload(submission) {
|
||||||
const endpoint = `/api/ask`;
|
const { conversation, messages, message, initialResponse, isRegenerate = false } = submission;
|
||||||
let payload = { ...message };
|
|
||||||
const { model } = message;
|
|
||||||
|
|
||||||
if (!payload.conversationId)
|
const endpoint = `/api/ask`;
|
||||||
if (convo?.conversationId && convo?.parentMessageId) {
|
const {
|
||||||
payload = {
|
model,
|
||||||
...payload,
|
chatGptLabel,
|
||||||
conversationId: convo.conversationId,
|
promptPrefix,
|
||||||
parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000'
|
jailbreakConversationId,
|
||||||
};
|
conversationId,
|
||||||
|
conversationSignature,
|
||||||
|
clientId,
|
||||||
|
invocationId,
|
||||||
|
toneStyle
|
||||||
|
} = conversation;
|
||||||
|
|
||||||
|
let payload = {
|
||||||
|
...message,
|
||||||
|
...{
|
||||||
|
model,
|
||||||
|
chatGptLabel,
|
||||||
|
promptPrefix,
|
||||||
|
conversationId
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// if (!payload.conversationId)
|
||||||
|
// if (convo?.conversationId && convo?.parentMessageId) {
|
||||||
|
// payload = {
|
||||||
|
// ...payload,
|
||||||
|
// conversationId: convo.conversationId,
|
||||||
|
// parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000'
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
const isBing = model === 'bingai' || model === 'sydney';
|
const isBing = model === 'bingai' || model === 'sydney';
|
||||||
if (isBing && !convo?.conversationId) {
|
if (isBing && !conversationId) {
|
||||||
payload.toneStyle = convo.toneStyle || 'fast';
|
payload.toneStyle = toneStyle || 'fast';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBing && convo?.conversationId) {
|
if (isBing && conversationId) {
|
||||||
payload = {
|
payload = {
|
||||||
...payload,
|
...payload,
|
||||||
jailbreakConversationId: convo.jailbreakConversationId,
|
jailbreakConversationId,
|
||||||
conversationId: convo.conversationId,
|
conversationSignature,
|
||||||
conversationSignature: convo.conversationSignature,
|
clientId,
|
||||||
clientId: convo.clientId,
|
invocationId
|
||||||
invocationId: convo.invocationId,
|
|
||||||
toneStyle: convo.toneStyle,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue