feat: new endpoint-style structure in client side

NOT FINISHED. model menu and icon and send message not work.
This commit is contained in:
Wentao Lyu 2023-03-31 00:20:45 +08:00
parent dd825dc6d4
commit c53778e7c1
10 changed files with 172 additions and 243 deletions

View file

@ -1,5 +1,5 @@
import React, { useState, useRef } from 'react';
import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil';
import { useRecoilState, useSetRecoilState } from 'recoil';
import RenameButton from './RenameButton';
import DeleteButton from './DeleteButton';
@ -10,9 +10,7 @@ import store from '~/store';
export default function Conversation({ conversation, retainView }) {
const [currentConversation, setCurrentConversation] = useRecoilState(store.conversation);
const setMessages = useSetRecoilState(store.messages);
const setSubmission = useSetRecoilState(store.submission);
const resetLatestMessage = useResetRecoilState(store.latestMessage);
const { refreshConversations } = store.useConversations();
const { switchToConversation } = store.useConversation();
@ -21,33 +19,10 @@ export default function Conversation({ conversation, retainView }) {
const [titleInput, setTitleInput] = useState(title);
const inputRef = useRef(null);
const {
model,
parentMessageId,
conversationId,
title,
chatGptLabel = null,
promptPrefix = null,
jailbreakConversationId,
conversationSignature,
clientId,
invocationId,
toneStyle
} = conversation;
const { conversationId, title } = conversation;
const rename = manualSWR(`/api/convos/update`, 'post');
const bingData = conversationSignature
? {
jailbreakConversationId: jailbreakConversationId,
conversationSignature: conversationSignature,
parentMessageId: parentMessageId || null,
clientId: clientId,
invocationId: invocationId,
toneStyle: toneStyle
}
: null;
const clickHandler = async () => {
if (currentConversation?.conversationId === conversationId) {
return;
@ -58,64 +33,6 @@ export default function Conversation({ conversation, retainView }) {
// set conversation to the new conversation
switchToConversation(conversation);
// if (!stopStream) {
// dispatch(setStopStream(true));
// dispatch(setSubmission({}));
// }
// dispatch(setEmptyMessage());
// const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix };
// if (bingData) {
// const {
// parentMessageId,
// conversationSignature,
// jailbreakConversationId,
// clientId,
// invocationId,
// toneStyle
// } = bingData;
// dispatch(
// setConversation({
// ...convo,
// parentMessageId,
// jailbreakConversationId,
// conversationSignature,
// clientId,
// invocationId,
// toneStyle,
// latestMessage: null
// })
// );
// } else {
// dispatch(
// setConversation({
// ...convo,
// parentMessageId,
// jailbreakConversationId: null,
// conversationSignature: null,
// clientId: null,
// invocationId: null,
// toneStyle: null,
// latestMessage: null
// })
// );
// }
// const data = await trigger();
// if (chatGptLabel) {
// dispatch(setModel('chatgptCustom'));
// dispatch(setCustomModel(chatGptLabel.toLowerCase()));
// } else {
// dispatch(setModel(model));
// dispatch(setCustomModel(null));
// }
// dispatch(setMessages(data));
// dispatch(setCustomGpt(convo));
// dispatch(setText(''));
// dispatch(setStopStream(false));
};
const renameHandler = e => {

View file

@ -3,7 +3,7 @@ import { useRecoilValue, useRecoilState } from 'recoil';
import SubmitButton from './SubmitButton';
import AdjustToneButton from './AdjustToneButton';
import BingStyles from './BingStyles';
import ModelMenu from './Models/ModelMenu';
// import ModelMenu from './Models/ModelMenu';
import Footer from './Footer';
import TextareaAutosize from 'react-textarea-autosize';
import RegenerateIcon from '../svg/RegenerateIcon';
@ -167,7 +167,7 @@ export default function TextChat({ isSearchView = false }) {
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 />
{/* <ModelMenu /> */}
<TextareaAutosize
tabIndex="0"
autoFocus

View file

@ -2,21 +2,22 @@ import React from 'react';
// import Clipboard from '../svg/Clipboard';
import EditIcon from '../svg/EditIcon';
export default function HoverButtons({ visible, onClick, model }) {
const isBing = model === 'bingai';
const enabled = !isBing;
export default function HoverButtons({ visible, onClick, endpoint }) {
const enabled = endpoint in ['azureOpenAI', 'openAI', 'chatGPTBrowser'];
return (
<div className="visible mt-2 flex justify-center gap-3 self-end text-gray-400 md:gap-4 lg:absolute lg:top-0 lg:right-0 lg:mt-0 lg:translate-x-full lg:gap-1 lg:self-center lg:pl-2">
{(visible&&enabled)?(
{visible && enabled ? (
<>
<button className="resubmit-edit-button rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible"
onClick={onClick}>
<button
className="resubmit-edit-button rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible"
onClick={onClick}
>
{/* <button className="rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400"> */}
<EditIcon />
</button>
</>
):null}
) : null}
{/* <button className="rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400">
<Clipboard />
</button> */}

View file

@ -6,7 +6,7 @@ import MultiMessage from './MultiMessage';
import HoverButtons from './HoverButtons';
import SiblingSwitch from './SiblingSwitch';
import { fetchById } from '~/utils/fetchers';
import { getIconOfModel } from '~/utils';
import { getIconOfAi } from '~/utils';
import { useMessageHandler } from '~/utils/handleSubmit';
import store from '~/store';
@ -23,32 +23,15 @@ export default function Message({
}) {
const isSubmitting = useRecoilValue(store.isSubmitting);
const setLatestMessage = useSetRecoilState(store.latestMessage);
const { model, chatGptLabel, promptPrefix } = conversation;
// const { model, chatGptLabel, promptPrefix } = conversation;
const [abortScroll, setAbort] = useState(false);
const {
sender,
text,
searchResult,
isCreatedByUser,
error,
submitting,
model: messageModel,
chatGptLabel: messageChatGptLabel,
searchResult: isSearchResult
} = message;
const { text, searchResult, isCreatedByUser, error, submitting } = message;
const textEditor = useRef(null);
const last = !message?.children?.length;
const edit = message.messageId == currentEditId;
const { ask } = useMessageHandler();
const { switchToConversation } = store.useConversation();
const blinker = submitting && isSubmitting;
const generateCursor = useCallback(() => {
if (!blinker) {
return '';
}
return <span className="result-streaming"></span>;
}, [blinker]);
useEffect(() => {
if (blinker && !abortScroll) {
@ -77,14 +60,9 @@ export default function Message({
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800'
};
const icon = getIconOfModel({
sender,
isCreatedByUser,
model: isSearchResult ? messageModel : model,
searchResult,
chatGptLabel: isSearchResult ? messageChatGptLabel : chatGptLabel,
promptPrefix,
error
const icon = getIconOfAi({
...conversation,
...message
});
if (!isCreatedByUser)
@ -199,7 +177,7 @@ export default function Message({
)}
</div>
<HoverButtons
model={model}
endpoint={conversation?.endpoint}
visible={!error && isCreatedByUser && !edit && !searchResult}
onClick={() => enterEdit()}
/>

View file

@ -1,38 +0,0 @@
import React, { useRef, useEffect, useState } from 'react';
const MessageBar = ({ children, dynamicProps, handleWheel, clickSearchResult }) => {
const ref = useRef(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.unobserve(ref.current);
}
},
{ threshold: 0.1 }
);
observer.observe(ref.current);
return () => {
observer.unobserve(ref.current);
};
}, []);
return (
<div
{...dynamicProps}
onWheel={handleWheel}
// onClick={clickSearchResult}
ref={ref}
>
{isVisible ? children : null}
</div>
);
};
export default MessageBar;

View file

@ -20,10 +20,10 @@ export default function Messages({ isSearchView = false }) {
const _messagesTree = isSearchView ? searchResultMessagesTree : messagesTree;
const conversation = useRecoilValue(store.conversation) || {};
const { conversationId, model, chatGptLabel, toneStyle } = conversation;
const { conversationId, endpoint } = conversation;
const models = useRecoilValue(store.models) || [];
const modelName = models.find(element => element.model == model)?.name;
// const models = useRecoilValue(store.models) || [];
// const modelName = models.find(element => element.model == model)?.name;
const searchQuery = useRecoilValue(store.searchQuery);
@ -82,9 +82,22 @@ export default function Messages({ isSearchView = false }) {
const getConversationTitle = () => {
if (isSearchView) return `Search: ${searchQuery}`;
else {
let _title = `Model: ${modelName}`;
if (chatGptLabel) _title += ` of ${chatGptLabel}`;
if (toneStyle) _title += ` (${toneStyle})`;
let _title = `${endpoint}`;
if (endpoint === 'azureOpenAI' || endpoint === 'openAI') {
const { chatGptLabel, model } = conversation;
if (model) _title += `: ${model}`;
if (chatGptLabel) _title += ` as ${chatGptLabel}`;
} else if (endpoint === 'bingAI') {
const { jailbreak, toneStyle } = conversation;
if (toneStyle) _title += `: ${toneStyle}`;
if (jailbreak) _title += ` as Sydney`;
} else if (endpoint === 'chatGPTBrowser') {
const { model } = conversation;
if (model) _title += `: ${model}`;
} else if (endpoint === null) {
null;
}
return _title;
}
};

View file

@ -1,23 +1,34 @@
import models from './models';
import endpoints from './endpoints';
import { atom, selector, useSetRecoilState, useResetRecoilState, useRecoilCallback } from 'recoil';
import buildTree from '~/utils/buildTree';
import getDefaultConversation from '~/utils/getDefaultConversation';
// current conversation, can be null (need to be fetched from server)
// sample structure
// {
// conversationId: "new",
// title: "New Chat",
// conversationId: 'new',
// title: 'New Chat',
// user: null,
// // endpoint: [azureOpenAI, openAI, bingAI, chatGPTBrowser]
// endpoint: 'azureOpenAI',
// // for azureOpenAI, openAI, chatGPTBrowser only
// model: 'gpt-3.5-turbo',
// // for azureOpenAI, openAI only
// chatGptLabel: null,
// promptPrefix: null,
// temperature: 0.8,
// top_p: 1,
// presence_penalty: 1,
// // for bingAI only
// jailbreak: false,
// jailbreakConversationId: null,
// conversationSignature: null,
// clientId: null,
// invocationId: null,
// model: "chatgpt",
// chatGptLabel: null,
// promptPrefix: null,
// user: null,
// suggestions: [],
// toneStyle: null,
// }
// suggestions: []
// };
const conversation = atom({
key: 'conversation',
default: null
@ -52,8 +63,8 @@ const useConversation = () => {
({ snapshot }) =>
async (_conversation, messages = null) => {
const prevConversation = await snapshot.getPromise(conversation);
const prevModelsFilter = await snapshot.getPromise(models.modelsFilter);
_switchToConversation(_conversation, messages, { prevModelsFilter, prevConversation });
const availableEndpoints = await snapshot.getPromise(endpoints.availableEndpoints);
_switchToConversation(_conversation, messages, { availableEndpoints, prevConversation });
},
[]
);
@ -61,71 +72,25 @@ const useConversation = () => {
const _switchToConversation = (
conversation,
messages = null,
{ prevModelsFilter = {}, prev_conversation = {} }
{ availableEndpoints = [], prevConversation = {} }
) => {
let { model = null, chatGptLabel = null, promptPrefix = null } = conversation;
const getDefaultModel = () => {
try {
// try to use current model
const { _model = null, _chatGptLabel = null, _promptPrefix = null } = prev_conversation || {};
if (prevModelsFilter[_model]) {
model = _model;
chatGptLabel = _chatGptLabel;
promptPrefix = _promptPrefix;
return;
}
} catch (error) {}
let { endpoint = null } = conversation;
try {
// try to read latest selected model from local storage
const lastSelected = JSON.parse(localStorage.getItem('model'));
const { model: _model, chatGptLabel: _chatGptLabel, promptPrefix: _promptPrefix } = lastSelected;
if (prevModelsFilter[_model]) {
model = _model;
chatGptLabel = _chatGptLabel;
promptPrefix = _promptPrefix;
return;
}
} catch (error) {}
// if anything happens, reset to default model
if (prevModelsFilter?.chatgpt) model = 'chatgpt';
else if (prevModelsFilter?.bingai) model = 'bingai';
else if (prevModelsFilter?.chatgptBrowser) model = 'chatgptBrowser';
chatGptLabel = null;
promptPrefix = null;
};
if (model === null)
if (endpoint === null)
// get the default model
getDefaultModel();
conversation = getDefaultConversation({ conversation, availableEndpoints, prevConversation });
setConversation({
...conversation,
model: model,
chatGptLabel: chatGptLabel,
promptPrefix: promptPrefix
});
setConversation(conversation);
setMessages(messages);
resetLatestMessage();
};
const newConversation = ({ model = null, chatGptLabel = null, promptPrefix = null } = {}) => {
const newConversation = (template = {}) => {
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
...template
},
[]
);
@ -135,17 +100,7 @@ const useConversation = () => {
switchToConversation(
{
conversationId: 'search',
title: 'Search',
jailbreakConversationId: null,
conversationSignature: null,
clientId: null,
invocationId: null,
model: null,
chatGptLabel: null,
promptPrefix: null,
user: null,
suggestions: [],
toneStyle: null
title: 'Search'
},
[]
);

View file

@ -0,0 +1,26 @@
import { atom, selector } from 'recoil';
const endpointsFilter = atom({
key: 'endpointsFilter',
default: {
azureOpenAI: false,
openAI: false,
bingAI: false,
chatGPTBrowser: false
}
});
const availableEndpoints = selector({
key: 'availableEndpoints',
get: ({ get }) => {
const endpoints = ['azureOpenAI', 'openAI', 'bingAI', 'chatGPTBrowser'];
const f = get(endpointsFilter);
return endpoints.filter(endpoint => f[endpoint]);
}
});
// const modelAvailable
export default {
endpointsFilter,
availableEndpoints
};

View file

@ -0,0 +1,77 @@
const buildDefaultConversation = ({ conversation, endpoint, lastConversationSetup = {} }) => {
if (endpoint === 'azureOpenAI' || endpoint === 'openAI') {
conversation = {
...conversation,
endpoint,
model: lastConversationSetup?.model || 'gpt-3.5-turbo',
chatGptLabel: lastConversationSetup?.chatGptLabel || null,
promptPrefix: lastConversationSetup?.promptPrefix || null,
temperature: lastConversationSetup?.temperature || 0.8,
top_p: lastConversationSetup?.top_p || 1,
presence_penalty: lastConversationSetup?.presence_penalty || 1
};
} else if (endpoint === 'bingAI') {
conversation = {
...conversation,
endpoint,
jailbreak: lastConversationSetup?.jailbreak || false,
jailbreakConversationId: lastConversationSetup?.jailbreakConversationId || null,
conversationSignature: lastConversationSetup?.conversationSignature || null,
clientId: lastConversationSetup?.clientId || null,
invocationId: lastConversationSetup?.invocationId || null,
toneStyle: lastConversationSetup?.toneStyle || null,
suggestions: lastConversationSetup?.suggestions || []
};
} else if (endpoint === 'chatGPTBrowser') {
conversation = {
...conversation,
endpoint,
model: lastConversationSetup?.model || 'text-davinci-002-render-sha'
};
} else if (endpoint === null) {
conversation = {
...conversation,
endpoint
};
}
return conversation;
};
const getDefaultConversation = ({ conversation, prevConversation, availableEndpoints }) => {
try {
// try to use current model
const { endpoint = null } = prevConversation || {};
if (endpoint in availableEndpoints) {
conversation = buildDefaultConversation({
conversation,
endpoint,
lastConversationSetup: prevConversation
});
return conversation;
}
} catch (error) {}
try {
// try to read latest selected model from local storage
const lastConversationSetup = JSON.parse(localStorage.getItem('lastConversationSetup'));
const { endpoint = null } = lastConversationSetup;
if (endpoint in availableEndpoints) {
conversation = buildDefaultConversation({ conversation, endpoint, lastConversationSetup });
return conversation;
}
} catch (error) {}
// if anything happens, reset to default model
const endpoint = availableEndpoints?.[0];
if (endpoint) {
conversation = buildDefaultConversation({ conversation, endpoint });
return conversation;
} else {
conversation = buildDefaultConversation({ conversation, endpoint: null });
return conversation;
}
};
export default getDefaultConversation;

View file

@ -38,7 +38,7 @@ export const languages = [
'pascal'
];
export const getIconOfModel = ({
export const getIconOfAi = ({
size = 30,
sender,
isCreatedByUser,