diff --git a/frontend/src/_locales/zh-hans/main.json b/frontend/src/_locales/zh-hans/main.json
index 2af1d89..025324a 100644
--- a/frontend/src/_locales/zh-hans/main.json
+++ b/frontend/src/_locales/zh-hans/main.json
@@ -20,11 +20,11 @@
"Manage Models": "管理模型",
"Model": "模型",
"Model Parameters": "模型参数",
- "Frequency Penalty *": "Frequency Penalty *",
- "Presence Penalty *": "Presence Penalty *",
- "Top_P *": "Top_P *",
- "Temperature *": "Temperature *",
- "Max Response Token *": "最大响应 Token *",
+ "Frequency Penalty": "Frequency Penalty",
+ "Presence Penalty": "Presence Penalty",
+ "Top_P": "Top_P",
+ "Temperature": "Temperature",
+ "Max Response Token": "最大响应 Token",
"API Port": "API 端口",
"Hover your mouse over the text to view a detailed description. Settings marked with * will take effect immediately after being saved.": "把鼠标悬停在文本上查看详细描述. 标记了星号 * 的设置在保存后会立即生效.",
"Default API Parameters": "默认 API 参数",
@@ -102,5 +102,17 @@
"Enabling this option can greatly improve inference speed, but there may be compatibility issues. If it fails to start, please turn off this option.": "开启这个选项能大大提升推理速度,但可能存在兼容性问题,如果启动失败,请关闭此选项",
"Supported custom cuda file not found": "没有找到支持的自定义cuda文件",
"Failed to copy custom cuda file": "自定义cuda文件复制失败",
- "Downloading update, please wait. If it is not completed, please manually download the program from GitHub and replace the original program.": "正在下载更新,请等待。如果一直未完成,请从Github手动下载并覆盖原程序"
+ "Downloading update, please wait. If it is not completed, please manually download the program from GitHub and replace the original program.": "正在下载更新,请等待。如果一直未完成,请从Github手动下载并覆盖原程序",
+ "Completion": "补全",
+ "Parameters": "参数",
+ "Stop Sequences": "停止词",
+ "When this content appears in the response result, the generation will end.": "响应结果出现该内容时就结束生成",
+ "Reset": "重置",
+ "Generate": "生成",
+ "Writer": "写作",
+ "Translator": "翻译",
+ "Catgirl": "猫娘",
+ "Explain Code": "代码解释",
+ "Werewolf": "狼人杀",
+ "Blank": "空白"
}
\ No newline at end of file
diff --git a/frontend/src/components/Labeled.tsx b/frontend/src/components/Labeled.tsx
index 483bc6a..b13e327 100644
--- a/frontend/src/components/Labeled.tsx
+++ b/frontend/src/components/Labeled.tsx
@@ -3,18 +3,25 @@ import { Label, Tooltip } from '@fluentui/react-components';
import classnames from 'classnames';
export const Labeled: FC<{
- label: string; desc?: string | null, content: ReactElement, flex?: boolean, spaceBetween?: boolean
+ label: string;
+ desc?: string | null,
+ content: ReactElement,
+ flex?: boolean,
+ spaceBetween?: boolean,
+ breakline?: boolean
}> = ({
label,
desc,
content,
flex,
- spaceBetween
+ spaceBetween,
+ breakline
}) => {
return (
{desc ?
diff --git a/frontend/src/components/WorkHeader.tsx b/frontend/src/components/WorkHeader.tsx
new file mode 100644
index 0000000..ec6100f
--- /dev/null
+++ b/frontend/src/components/WorkHeader.tsx
@@ -0,0 +1,46 @@
+import React, { FC } from 'react';
+import { observer } from 'mobx-react-lite';
+import { Divider, PresenceBadge, Text } from '@fluentui/react-components';
+import commonStore, { ModelStatus } from '../stores/commonStore';
+import { ConfigSelector } from './ConfigSelector';
+import { RunButton } from './RunButton';
+import { PresenceBadgeStatus } from '@fluentui/react-badge';
+import { useTranslation } from 'react-i18next';
+
+const statusText = {
+ [ModelStatus.Offline]: 'Offline',
+ [ModelStatus.Starting]: 'Starting',
+ [ModelStatus.Loading]: 'Loading',
+ [ModelStatus.Working]: 'Working'
+};
+
+const badgeStatus: { [modelStatus: number]: PresenceBadgeStatus } = {
+ [ModelStatus.Offline]: 'unknown',
+ [ModelStatus.Starting]: 'away',
+ [ModelStatus.Loading]: 'away',
+ [ModelStatus.Working]: 'available'
+};
+
+export const WorkHeader: FC = observer(() => {
+ const { t } = useTranslation();
+ const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
+
+ return (
+
+
+
+
+
{t('Model Status') + ': ' + t(statusText[commonStore.status.modelStatus])}
+
+
+
+
+
+
+
+ {t('This tool\'s API is compatible with OpenAI API. It can be used with any ChatGPT tool you like. Go to the settings of some ChatGPT tool, replace the \'https://api.openai.com\' part in the API address with \'') + `http://127.0.0.1:${port}` + '\'.'}
+
+
+
+ );
+});
\ No newline at end of file
diff --git a/frontend/src/pages/Chat.tsx b/frontend/src/pages/Chat.tsx
index 682754d..44f4b62 100644
--- a/frontend/src/pages/Chat.tsx
+++ b/frontend/src/pages/Chat.tsx
@@ -1,11 +1,8 @@
import React, { FC, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { RunButton } from '../components/RunButton';
-import { Avatar, Divider, PresenceBadge, Text, Textarea } from '@fluentui/react-components';
+import { Avatar, PresenceBadge, Textarea } from '@fluentui/react-components';
import commonStore, { ModelStatus } from '../stores/commonStore';
import { observer } from 'mobx-react-lite';
-import { PresenceBadgeStatus } from '@fluentui/react-badge';
-import { ConfigSelector } from '../components/ConfigSelector';
import { v4 as uuid } from 'uuid';
import classnames from 'classnames';
import { fetchEventSource } from '@microsoft/fetch-event-source';
@@ -17,6 +14,7 @@ import { ArrowCircleUp28Regular, Delete28Regular, RecordStop28Regular } from '@f
import { CopyButton } from '../components/CopyButton';
import { ReadButton } from '../components/ReadButton';
import { toast } from 'react-toastify';
+import { WorkHeader } from '../components/WorkHeader';
export const userName = 'M E';
export const botName = 'A I';
@@ -293,40 +291,10 @@ const ChatPanel: FC = observer(() => {
);
});
-const statusText = {
- [ModelStatus.Offline]: 'Offline',
- [ModelStatus.Starting]: 'Starting',
- [ModelStatus.Loading]: 'Loading',
- [ModelStatus.Working]: 'Working'
-};
-
-const badgeStatus: { [modelStatus: number]: PresenceBadgeStatus } = {
- [ModelStatus.Offline]: 'unknown',
- [ModelStatus.Starting]: 'away',
- [ModelStatus.Loading]: 'away',
- [ModelStatus.Working]: 'available'
-};
-
export const Chat: FC = observer(() => {
- const { t } = useTranslation();
- const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
-
return (
-
-
-
-
{t('Model Status') + ': ' + t(statusText[commonStore.status.modelStatus])}
-
-
-
-
-
-
-
- {t('This tool\'s API is compatible with OpenAI API. It can be used with any ChatGPT tool you like. Go to the settings of some ChatGPT tool, replace the \'https://api.openai.com\' part in the API address with \'') + `http://127.0.0.1:${port}` + '\'.'}
-
-
+
);
diff --git a/frontend/src/pages/Completion.tsx b/frontend/src/pages/Completion.tsx
new file mode 100644
index 0000000..4b74352
--- /dev/null
+++ b/frontend/src/pages/Completion.tsx
@@ -0,0 +1,302 @@
+import React, { FC, useEffect, useRef } from 'react';
+import { observer } from 'mobx-react-lite';
+import { WorkHeader } from '../components/WorkHeader';
+import { Button, Dropdown, Input, Option, Textarea } from '@fluentui/react-components';
+import { Labeled } from '../components/Labeled';
+import { ValuedSlider } from '../components/ValuedSlider';
+import { useTranslation } from 'react-i18next';
+import { ApiParameters } from './Configs';
+import commonStore, { ModelStatus } from '../stores/commonStore';
+import { fetchEventSource } from '@microsoft/fetch-event-source';
+import { toast } from 'react-toastify';
+
+export type CompletionParams = Omit
& { stop: string }
+
+export type CompletionPreset = {
+ name: string,
+ prompt: string,
+ params: CompletionParams
+}
+
+export const defaultPresets: CompletionPreset[] = [{
+ name: 'Writer',
+ prompt: '以下是不朽的科幻史诗巨著,描写细腻,刻画了宏大的星际文明战争。\n第一章\n',
+ params: {
+ maxResponseToken: 4100,
+ temperature: 1,
+ topP: 0.5,
+ presencePenalty: 0.4,
+ frequencyPenalty: 0.4,
+ stop: ''
+ }
+}, {
+ name: 'Translator',
+ prompt: '',
+ params: {
+ maxResponseToken: 4100,
+ temperature: 1,
+ topP: 0.5,
+ presencePenalty: 0.4,
+ frequencyPenalty: 0.4,
+ stop: ''
+ }
+}, {
+ name: 'Catgirl',
+ prompt: '',
+ params: {
+ maxResponseToken: 4100,
+ temperature: 1,
+ topP: 0.5,
+ presencePenalty: 0.4,
+ frequencyPenalty: 0.4,
+ stop: ''
+ }
+}, {
+ name: 'Explain Code',
+ prompt: '',
+ params: {
+ maxResponseToken: 4100,
+ temperature: 1,
+ topP: 0.5,
+ presencePenalty: 0.4,
+ frequencyPenalty: 0.4,
+ stop: ''
+ }
+}, {
+ name: 'Werewolf',
+ prompt: '',
+ params: {
+ maxResponseToken: 4100,
+ temperature: 1,
+ topP: 0.5,
+ presencePenalty: 0.4,
+ frequencyPenalty: 0.4,
+ stop: ''
+ }
+}, {
+ name: 'Blank',
+ prompt: '',
+ params: {
+ maxResponseToken: 4100,
+ temperature: 1,
+ topP: 0.5,
+ presencePenalty: 0.4,
+ frequencyPenalty: 0.4,
+ stop: ''
+ }
+}];
+
+const CompletionPanel: FC = observer(() => {
+ const { t } = useTranslation();
+ const inputRef = useRef(null);
+ const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
+ const sseControllerRef = useRef(null);
+
+ const scrollToBottom = () => {
+ if (inputRef.current)
+ inputRef.current.scrollTop = inputRef.current.scrollHeight;
+ };
+
+ useEffect(() => {
+ if (inputRef.current)
+ inputRef.current.style.height = '100%';
+ scrollToBottom();
+ }, []);
+
+ if (!commonStore.completionPreset)
+ commonStore.setCompletionPreset(defaultPresets[0]);
+
+ const name = commonStore.completionPreset!.name;
+
+ const prompt = commonStore.completionPreset!.prompt;
+ const setPrompt = (prompt: string) => {
+ commonStore.setCompletionPreset({
+ ...commonStore.completionPreset!,
+ prompt
+ });
+ };
+
+ const params = commonStore.completionPreset!.params;
+ const setParams = (newParams: Partial) => {
+ commonStore.setCompletionPreset({
+ ...commonStore.completionPreset!,
+ params: {
+ ...commonStore.completionPreset!.params,
+ ...newParams
+ }
+ });
+ };
+
+ const onSubmit = (prompt: string) => {
+ if (commonStore.status.modelStatus === ModelStatus.Offline) {
+ toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
+ return;
+ }
+
+ let answer = '';
+ sseControllerRef.current = new AbortController();
+ fetchEventSource(`http://127.0.0.1:${port}/completions`, // https://api.openai.com/v1/completions || http://127.0.0.1:${port}/completions
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer sk-`
+ },
+ body: JSON.stringify({
+ prompt,
+ stream: true,
+ model: 'text-davinci-003',
+ max_tokens: params.maxResponseToken,
+ temperature: params.temperature,
+ top_p: params.topP,
+ presence_penalty: params.presencePenalty,
+ frequency_penalty: params.frequencyPenalty,
+ stop: params.stop || undefined
+ }),
+ signal: sseControllerRef.current?.signal,
+ onmessage(e) {
+ console.log('sse message', e);
+ scrollToBottom();
+ if (e.data === '[DONE]') {
+ commonStore.setCompletionGenerating(false);
+ return;
+ }
+ let data;
+ try {
+ data = JSON.parse(e.data);
+ } catch (error) {
+ console.debug('json error', error);
+ return;
+ }
+ if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) {
+ answer += data.choices[0].text;
+ setPrompt(prompt + answer);
+ }
+ },
+ onclose() {
+ console.log('Connection closed');
+ },
+ onerror(err) {
+ commonStore.setCompletionGenerating(false);
+ throw err;
+ }
+ });
+ };
+
+ return (
+
+ );
+});
+
+export const Completion: FC = observer(() => {
+ return (
+
+
+
+
+ );
+});
diff --git a/frontend/src/pages/Configs.tsx b/frontend/src/pages/Configs.tsx
index b124726..f1b6d02 100644
--- a/frontend/src/pages/Configs.tsx
+++ b/frontend/src/pages/Configs.tsx
@@ -643,7 +643,7 @@ export const Configs: FC = observer(() => {
});
}} />
} />
- {
});
}} />
} />
- {
});
}} />
} />
- {
});
}} />
} />
- {
});
}} />
} />
- ,
top: true
},
+ {
+ label: 'Completion',
+ path: '/completion',
+ icon: ,
+ element: ,
+ top: true
+ },
{
label: 'Configs',
path: '/configs',
diff --git a/frontend/src/stores/commonStore.ts b/frontend/src/stores/commonStore.ts
index 5a3812f..e576787 100644
--- a/frontend/src/stores/commonStore.ts
+++ b/frontend/src/stores/commonStore.ts
@@ -10,6 +10,7 @@ import { SettingsType } from '../pages/Settings';
import { IntroductionContent } from '../pages/Home';
import { AboutContent } from '../pages/About';
import i18n from 'i18next';
+import { CompletionPreset } from '../pages/Completion';
export enum ModelStatus {
Offline,
@@ -37,6 +38,9 @@ class CommonStore {
// chat
conversations: Conversations = {};
conversationsOrder: string[] = [];
+ // completion
+ completionPreset: CompletionPreset | null = null;
+ completionGenerating: boolean = false;
// configs
currentModelConfigIndex: number = 0;
modelConfigs: ModelConfig[] = [];
@@ -155,6 +159,14 @@ class CommonStore {
setConversationsOrder = (value: string[]) => {
this.conversationsOrder = value;
};
+
+ setCompletionPreset(value: CompletionPreset) {
+ this.completionPreset = value;
+ }
+
+ setCompletionGenerating(value: boolean) {
+ this.completionGenerating = value;
+ }
}
export default new CommonStore();
\ No newline at end of file