mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-19 12:06:42 +08:00
feat: create a chat assistant and extract SimilaritySlider (#67)
* feat: extract SimilaritySlider * feat: create a chat assistant
This commit is contained in:
@ -53,7 +53,7 @@ const RenameModal = () => {
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldValue('name', initialName);
|
||||
}, [initialName, documentId]);
|
||||
}, [initialName, documentId, form]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@ -17,13 +17,14 @@ import {
|
||||
UploadFile,
|
||||
} from 'antd';
|
||||
import pick from 'lodash/pick';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'umi';
|
||||
|
||||
import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
|
||||
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
||||
import { IKnowledge } from '@/interfaces/database/knowledge';
|
||||
import { IThirdOAIModelCollection } from '@/interfaces/database/llm';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { LlmModelType } from '../../constant';
|
||||
import styles from './index.less';
|
||||
|
||||
const { Title } = Typography;
|
||||
@ -35,9 +36,6 @@ const Configuration = () => {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
const loading = useOneNamespaceEffectsLoading('kSModel', ['updateKb']);
|
||||
|
||||
const llmInfo: IThirdOAIModelCollection = useSelector(
|
||||
(state: any) => state.settingModel.llmInfo,
|
||||
);
|
||||
const knowledgeDetails: IKnowledge = useSelector(
|
||||
(state: any) => state.kSModel.knowledgeDetails,
|
||||
);
|
||||
@ -51,17 +49,7 @@ const Configuration = () => {
|
||||
|
||||
const parserList = useSelectParserList();
|
||||
|
||||
const embeddingModelOptions = useMemo(() => {
|
||||
return Object.entries(llmInfo).map(([key, value]) => {
|
||||
return {
|
||||
label: key,
|
||||
options: value.map((x) => ({
|
||||
label: x.llm_name,
|
||||
value: x.llm_name,
|
||||
})),
|
||||
};
|
||||
});
|
||||
}, [llmInfo]);
|
||||
const embeddingModelOptions = useSelectLlmOptions();
|
||||
|
||||
const onFinish = async (values: any) => {
|
||||
console.info(values);
|
||||
@ -86,13 +74,6 @@ const Configuration = () => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
|
||||
const fetchLlmList = useCallback(() => {
|
||||
dispatch({
|
||||
type: 'settingModel/llm_list',
|
||||
payload: { model_type: 'embedding' },
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const avatar = knowledgeDetails.avatar;
|
||||
let fileList: UploadFile[] = [];
|
||||
@ -115,9 +96,7 @@ const Configuration = () => {
|
||||
useFetchParserList();
|
||||
useFetchKnowledgeBaseConfiguration();
|
||||
|
||||
useEffect(() => {
|
||||
fetchLlmList();
|
||||
}, [fetchLlmList]);
|
||||
useFetchLlmList(LlmModelType.Embedding);
|
||||
|
||||
return (
|
||||
<div className={styles.configurationWrapper}>
|
||||
|
||||
@ -16,14 +16,10 @@ const KnowledgeTesting = () => {
|
||||
const handleTesting = async () => {
|
||||
const values = await form.validateFields();
|
||||
console.info(values);
|
||||
const similarity_threshold = values.similarity_threshold / 100;
|
||||
const vector_similarity_weight = values.vector_similarity_weight / 100;
|
||||
dispatch({
|
||||
type: 'testingModel/testDocumentChunk',
|
||||
payload: {
|
||||
...values,
|
||||
similarity_threshold,
|
||||
vector_similarity_weight,
|
||||
kb_id: knowledgeBaseId,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import SimilaritySlider from '@/components/similarity-slider';
|
||||
import { DeleteOutlined, HistoryOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@ -6,22 +8,15 @@ import {
|
||||
Form,
|
||||
Input,
|
||||
Slider,
|
||||
SliderSingleProps,
|
||||
Space,
|
||||
Tag,
|
||||
} from 'antd';
|
||||
|
||||
import { DeleteOutlined, HistoryOutlined } from '@ant-design/icons';
|
||||
import { FormInstance } from 'antd/lib';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const list = [1, 2, 3];
|
||||
|
||||
const marks: SliderSingleProps['marks'] = {
|
||||
0: '0',
|
||||
100: '1',
|
||||
};
|
||||
|
||||
type FieldType = {
|
||||
similarity_threshold?: number;
|
||||
vector_similarity_weight?: number;
|
||||
@ -29,12 +24,6 @@ type FieldType = {
|
||||
question: string;
|
||||
};
|
||||
|
||||
const formatter = (value: number | undefined) => {
|
||||
return typeof value === 'number' ? value / 100 : 0;
|
||||
};
|
||||
|
||||
const tooltip = { formatter };
|
||||
|
||||
interface IProps {
|
||||
form: FormInstance;
|
||||
handleTesting: () => Promise<any>;
|
||||
@ -59,23 +48,12 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
|
||||
layout="vertical"
|
||||
form={form}
|
||||
initialValues={{
|
||||
similarity_threshold: 20,
|
||||
vector_similarity_weight: 30,
|
||||
similarity_threshold: 0.2,
|
||||
vector_similarity_weight: 0.3,
|
||||
top_k: 1024,
|
||||
}}
|
||||
>
|
||||
<Form.Item<FieldType>
|
||||
label="Similarity threshold"
|
||||
name={'similarity_threshold'}
|
||||
>
|
||||
<Slider marks={marks} defaultValue={0} tooltip={tooltip} />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label="Vector similarity weight"
|
||||
name={'vector_similarity_weight'}
|
||||
>
|
||||
<Slider marks={marks} defaultValue={0} tooltip={tooltip} />
|
||||
</Form.Item>
|
||||
<SimilaritySlider></SimilaritySlider>
|
||||
<Form.Item<FieldType> label="Top k" name={'top_k'}>
|
||||
<Slider marks={{ 0: 0, 2048: 2048 }} defaultValue={0} max={2048} />
|
||||
</Form.Item>
|
||||
|
||||
@ -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);
|
||||
|
||||
29
web/src/pages/chat/hooks.ts
Normal file
29
web/src/pages/chat/hooks.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { IDialog } from '@/interfaces/database/chat';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'umi';
|
||||
|
||||
export const useFetchDialogList = () => {
|
||||
const dispatch = useDispatch();
|
||||
const dialogList: IDialog[] = useSelector(
|
||||
(state: any) => state.chatModel.dialogList,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'chatModel/listDialog' });
|
||||
}, [dispatch]);
|
||||
|
||||
return dialogList;
|
||||
};
|
||||
|
||||
export const useSetDialog = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const setDialog = useCallback(
|
||||
(payload: IDialog) => {
|
||||
dispatch({ type: 'chatModel/setDialog', payload });
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
return setDialog;
|
||||
};
|
||||
@ -4,6 +4,17 @@
|
||||
.chatAppWrapper {
|
||||
width: 288px;
|
||||
padding: 26px;
|
||||
|
||||
.chatAppCard {
|
||||
:global(.ant-card-body) {
|
||||
padding: 10px;
|
||||
}
|
||||
.cubeIcon {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.chatTitleWrapper {
|
||||
width: 220px;
|
||||
|
||||
@ -1,14 +1,73 @@
|
||||
import { FormOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Divider, Flex, Space, Tag } from 'antd';
|
||||
import { useSelector } from 'umi';
|
||||
import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Flex,
|
||||
MenuProps,
|
||||
Space,
|
||||
Tag,
|
||||
} from 'antd';
|
||||
import ChatContainer from './chat-container';
|
||||
|
||||
import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
|
||||
import ModalManager from '@/components/modal-manager';
|
||||
import classNames from 'classnames';
|
||||
import ChatConfigurationModal from './chat-configuration-modal';
|
||||
import { useFetchDialogList } from './hooks';
|
||||
|
||||
import { useState } from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
const Chat = () => {
|
||||
const { name } = useSelector((state: any) => state.chatModel);
|
||||
const dialogList = useFetchDialogList();
|
||||
const [activated, setActivated] = useState<string>('');
|
||||
|
||||
const handleAppCardEnter = (id: string) => () => {
|
||||
setActivated(id);
|
||||
};
|
||||
|
||||
const handleAppCardLeave = () => {
|
||||
setActivated('');
|
||||
};
|
||||
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://www.antgroup.com"
|
||||
>
|
||||
1st menu item
|
||||
</a>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const appItems: MenuProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<Space>
|
||||
<EditOutlined />
|
||||
Edit
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: '2',
|
||||
label: (
|
||||
<Space>
|
||||
<DeleteOutlined />
|
||||
Delete chat
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Flex className={styles.chatWrapper}>
|
||||
@ -32,9 +91,33 @@ const Chat = () => {
|
||||
</ModalManager>
|
||||
|
||||
<Divider></Divider>
|
||||
<Card>
|
||||
<p>Card content</p>
|
||||
</Card>
|
||||
<Space direction={'vertical'} size={'middle'}>
|
||||
{dialogList.map((x) => (
|
||||
<Card
|
||||
key={x.id}
|
||||
className={classNames(styles.chatAppCard)}
|
||||
onMouseEnter={handleAppCardEnter(x.id)}
|
||||
onMouseLeave={handleAppCardLeave}
|
||||
>
|
||||
<Flex justify="space-between" align="center">
|
||||
<Space>
|
||||
{x.icon}
|
||||
<section>
|
||||
<b>{x.name}</b>
|
||||
<div>{x.description}</div>
|
||||
</section>
|
||||
</Space>
|
||||
{activated === x.id && (
|
||||
<section>
|
||||
<Dropdown menu={{ items: appItems }}>
|
||||
<ChatAppCube className={styles.cubeIcon}></ChatAppCube>
|
||||
</Dropdown>
|
||||
</section>
|
||||
)}
|
||||
</Flex>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Divider type={'vertical'} className={styles.divider}></Divider>
|
||||
@ -49,7 +132,9 @@ const Chat = () => {
|
||||
<b>Chat</b>
|
||||
<Tag>25</Tag>
|
||||
</Space>
|
||||
<FormOutlined />
|
||||
<Dropdown menu={{ items }}>
|
||||
<FormOutlined />
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
<Divider></Divider>
|
||||
<section className={styles.chatTitleContent}>today</section>
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import { IDialog } from '@/interfaces/database/chat';
|
||||
import chatService from '@/services/chatService';
|
||||
import { message } from 'antd';
|
||||
import { DvaModel } from 'umi';
|
||||
|
||||
export interface ChatModelState {
|
||||
name: string;
|
||||
dialogList: IDialog[];
|
||||
}
|
||||
|
||||
const model: DvaModel<ChatModelState> = {
|
||||
namespace: 'chatModel',
|
||||
state: {
|
||||
name: 'kate',
|
||||
dialogList: [],
|
||||
},
|
||||
reducers: {
|
||||
save(state, action) {
|
||||
@ -16,16 +21,41 @@ const model: DvaModel<ChatModelState> = {
|
||||
...action.payload,
|
||||
};
|
||||
},
|
||||
},
|
||||
subscriptions: {
|
||||
setup({ dispatch, history }) {
|
||||
return history.listen((query) => {
|
||||
console.log(query);
|
||||
});
|
||||
setDialogList(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
dialogList: payload,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
effects: {
|
||||
*query({ payload }, { call, put }) {},
|
||||
*getDialog({ payload }, { call, put }) {
|
||||
const { data } = yield call(chatService.getDialog, payload);
|
||||
},
|
||||
*setDialog({ payload }, { call, put }) {
|
||||
const { data } = yield call(chatService.setDialog, payload);
|
||||
if (data.retcode === 0) {
|
||||
yield put({ type: 'listDialog' });
|
||||
message.success('Created successfully !');
|
||||
}
|
||||
},
|
||||
*listDialog({ payload }, { call, put }) {
|
||||
const { data } = yield call(chatService.listDialog, payload);
|
||||
yield put({ type: 'setDialogList', payload: data.data });
|
||||
},
|
||||
*listConversation({ payload }, { call, put }) {
|
||||
const { data } = yield call(chatService.listConversation, payload);
|
||||
},
|
||||
*getConversation({ payload }, { call, put }) {
|
||||
const { data } = yield call(chatService.getConversation, payload);
|
||||
},
|
||||
*setConversation({ payload }, { call, put }) {
|
||||
const { data } = yield call(chatService.setConversation, payload);
|
||||
},
|
||||
*completeConversation({ payload }, { call, put }) {
|
||||
const { data } = yield call(chatService.completeConversation, payload);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -2,33 +2,14 @@ import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
|
||||
import ModalManager from '@/components/modal-manager';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Flex, Space } from 'antd';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useDispatch, useNavigate, useSelector } from 'umi';
|
||||
import KnowledgeCard from './knowledge-card';
|
||||
import KnowledgeCreatingModal from './knowledge-creating-modal';
|
||||
|
||||
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
|
||||
import styles from './index.less';
|
||||
|
||||
const Knowledge = () => {
|
||||
const dispatch = useDispatch();
|
||||
const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
|
||||
const navigate = useNavigate();
|
||||
const { data = [] } = knowledgeModel;
|
||||
|
||||
const fetchList = useCallback(() => {
|
||||
dispatch({
|
||||
type: 'knowledgeModel/getList',
|
||||
payload: {},
|
||||
});
|
||||
}, []);
|
||||
|
||||
// const handleAddKnowledge = () => {
|
||||
// navigate(`/knowledge/${KnowledgeRouteKey.Configuration}`);
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
fetchList();
|
||||
}, [fetchList]);
|
||||
const data = useFetchKnowledgeList();
|
||||
|
||||
return (
|
||||
<div className={styles.knowledge}>
|
||||
|
||||
@ -37,7 +37,7 @@ const SettingList = () => {
|
||||
type: 'settingModel/my_llm',
|
||||
payload: {},
|
||||
});
|
||||
}, []);
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user