feat: user settings

This commit is contained in:
jialin
2024-05-30 09:26:09 +08:00
parent a99b58fbb4
commit 69f354b278
14 changed files with 382 additions and 22 deletions
+9 -1
View File
@@ -26,7 +26,7 @@ export default [
path: '/models',
key: 'models',
icon: 'Block',
component: './llm-models'
component: './llmodels'
},
{
name: 'Nodes',
@@ -49,6 +49,14 @@ export default [
icon: 'Team',
component: './users'
},
{
name: 'Profile',
path: '/profile',
key: 'profile',
hideInMenu: true,
component: './profile',
icon: 'User'
},
{
name: '404',
path: '*',
+1
View File
@@ -14,6 +14,7 @@
"@ant-design/icons": "^5.3.7",
"@ant-design/plots": "^2.2.2",
"@ant-design/pro-components": "^2.7.1",
"@monaco-editor/react": "^4.6.0",
"@types/lodash": "^4.17.4",
"@umijs/max": "^4.2.1",
"antd": "^5.17.0",
+33
View File
@@ -14,6 +14,9 @@ dependencies:
'@ant-design/pro-components':
specifier: ^2.7.1
version: 2.7.1(antd@5.17.0)(rc-field-form@1.44.0)(react-dom@18.3.1)(react@18.3.1)
'@monaco-editor/react':
specifier: ^4.6.0
version: 4.6.0(monaco-editor@0.49.0)(react-dom@18.3.1)(react@18.3.1)
'@types/lodash':
specifier: ^4.17.4
version: 4.17.4
@@ -2418,6 +2421,28 @@ packages:
react-is: 16.13.1
dev: false
/@monaco-editor/loader@1.4.0(monaco-editor@0.49.0):
resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==}
peerDependencies:
monaco-editor: '>= 0.21.0 < 1'
dependencies:
monaco-editor: 0.49.0
state-local: 1.0.7
dev: false
/@monaco-editor/react@4.6.0(monaco-editor@0.49.0)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==}
peerDependencies:
monaco-editor: '>= 0.25.0 < 1'
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@monaco-editor/loader': 1.4.0(monaco-editor@0.49.0)
monaco-editor: 0.49.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
dev: false
/@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1:
resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
dependencies:
@@ -8316,6 +8341,10 @@ packages:
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
dev: false
/monaco-editor@0.49.0:
resolution: {integrity: sha512-2I8/T3X/hLxB2oPHgqcNYUVdA/ZEFShT7IAujifIPMfKkNbLOqY8XCoyHCXrsdjb36dW9MwoTwBCFpXKMwNwaQ==}
dev: false
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: false
@@ -11552,6 +11581,10 @@ packages:
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
dev: false
/state-local@1.0.7:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
dev: false
/statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
+28
View File
@@ -0,0 +1,28 @@
.editor-wrap {
border-radius: 16px;
overflow: hidden;
.editor-header {
display: flex;
padding: 6px 10px;
justify-content: space-between;
align-items: center;
background-color: rgb(56, 56, 56);
.ant-select-selector {
background-color: rgba(255, 255, 255, 0.09) !important;
color: rgba(255, 255, 255, 0.7);
.ant-select-selection-item {
color: rgba(255, 255, 255, 0.7);
}
}
.ant-select-arrow {
color: rgba(255, 255, 255, 0.7);
}
}
// set scrollbar style
.scrollbar {
.slider {
border-radius: 6px;
}
}
}
+54
View File
@@ -0,0 +1,54 @@
import { Select } from 'antd';
import React from 'react';
import CopyButton from '../copy-button';
import './index.less';
interface EditorwrapProps {
header?: React.ReactNode;
children: React.ReactNode;
showHeader?: boolean;
copyText: string;
langOptions?: { label: string; value: string }[];
onChangeLang?: (value: string) => void;
}
const EditorWrap: React.FC<EditorwrapProps> = ({
header,
children,
copyText,
langOptions,
onChangeLang,
showHeader = true
}) => {
const handleChangeLang = (value: string) => {
onChangeLang && onChangeLang(value);
};
const renderHeader = () => {
if (header) {
return <div className="editor-header">{header}</div>;
}
if (showHeader) {
return (
<div className="editor-header">
<Select
defaultValue="JSON"
style={{ width: 120 }}
size="middle"
variant="filled"
options={langOptions}
onChange={handleChangeLang}
></Select>
<CopyButton text={copyText} />
</div>
);
}
return null;
};
return (
<div className="editor-wrap">
{renderHeader()}
<div className="editor-content">{children}</div>
</div>
);
};
export default EditorWrap;
+27
View File
@@ -0,0 +1,27 @@
import { Button, Space } from 'antd';
type FormButtonsProps = {
onOk: () => void;
onCancel: () => void;
cancelText?: string;
okText?: string;
};
const FormButtons: React.FC<FormButtonsProps> = ({
onOk,
onCancel,
cancelText,
okText
}) => {
return (
<Space size={40} style={{ marginTop: '80px' }}>
<Button type="primary" onClick={onOk} style={{ width: '120px' }}>
{okText || '保存'}
</Button>
<Button onClick={onCancel} style={{ width: '98px' }}>
{cancelText || '取消'}
</Button>
</Space>
);
};
export default FormButtons;
+6
View File
@@ -230,5 +230,11 @@ body {
}
}
// ======== menu style end ============
// ======== monaco editor style ============
.monaco-editor {
border-radius: 16px;
}
@import url('src/assets/styles/common.less');
@import url('src/assets/styles/menu.less');
+45 -15
View File
@@ -1,7 +1,13 @@
// @ts-nocheck
import avatarImg from '@/assets/images/avatar.png';
import { LogoutOutlined } from '@ant-design/icons';
import {
GlobalOutlined,
LogoutOutlined,
SettingOutlined,
SunOutlined
} from '@ant-design/icons';
import { useNavigate } from '@umijs/max';
import { Avatar, Dropdown, Menu, Spin, version } from 'antd';
export function getRightRenderContent(opts: {
@@ -52,10 +58,48 @@ export function getRightRenderContent(opts: {
// 如果没有打开Locale,并且头像为空就取消掉这个返回的内容
if (!avatar) return null;
const navigate = useNavigate();
const langMenu = {
className: 'umi-plugin-layout-menu',
selectedKeys: [],
items: [
{
key: 'settings',
label: (
<>
<SettingOutlined />
</>
),
onClick: () => {
navigate('/profile');
}
},
{
key: 'theme',
label: (
<>
<SunOutlined />
</>
),
onClick: () => {
console.log('theme');
}
},
{
key: 'lang',
label: (
<>
<GlobalOutlined />
</>
),
onClick: () => {
console.log('lang');
}
},
{
key: 'logout',
label: (
@@ -67,20 +111,6 @@ export function getRightRenderContent(opts: {
onClick: () => {
opts?.runtimeConfig?.logout?.(opts.initialState);
}
},
{
key: 'theme',
label: <></>,
onClick: () => {
console.log('theme');
}
},
{
key: 'lang',
label: '语言',
onClick: () => {
console.log('lang');
}
}
]
};
@@ -4,6 +4,7 @@ import '../style/ground-left.less';
import ChatFooter from './chat-footer';
import MessageItem from './message-item';
import ReferenceParams from './reference-params';
import ViewCodeModal from './view-code-modal';
const MessageList: React.FC = () => {
const [messageList, setMessageList] = useState<any[]>([
@@ -16,6 +17,7 @@ const MessageList: React.FC = () => {
message: 'hello, nice to meet you!'
}
]);
const [show, setShow] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const handleNewMessage = () => {
@@ -33,13 +35,17 @@ const MessageList: React.FC = () => {
};
const handleView = () => {
console.log('view');
setShow(true);
};
const handleSubmit = () => {
console.log('submit');
};
const handleCloseViewCode = () => {
setShow(false);
};
const handleDelete = (index: number) => {
messageList.splice(index, 1);
setMessageList([...messageList]);
@@ -71,6 +77,11 @@ const MessageList: React.FC = () => {
feedback={<ReferenceParams></ReferenceParams>}
></ChatFooter>
</div>
<ViewCodeModal
open={show}
onCancel={handleCloseViewCode}
title="View code"
></ViewCodeModal>
</div>
);
};
@@ -68,10 +68,9 @@ const ParamsSettings: React.FC<{ onClose: () => void }> = ({ onClose }) => {
name="temperature"
rules={[{ required: true }]}
>
<SealInput.Input
label="Temperature"
style={{ width: INPUT_WIDTH.mini }}
></SealInput.Input>
<FieldWrapper label="Temperature">
<Slider defaultValue={50}></Slider>
</FieldWrapper>
</Form.Item>
<Form.Item<ParamsSettingsProps>
name="maxTokens"
@@ -86,7 +85,7 @@ const ParamsSettings: React.FC<{ onClose: () => void }> = ({ onClose }) => {
name="topP"
rules={[{ required: true }]}
>
<FieldWrapper label="TopP">
<FieldWrapper label="Top P">
<Slider defaultValue={50}></Slider>
</FieldWrapper>
</Form.Item>
@@ -0,0 +1,84 @@
import EditorWrap from '@/components/editor-wrap';
import Editor from '@monaco-editor/react';
import { Modal } from 'antd';
import React, { useRef, useState } from 'react';
type ViewModalProps = {
title: string;
open: boolean;
onCancel: () => void;
};
const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const { title, open, onCancel } = props || {};
if (!open) {
return null;
}
const editorRef = useRef(null);
const [codeValue, setCodeValue] = useState('');
const [lang, setLang] = useState('json');
const langOptions = [
{ label: 'curl', value: 'curl' },
{ label: 'JSON', value: 'json' },
{ label: 'JavaScript', value: 'javascript' },
{ label: 'Python', value: 'python' }
];
const handleEditorDidMount = (editor: any, monaco: any) => {
editorRef.current = editor;
};
const handleOnChangeLang = (value: string) => {
setLang(value);
};
const editorConfig = {
minimap: {
enabled: false
},
scrollbar: {
verticalSliderSize: 8
}
};
return (
<Modal
title={title}
open={open}
onCancel={onCancel}
destroyOnClose={true}
closeIcon={true}
maskClosable={false}
keyboard={false}
width={600}
style={{ top: '80px' }}
footer={null}
>
<div style={{ marginBottom: '10px' }}>
You can use the following code to start integrating your current prompt
and settings into your application.
</div>
<EditorWrap
copyText={codeValue}
langOptions={langOptions}
onChangeLang={handleOnChangeLang}
>
<Editor
height="400px"
theme="vs-dark"
className="monaco-editor"
defaultLanguage="javascript"
defaultValue="// some comment"
language={lang}
options={editorConfig}
onMount={handleEditorDidMount}
/>
</EditorWrap>
<div style={{ marginTop: '10px' }}>
our API Key can be foundhere You should use environment variables or a
secret management tool to expose your key to your applications.
</div>
</Modal>
);
};
export default ViewCodeModal;
+79
View File
@@ -0,0 +1,79 @@
import FormButtons from '@/components/form-buttons';
import SealInput from '@/components/seal-form/seal-input';
import { INPUT_WIDTH } from '@/constants';
import { PageContainer } from '@ant-design/pro-components';
import { Form } from 'antd';
import { StrictMode } from 'react';
interface ProfileProps {
name: string;
password: string;
originalPassword: string;
email: string;
}
const Profile: React.FC = () => {
const [form] = Form.useForm();
const handleOnFinish = (values: any) => {
console.log('handleOnFinish', values);
};
const handleOnFinishFailed = (errorInfo: any) => {
console.log('handleOnFinishFailed', errorInfo);
};
return (
<StrictMode>
<PageContainer
ghost
header={{
title: 'Profile'
}}
extra={[]}
>
<Form
style={{ width: '524px' }}
name="profileForm"
form={form}
onFinish={handleOnFinish}
onFinishFailed={handleOnFinishFailed}
>
<Form.Item<ProfileProps> name="name" rules={[{ required: true }]}>
<SealInput.Input
label="Name"
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Input>
</Form.Item>
<Form.Item<ProfileProps> name="email" rules={[{ required: true }]}>
<SealInput.Input
label="Email"
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Input>
</Form.Item>
<Form.Item<ProfileProps>
name="originalPassword"
rules={[{ required: true }]}
>
<SealInput.Password
label="Original Password"
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
<Form.Item<ProfileProps> name="password" rules={[{ required: true }]}>
<SealInput.Password
label="Password"
required
style={{ width: INPUT_WIDTH.default }}
></SealInput.Password>
</Form.Item>
</Form>
<FormButtons></FormButtons>
</PageContainer>
</StrictMode>
);
};
export default Profile;