|
|
|
|
@@ -16,8 +16,11 @@ import {
|
|
|
|
|
Attach16Regular,
|
|
|
|
|
Delete28Regular,
|
|
|
|
|
Dismiss16Regular,
|
|
|
|
|
Dismiss24Regular,
|
|
|
|
|
RecordStop28Regular,
|
|
|
|
|
Save28Regular
|
|
|
|
|
SaveRegular,
|
|
|
|
|
TextAlignJustify24Regular,
|
|
|
|
|
TextAlignJustifyRotate9024Regular
|
|
|
|
|
} from '@fluentui/react-icons';
|
|
|
|
|
import { CopyButton } from '../components/CopyButton';
|
|
|
|
|
import { ReadButton } from '../components/ReadButton';
|
|
|
|
|
@@ -26,9 +29,11 @@ import { WorkHeader } from '../components/WorkHeader';
|
|
|
|
|
import { DialogButton } from '../components/DialogButton';
|
|
|
|
|
import { OpenFileFolder, OpenOpenFileDialog, OpenSaveFileDialog } from '../../wailsjs/go/backend_golang/App';
|
|
|
|
|
import { absPathAsset, bytesToReadable, getServerRoot, toastWithButton } from '../utils';
|
|
|
|
|
import { PresetsButton } from './PresetsManager/PresetsButton';
|
|
|
|
|
import { useMediaQuery } from 'usehooks-ts';
|
|
|
|
|
import { botName, ConversationMessage, MessageType, userName, welcomeUuid } from '../types/chat';
|
|
|
|
|
import { Labeled } from '../components/Labeled';
|
|
|
|
|
import { ValuedSlider } from '../components/ValuedSlider';
|
|
|
|
|
import { PresetsButton } from './PresetsManager/PresetsButton';
|
|
|
|
|
|
|
|
|
|
let chatSseControllers: {
|
|
|
|
|
[id: string]: AbortController
|
|
|
|
|
@@ -188,11 +193,125 @@ const ChatMessageItem: FC<{
|
|
|
|
|
</div>;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const SidePanel: FC = observer(() => {
|
|
|
|
|
const [t] = useTranslation();
|
|
|
|
|
const mq = useMediaQuery('(min-width: 640px)');
|
|
|
|
|
const params = commonStore.chatParams;
|
|
|
|
|
|
|
|
|
|
return <div
|
|
|
|
|
className={classnames(
|
|
|
|
|
'flex flex-col gap-1 h-full flex-shrink-0 transition-width duration-300 ease-in-out',
|
|
|
|
|
commonStore.sidePanelCollapsed ? 'w-0' : (mq ? 'w-64' : 'w-full'),
|
|
|
|
|
!commonStore.sidePanelCollapsed && 'ml-1')
|
|
|
|
|
}>
|
|
|
|
|
<div className="flex m-1">
|
|
|
|
|
<div className="grow" />
|
|
|
|
|
<PresetsButton tab="Chat" size="medium" shape="circular" appearance="subtle" />
|
|
|
|
|
<Button size="medium" shape="circular" appearance="subtle" icon={<Dismiss24Regular />}
|
|
|
|
|
onClick={() => commonStore.setSidePanelCollapsed(true)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-1 overflow-x-hidden overflow-y-auto p-1">
|
|
|
|
|
<Labeled flex breakline label={t('Max Response Token')}
|
|
|
|
|
desc={t('By default, the maximum number of tokens that can be answered in a single response, it can be changed by the user by specifying API parameters.')}
|
|
|
|
|
content={
|
|
|
|
|
<ValuedSlider value={params.maxResponseToken} min={100} max={8100}
|
|
|
|
|
step={100}
|
|
|
|
|
input
|
|
|
|
|
onChange={(e, data) => {
|
|
|
|
|
commonStore.setChatParams({
|
|
|
|
|
maxResponseToken: data.value
|
|
|
|
|
});
|
|
|
|
|
}} />
|
|
|
|
|
} />
|
|
|
|
|
<Labeled flex breakline label={t('Temperature')}
|
|
|
|
|
desc={t('Sampling temperature, it\'s like giving alcohol to a model, the higher the stronger the randomness and creativity, while the lower, the more focused and deterministic it will be.')}
|
|
|
|
|
content={
|
|
|
|
|
<ValuedSlider value={params.temperature} min={0} max={2} step={0.1}
|
|
|
|
|
input
|
|
|
|
|
onChange={(e, data) => {
|
|
|
|
|
commonStore.setChatParams({
|
|
|
|
|
temperature: data.value
|
|
|
|
|
});
|
|
|
|
|
}} />
|
|
|
|
|
} />
|
|
|
|
|
<Labeled flex breakline label={t('Top_P')}
|
|
|
|
|
desc={t('Just like feeding sedatives to the model. Consider the results of the top n% probability mass, 0.1 considers the top 10%, with higher quality but more conservative, 1 considers all results, with lower quality but more diverse.')}
|
|
|
|
|
content={
|
|
|
|
|
<ValuedSlider value={params.topP} min={0} max={1} step={0.1} input
|
|
|
|
|
onChange={(e, data) => {
|
|
|
|
|
commonStore.setChatParams({
|
|
|
|
|
topP: data.value
|
|
|
|
|
});
|
|
|
|
|
}} />
|
|
|
|
|
} />
|
|
|
|
|
<Labeled flex breakline label={t('Presence Penalty')}
|
|
|
|
|
desc={t('Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.')}
|
|
|
|
|
content={
|
|
|
|
|
<ValuedSlider value={params.presencePenalty} min={0} max={2}
|
|
|
|
|
step={0.1} input
|
|
|
|
|
onChange={(e, data) => {
|
|
|
|
|
commonStore.setChatParams({
|
|
|
|
|
presencePenalty: data.value
|
|
|
|
|
});
|
|
|
|
|
}} />
|
|
|
|
|
} />
|
|
|
|
|
<Labeled flex breakline label={t('Frequency Penalty')}
|
|
|
|
|
desc={t('Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.')}
|
|
|
|
|
content={
|
|
|
|
|
<ValuedSlider value={params.frequencyPenalty} min={0} max={2}
|
|
|
|
|
step={0.1} input
|
|
|
|
|
onChange={(e, data) => {
|
|
|
|
|
commonStore.setChatParams({
|
|
|
|
|
frequencyPenalty: data.value
|
|
|
|
|
});
|
|
|
|
|
}} />
|
|
|
|
|
} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="grow" />
|
|
|
|
|
{/*<Button*/}
|
|
|
|
|
{/* icon={<FolderOpenVerticalRegular />}*/}
|
|
|
|
|
{/* onClick={() => {*/}
|
|
|
|
|
{/* }}>*/}
|
|
|
|
|
{/* {t('Load Conversation')}*/}
|
|
|
|
|
{/*</Button>*/}
|
|
|
|
|
<Button
|
|
|
|
|
icon={<SaveRegular />}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
let savedContent: string = '';
|
|
|
|
|
const isWorldModel = commonStore.getCurrentModelConfig().modelParameters.modelName.toLowerCase().includes('world');
|
|
|
|
|
const user = isWorldModel ? 'User' : 'Bob';
|
|
|
|
|
const bot = isWorldModel ? 'Assistant' : 'Alice';
|
|
|
|
|
commonStore.conversationOrder.forEach((uuid) => {
|
|
|
|
|
if (uuid === welcomeUuid)
|
|
|
|
|
return;
|
|
|
|
|
const messageItem = commonStore.conversation[uuid];
|
|
|
|
|
if (messageItem.type !== MessageType.Error) {
|
|
|
|
|
savedContent += `${messageItem.sender === userName ? user : bot}: ${messageItem.content}\n\n`;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
OpenSaveFileDialog('*.txt', 'conversation.txt', savedContent).then((path) => {
|
|
|
|
|
if (path)
|
|
|
|
|
toastWithButton(t('Conversation Saved'), t('Open'), () => {
|
|
|
|
|
OpenFileFolder(path, false);
|
|
|
|
|
});
|
|
|
|
|
}).catch(e => {
|
|
|
|
|
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
|
|
|
|
});
|
|
|
|
|
}}>
|
|
|
|
|
{t('Save Conversation')}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const ChatPanel: FC = observer(() => {
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const bodyRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
|
const mq = useMediaQuery('(min-width: 640px)');
|
|
|
|
|
if (commonStore.sidePanelCollapsed === 'auto')
|
|
|
|
|
commonStore.setSidePanelCollapsed(!mq);
|
|
|
|
|
const currentConfig = commonStore.getCurrentModelConfig();
|
|
|
|
|
const apiParams = currentConfig.apiParameters;
|
|
|
|
|
const port = apiParams.apiPort;
|
|
|
|
|
@@ -327,8 +446,10 @@ const ChatPanel: FC = observer(() => {
|
|
|
|
|
messages,
|
|
|
|
|
stream: true,
|
|
|
|
|
model: commonStore.settings.apiChatModelName, // 'gpt-3.5-turbo'
|
|
|
|
|
temperature: apiParams.temperature,
|
|
|
|
|
top_p: apiParams.topP,
|
|
|
|
|
temperature: commonStore.chatParams.temperature,
|
|
|
|
|
top_p: commonStore.chatParams.topP,
|
|
|
|
|
presence_penalty: commonStore.chatParams.presencePenalty,
|
|
|
|
|
frequency_penalty: commonStore.chatParams.frequencyPenalty,
|
|
|
|
|
user_name: commonStore.activePreset?.userName || undefined,
|
|
|
|
|
assistant_name: commonStore.activePreset?.assistantName || undefined,
|
|
|
|
|
presystem: commonStore.activePreset?.presystem && undefined
|
|
|
|
|
@@ -391,14 +512,17 @@ const ChatPanel: FC = observer(() => {
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col w-full grow gap-4 pt-4 overflow-hidden">
|
|
|
|
|
<div className="flex h-full grow pt-4 overflow-hidden">
|
|
|
|
|
<div className="relative flex flex-col w-full grow gap-4 overflow-hidden">
|
|
|
|
|
<Button className="absolute top-1 right-1" size="medium" shape="circular" appearance="subtle"
|
|
|
|
|
icon={commonStore.sidePanelCollapsed ? <TextAlignJustify24Regular /> : <TextAlignJustifyRotate9024Regular />}
|
|
|
|
|
onClick={() => commonStore.setSidePanelCollapsed(!commonStore.sidePanelCollapsed)} />
|
|
|
|
|
<div ref={bodyRef} className="grow overflow-y-scroll overflow-x-hidden pr-2">
|
|
|
|
|
{commonStore.conversationOrder.map(uuid =>
|
|
|
|
|
<ChatMessageItem key={uuid} uuid={uuid} onSubmit={onSubmit} />
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className={classnames('flex items-end', mq ? 'gap-2' : '')}>
|
|
|
|
|
<PresetsButton tab="Chat" size={mq ? 'large' : 'small'} shape="circular" appearance="subtle" />
|
|
|
|
|
<DialogButton tooltip={t('Clear')}
|
|
|
|
|
icon={<Delete28Regular />}
|
|
|
|
|
size={mq ? 'large' : 'small'} shape="circular" appearance="subtle" title={t('Clear')}
|
|
|
|
|
@@ -543,34 +667,10 @@ const ChatPanel: FC = observer(() => {
|
|
|
|
|
handleKeyDownOrClick(e);
|
|
|
|
|
}
|
|
|
|
|
}} />
|
|
|
|
|
<ToolTipButton desc={t('Save')}
|
|
|
|
|
icon={<Save28Regular />}
|
|
|
|
|
size={mq ? 'large' : 'small'} shape="circular" appearance="subtle"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
let savedContent: string = '';
|
|
|
|
|
const isWorldModel = commonStore.getCurrentModelConfig().modelParameters.modelName.toLowerCase().includes('world');
|
|
|
|
|
const user = isWorldModel ? 'User' : 'Bob';
|
|
|
|
|
const bot = isWorldModel ? 'Assistant' : 'Alice';
|
|
|
|
|
commonStore.conversationOrder.forEach((uuid) => {
|
|
|
|
|
if (uuid === welcomeUuid)
|
|
|
|
|
return;
|
|
|
|
|
const messageItem = commonStore.conversation[uuid];
|
|
|
|
|
if (messageItem.type !== MessageType.Error) {
|
|
|
|
|
savedContent += `${messageItem.sender === userName ? user : bot}: ${messageItem.content}\n\n`;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
OpenSaveFileDialog('*.txt', 'conversation.txt', savedContent).then((path) => {
|
|
|
|
|
if (path)
|
|
|
|
|
toastWithButton(t('Conversation Saved'), t('Open'), () => {
|
|
|
|
|
OpenFileFolder(path, false);
|
|
|
|
|
});
|
|
|
|
|
}).catch(e => {
|
|
|
|
|
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
|
|
|
|
});
|
|
|
|
|
}} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<SidePanel />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|