mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-19 19:59:09 +08:00
feat: fetch conversation and delete chat dialog (#69)
* feat: set chat configuration to backend * feat: exclude unEnabled variables * feat: delete chat dialog * feat: fetch conversation
This commit is contained in:
parent
cacd36c5e1
commit
5a0f1d2b84
4
web/src/constants/chat.ts
Normal file
4
web/src/constants/chat.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum MessageType {
|
||||||
|
Assistant = 'assistant',
|
||||||
|
User = 'user',
|
||||||
|
}
|
34
web/src/hooks/commonHooks.ts
Normal file
34
web/src/hooks/commonHooks.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
export const useSetModalState = () => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
const showModal = () => {
|
||||||
|
setVisible(true);
|
||||||
|
};
|
||||||
|
const hideModal = () => {
|
||||||
|
setVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { visible, showModal, hideModal };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeepCompareEffect = (
|
||||||
|
effect: React.EffectCallback,
|
||||||
|
deps: React.DependencyList,
|
||||||
|
) => {
|
||||||
|
const ref = useRef<React.DependencyList>();
|
||||||
|
let callback: ReturnType<React.EffectCallback> = () => {};
|
||||||
|
if (!isEqual(deps, ref.current)) {
|
||||||
|
callback = effect();
|
||||||
|
ref.current = deps;
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
};
|
@ -125,11 +125,18 @@ export const useFetchKnowledgeBaseConfiguration = () => {
|
|||||||
}, [fetchKnowledgeBaseConfiguration]);
|
}, [fetchKnowledgeBaseConfiguration]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFetchKnowledgeList = (): IKnowledge[] => {
|
export const useFetchKnowledgeList = (
|
||||||
|
shouldFilterListWithoutDocument: boolean = false,
|
||||||
|
): IKnowledge[] => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
|
const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
|
||||||
const { data = [] } = knowledgeModel;
|
const { data = [] } = knowledgeModel;
|
||||||
|
const list = useMemo(() => {
|
||||||
|
return shouldFilterListWithoutDocument
|
||||||
|
? data.filter((x: IKnowledge) => x.doc_num > 0)
|
||||||
|
: data;
|
||||||
|
}, [data, shouldFilterListWithoutDocument]);
|
||||||
|
|
||||||
const fetchList = useCallback(() => {
|
const fetchList = useCallback(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -141,5 +148,5 @@ export const useFetchKnowledgeList = (): IKnowledge[] => {
|
|||||||
fetchList();
|
fetchList();
|
||||||
}, [fetchList]);
|
}, [fetchList]);
|
||||||
|
|
||||||
return data;
|
return list;
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { MessageType } from '@/constants/chat';
|
||||||
|
|
||||||
export interface PromptConfig {
|
export interface PromptConfig {
|
||||||
empty_response: string;
|
empty_response: string;
|
||||||
parameters: Parameter[];
|
parameters: Parameter[];
|
||||||
@ -45,3 +47,20 @@ export interface IDialog {
|
|||||||
update_date: string;
|
update_date: string;
|
||||||
update_time: number;
|
update_time: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IConversation {
|
||||||
|
create_date: string;
|
||||||
|
create_time: number;
|
||||||
|
dialog_id: string;
|
||||||
|
id: string;
|
||||||
|
message: Message[];
|
||||||
|
reference: any[];
|
||||||
|
name: string;
|
||||||
|
update_date: string;
|
||||||
|
update_time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
content: string;
|
||||||
|
role: MessageType;
|
||||||
|
}
|
||||||
|
@ -75,9 +75,7 @@ export const ParsingStatusCell = ({ record }: IProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify={'space-between'}>
|
<Flex justify={'space-between'}>
|
||||||
<Popover
|
<Popover content={<PopoverContent record={record}></PopoverContent>}>
|
||||||
content={isRunning && <PopoverContent record={record}></PopoverContent>}
|
|
||||||
>
|
|
||||||
<Tag color={runningStatus.color}>
|
<Tag color={runningStatus.color}>
|
||||||
{isRunning ? (
|
{isRunning ? (
|
||||||
<Space>
|
<Space>
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import { Form, Input, Select } from 'antd';
|
import { Form, Input, Select } from 'antd';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { ISegmentedContentProps } from './interface';
|
import { ISegmentedContentProps } from '../interface';
|
||||||
|
|
||||||
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
|
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
|
|
||||||
const AssistantSetting = ({ show }: ISegmentedContentProps) => {
|
const AssistantSetting = ({ show }: ISegmentedContentProps) => {
|
||||||
const knowledgeList = useFetchKnowledgeList();
|
const knowledgeList = useFetchKnowledgeList(true);
|
||||||
const knowledgeOptions = knowledgeList.map((x) => ({
|
const knowledgeOptions = knowledgeList.map((x) => ({
|
||||||
label: x.name,
|
label: x.name,
|
||||||
value: x.id,
|
value: x.id,
|
||||||
|
@ -3,13 +3,16 @@ import { IModalManagerChildrenProps } from '@/components/modal-manager';
|
|||||||
import { Divider, Flex, Form, Modal, Segmented } from 'antd';
|
import { Divider, Flex, Form, Modal, Segmented } from 'antd';
|
||||||
import { SegmentedValue } from 'antd/es/segmented';
|
import { SegmentedValue } from 'antd/es/segmented';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
import { useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import AssistantSetting from './assistant-setting';
|
import AssistantSetting from './assistant-setting';
|
||||||
import ModelSetting from './model-setting';
|
import ModelSetting from './model-setting';
|
||||||
import PromptEngine from './prompt-engine';
|
import PromptEngine from './prompt-engine';
|
||||||
|
|
||||||
import { useSetDialog } from '../hooks';
|
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
|
||||||
import { variableEnabledFieldMap } from './constants';
|
import { variableEnabledFieldMap } from '../constants';
|
||||||
|
import { useFetchDialog, useResetCurrentDialog, useSetDialog } from '../hooks';
|
||||||
|
import { IPromptConfigParameters } from '../interface';
|
||||||
|
import { excludeUnEnabledVariables } from '../utils';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
enum ConfigurationSegmented {
|
enum ConfigurationSegmented {
|
||||||
@ -40,32 +43,46 @@ const validateMessages = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ChatConfigurationModal = ({
|
interface IProps extends IModalManagerChildrenProps {
|
||||||
visible,
|
id: string;
|
||||||
hideModal,
|
}
|
||||||
}: IModalManagerChildrenProps) => {
|
|
||||||
|
const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [value, setValue] = useState<ConfigurationSegmented>(
|
const [value, setValue] = useState<ConfigurationSegmented>(
|
||||||
ConfigurationSegmented.AssistantSetting,
|
ConfigurationSegmented.AssistantSetting,
|
||||||
);
|
);
|
||||||
const promptEngineRef = useRef(null);
|
const promptEngineRef = useRef<Array<IPromptConfigParameters>>([]);
|
||||||
|
const loading = useOneNamespaceEffectsLoading('chatModel', ['setDialog']);
|
||||||
|
|
||||||
const setDialog = useSetDialog();
|
const setDialog = useSetDialog();
|
||||||
|
const currentDialog = useFetchDialog(id, visible);
|
||||||
|
const { resetCurrentDialog } = useResetCurrentDialog();
|
||||||
|
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
const nextValues: any = omit(values, Object.keys(variableEnabledFieldMap));
|
const nextValues: any = omit(values, [
|
||||||
|
...Object.keys(variableEnabledFieldMap),
|
||||||
|
'parameters',
|
||||||
|
...excludeUnEnabledVariables(values),
|
||||||
|
]);
|
||||||
|
const emptyResponse = nextValues.prompt_config?.empty_response ?? '';
|
||||||
const finalValues = {
|
const finalValues = {
|
||||||
|
dialog_id: id,
|
||||||
...nextValues,
|
...nextValues,
|
||||||
prompt_config: {
|
prompt_config: {
|
||||||
...nextValues.prompt_config,
|
...nextValues.prompt_config,
|
||||||
parameters: promptEngineRef.current,
|
parameters: promptEngineRef.current,
|
||||||
|
empty_response: emptyResponse,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
console.info(promptEngineRef.current);
|
console.info(promptEngineRef.current);
|
||||||
console.info(nextValues);
|
console.info(nextValues);
|
||||||
console.info(finalValues);
|
console.info(finalValues);
|
||||||
setDialog(finalValues);
|
const retcode: number = await setDialog(finalValues);
|
||||||
|
if (retcode === 0) {
|
||||||
|
hideModal();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
@ -76,6 +93,11 @@ const ChatConfigurationModal = ({
|
|||||||
setValue(val as ConfigurationSegmented);
|
setValue(val as ConfigurationSegmented);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleModalAfterClose = () => {
|
||||||
|
resetCurrentDialog();
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
const title = (
|
const title = (
|
||||||
<Flex gap={16}>
|
<Flex gap={16}>
|
||||||
<ChatConfigurationAtom></ChatConfigurationAtom>
|
<ChatConfigurationAtom></ChatConfigurationAtom>
|
||||||
@ -89,6 +111,10 @@ const ChatConfigurationModal = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setFieldsValue(currentDialog);
|
||||||
|
}, [currentDialog, form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={title}
|
title={title}
|
||||||
@ -96,6 +122,9 @@ const ChatConfigurationModal = ({
|
|||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
confirmLoading={loading}
|
||||||
|
destroyOnClose
|
||||||
|
afterClose={handleModalAfterClose}
|
||||||
>
|
>
|
||||||
<Segmented
|
<Segmented
|
||||||
size={'large'}
|
size={'large'}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
@ -6,10 +6,10 @@ import {
|
|||||||
import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd';
|
import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { ISegmentedContentProps } from './interface';
|
import { ISegmentedContentProps } from '../interface';
|
||||||
|
|
||||||
import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
|
import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
|
||||||
import { variableEnabledFieldMap } from './constants';
|
import { variableEnabledFieldMap } from '../constants';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
const ModelSetting = ({ show, form }: ISegmentedContentProps) => {
|
const ModelSetting = ({ show, form }: ISegmentedContentProps) => {
|
||||||
|
@ -21,17 +21,16 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import {
|
||||||
|
VariableTableDataType as DataType,
|
||||||
|
IPromptConfigParameters,
|
||||||
|
ISegmentedContentProps,
|
||||||
|
} from '../interface';
|
||||||
import { EditableCell, EditableRow } from './editable-cell';
|
import { EditableCell, EditableRow } from './editable-cell';
|
||||||
import { ISegmentedContentProps } from './interface';
|
|
||||||
|
|
||||||
|
import { useSelectPromptConfigParameters } from '../hooks';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
interface DataType {
|
|
||||||
key: string;
|
|
||||||
variable: string;
|
|
||||||
optional: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type FieldType = {
|
type FieldType = {
|
||||||
similarity_threshold?: number;
|
similarity_threshold?: number;
|
||||||
vector_similarity_weight?: number;
|
vector_similarity_weight?: number;
|
||||||
@ -39,10 +38,11 @@ type FieldType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PromptEngine = (
|
const PromptEngine = (
|
||||||
{ show, form }: ISegmentedContentProps,
|
{ show }: ISegmentedContentProps,
|
||||||
ref: ForwardedRef<Array<Omit<DataType, 'variable'>>>,
|
ref: ForwardedRef<Array<IPromptConfigParameters>>,
|
||||||
) => {
|
) => {
|
||||||
const [dataSource, setDataSource] = useState<DataType[]>([]);
|
const [dataSource, setDataSource] = useState<DataType[]>([]);
|
||||||
|
const parameters = useSelectPromptConfigParameters();
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
body: {
|
body: {
|
||||||
@ -99,12 +99,6 @@ const PromptEngine = (
|
|||||||
[dataSource],
|
[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'] = [
|
const columns: TableProps<DataType>['columns'] = [
|
||||||
{
|
{
|
||||||
title: 'key',
|
title: 'key',
|
||||||
@ -146,6 +140,10 @@ const PromptEngine = (
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDataSource(parameters);
|
||||||
|
}, [parameters]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={classNames({
|
className={classNames({
|
||||||
@ -153,7 +151,7 @@ const PromptEngine = (
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Orchestrate"
|
label="System"
|
||||||
rules={[{ required: true, message: 'Please input!' }]}
|
rules={[{ required: true, message: 'Please input!' }]}
|
||||||
name={['prompt_config', 'system']}
|
name={['prompt_config', 'system']}
|
||||||
initialValue={`你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。
|
initialValue={`你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。
|
||||||
@ -161,7 +159,7 @@ const PromptEngine = (
|
|||||||
{knowledge}
|
{knowledge}
|
||||||
以上是知识库。`}
|
以上是知识库。`}
|
||||||
>
|
>
|
||||||
<Input.TextArea autoSize={{ maxRows: 5, minRows: 5 }} />
|
<Input.TextArea autoSize={{ maxRows: 8, minRows: 5 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Divider></Divider>
|
<Divider></Divider>
|
||||||
<SimilaritySlider></SimilaritySlider>
|
<SimilaritySlider></SimilaritySlider>
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
.chatContainer {
|
.chatContainer {
|
||||||
padding: 0 24px 24px;
|
padding: 0 24px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.messageItem {
|
||||||
|
.messageItemContent {
|
||||||
|
display: inline-block;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageItemLeft {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messageItemRight {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
@ -1,13 +1,41 @@
|
|||||||
import { Button, Flex, Input } from 'antd';
|
import { Button, Flex, Input, Typography } from 'antd';
|
||||||
import { ChangeEventHandler, useState } from 'react';
|
import { ChangeEventHandler, useState } from 'react';
|
||||||
|
|
||||||
|
import { Message } from '@/interfaces/database/chat';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useFetchConversation, useSendMessage } from '../hooks';
|
||||||
|
|
||||||
|
import { MessageType } from '@/constants/chat';
|
||||||
|
import { IClientConversation } from '../interface';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const { Paragraph } = Typography;
|
||||||
|
|
||||||
|
const MessageItem = ({ item }: { item: Message }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(styles.messageItem, {
|
||||||
|
[styles.messageItemLeft]: item.role === MessageType.Assistant,
|
||||||
|
[styles.messageItemRight]: item.role === MessageType.User,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className={styles.messageItemContent}>
|
||||||
|
<Paragraph ellipsis={{ tooltip: item.content, rows: 3 }}>
|
||||||
|
{item.content}
|
||||||
|
</Paragraph>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ChatContainer = () => {
|
const ChatContainer = () => {
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
|
const conversation: IClientConversation = useFetchConversation();
|
||||||
|
const { sendMessage } = useSendMessage();
|
||||||
|
|
||||||
const handlePressEnter = () => {
|
const handlePressEnter = () => {
|
||||||
console.info(value);
|
console.info(value);
|
||||||
|
sendMessage(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
@ -16,7 +44,11 @@ const ChatContainer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flex={1} className={styles.chatContainer} vertical>
|
<Flex flex={1} className={styles.chatContainer} vertical>
|
||||||
<Flex flex={1}>xx</Flex>
|
<Flex flex={1} vertical>
|
||||||
|
{conversation?.message?.map((message) => (
|
||||||
|
<MessageItem key={message.id} item={message}></MessageItem>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
<Input
|
<Input
|
||||||
size="large"
|
size="large"
|
||||||
placeholder="Message Resume Assistant..."
|
placeholder="Message Resume Assistant..."
|
||||||
|
@ -5,3 +5,10 @@ export const variableEnabledFieldMap = {
|
|||||||
frequencyPenaltyEnabled: 'frequency_penalty',
|
frequencyPenaltyEnabled: 'frequency_penalty',
|
||||||
maxTokensEnabled: 'max_tokens',
|
maxTokensEnabled: 'max_tokens',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum ChatSearchParams {
|
||||||
|
DialogId = 'dialogId',
|
||||||
|
ConversationId = 'conversationId',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmptyConversationId = 'empty';
|
@ -1,6 +1,16 @@
|
|||||||
|
import showDeleteConfirm from '@/components/deleting-confirm';
|
||||||
|
import { MessageType } from '@/constants/chat';
|
||||||
import { IDialog } from '@/interfaces/database/chat';
|
import { IDialog } from '@/interfaces/database/chat';
|
||||||
import { useCallback, useEffect } from 'react';
|
import omit from 'lodash/omit';
|
||||||
import { useDispatch, useSelector } from 'umi';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { useDispatch, useSearchParams, useSelector } from 'umi';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { ChatSearchParams, EmptyConversationId } from './constants';
|
||||||
|
import {
|
||||||
|
IClientConversation,
|
||||||
|
IMessage,
|
||||||
|
VariableTableDataType,
|
||||||
|
} from './interface';
|
||||||
|
|
||||||
export const useFetchDialogList = () => {
|
export const useFetchDialogList = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -20,10 +30,336 @@ export const useSetDialog = () => {
|
|||||||
|
|
||||||
const setDialog = useCallback(
|
const setDialog = useCallback(
|
||||||
(payload: IDialog) => {
|
(payload: IDialog) => {
|
||||||
dispatch({ type: 'chatModel/setDialog', payload });
|
return dispatch<any>({ type: 'chatModel/setDialog', payload });
|
||||||
},
|
},
|
||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
return setDialog;
|
return setDialog;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useFetchDialog = (dialogId: string, visible: boolean): IDialog => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const currentDialog: IDialog = useSelector(
|
||||||
|
(state: any) => state.chatModel.currentDialog,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchDialog = useCallback(() => {
|
||||||
|
if (dialogId) {
|
||||||
|
dispatch({
|
||||||
|
type: 'chatModel/getDialog',
|
||||||
|
payload: { dialog_id: dialogId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [dispatch, dialogId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogId && visible) {
|
||||||
|
fetchDialog();
|
||||||
|
}
|
||||||
|
}, [dialogId, fetchDialog, visible]);
|
||||||
|
|
||||||
|
return currentDialog;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSetCurrentDialog = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const currentDialog: IDialog = useSelector(
|
||||||
|
(state: any) => state.chatModel.currentDialog,
|
||||||
|
);
|
||||||
|
|
||||||
|
const setCurrentDialog = useCallback(
|
||||||
|
(dialogId: string) => {
|
||||||
|
if (dialogId) {
|
||||||
|
dispatch({
|
||||||
|
type: 'chatModel/setCurrentDialog',
|
||||||
|
payload: { id: dialogId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { currentDialog, setCurrentDialog };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResetCurrentDialog = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const resetCurrentDialog = useCallback(() => {
|
||||||
|
dispatch({
|
||||||
|
type: 'chatModel/setCurrentDialog',
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
return { resetCurrentDialog };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSelectPromptConfigParameters = (): VariableTableDataType[] => {
|
||||||
|
const currentDialog: IDialog = useSelector(
|
||||||
|
(state: any) => state.chatModel.currentDialog,
|
||||||
|
);
|
||||||
|
|
||||||
|
const finalParameters: VariableTableDataType[] = useMemo(() => {
|
||||||
|
const parameters = currentDialog?.prompt_config?.parameters ?? [];
|
||||||
|
if (!currentDialog.id) {
|
||||||
|
// The newly created chat has a default parameter
|
||||||
|
return [{ key: uuid(), variable: 'knowledge', optional: false }];
|
||||||
|
}
|
||||||
|
return parameters.map((x) => ({
|
||||||
|
key: uuid(),
|
||||||
|
variable: x.key,
|
||||||
|
optional: x.optional,
|
||||||
|
}));
|
||||||
|
}, [currentDialog]);
|
||||||
|
|
||||||
|
return finalParameters;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRemoveDialog = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const removeDocument = (dialogIds: Array<string>) => () => {
|
||||||
|
return dispatch({
|
||||||
|
type: 'chatModel/removeDialog',
|
||||||
|
payload: {
|
||||||
|
dialog_ids: dialogIds,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRemoveDialog = (dialogIds: Array<string>) => {
|
||||||
|
showDeleteConfirm({ onOk: removeDocument(dialogIds) });
|
||||||
|
};
|
||||||
|
|
||||||
|
return { onRemoveDialog };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useClickDialogCard = () => {
|
||||||
|
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const newQueryParameters: URLSearchParams = useMemo(() => {
|
||||||
|
return new URLSearchParams(currentQueryParameters.toString());
|
||||||
|
}, [currentQueryParameters]);
|
||||||
|
|
||||||
|
const handleClickDialog = useCallback(
|
||||||
|
(dialogId: string) => {
|
||||||
|
newQueryParameters.set(ChatSearchParams.DialogId, dialogId);
|
||||||
|
setSearchParams(newQueryParameters);
|
||||||
|
},
|
||||||
|
[newQueryParameters, setSearchParams],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { handleClickDialog };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetChatSearchParams = () => {
|
||||||
|
const [currentQueryParameters] = useSearchParams();
|
||||||
|
|
||||||
|
return {
|
||||||
|
dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '',
|
||||||
|
conversationId:
|
||||||
|
currentQueryParameters.get(ChatSearchParams.ConversationId) || '',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSelectFirstDialogOnMount = () => {
|
||||||
|
const dialogList = useFetchDialogList();
|
||||||
|
const { dialogId } = useGetChatSearchParams();
|
||||||
|
|
||||||
|
const { handleClickDialog } = useClickDialogCard();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogList.length > 0 && !dialogId) {
|
||||||
|
handleClickDialog(dialogList[0].id);
|
||||||
|
}
|
||||||
|
}, [dialogList, handleClickDialog, dialogId]);
|
||||||
|
|
||||||
|
return dialogList;
|
||||||
|
};
|
||||||
|
|
||||||
|
//#region conversation
|
||||||
|
|
||||||
|
export const useFetchConversationList = (dialogId?: string) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const conversationList: any[] = useSelector(
|
||||||
|
(state: any) => state.chatModel.conversationList,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchConversationList = useCallback(() => {
|
||||||
|
if (dialogId) {
|
||||||
|
dispatch({
|
||||||
|
type: 'chatModel/listConversation',
|
||||||
|
payload: { dialog_id: dialogId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [dispatch, dialogId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchConversationList();
|
||||||
|
}, [fetchConversationList]);
|
||||||
|
|
||||||
|
return conversationList;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useClickConversationCard = () => {
|
||||||
|
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||||
|
const newQueryParameters: URLSearchParams = new URLSearchParams(
|
||||||
|
currentQueryParameters.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClickConversation = (conversationId: string) => {
|
||||||
|
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
|
||||||
|
setSearchParams(newQueryParameters);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { handleClickConversation };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCreateTemporaryConversation = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { dialogId } = useGetChatSearchParams();
|
||||||
|
const { handleClickConversation } = useClickConversationCard();
|
||||||
|
let chatModel = useSelector((state: any) => state.chatModel);
|
||||||
|
let currentConversation: Pick<
|
||||||
|
IClientConversation,
|
||||||
|
'id' | 'message' | 'name' | 'dialog_id'
|
||||||
|
> = chatModel.currentConversation;
|
||||||
|
let conversationList: IClientConversation[] = chatModel.conversationList;
|
||||||
|
|
||||||
|
const createTemporaryConversation = (message: string) => {
|
||||||
|
const messages = [...(currentConversation?.message ?? [])];
|
||||||
|
if (messages.some((x) => x.id === EmptyConversationId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
messages.unshift({
|
||||||
|
id: EmptyConversationId,
|
||||||
|
content: message,
|
||||||
|
role: MessageType.Assistant,
|
||||||
|
});
|
||||||
|
|
||||||
|
// It’s the back-end data.
|
||||||
|
if ('id' in currentConversation) {
|
||||||
|
currentConversation = { ...currentConversation, message: messages };
|
||||||
|
} else {
|
||||||
|
// client data
|
||||||
|
currentConversation = {
|
||||||
|
id: EmptyConversationId,
|
||||||
|
name: 'New conversation',
|
||||||
|
dialog_id: dialogId,
|
||||||
|
message: messages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextConversationList = [...conversationList];
|
||||||
|
|
||||||
|
nextConversationList.push(currentConversation as IClientConversation);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'chatModel/setCurrentConversation',
|
||||||
|
payload: currentConversation,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'chatModel/setConversationList',
|
||||||
|
payload: nextConversationList,
|
||||||
|
});
|
||||||
|
handleClickConversation(EmptyConversationId);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { createTemporaryConversation };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSetConversation = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { dialogId } = useGetChatSearchParams();
|
||||||
|
|
||||||
|
const setConversation = (message: string) => {
|
||||||
|
return dispatch<any>({
|
||||||
|
type: 'chatModel/setConversation',
|
||||||
|
payload: {
|
||||||
|
// conversation_id: '',
|
||||||
|
dialog_id: dialogId,
|
||||||
|
name: message,
|
||||||
|
message: [
|
||||||
|
{
|
||||||
|
role: MessageType.Assistant,
|
||||||
|
content: message,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { setConversation };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFetchConversation = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { conversationId } = useGetChatSearchParams();
|
||||||
|
const conversation = useSelector(
|
||||||
|
(state: any) => state.chatModel.currentConversation,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchConversation = useCallback(() => {
|
||||||
|
if (conversationId !== EmptyConversationId && conversationId !== '') {
|
||||||
|
dispatch({
|
||||||
|
type: 'chatModel/getConversation',
|
||||||
|
payload: {
|
||||||
|
conversation_id: conversationId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [dispatch, conversationId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchConversation();
|
||||||
|
}, [fetchConversation]);
|
||||||
|
|
||||||
|
return conversation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSendMessage = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { setConversation } = useSetConversation();
|
||||||
|
const { conversationId } = useGetChatSearchParams();
|
||||||
|
const conversation = useSelector(
|
||||||
|
(state: any) => state.chatModel.currentConversation,
|
||||||
|
);
|
||||||
|
const { handleClickConversation } = useClickConversationCard();
|
||||||
|
|
||||||
|
const sendMessage = (message: string, id?: string) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'chatModel/completeConversation',
|
||||||
|
payload: {
|
||||||
|
conversation_id: id ?? conversationId,
|
||||||
|
messages: [
|
||||||
|
...(conversation?.message ?? []).map((x: IMessage) => omit(x, 'id')),
|
||||||
|
{
|
||||||
|
role: MessageType.User,
|
||||||
|
content: message,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendMessage = async (message: string) => {
|
||||||
|
if (conversationId !== EmptyConversationId) {
|
||||||
|
sendMessage(message);
|
||||||
|
} else {
|
||||||
|
const data = await setConversation(message);
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
const id = data.data.id;
|
||||||
|
handleClickConversation(id);
|
||||||
|
sendMessage(message, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { sendMessage: handleSendMessage };
|
||||||
|
};
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
width: 288px;
|
width: 288px;
|
||||||
padding: 26px;
|
padding: 26px;
|
||||||
|
|
||||||
|
.chatAppContent {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.chatAppCard {
|
.chatAppCard {
|
||||||
:global(.ant-card-body) {
|
:global(.ant-card-body) {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -15,6 +19,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.chatAppCardSelected {
|
||||||
|
:global(.ant-card-body) {
|
||||||
|
background-color: @gray11;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.chatTitleWrapper {
|
.chatTitleWrapper {
|
||||||
width: 220px;
|
width: 220px;
|
||||||
@ -29,6 +39,19 @@
|
|||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatTitleCard {
|
||||||
|
:global(.ant-card-body) {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatTitleCardSelected {
|
||||||
|
:global(.ant-card-body) {
|
||||||
|
background-color: @gray11;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
|
||||||
|
import { useSetModalState } from '@/hooks/commonHooks';
|
||||||
import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -9,20 +11,39 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
Tag,
|
Tag,
|
||||||
} from 'antd';
|
} 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 classNames from 'classnames';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
import ChatConfigurationModal from './chat-configuration-modal';
|
import ChatConfigurationModal from './chat-configuration-modal';
|
||||||
import { useFetchDialogList } from './hooks';
|
import ChatContainer from './chat-container';
|
||||||
|
import {
|
||||||
|
useClickConversationCard,
|
||||||
|
useClickDialogCard,
|
||||||
|
useCreateTemporaryConversation,
|
||||||
|
useFetchConversationList,
|
||||||
|
useFetchDialog,
|
||||||
|
useGetChatSearchParams,
|
||||||
|
useRemoveDialog,
|
||||||
|
useSelectFirstDialogOnMount,
|
||||||
|
useSetCurrentDialog,
|
||||||
|
} from './hooks';
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
const Chat = () => {
|
const Chat = () => {
|
||||||
const dialogList = useFetchDialogList();
|
const dialogList = useSelectFirstDialogOnMount();
|
||||||
const [activated, setActivated] = useState<string>('');
|
const [activated, setActivated] = useState<string>('');
|
||||||
|
const { visible, hideModal, showModal } = useSetModalState();
|
||||||
|
const { setCurrentDialog, currentDialog } = useSetCurrentDialog();
|
||||||
|
const { onRemoveDialog } = useRemoveDialog();
|
||||||
|
const { handleClickDialog } = useClickDialogCard();
|
||||||
|
const { handleClickConversation } = useClickConversationCard();
|
||||||
|
const { dialogId, conversationId } = useGetChatSearchParams();
|
||||||
|
const list = useFetchConversationList(dialogId);
|
||||||
|
const { createTemporaryConversation } = useCreateTemporaryConversation();
|
||||||
|
|
||||||
|
const selectedDialog = useFetchDialog(dialogId, true);
|
||||||
|
|
||||||
|
const prologue = selectedDialog?.prompt_config?.prologue || '';
|
||||||
|
|
||||||
const handleAppCardEnter = (id: string) => () => {
|
const handleAppCardEnter = (id: string) => () => {
|
||||||
setActivated(id);
|
setActivated(id);
|
||||||
@ -32,24 +53,42 @@ const Chat = () => {
|
|||||||
setActivated('');
|
setActivated('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleShowChatConfigurationModal = (dialogId?: string) => () => {
|
||||||
|
if (dialogId) {
|
||||||
|
setCurrentDialog(dialogId);
|
||||||
|
}
|
||||||
|
showModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogCardClick = (dialogId: string) => () => {
|
||||||
|
handleClickDialog(dialogId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConversationCardClick = (dialogId: string) => () => {
|
||||||
|
handleClickConversation(dialogId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateTemporaryConversation = useCallback(() => {
|
||||||
|
createTemporaryConversation(prologue);
|
||||||
|
}, [createTemporaryConversation, prologue]);
|
||||||
|
|
||||||
const items: MenuProps['items'] = [
|
const items: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
|
onClick: handleCreateTemporaryConversation,
|
||||||
label: (
|
label: (
|
||||||
<a
|
<Space>
|
||||||
target="_blank"
|
<EditOutlined /> New chat
|
||||||
rel="noopener noreferrer"
|
</Space>
|
||||||
href="https://www.antgroup.com"
|
|
||||||
>
|
|
||||||
1st menu item
|
|
||||||
</a>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const buildAppItems = (dialogId: string) => {
|
||||||
const appItems: MenuProps['items'] = [
|
const appItems: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
|
onClick: handleShowChatConfigurationModal(dialogId),
|
||||||
label: (
|
label: (
|
||||||
<Space>
|
<Space>
|
||||||
<EditOutlined />
|
<EditOutlined />
|
||||||
@ -60,6 +99,7 @@ const Chat = () => {
|
|||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{
|
{
|
||||||
key: '2',
|
key: '2',
|
||||||
|
onClick: () => onRemoveDialog([dialogId]),
|
||||||
label: (
|
label: (
|
||||||
<Space>
|
<Space>
|
||||||
<DeleteOutlined />
|
<DeleteOutlined />
|
||||||
@ -69,35 +109,28 @@ const Chat = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return appItems;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex className={styles.chatWrapper}>
|
<Flex className={styles.chatWrapper}>
|
||||||
<Flex className={styles.chatAppWrapper}>
|
<Flex className={styles.chatAppWrapper}>
|
||||||
<Flex flex={1} vertical>
|
<Flex flex={1} vertical>
|
||||||
<ModalManager>
|
<Button type="primary" onClick={handleShowChatConfigurationModal()}>
|
||||||
{({ visible, showModal, hideModal }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button type="primary" onClick={() => showModal()}>
|
|
||||||
Create an Assistant
|
Create an Assistant
|
||||||
</Button>
|
</Button>
|
||||||
<ChatConfigurationModal
|
|
||||||
visible={visible}
|
|
||||||
showModal={showModal}
|
|
||||||
hideModal={hideModal}
|
|
||||||
></ChatConfigurationModal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</ModalManager>
|
|
||||||
|
|
||||||
<Divider></Divider>
|
<Divider></Divider>
|
||||||
<Space direction={'vertical'} size={'middle'}>
|
<Flex className={styles.chatAppContent} vertical gap={10}>
|
||||||
{dialogList.map((x) => (
|
{dialogList.map((x) => (
|
||||||
<Card
|
<Card
|
||||||
key={x.id}
|
key={x.id}
|
||||||
className={classNames(styles.chatAppCard)}
|
hoverable
|
||||||
|
className={classNames(styles.chatAppCard, {
|
||||||
|
[styles.chatAppCardSelected]: dialogId === x.id,
|
||||||
|
})}
|
||||||
onMouseEnter={handleAppCardEnter(x.id)}
|
onMouseEnter={handleAppCardEnter(x.id)}
|
||||||
onMouseLeave={handleAppCardLeave}
|
onMouseLeave={handleAppCardLeave}
|
||||||
|
onClick={handleDialogCardClick(x.id)}
|
||||||
>
|
>
|
||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
<Space>
|
<Space>
|
||||||
@ -109,7 +142,7 @@ const Chat = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
{activated === x.id && (
|
{activated === x.id && (
|
||||||
<section>
|
<section>
|
||||||
<Dropdown menu={{ items: appItems }}>
|
<Dropdown menu={{ items: buildAppItems(x.id) }}>
|
||||||
<ChatAppCube className={styles.cubeIcon}></ChatAppCube>
|
<ChatAppCube className={styles.cubeIcon}></ChatAppCube>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</section>
|
</section>
|
||||||
@ -117,7 +150,7 @@ const Chat = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider type={'vertical'} className={styles.divider}></Divider>
|
<Divider type={'vertical'} className={styles.divider}></Divider>
|
||||||
@ -137,11 +170,30 @@ const Chat = () => {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider></Divider>
|
<Divider></Divider>
|
||||||
<section className={styles.chatTitleContent}>today</section>
|
<Flex vertical gap={10} className={styles.chatTitleContent}>
|
||||||
|
{list.map((x) => (
|
||||||
|
<Card
|
||||||
|
key={x.id}
|
||||||
|
hoverable
|
||||||
|
onClick={handleConversationCardClick(x.id)}
|
||||||
|
className={classNames(styles.chatTitleCard, {
|
||||||
|
[styles.chatTitleCardSelected]: x.id === conversationId,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div>{x.name}</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider type={'vertical'} className={styles.divider}></Divider>
|
<Divider type={'vertical'} className={styles.divider}></Divider>
|
||||||
<ChatContainer></ChatContainer>
|
<ChatContainer></ChatContainer>
|
||||||
|
<ChatConfigurationModal
|
||||||
|
visible={visible}
|
||||||
|
showModal={showModal}
|
||||||
|
hideModal={hideModal}
|
||||||
|
id={currentDialog.id}
|
||||||
|
></ChatConfigurationModal>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
31
web/src/pages/chat/interface.ts
Normal file
31
web/src/pages/chat/interface.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { IConversation, Message } from '@/interfaces/database/chat';
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VariableTableDataType {
|
||||||
|
key: string;
|
||||||
|
variable: string;
|
||||||
|
optional: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IPromptConfigParameters = Omit<VariableTableDataType, 'variable'>;
|
||||||
|
|
||||||
|
export interface IMessage extends Message {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IClientConversation extends IConversation {
|
||||||
|
message: IMessage[];
|
||||||
|
}
|
@ -1,11 +1,16 @@
|
|||||||
import { IDialog } from '@/interfaces/database/chat';
|
import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
|
||||||
import chatService from '@/services/chatService';
|
import chatService from '@/services/chatService';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { DvaModel } from 'umi';
|
import { DvaModel } from 'umi';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { IClientConversation, IMessage } from './interface';
|
||||||
|
|
||||||
export interface ChatModelState {
|
export interface ChatModelState {
|
||||||
name: string;
|
name: string;
|
||||||
dialogList: IDialog[];
|
dialogList: IDialog[];
|
||||||
|
currentDialog: IDialog;
|
||||||
|
conversationList: IConversation[];
|
||||||
|
currentConversation: IClientConversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
const model: DvaModel<ChatModelState> = {
|
const model: DvaModel<ChatModelState> = {
|
||||||
@ -13,6 +18,9 @@ const model: DvaModel<ChatModelState> = {
|
|||||||
state: {
|
state: {
|
||||||
name: 'kate',
|
name: 'kate',
|
||||||
dialogList: [],
|
dialogList: [],
|
||||||
|
currentDialog: <IDialog>{},
|
||||||
|
conversationList: [],
|
||||||
|
currentConversation: {} as IClientConversation,
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
save(state, action) {
|
save(state, action) {
|
||||||
@ -27,11 +35,50 @@ const model: DvaModel<ChatModelState> = {
|
|||||||
dialogList: payload,
|
dialogList: payload,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
setCurrentDialog(state, { payload }) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentDialog: payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setConversationList(state, { payload }) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
conversationList: payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setCurrentConversation(state, { payload }) {
|
||||||
|
const messageList = payload?.message.map((x: Message | IMessage) => ({
|
||||||
|
...x,
|
||||||
|
id: 'id' in x ? x.id : uuid(),
|
||||||
|
}));
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentConversation: { ...payload, message: messageList },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
addEmptyConversationToList(state, {}) {
|
||||||
|
const list = [...state.conversationList];
|
||||||
|
// if (list.every((x) => x.id !== 'empty')) {
|
||||||
|
// list.push({
|
||||||
|
// id: 'empty',
|
||||||
|
// name: 'New conversation',
|
||||||
|
// message: [],
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
conversationList: list,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
effects: {
|
effects: {
|
||||||
*getDialog({ payload }, { call, put }) {
|
*getDialog({ payload }, { call, put }) {
|
||||||
const { data } = yield call(chatService.getDialog, payload);
|
const { data } = yield call(chatService.getDialog, payload);
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
yield put({ type: 'setCurrentDialog', payload: data.data });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
*setDialog({ payload }, { call, put }) {
|
*setDialog({ payload }, { call, put }) {
|
||||||
const { data } = yield call(chatService.setDialog, payload);
|
const { data } = yield call(chatService.setDialog, payload);
|
||||||
@ -39,6 +86,15 @@ const model: DvaModel<ChatModelState> = {
|
|||||||
yield put({ type: 'listDialog' });
|
yield put({ type: 'listDialog' });
|
||||||
message.success('Created successfully !');
|
message.success('Created successfully !');
|
||||||
}
|
}
|
||||||
|
return data.retcode;
|
||||||
|
},
|
||||||
|
*removeDialog({ payload }, { call, put }) {
|
||||||
|
const { data } = yield call(chatService.removeDialog, payload);
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
yield put({ type: 'listDialog' });
|
||||||
|
message.success('Deleted successfully !');
|
||||||
|
}
|
||||||
|
return data.retcode;
|
||||||
},
|
},
|
||||||
*listDialog({ payload }, { call, put }) {
|
*listDialog({ payload }, { call, put }) {
|
||||||
const { data } = yield call(chatService.listDialog, payload);
|
const { data } = yield call(chatService.listDialog, payload);
|
||||||
@ -46,15 +102,40 @@ const model: DvaModel<ChatModelState> = {
|
|||||||
},
|
},
|
||||||
*listConversation({ payload }, { call, put }) {
|
*listConversation({ payload }, { call, put }) {
|
||||||
const { data } = yield call(chatService.listConversation, payload);
|
const { data } = yield call(chatService.listConversation, payload);
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
yield put({ type: 'setConversationList', payload: data.data });
|
||||||
|
}
|
||||||
|
return data.retcode;
|
||||||
},
|
},
|
||||||
*getConversation({ payload }, { call, put }) {
|
*getConversation({ payload }, { call, put }) {
|
||||||
const { data } = yield call(chatService.getConversation, payload);
|
const { data } = yield call(chatService.getConversation, payload);
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
yield put({ type: 'setCurrentConversation', payload: data.data });
|
||||||
|
}
|
||||||
|
return data.retcode;
|
||||||
},
|
},
|
||||||
*setConversation({ payload }, { call, put }) {
|
*setConversation({ payload }, { call, put }) {
|
||||||
const { data } = yield call(chatService.setConversation, payload);
|
const { data } = yield call(chatService.setConversation, payload);
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
yield put({
|
||||||
|
type: 'listConversation',
|
||||||
|
payload: {
|
||||||
|
dialog_id: data.data.dialog_id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return data;
|
||||||
},
|
},
|
||||||
*completeConversation({ payload }, { call, put }) {
|
*completeConversation({ payload }, { call, put }) {
|
||||||
const { data } = yield call(chatService.completeConversation, payload);
|
const { data } = yield call(chatService.completeConversation, payload);
|
||||||
|
if (data.retcode === 0) {
|
||||||
|
yield put({
|
||||||
|
type: 'getConversation',
|
||||||
|
payload: {
|
||||||
|
conversation_id: payload.conversation_id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
12
web/src/pages/chat/utils.ts
Normal file
12
web/src/pages/chat/utils.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { variableEnabledFieldMap } from './constants';
|
||||||
|
|
||||||
|
export const excludeUnEnabledVariables = (values: any) => {
|
||||||
|
const unEnabledFields: Array<keyof typeof variableEnabledFieldMap> =
|
||||||
|
Object.keys(variableEnabledFieldMap).filter((key) => !values[key]) as Array<
|
||||||
|
keyof typeof variableEnabledFieldMap
|
||||||
|
>;
|
||||||
|
|
||||||
|
return unEnabledFields.map(
|
||||||
|
(key) => `llm_setting.${variableEnabledFieldMap[key]}`,
|
||||||
|
);
|
||||||
|
};
|
@ -31,7 +31,7 @@ const model: DvaModel<KnowledgeModelState> = {
|
|||||||
},
|
},
|
||||||
*getList({ payload = {} }, { call, put }) {
|
*getList({ payload = {} }, { call, put }) {
|
||||||
const { data } = yield call(kbService.getList, payload);
|
const { data } = yield call(kbService.getList, payload);
|
||||||
const { retcode, data: res, retmsg } = data;
|
const { retcode, data: res } = data;
|
||||||
|
|
||||||
if (retcode === 0) {
|
if (retcode === 0) {
|
||||||
yield put({
|
yield put({
|
||||||
|
@ -6,6 +6,7 @@ const {
|
|||||||
getDialog,
|
getDialog,
|
||||||
setDialog,
|
setDialog,
|
||||||
listDialog,
|
listDialog,
|
||||||
|
removeDialog,
|
||||||
getConversation,
|
getConversation,
|
||||||
setConversation,
|
setConversation,
|
||||||
completeConversation,
|
completeConversation,
|
||||||
@ -21,6 +22,10 @@ const methods = {
|
|||||||
url: setDialog,
|
url: setDialog,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
},
|
},
|
||||||
|
removeDialog: {
|
||||||
|
url: removeDialog,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
listDialog: {
|
listDialog: {
|
||||||
url: listDialog,
|
url: listDialog,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
@ -45,6 +45,7 @@ export default {
|
|||||||
|
|
||||||
setDialog: `${api_host}/dialog/set`,
|
setDialog: `${api_host}/dialog/set`,
|
||||||
getDialog: `${api_host}/dialog/get`,
|
getDialog: `${api_host}/dialog/get`,
|
||||||
|
removeDialog: `${api_host}/dialog/rm`,
|
||||||
listDialog: `${api_host}/dialog/list`,
|
listDialog: `${api_host}/dialog/list`,
|
||||||
|
|
||||||
setConversation: `${api_host}/conversation/set`,
|
setConversation: `${api_host}/conversation/set`,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user