mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-20 12:56:55 +08:00
feat: create a chat assistant and extract SimilaritySlider (#67)
* feat: extract SimilaritySlider * feat: create a chat assistant
This commit is contained in:
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
7
web/src/pages/chat/chat-configuration-modal/constants.ts
Normal file
7
web/src/pages/chat/chat-configuration-modal/constants.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const variableEnabledFieldMap = {
|
||||
temperatureEnabled: 'temperature',
|
||||
topPEnabled: 'top_p',
|
||||
presencePenaltyEnabled: 'presence_penalty',
|
||||
frequencyPenaltyEnabled: 'frequency_penalty',
|
||||
maxTokensEnabled: 'max_tokens',
|
||||
};
|
||||
@ -41,3 +41,10 @@
|
||||
width: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sliderInputNumber {
|
||||
width: 80px;
|
||||
}
|
||||
.variableSlider {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user