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

@ -53,7 +53,7 @@ const RenameModal = () => {
useEffect(() => {
form.setFieldValue('name', initialName);
}, [initialName, documentId]);
}, [initialName, documentId, form]);
return (
<Modal

View File

@ -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}>

View File

@ -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,
},
});

View File

@ -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>

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);

View 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;
};

View File

@ -4,6 +4,17 @@
.chatAppWrapper {
width: 288px;
padding: 26px;
.chatAppCard {
:global(.ant-card-body) {
padding: 10px;
}
.cubeIcon {
&:hover {
cursor: pointer;
}
}
}
}
.chatTitleWrapper {
width: 220px;

View File

@ -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>

View File

@ -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);
},
},
};

View File

@ -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}>

View File

@ -37,7 +37,7 @@ const SettingList = () => {
type: 'settingModel/my_llm',
payload: {},
});
}, []);
}, [dispatch]);
return (
<div