feat: create a chat assistant and extract SimilaritySlider (#67)

* feat: extract SimilaritySlider

* feat: create a chat assistant
This commit is contained in:
balibabu
2024-02-20 18:10:20 +08:00
committed by GitHub
parent a8294f2168
commit 8c4ec9955e
26 changed files with 716 additions and 196 deletions

View File

@ -1,11 +1,20 @@
import { Form, Input } from 'antd';
import { Form, Input, Select } from 'antd';
import classNames from 'classnames';
import { ISegmentedContentProps } from './interface';
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
import styles from './index.less';
const { Option } = Select;
const AssistantSetting = ({ show }: ISegmentedContentProps) => {
const knowledgeList = useFetchKnowledgeList();
const knowledgeOptions = knowledgeList.map((x) => ({
label: x.name,
value: x.id,
}));
return (
<section
className={classNames({
@ -19,15 +28,43 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
>
<Input placeholder="e.g. Resume Jarvis" />
</Form.Item>
<Form.Item name={'avatar'} label="Assistant avatar">
<Form.Item name={'icon'} label="Assistant avatar">
<Input />
</Form.Item>
<Form.Item name={'keywords'} label="Keywords">
<Input.TextArea autoSize={{ minRows: 3 }} />
<Form.Item name={'language'} label="Language" initialValue={'Chinese'}>
<Select
options={[
{ value: 'Chinese', label: 'Chinese' },
{ value: 'English', label: 'English' },
]}
/>
</Form.Item>
<Form.Item name={'opener'} label="Set an opener">
<Form.Item
name={['prompt_config', 'empty_response']}
label="Empty response"
>
<Input placeholder="" />
</Form.Item>
<Form.Item name={['prompt_config', 'prologue']} label="Set an opener">
<Input.TextArea autoSize={{ minRows: 5 }} />
</Form.Item>
<Form.Item
label="Select one context"
name="kb_ids"
rules={[
{
required: true,
message: 'Please select!',
type: 'array',
},
]}
>
<Select
mode="multiple"
options={knowledgeOptions}
placeholder="Please select"
></Select>
</Form.Item>
</section>
);
};

View File

@ -0,0 +1,7 @@
export const variableEnabledFieldMap = {
temperatureEnabled: 'temperature',
topPEnabled: 'top_p',
presencePenaltyEnabled: 'presence_penalty',
frequencyPenaltyEnabled: 'frequency_penalty',
maxTokensEnabled: 'max_tokens',
};

View File

@ -41,3 +41,10 @@
width: 0;
margin: 0;
}
.sliderInputNumber {
width: 80px;
}
.variableSlider {
width: 100%;
}

View File

@ -2,17 +2,20 @@ import { ReactComponent as ChatConfigurationAtom } from '@/assets/svg/chat-confi
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import { Divider, Flex, Form, Modal, Segmented } from 'antd';
import { SegmentedValue } from 'antd/es/segmented';
import { useState } from 'react';
import omit from 'lodash/omit';
import { useRef, useState } from 'react';
import AssistantSetting from './assistant-setting';
import ModelSetting from './model-setting';
import PromptEngine from './prompt-engine';
import { useSetDialog } from '../hooks';
import { variableEnabledFieldMap } from './constants';
import styles from './index.less';
enum ConfigurationSegmented {
AssistantSetting = 'Assistant Setting',
ModelSetting = 'Model Setting',
PromptEngine = 'Prompt Engine',
ModelSetting = 'Model Setting',
}
const segmentedMap = {
@ -45,10 +48,24 @@ const ChatConfigurationModal = ({
const [value, setValue] = useState<ConfigurationSegmented>(
ConfigurationSegmented.AssistantSetting,
);
const promptEngineRef = useRef(null);
const setDialog = useSetDialog();
const handleOk = async () => {
const x = await form.validateFields();
console.info(x);
const values = await form.validateFields();
const nextValues: any = omit(values, Object.keys(variableEnabledFieldMap));
const finalValues = {
...nextValues,
prompt_config: {
...nextValues.prompt_config,
parameters: promptEngineRef.current,
},
};
console.info(promptEngineRef.current);
console.info(nextValues);
console.info(finalValues);
setDialog(finalValues);
};
const handleCancel = () => {
@ -97,7 +114,14 @@ const ChatConfigurationModal = ({
colon={false}
>
{Object.entries(segmentedMap).map(([key, Element]) => (
<Element key={key} show={key === value}></Element>
<Element
key={key}
show={key === value}
form={form}
{...(key === ConfigurationSegmented.PromptEngine
? { ref: promptEngineRef }
: {})}
></Element>
))}
</Form>
</Modal>

View File

@ -1,3 +1,14 @@
import { FormInstance } from 'antd';
export interface ISegmentedContentProps {
show: boolean;
form: FormInstance;
}
export interface IVariable {
temperature: number;
top_p: number;
frequency_penalty: number;
presence_penalty: number;
max_tokens: number;
}

View File

@ -1,12 +1,48 @@
import { Divider, Flex, Form, InputNumber, Select, Slider } from 'antd';
import {
LlmModelType,
ModelVariableType,
settledModelVariableMap,
} from '@/constants/knowledge';
import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd';
import classNames from 'classnames';
import { useEffect } from 'react';
import { ISegmentedContentProps } from './interface';
import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
import { variableEnabledFieldMap } from './constants';
import styles from './index.less';
const { Option } = Select;
const ModelSetting = ({ show, form }: ISegmentedContentProps) => {
const parameterOptions = Object.values(ModelVariableType).map((x) => ({
label: x,
value: x,
}));
const parameters: ModelVariableType = Form.useWatch('parameters', form);
const modelOptions = useSelectLlmOptions();
const handleParametersChange = (value: ModelVariableType) => {
console.info(value);
};
useEffect(() => {
const variable = settledModelVariableMap[parameters];
form.setFieldsValue({ llm_setting: variable });
}, [parameters, form]);
useEffect(() => {
const values = Object.keys(variableEnabledFieldMap).reduce<
Record<string, boolean>
>((pre, field) => {
pre[field] = true;
return pre;
}, {});
form.setFieldsValue(values);
}, [form]);
useFetchLlmList(LlmModelType.Chat);
const ModelSetting = ({ show }: ISegmentedContentProps) => {
return (
<section
className={classNames({
@ -15,135 +51,170 @@ const ModelSetting = ({ show }: ISegmentedContentProps) => {
>
<Form.Item
label="Model"
name="model"
// rules={[{ required: true, message: 'Please input!' }]}
name="llm_id"
rules={[{ required: true, message: 'Please select!' }]}
>
<Select />
<Select options={modelOptions} />
</Form.Item>
<Divider></Divider>
<Form.Item
label="Parameters"
name="parameters"
initialValue={ModelVariableType.Precise}
// rules={[{ required: true, message: 'Please input!' }]}
>
<Select />
<Select<ModelVariableType>
options={parameterOptions}
onChange={handleParametersChange}
/>
</Form.Item>
<Form.Item label="Temperature">
<Flex gap={20}>
<Form.Item label="Temperature" tooltip={'xx'}>
<Flex gap={20} align="center">
<Form.Item
name={'temperatureEnabled'}
valuePropName="checked"
noStyle
>
<Switch size="small" />
</Form.Item>
<Flex flex={1}>
<Form.Item
name={['address', 'province']}
name={['llm_setting', 'temperature']}
noStyle
rules={[{ required: true, message: 'Province is required' }]}
>
<Slider style={{ display: 'inline-block', width: '100%' }} />
<Slider className={styles.variableSlider} max={1} step={0.01} />
</Form.Item>
</Flex>
<Form.Item
name={['address', 'street']}
name={['llm_setting', 'temperature']}
noStyle
rules={[{ required: true, message: 'Street is required' }]}
>
<InputNumber
style={{
width: 50,
}}
className={styles.sliderInputNumber}
max={1}
min={0}
step={0.01}
/>
</Form.Item>
</Flex>
</Form.Item>
<Form.Item label="Top P">
<Flex gap={20}>
<Form.Item label="Top P" tooltip={'xx'}>
<Flex gap={20} align="center">
<Form.Item name={'topPEnabled'} valuePropName="checked" noStyle>
<Switch size="small" />
</Form.Item>
<Flex flex={1}>
<Form.Item
name={['address', 'province']}
name={['llm_setting', 'top_p']}
noStyle
rules={[{ required: true, message: 'Province is required' }]}
>
<Slider style={{ display: 'inline-block', width: '100%' }} />
<Slider className={styles.variableSlider} max={1} step={0.01} />
</Form.Item>
</Flex>
<Form.Item
name={['address', 'street']}
name={['llm_setting', 'top_p']}
noStyle
rules={[{ required: true, message: 'Street is required' }]}
>
<InputNumber
style={{
width: 50,
}}
className={styles.sliderInputNumber}
max={1}
min={0}
step={0.01}
/>
</Form.Item>
</Flex>
</Form.Item>
<Form.Item label="Presence Penalty">
<Flex gap={20}>
<Form.Item label="Presence Penalty" tooltip={'xx'}>
<Flex gap={20} align="center">
<Form.Item
name={'presencePenaltyEnabled'}
valuePropName="checked"
noStyle
>
<Switch size="small" />
</Form.Item>
<Flex flex={1}>
<Form.Item
name={['address', 'province']}
name={['llm_setting', 'presence_penalty']}
noStyle
rules={[{ required: true, message: 'Province is required' }]}
>
<Slider style={{ display: 'inline-block', width: '100%' }} />
<Slider className={styles.variableSlider} max={1} step={0.01} />
</Form.Item>
</Flex>
<Form.Item
name={['address', 'street']}
name={['llm_setting', 'presence_penalty']}
noStyle
rules={[{ required: true, message: 'Street is required' }]}
>
<InputNumber
style={{
width: 50,
}}
className={styles.sliderInputNumber}
max={1}
min={0}
step={0.01}
/>
</Form.Item>
</Flex>
</Form.Item>
<Form.Item label="Frequency Penalty">
<Flex gap={20}>
<Form.Item label="Frequency Penalty" tooltip={'xx'}>
<Flex gap={20} align="center">
<Form.Item
name={'frequencyPenaltyEnabled'}
valuePropName="checked"
noStyle
>
<Switch size="small" />
</Form.Item>
<Flex flex={1}>
<Form.Item
name={['address', 'province']}
name={['llm_setting', 'frequency_penalty']}
noStyle
rules={[{ required: true, message: 'Province is required' }]}
>
<Slider style={{ display: 'inline-block', width: '100%' }} />
<Slider className={styles.variableSlider} max={1} step={0.01} />
</Form.Item>
</Flex>
<Form.Item
name={['address', 'street']}
name={['llm_setting', 'frequency_penalty']}
noStyle
rules={[{ required: true, message: 'Street is required' }]}
>
<InputNumber
style={{
width: 50,
}}
className={styles.sliderInputNumber}
max={1}
min={0}
step={0.01}
/>
</Form.Item>
</Flex>
</Form.Item>
<Form.Item label="Max Tokens">
<Flex gap={20}>
<Form.Item label="Max Tokens" tooltip={'xx'}>
<Flex gap={20} align="center">
<Form.Item name={'maxTokensEnabled'} valuePropName="checked" noStyle>
<Switch size="small" />
</Form.Item>
<Flex flex={1}>
<Form.Item
name={['address', 'province']}
name={['llm_setting', 'max_tokens']}
noStyle
rules={[{ required: true, message: 'Province is required' }]}
>
<Slider style={{ display: 'inline-block', width: '100%' }} />
<Slider className={styles.variableSlider} max={2048} />
</Form.Item>
</Flex>
<Form.Item
name={['address', 'street']}
name={['llm_setting', 'max_tokens']}
noStyle
rules={[{ required: true, message: 'Street is required' }]}
>
<InputNumber
style={{
width: 50,
}}
className={styles.sliderInputNumber}
max={2048}
min={0}
/>
</Form.Item>
</Flex>

View File

@ -1,3 +1,4 @@
import SimilaritySlider from '@/components/similarity-slider';
import { DeleteOutlined } from '@ant-design/icons';
import {
Button,
@ -6,13 +7,19 @@ import {
Form,
Input,
Row,
Select,
Slider,
Switch,
Table,
TableProps,
} from 'antd';
import classNames from 'classnames';
import { useState } from 'react';
import {
ForwardedRef,
forwardRef,
useEffect,
useImperativeHandle,
useState,
} from 'react';
import { v4 as uuid } from 'uuid';
import { EditableCell, EditableRow } from './editable-cell';
import { ISegmentedContentProps } from './interface';
@ -21,12 +28,20 @@ import styles from './index.less';
interface DataType {
key: string;
variable: string;
optional: boolean;
}
const { Option } = Select;
type FieldType = {
similarity_threshold?: number;
vector_similarity_weight?: number;
top_n?: number;
};
const PromptEngine = ({ show }: ISegmentedContentProps) => {
const PromptEngine = (
{ show, form }: ISegmentedContentProps,
ref: ForwardedRef<Array<Omit<DataType, 'variable'>>>,
) => {
const [dataSource, setDataSource] = useState<DataType[]>([]);
const components = {
@ -52,6 +67,44 @@ const PromptEngine = ({ show }: ISegmentedContentProps) => {
setDataSource(newData);
};
const handleAdd = () => {
setDataSource((state) => [
...state,
{
key: uuid(),
variable: '',
optional: true,
},
]);
};
const handleOptionalChange = (row: DataType) => (checked: boolean) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, {
...item,
optional: checked,
});
setDataSource(newData);
};
useImperativeHandle(
ref,
() => {
return dataSource
.filter((x) => x.variable.trim() !== '')
.map((x) => ({ key: x.variable, optional: x.optional }));
},
[dataSource],
);
useEffect(() => {
form.setFieldValue(['prompt_config', 'parameters'], dataSource);
const x = form.getFieldValue(['prompt_config', 'parameters']);
console.info(x);
}, [dataSource, form]);
const columns: TableProps<DataType>['columns'] = [
{
title: 'key',
@ -71,8 +124,14 @@ const PromptEngine = ({ show }: ISegmentedContentProps) => {
key: 'optional',
width: 40,
align: 'center',
render() {
return <Switch size="small" />;
render(text, record) {
return (
<Switch
size="small"
checked={text}
onChange={handleOptionalChange(record)}
/>
);
},
},
{
@ -87,17 +146,6 @@ const PromptEngine = ({ show }: ISegmentedContentProps) => {
},
];
const handleAdd = () => {
setDataSource((state) => [
...state,
{
key: uuid(),
variable: '',
optional: true,
},
]);
};
return (
<section
className={classNames({
@ -106,12 +154,20 @@ const PromptEngine = ({ show }: ISegmentedContentProps) => {
>
<Form.Item
label="Orchestrate"
name="orchestrate"
rules={[{ required: true, message: 'Please input!' }]}
name={['prompt_config', 'system']}
initialValue={`你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。
以下是知识库:
{knowledge}
以上是知识库。`}
>
<Input.TextArea autoSize={{ maxRows: 5, minRows: 5 }} />
</Form.Item>
<Divider></Divider>
<SimilaritySlider></SimilaritySlider>
<Form.Item<FieldType> label="Top n" name={'top_n'} initialValue={0}>
<Slider max={30} />
</Form.Item>
<section className={classNames(styles.variableContainer)}>
<Row align={'middle'} justify="end">
<Col span={6} className={styles.variableAlign}>
@ -139,25 +195,8 @@ const PromptEngine = ({ show }: ISegmentedContentProps) => {
</Row>
)}
</section>
<Form.Item
label="Select one context"
name="context"
rules={[
{
required: true,
message: 'Please select your favourite colors!',
type: 'array',
},
]}
>
<Select mode="multiple" placeholder="Please select favourite colors">
<Option value="red">Red</Option>
<Option value="green">Green</Option>
<Option value="blue">Blue</Option>
</Select>
</Form.Item>
</section>
);
};
export default PromptEngine;
export default forwardRef(PromptEngine);