mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-12 20:39:03 +08:00
### What problem does this PR solve? feat: Translation test run form #3355 feat: Wrap QueryTable with Collapse #3355 feat: If the required fields are not filled in, the submit button will be grayed out. #3355 feat: Add RunDrawer #3355 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
a854bc22d1
commit
e0659a4f0e
@ -4,8 +4,9 @@ import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
|
|||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import chatService from '@/services/chat-service';
|
import chatService from '@/services/chat-service';
|
||||||
import kbService from '@/services/knowledge-service';
|
import kbService from '@/services/knowledge-service';
|
||||||
import { api_host } from '@/utils/api';
|
import api, { api_host } from '@/utils/api';
|
||||||
import { buildChunkHighlights } from '@/utils/document-util';
|
import { buildChunkHighlights } from '@/utils/document-util';
|
||||||
|
import { post } from '@/utils/request';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { UploadFile, message } from 'antd';
|
import { UploadFile, message } from 'antd';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
@ -442,3 +443,27 @@ export const useUploadAndParseDocument = (uploadMethod: string) => {
|
|||||||
|
|
||||||
return { data, loading, uploadAndParseDocument: mutateAsync };
|
return { data, loading, uploadAndParseDocument: mutateAsync };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useParseDocument = () => {
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isPending: loading,
|
||||||
|
mutateAsync,
|
||||||
|
} = useMutation({
|
||||||
|
mutationKey: ['parseDocument'],
|
||||||
|
mutationFn: async (url: string) => {
|
||||||
|
try {
|
||||||
|
const data = await post(api.parse, { url });
|
||||||
|
if (data?.code === 0) {
|
||||||
|
message.success(i18n.t('message.uploaded'));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('🚀 ~ mutationFn: ~ error:', error);
|
||||||
|
message.error('error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { parseDocument: mutateAsync, data, loading };
|
||||||
|
};
|
||||||
|
@ -2,7 +2,9 @@ import { Authorization } from '@/constants/authorization';
|
|||||||
import userService from '@/services/user-service';
|
import userService from '@/services/user-service';
|
||||||
import authorizationUtil from '@/utils/authorization-util';
|
import authorizationUtil from '@/utils/authorization-util';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { message } from 'antd';
|
import { Form, message } from 'antd';
|
||||||
|
import { FormInstance } from 'antd/lib';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { history } from 'umi';
|
import { history } from 'umi';
|
||||||
|
|
||||||
@ -95,3 +97,19 @@ export const useLogout = () => {
|
|||||||
|
|
||||||
return { data, loading, logout: mutateAsync };
|
return { data, loading, logout: mutateAsync };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useHandleSubmittable = (form: FormInstance) => {
|
||||||
|
const [submittable, setSubmittable] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// Watch all values
|
||||||
|
const values = Form.useWatch([], form);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form
|
||||||
|
.validateFields({ validateOnly: true })
|
||||||
|
.then(() => setSubmittable(true))
|
||||||
|
.catch(() => setSubmittable(false));
|
||||||
|
}, [form, values]);
|
||||||
|
|
||||||
|
return { submittable };
|
||||||
|
};
|
||||||
|
@ -32,6 +32,7 @@ export default {
|
|||||||
s: 'S',
|
s: 'S',
|
||||||
pleaseSelect: 'Please select',
|
pleaseSelect: 'Please select',
|
||||||
pleaseInput: 'Please input',
|
pleaseInput: 'Please input',
|
||||||
|
submit: 'Submit',
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
login: 'Sign in',
|
login: 'Sign in',
|
||||||
@ -176,7 +177,7 @@ export default {
|
|||||||
chunkTokenNumber: 'Chunk token number',
|
chunkTokenNumber: 'Chunk token number',
|
||||||
chunkTokenNumberMessage: 'Chunk token number is required',
|
chunkTokenNumberMessage: 'Chunk token number is required',
|
||||||
embeddingModelTip:
|
embeddingModelTip:
|
||||||
"The model that converts chunks into embeddings. It cannot be changed once the knowledge base has chunks. To switch to a different embedding model, You must delete all chunks in the knowledge base.",
|
'The model that converts chunks into embeddings. It cannot be changed once the knowledge base has chunks. To switch to a different embedding model, You must delete all chunks in the knowledge base.',
|
||||||
permissionsTip:
|
permissionsTip:
|
||||||
"If set to 'Team', all team members will be able to manage the knowledge base.",
|
"If set to 'Team', all team members will be able to manage the knowledge base.",
|
||||||
chunkTokenNumberTip:
|
chunkTokenNumberTip:
|
||||||
@ -1025,6 +1026,9 @@ The above is the content you need to summarize.`,
|
|||||||
content: 'Content',
|
content: 'Content',
|
||||||
operationResults: 'Operation Results',
|
operationResults: 'Operation Results',
|
||||||
autosaved: 'Autosaved',
|
autosaved: 'Autosaved',
|
||||||
|
optional: 'Optional',
|
||||||
|
pasteFileLink: 'Paste file link',
|
||||||
|
testRun: 'Test Run',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
|
@ -32,6 +32,7 @@ export default {
|
|||||||
s: '秒',
|
s: '秒',
|
||||||
pleaseSelect: '請選擇',
|
pleaseSelect: '請選擇',
|
||||||
pleaseInput: '請輸入',
|
pleaseInput: '請輸入',
|
||||||
|
submit: '提交',
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
login: '登入',
|
login: '登入',
|
||||||
@ -985,6 +986,9 @@ export default {
|
|||||||
content: '內容',
|
content: '內容',
|
||||||
operationResults: '運行結果',
|
operationResults: '運行結果',
|
||||||
autosaved: '已自動儲存',
|
autosaved: '已自動儲存',
|
||||||
|
optional: '可選項',
|
||||||
|
pasteFileLink: '貼上文件連結',
|
||||||
|
testRun: '試運行',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: '“保留所有權利 @ react”',
|
profile: '“保留所有權利 @ react”',
|
||||||
|
@ -32,6 +32,7 @@ export default {
|
|||||||
s: '秒',
|
s: '秒',
|
||||||
pleaseSelect: '请选择',
|
pleaseSelect: '请选择',
|
||||||
pleaseInput: '请输入',
|
pleaseInput: '请输入',
|
||||||
|
submit: '提交',
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
login: '登录',
|
login: '登录',
|
||||||
@ -1005,6 +1006,9 @@ export default {
|
|||||||
content: '内容',
|
content: '内容',
|
||||||
operationResults: '运行结果',
|
operationResults: '运行结果',
|
||||||
autosaved: '已自动保存',
|
autosaved: '已自动保存',
|
||||||
|
optional: '可选项',
|
||||||
|
pasteFileLink: '粘贴文件链接',
|
||||||
|
testRun: '试运行',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
ConnectionMode,
|
ConnectionMode,
|
||||||
@ -8,14 +9,17 @@ import ReactFlow, {
|
|||||||
import 'reactflow/dist/style.css';
|
import 'reactflow/dist/style.css';
|
||||||
import ChatDrawer from '../chat/drawer';
|
import ChatDrawer from '../chat/drawer';
|
||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
import FlowDrawer from '../flow-drawer';
|
import FormDrawer from '../flow-drawer';
|
||||||
import {
|
import {
|
||||||
|
useGetBeginNodeDataQuery,
|
||||||
useHandleDrop,
|
useHandleDrop,
|
||||||
useSelectCanvasData,
|
useSelectCanvasData,
|
||||||
useShowDrawer,
|
useShowFormDrawer,
|
||||||
useValidateConnection,
|
useValidateConnection,
|
||||||
useWatchNodeFormDataChange,
|
useWatchNodeFormDataChange,
|
||||||
} from '../hooks';
|
} from '../hooks';
|
||||||
|
import { BeginQuery } from '../interface';
|
||||||
|
import RunDrawer from '../run-drawer';
|
||||||
import { ButtonEdge } from './edge';
|
import { ButtonEdge } from './edge';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { RagNode } from './node';
|
import { RagNode } from './node';
|
||||||
@ -53,11 +57,11 @@ const edgeTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
chatDrawerVisible: boolean;
|
drawerVisible: boolean;
|
||||||
hideChatDrawer(): void;
|
hideDrawer(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
|
function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||||
const {
|
const {
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
@ -67,27 +71,66 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
|
|||||||
onSelectionChange,
|
onSelectionChange,
|
||||||
} = useSelectCanvasData();
|
} = useSelectCanvasData();
|
||||||
const isValidConnection = useValidateConnection();
|
const isValidConnection = useValidateConnection();
|
||||||
|
const {
|
||||||
|
visible: runVisible,
|
||||||
|
showModal: showRunModal,
|
||||||
|
hideModal: hideRunModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const {
|
||||||
|
visible: chatVisible,
|
||||||
|
showModal: showChatModal,
|
||||||
|
hideModal: hideChatModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
|
||||||
const { drawerVisible, hideDrawer, showDrawer, clickedNode } =
|
const { formDrawerVisible, hideFormDrawer, showFormDrawer, clickedNode } =
|
||||||
useShowDrawer();
|
useShowFormDrawer();
|
||||||
|
|
||||||
const onNodeClick: NodeMouseHandler = useCallback(
|
|
||||||
(e, node) => {
|
|
||||||
if (node.data.label !== Operator.Note) {
|
|
||||||
showDrawer(node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[showDrawer],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onPaneClick = useCallback(() => {
|
const onPaneClick = useCallback(() => {
|
||||||
hideDrawer();
|
hideFormDrawer();
|
||||||
}, [hideDrawer]);
|
}, [hideFormDrawer]);
|
||||||
|
|
||||||
const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop();
|
const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop();
|
||||||
|
|
||||||
useWatchNodeFormDataChange();
|
useWatchNodeFormDataChange();
|
||||||
|
|
||||||
|
const hideRunOrChatDrawer = useCallback(() => {
|
||||||
|
hideChatModal();
|
||||||
|
hideRunModal();
|
||||||
|
hideDrawer();
|
||||||
|
}, [hideChatModal, hideDrawer, hideRunModal]);
|
||||||
|
|
||||||
|
const onNodeClick: NodeMouseHandler = useCallback(
|
||||||
|
(e, node) => {
|
||||||
|
if (node.data.label !== Operator.Note) {
|
||||||
|
hideRunOrChatDrawer();
|
||||||
|
showFormDrawer(node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[hideRunOrChatDrawer, showFormDrawer],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (drawerVisible) {
|
||||||
|
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||||
|
if (query.length > 0) {
|
||||||
|
showRunModal();
|
||||||
|
hideChatModal();
|
||||||
|
} else {
|
||||||
|
showChatModal();
|
||||||
|
hideRunModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
hideChatModal,
|
||||||
|
hideRunModal,
|
||||||
|
showChatModal,
|
||||||
|
showRunModal,
|
||||||
|
drawerVisible,
|
||||||
|
getBeginNodeDataQuery,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.canvasWrapper}>
|
<div className={styles.canvasWrapper}>
|
||||||
<svg
|
<svg
|
||||||
@ -147,17 +190,26 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
|
|||||||
<Background />
|
<Background />
|
||||||
<Controls />
|
<Controls />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
<FlowDrawer
|
{formDrawerVisible && (
|
||||||
node={clickedNode}
|
<FormDrawer
|
||||||
visible={drawerVisible}
|
node={clickedNode}
|
||||||
hideModal={hideDrawer}
|
visible={formDrawerVisible}
|
||||||
></FlowDrawer>
|
hideModal={hideFormDrawer}
|
||||||
{chatDrawerVisible && (
|
></FormDrawer>
|
||||||
|
)}
|
||||||
|
{chatVisible && (
|
||||||
<ChatDrawer
|
<ChatDrawer
|
||||||
visible={chatDrawerVisible}
|
visible={chatVisible}
|
||||||
hideModal={hideChatDrawer}
|
hideModal={hideRunOrChatDrawer}
|
||||||
></ChatDrawer>
|
></ChatDrawer>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{runVisible && (
|
||||||
|
<RunDrawer
|
||||||
|
hideModal={hideRunOrChatDrawer}
|
||||||
|
showModal={showChatModal}
|
||||||
|
></RunDrawer>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import { Flex } from 'antd';
|
import { Flex } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import get from 'lodash/get';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Handle, NodeProps, Position } from 'reactflow';
|
import { Handle, NodeProps, Position } from 'reactflow';
|
||||||
import { Operator, operatorMap } from '../../constant';
|
import {
|
||||||
import { NodeData } from '../../interface';
|
BeginQueryType,
|
||||||
|
BeginQueryTypeIconMap,
|
||||||
|
Operator,
|
||||||
|
operatorMap,
|
||||||
|
} from '../../constant';
|
||||||
|
import { BeginQuery, NodeData } from '../../interface';
|
||||||
import OperatorIcon from '../../operator-icon';
|
import OperatorIcon from '../../operator-icon';
|
||||||
import { RightHandleStyle } from './handle-icon';
|
import { RightHandleStyle } from './handle-icon';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
@ -11,15 +17,13 @@ import styles from './index.less';
|
|||||||
// TODO: do not allow other nodes to connect to this node
|
// TODO: do not allow other nodes to connect to this node
|
||||||
export function BeginNode({ selected, data }: NodeProps<NodeData>) {
|
export function BeginNode({ selected, data }: NodeProps<NodeData>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const query: BeginQuery[] = get(data, 'form.query', []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={classNames(styles.ragNode, {
|
className={classNames(styles.ragNode, {
|
||||||
[styles.selectedNode]: selected,
|
[styles.selectedNode]: selected,
|
||||||
})}
|
})}
|
||||||
style={{
|
|
||||||
width: 100,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Handle
|
<Handle
|
||||||
type="source"
|
type="source"
|
||||||
@ -29,7 +33,7 @@ export function BeginNode({ selected, data }: NodeProps<NodeData>) {
|
|||||||
style={RightHandleStyle}
|
style={RightHandleStyle}
|
||||||
></Handle>
|
></Handle>
|
||||||
|
|
||||||
<Flex align="center" justify={'space-around'}>
|
<Flex align="center" justify={'center'} gap={10}>
|
||||||
<OperatorIcon
|
<OperatorIcon
|
||||||
name={data.label as Operator}
|
name={data.label as Operator}
|
||||||
fontSize={24}
|
fontSize={24}
|
||||||
@ -37,6 +41,24 @@ export function BeginNode({ selected, data }: NodeProps<NodeData>) {
|
|||||||
></OperatorIcon>
|
></OperatorIcon>
|
||||||
<div className={styles.nodeTitle}>{t(`flow.begin`)}</div>
|
<div className={styles.nodeTitle}>{t(`flow.begin`)}</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex gap={8} vertical className={styles.generateParameters}>
|
||||||
|
{query.map((x, idx) => {
|
||||||
|
const Icon = BeginQueryTypeIconMap[x.type as BeginQueryType];
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={idx}
|
||||||
|
align="center"
|
||||||
|
gap={6}
|
||||||
|
className={styles.conditionBlock}
|
||||||
|
>
|
||||||
|
<Icon className="size-4" />
|
||||||
|
<label htmlFor="">{x.key}</label>
|
||||||
|
<span className={styles.parameterValue}>{x.name}</span>
|
||||||
|
<span className="flex-1">{x.optional ? 'Yes' : 'No'}</span>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,15 @@ import {
|
|||||||
SendOutlined,
|
SendOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import upperFirst from 'lodash/upperFirst';
|
import upperFirst from 'lodash/upperFirst';
|
||||||
|
import {
|
||||||
|
CloudUpload,
|
||||||
|
Link2,
|
||||||
|
ListOrdered,
|
||||||
|
OptionIcon,
|
||||||
|
TextCursorInput,
|
||||||
|
ToggleLeft,
|
||||||
|
WrapText,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
export enum Operator {
|
export enum Operator {
|
||||||
Begin = 'Begin',
|
Begin = 'Begin',
|
||||||
@ -2870,12 +2879,12 @@ export enum BeginQueryType {
|
|||||||
Url = 'url',
|
Url = 'url',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BeginQueryTypeMap = {
|
export const BeginQueryTypeIconMap = {
|
||||||
[BeginQueryType.Line]: 'input',
|
[BeginQueryType.Line]: TextCursorInput,
|
||||||
[BeginQueryType.Paragraph]: 'textarea',
|
[BeginQueryType.Paragraph]: WrapText,
|
||||||
[BeginQueryType.Options]: 'select',
|
[BeginQueryType.Options]: OptionIcon,
|
||||||
[BeginQueryType.File]: 'file',
|
[BeginQueryType.File]: CloudUpload,
|
||||||
[BeginQueryType.Integer]: 'inputnumber',
|
[BeginQueryType.Integer]: ListOrdered,
|
||||||
[BeginQueryType.Boolean]: 'switch',
|
[BeginQueryType.Boolean]: ToggleLeft,
|
||||||
[BeginQueryType.Url]: 'input',
|
[BeginQueryType.Url]: Link2,
|
||||||
};
|
};
|
||||||
|
@ -83,7 +83,7 @@ const FormMap = {
|
|||||||
|
|
||||||
const EmptyContent = () => <div></div>;
|
const EmptyContent = () => <div></div>;
|
||||||
|
|
||||||
const FlowDrawer = ({
|
const FormDrawer = ({
|
||||||
visible,
|
visible,
|
||||||
hideModal,
|
hideModal,
|
||||||
node,
|
node,
|
||||||
@ -152,4 +152,4 @@ const FlowDrawer = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FlowDrawer;
|
export default FormDrawer;
|
||||||
|
24
web/src/pages/flow/form/begin-form/index.less
Normal file
24
web/src/pages/flow/form/begin-form/index.less
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
.dynamicInputVariable {
|
||||||
|
background-color: #ebe9e9;
|
||||||
|
:global(.ant-collapse-content) {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
}
|
||||||
|
:global(.ant-collapse-content-box) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
margin-bottom: 20px;
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
color: rgb(22, 119, 255);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.addButton {
|
||||||
|
color: rgb(22, 119, 255);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
@ -1,17 +1,20 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Button, Form, Input } from 'antd';
|
import { Button, Form, Input } from 'antd';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BeginQuery, IOperatorForm } from '../../interface';
|
import { BeginQuery, IOperatorForm } from '../../interface';
|
||||||
import { useEditQueryRecord } from './hooks';
|
import { useEditQueryRecord } from './hooks';
|
||||||
import { ModalForm } from './paramater-modal';
|
import { ModalForm } from './paramater-modal';
|
||||||
import QueryTable from './query-table';
|
import QueryTable from './query-table';
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
type FieldType = {
|
type FieldType = {
|
||||||
prologue?: string;
|
prologue?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
||||||
const { t } = useTranslate('chat');
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
ok,
|
ok,
|
||||||
currentRecord,
|
currentRecord,
|
||||||
@ -55,9 +58,9 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|||||||
>
|
>
|
||||||
<Form.Item<FieldType>
|
<Form.Item<FieldType>
|
||||||
name={'prologue'}
|
name={'prologue'}
|
||||||
label={t('setAnOpener')}
|
label={t('chat.setAnOpener')}
|
||||||
tooltip={t('setAnOpenerTip')}
|
tooltip={t('chat.setAnOpenerTip')}
|
||||||
initialValue={t('setAnOpenerInitial')}
|
initialValue={t('chat.setAnOpenerInitial')}
|
||||||
>
|
>
|
||||||
<Input.TextArea autoSize={{ minRows: 5 }} />
|
<Input.TextArea autoSize={{ minRows: 5 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -65,7 +68,6 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|||||||
<Form.Item name="query" noStyle />
|
<Form.Item name="query" noStyle />
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Query List"
|
|
||||||
shouldUpdate={(prevValues, curValues) =>
|
shouldUpdate={(prevValues, curValues) =>
|
||||||
prevValues.query !== curValues.query
|
prevValues.query !== curValues.query
|
||||||
}
|
}
|
||||||
@ -86,9 +88,11 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|||||||
htmlType="button"
|
htmlType="button"
|
||||||
style={{ margin: '0 8px' }}
|
style={{ margin: '0 8px' }}
|
||||||
onClick={() => showModal()}
|
onClick={() => showModal()}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
block
|
block
|
||||||
|
className={styles.addButton}
|
||||||
>
|
>
|
||||||
Add +
|
{t('flow.addItem')}
|
||||||
</Button>
|
</Button>
|
||||||
{visible && (
|
{visible && (
|
||||||
<ModalForm
|
<ModalForm
|
||||||
|
@ -3,7 +3,7 @@ import { IModalProps } from '@/interfaces/common';
|
|||||||
import { Form, Input, Modal, Select, Switch } from 'antd';
|
import { Form, Input, Modal, Select, Switch } from 'antd';
|
||||||
import { DefaultOptionType } from 'antd/es/select';
|
import { DefaultOptionType } from 'antd/es/select';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { BeginQueryType } from '../../constant';
|
import { BeginQueryType, BeginQueryTypeIconMap } from '../../constant';
|
||||||
import { BeginQuery } from '../../interface';
|
import { BeginQuery } from '../../interface';
|
||||||
import BeginDynamicOptions from './begin-dynamic-options';
|
import BeginDynamicOptions from './begin-dynamic-options';
|
||||||
|
|
||||||
@ -20,10 +20,19 @@ export const ModalForm = ({
|
|||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
return Object.values(BeginQueryType).reduce<DefaultOptionType[]>(
|
return Object.values(BeginQueryType).reduce<DefaultOptionType[]>(
|
||||||
(pre, cur) => {
|
(pre, cur) => {
|
||||||
|
const Icon = BeginQueryTypeIconMap[cur];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...pre,
|
...pre,
|
||||||
{
|
{
|
||||||
label: cur,
|
label: (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Icon
|
||||||
|
className={`size-${cur === BeginQueryType.Options ? 4 : 5}`}
|
||||||
|
></Icon>
|
||||||
|
{cur}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
value: cur,
|
value: cur,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||||
import type { TableProps } from 'antd';
|
import type { TableProps } from 'antd';
|
||||||
import { Space, Table, Tooltip } from 'antd';
|
import { Collapse, Space, Table, Tooltip } from 'antd';
|
||||||
import { BeginQuery } from '../../interface';
|
import { BeginQuery } from '../../interface';
|
||||||
|
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
data: BeginQuery[];
|
data: BeginQuery[];
|
||||||
deleteRecord(index: number): void;
|
deleteRecord(index: number): void;
|
||||||
@ -10,6 +13,8 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const columns: TableProps<BeginQuery>['columns'] = [
|
const columns: TableProps<BeginQuery>['columns'] = [
|
||||||
{
|
{
|
||||||
title: 'Key',
|
title: 'Key',
|
||||||
@ -25,7 +30,7 @@ const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Name',
|
title: t('flow.name'),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
@ -38,18 +43,18 @@ const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Type',
|
title: t('flow.type'),
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
key: 'type',
|
key: 'type',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Optional',
|
title: t('flow.optional'),
|
||||||
dataIndex: 'optional',
|
dataIndex: 'optional',
|
||||||
key: 'optional',
|
key: 'optional',
|
||||||
render: (optional) => (optional ? 'Yes' : 'No'),
|
render: (optional) => (optional ? 'Yes' : 'No'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Action',
|
title: t('common.action'),
|
||||||
key: 'action',
|
key: 'action',
|
||||||
render: (_, record, idx) => (
|
render: (_, record, idx) => (
|
||||||
<Space>
|
<Space>
|
||||||
@ -64,7 +69,23 @@ const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table<BeginQuery> columns={columns} dataSource={data} pagination={false} />
|
<Collapse
|
||||||
|
defaultActiveKey={['1']}
|
||||||
|
className={styles.dynamicInputVariable}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: <span className={styles.title}>{t('flow.input')}</span>,
|
||||||
|
children: (
|
||||||
|
<Table<BeginQuery>
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { Button, Collapse, Flex, Form, Input, Select } from 'antd';
|
import { Button, Collapse, Flex, Form, Input, Select } from 'antd';
|
||||||
|
|
||||||
import { useCallback } from 'react';
|
import { PropsWithChildren, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useBuildComponentIdSelectOptions } from '../../hooks';
|
import { useBuildComponentIdSelectOptions } from '../../hooks';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
@ -95,9 +95,10 @@ const DynamicVariableForm = ({ nodeId }: IProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DynamicInputVariable = ({ nodeId }: IProps) => {
|
export function FormCollapse({
|
||||||
const { t } = useTranslation();
|
children,
|
||||||
|
title,
|
||||||
|
}: PropsWithChildren<{ title: string }>) {
|
||||||
return (
|
return (
|
||||||
<Collapse
|
<Collapse
|
||||||
className={styles.dynamicInputVariable}
|
className={styles.dynamicInputVariable}
|
||||||
@ -105,12 +106,21 @@ const DynamicInputVariable = ({ nodeId }: IProps) => {
|
|||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
label: <span className={styles.title}>{t('flow.input')}</span>,
|
label: <span className={styles.title}>{title}</span>,
|
||||||
children: <DynamicVariableForm nodeId={nodeId}></DynamicVariableForm>,
|
children,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DynamicInputVariable = ({ nodeId }: IProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<FormCollapse title={t('flow.input')}>
|
||||||
|
<DynamicVariableForm nodeId={nodeId}></DynamicVariableForm>
|
||||||
|
</FormCollapse>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DynamicInputVariable;
|
export default DynamicInputVariable;
|
||||||
|
@ -3,13 +3,16 @@ import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
|
|||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
import { useFetchFlow } from '@/hooks/flow-hooks';
|
||||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||||
import { Button, Flex, Space } from 'antd';
|
import { Button, Flex, Space } from 'antd';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { Link, useParams } from 'umi';
|
import { Link, useParams } from 'umi';
|
||||||
import FlowIdModal from '../flow-id-modal';
|
import FlowIdModal from '../flow-id-modal';
|
||||||
import {
|
import {
|
||||||
|
useGetBeginNodeDataQuery,
|
||||||
useSaveGraph,
|
useSaveGraph,
|
||||||
useSaveGraphBeforeOpeningDebugDrawer,
|
useSaveGraphBeforeOpeningDebugDrawer,
|
||||||
useWatchAgentChange,
|
useWatchAgentChange,
|
||||||
} from '../hooks';
|
} from '../hooks';
|
||||||
|
import { BeginQuery } from '../interface';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -19,7 +22,7 @@ interface IProps {
|
|||||||
|
|
||||||
const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
|
const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
|
||||||
const { saveGraph } = useSaveGraph();
|
const { saveGraph } = useSaveGraph();
|
||||||
const handleRun = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
||||||
const { data } = useFetchFlow();
|
const { data } = useFetchFlow();
|
||||||
const { t } = useTranslate('flow');
|
const { t } = useTranslate('flow');
|
||||||
const {
|
const {
|
||||||
@ -30,6 +33,16 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
|
|||||||
const { visible, hideModal, showModal } = useSetModalState();
|
const { visible, hideModal, showModal } = useSetModalState();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const time = useWatchAgentChange(chatDrawerVisible);
|
const time = useWatchAgentChange(chatDrawerVisible);
|
||||||
|
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||||
|
|
||||||
|
const handleRunAgent = useCallback(() => {
|
||||||
|
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||||
|
if (query.length > 0) {
|
||||||
|
showChatDrawer();
|
||||||
|
} else {
|
||||||
|
handleRun();
|
||||||
|
}
|
||||||
|
}, [getBeginNodeDataQuery, handleRun, showChatDrawer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -51,10 +64,10 @@ const FlowHeader = ({ showChatDrawer, chatDrawerVisible }: IProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
<Space size={'large'}>
|
<Space size={'large'}>
|
||||||
<Button onClick={handleRun}>
|
<Button onClick={handleRunAgent}>
|
||||||
<b>{t('run')}</b>
|
<b>{t('run')}</b>
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" onClick={saveGraph}>
|
<Button type="primary" onClick={() => saveGraph()}>
|
||||||
<b>{t('save')}</b>
|
<b>{t('save')}</b>
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Button type="primary" onClick={showOverviewModal} disabled>
|
{/* <Button type="primary" onClick={showOverviewModal} disabled>
|
||||||
|
@ -21,6 +21,7 @@ import { Variable } from '@/interfaces/database/chat';
|
|||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { useDebounceEffect } from 'ahooks';
|
import { useDebounceEffect } from 'ahooks';
|
||||||
import { FormInstance, message } from 'antd';
|
import { FormInstance, message } from 'antd';
|
||||||
|
import { DefaultOptionType } from 'antd/es/select';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { humanId } from 'human-id';
|
import { humanId } from 'human-id';
|
||||||
import { get, lowerFirst } from 'lodash';
|
import { get, lowerFirst } from 'lodash';
|
||||||
@ -65,7 +66,12 @@ import {
|
|||||||
initialWikipediaValues,
|
initialWikipediaValues,
|
||||||
initialYahooFinanceValues,
|
initialYahooFinanceValues,
|
||||||
} from './constant';
|
} from './constant';
|
||||||
import { ICategorizeForm, IRelevantForm, ISwitchForm } from './interface';
|
import {
|
||||||
|
BeginQuery,
|
||||||
|
ICategorizeForm,
|
||||||
|
IRelevantForm,
|
||||||
|
ISwitchForm,
|
||||||
|
} from './interface';
|
||||||
import useGraphStore, { RFState } from './store';
|
import useGraphStore, { RFState } from './store';
|
||||||
import {
|
import {
|
||||||
buildDslComponentsByGraph,
|
buildDslComponentsByGraph,
|
||||||
@ -225,49 +231,60 @@ export const useHandleDrop = () => {
|
|||||||
return { onDrop, onDragOver, setReactFlowInstance };
|
return { onDrop, onDragOver, setReactFlowInstance };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useShowDrawer = () => {
|
export const useShowFormDrawer = () => {
|
||||||
const {
|
const {
|
||||||
clickedNodeId: clickNodeId,
|
clickedNodeId: clickNodeId,
|
||||||
setClickedNodeId,
|
setClickedNodeId,
|
||||||
getNode,
|
getNode,
|
||||||
} = useGraphStore((state) => state);
|
} = useGraphStore((state) => state);
|
||||||
const {
|
const {
|
||||||
visible: drawerVisible,
|
visible: formDrawerVisible,
|
||||||
hideModal: hideDrawer,
|
hideModal: hideFormDrawer,
|
||||||
showModal: showDrawer,
|
showModal: showFormDrawer,
|
||||||
} = useSetModalState();
|
} = useSetModalState();
|
||||||
|
|
||||||
const handleShow = useCallback(
|
const handleShow = useCallback(
|
||||||
(node: Node) => {
|
(node: Node) => {
|
||||||
setClickedNodeId(node.id);
|
setClickedNodeId(node.id);
|
||||||
showDrawer();
|
showFormDrawer();
|
||||||
},
|
},
|
||||||
[showDrawer, setClickedNodeId],
|
[showFormDrawer, setClickedNodeId],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
drawerVisible,
|
formDrawerVisible,
|
||||||
hideDrawer,
|
hideFormDrawer,
|
||||||
showDrawer: handleShow,
|
showFormDrawer: handleShow,
|
||||||
clickedNode: getNode(clickNodeId),
|
clickedNode: getNode(clickNodeId),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSaveGraph = () => {
|
export const useSaveGraph = () => {
|
||||||
const { data } = useFetchFlow();
|
const { data } = useFetchFlow();
|
||||||
const { setFlow } = useSetFlow();
|
const { setFlow, loading } = useSetFlow();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { nodes, edges } = useGraphStore((state) => state);
|
const { nodes, edges } = useGraphStore((state) => state);
|
||||||
const saveGraph = useCallback(async () => {
|
useEffect(() => {}, [nodes]);
|
||||||
const dslComponents = buildDslComponentsByGraph(nodes, edges);
|
const saveGraph = useCallback(
|
||||||
return setFlow({
|
async (currentNodes?: Node[]) => {
|
||||||
id,
|
const dslComponents = buildDslComponentsByGraph(
|
||||||
title: data.title,
|
currentNodes ?? nodes,
|
||||||
dsl: { ...data.dsl, graph: { nodes, edges }, components: dslComponents },
|
edges,
|
||||||
});
|
);
|
||||||
}, [nodes, edges, setFlow, id, data]);
|
return setFlow({
|
||||||
|
id,
|
||||||
|
title: data.title,
|
||||||
|
dsl: {
|
||||||
|
...data.dsl,
|
||||||
|
graph: { nodes: currentNodes ?? nodes, edges },
|
||||||
|
components: dslComponents,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[nodes, edges, setFlow, id, data],
|
||||||
|
);
|
||||||
|
|
||||||
return { saveGraph };
|
return { saveGraph, loading };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useHandleFormValuesChange = (id?: string) => {
|
export const useHandleFormValuesChange = (id?: string) => {
|
||||||
@ -420,32 +437,46 @@ export const useHandleNodeNameChange = ({
|
|||||||
return { name, handleNameBlur, handleNameChange };
|
return { name, handleNameBlur, handleNameChange };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetBeginNodeDataQuery = () => {
|
||||||
|
const getNode = useGraphStore((state) => state.getNode);
|
||||||
|
|
||||||
|
const getBeginNodeDataQuery = useCallback(() => {
|
||||||
|
return get(getNode('begin'), 'data.form.query', []);
|
||||||
|
}, [getNode]);
|
||||||
|
|
||||||
|
return getBeginNodeDataQuery;
|
||||||
|
};
|
||||||
|
|
||||||
export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
|
export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { saveGraph } = useSaveGraph();
|
const { saveGraph, loading } = useSaveGraph();
|
||||||
const { resetFlow } = useResetFlow();
|
const { resetFlow } = useResetFlow();
|
||||||
const { refetch } = useFetchFlow();
|
const { refetch } = useFetchFlow();
|
||||||
const { send } = useSendMessageWithSse(api.runCanvas);
|
const { send } = useSendMessageWithSse(api.runCanvas);
|
||||||
const handleRun = useCallback(async () => {
|
|
||||||
const saveRet = await saveGraph();
|
const handleRun = useCallback(
|
||||||
if (saveRet?.code === 0) {
|
async (nextNodes?: Node[]) => {
|
||||||
// Call the reset api before opening the run drawer each time
|
const saveRet = await saveGraph(nextNodes);
|
||||||
const resetRet = await resetFlow();
|
if (saveRet?.code === 0) {
|
||||||
// After resetting, all previous messages will be cleared.
|
// Call the reset api before opening the run drawer each time
|
||||||
if (resetRet?.code === 0) {
|
const resetRet = await resetFlow();
|
||||||
// fetch prologue
|
// After resetting, all previous messages will be cleared.
|
||||||
const sendRet = await send({ id });
|
if (resetRet?.code === 0) {
|
||||||
if (receiveMessageError(sendRet)) {
|
// fetch prologue
|
||||||
message.error(sendRet?.data?.message);
|
const sendRet = await send({ id });
|
||||||
} else {
|
if (receiveMessageError(sendRet)) {
|
||||||
refetch();
|
message.error(sendRet?.data?.message);
|
||||||
show();
|
} else {
|
||||||
|
refetch();
|
||||||
|
show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, [saveGraph, resetFlow, id, send, show, refetch]);
|
[saveGraph, resetFlow, send, id, refetch, show],
|
||||||
|
);
|
||||||
|
|
||||||
return handleRun;
|
return { handleRun, loading };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useReplaceIdWithName = () => {
|
export const useReplaceIdWithName = () => {
|
||||||
@ -596,8 +627,10 @@ const ExcludedNodes = [
|
|||||||
|
|
||||||
export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
|
export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
const nodes = useGraphStore((state) => state.nodes);
|
||||||
|
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||||
|
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const componentIdOptions = useMemo(() => {
|
||||||
return nodes
|
return nodes
|
||||||
.filter(
|
.filter(
|
||||||
(x) =>
|
(x) =>
|
||||||
@ -606,17 +639,40 @@ export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
|
|||||||
.map((x) => ({ label: x.data.name, value: x.id }));
|
.map((x) => ({ label: x.data.name, value: x.id }));
|
||||||
}, [nodes, nodeId]);
|
}, [nodes, nodeId]);
|
||||||
|
|
||||||
return options;
|
const groupedOptions = [
|
||||||
|
{
|
||||||
|
label: <span>Component id</span>,
|
||||||
|
title: 'Component Id',
|
||||||
|
options: componentIdOptions,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: <span>Begin input</span>,
|
||||||
|
title: 'Begin input',
|
||||||
|
options: query.map((x) => ({
|
||||||
|
label: x.name,
|
||||||
|
value: `begin@${x.key}`,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return groupedOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetComponentLabelByValue = (nodeId: string) => {
|
export const useGetComponentLabelByValue = (nodeId: string) => {
|
||||||
const options = useBuildComponentIdSelectOptions(nodeId);
|
const options = useBuildComponentIdSelectOptions(nodeId);
|
||||||
|
const flattenOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
options.reduce<DefaultOptionType[]>((pre, cur) => {
|
||||||
|
return [...pre, ...cur.options];
|
||||||
|
}, []),
|
||||||
|
[options],
|
||||||
|
);
|
||||||
|
|
||||||
const getLabel = useCallback(
|
const getLabel = useCallback(
|
||||||
(val?: string) => {
|
(val?: string) => {
|
||||||
return options.find((x) => x.value === val)?.label;
|
return flattenOptions.find((x) => x.value === val)?.label;
|
||||||
},
|
},
|
||||||
[options],
|
[flattenOptions],
|
||||||
);
|
);
|
||||||
return getLabel;
|
return getLabel;
|
||||||
};
|
};
|
@ -31,8 +31,8 @@ function RagFlow() {
|
|||||||
></FlowHeader>
|
></FlowHeader>
|
||||||
<Content style={{ margin: 0 }}>
|
<Content style={{ margin: 0 }}>
|
||||||
<FlowCanvas
|
<FlowCanvas
|
||||||
chatDrawerVisible={chatDrawerVisible}
|
drawerVisible={chatDrawerVisible}
|
||||||
hideChatDrawer={hideChatDrawer}
|
hideDrawer={hideChatDrawer}
|
||||||
></FlowCanvas>
|
></FlowCanvas>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
5
web/src/pages/flow/run-drawer/index.less
Normal file
5
web/src/pages/flow/run-drawer/index.less
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.formWrapper {
|
||||||
|
:global(.ant-form-item-label) {
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
}
|
284
web/src/pages/flow/run-drawer/index.tsx
Normal file
284
web/src/pages/flow/run-drawer/index.tsx
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
import { Authorization } from '@/constants/authorization';
|
||||||
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
|
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
|
||||||
|
import { useHandleSubmittable } from '@/hooks/login-hooks';
|
||||||
|
import { IModalProps } from '@/interfaces/common';
|
||||||
|
import api from '@/utils/api';
|
||||||
|
import { getAuthorization } from '@/utils/authorization-util';
|
||||||
|
import { InboxOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Drawer,
|
||||||
|
Flex,
|
||||||
|
Form,
|
||||||
|
FormItemProps,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Upload,
|
||||||
|
} from 'antd';
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
import { Link2, Trash2 } from 'lucide-react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { BeginQueryType } from '../constant';
|
||||||
|
import {
|
||||||
|
useGetBeginNodeDataQuery,
|
||||||
|
useSaveGraphBeforeOpeningDebugDrawer,
|
||||||
|
} from '../hooks';
|
||||||
|
import { BeginQuery } from '../interface';
|
||||||
|
import useGraphStore from '../store';
|
||||||
|
import { getDrawerWidth } from '../utils';
|
||||||
|
import { PopoverForm } from './popover-form';
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const RunDrawer = ({
|
||||||
|
hideModal,
|
||||||
|
showModal: showChatModal,
|
||||||
|
}: IModalProps<any>) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
||||||
|
const {
|
||||||
|
visible,
|
||||||
|
hideModal: hidePopover,
|
||||||
|
switchVisible,
|
||||||
|
showModal: showPopover,
|
||||||
|
} = useSetModalState();
|
||||||
|
const { setRecord, currentRecord } = useSetSelectedRecord<number>();
|
||||||
|
const { submittable } = useHandleSubmittable(form);
|
||||||
|
|
||||||
|
const handleShowPopover = useCallback(
|
||||||
|
(idx: number) => () => {
|
||||||
|
setRecord(idx);
|
||||||
|
showPopover();
|
||||||
|
},
|
||||||
|
[setRecord, showPopover],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRemoveUrl = useCallback(
|
||||||
|
(key: number, index: number) => () => {
|
||||||
|
const list: any[] = form.getFieldValue(key);
|
||||||
|
|
||||||
|
form.setFieldValue(
|
||||||
|
key,
|
||||||
|
list.filter((_, idx) => idx !== index),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[form],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||||
|
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||||
|
|
||||||
|
const normFile = (e: any) => {
|
||||||
|
if (Array.isArray(e)) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
return e?.fileList;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderWidget = useCallback(
|
||||||
|
(q: BeginQuery, idx: number) => {
|
||||||
|
const props: FormItemProps & { key: number } = {
|
||||||
|
key: idx,
|
||||||
|
label: q.name,
|
||||||
|
name: idx,
|
||||||
|
};
|
||||||
|
if (q.optional === false) {
|
||||||
|
props.rules = [{ required: true }];
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlList: { url: string; result: string }[] =
|
||||||
|
form.getFieldValue(idx) || [];
|
||||||
|
|
||||||
|
const BeginQueryTypeMap = {
|
||||||
|
[BeginQueryType.Line]: (
|
||||||
|
<Form.Item {...props}>
|
||||||
|
<Input></Input>
|
||||||
|
</Form.Item>
|
||||||
|
),
|
||||||
|
[BeginQueryType.Paragraph]: (
|
||||||
|
<Form.Item {...props}>
|
||||||
|
<Input.TextArea rows={4}></Input.TextArea>
|
||||||
|
</Form.Item>
|
||||||
|
),
|
||||||
|
[BeginQueryType.Options]: (
|
||||||
|
<Form.Item {...props}>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
options={q.options?.map((x) => ({ label: x, value: x })) ?? []}
|
||||||
|
></Select>
|
||||||
|
</Form.Item>
|
||||||
|
),
|
||||||
|
[BeginQueryType.File]: (
|
||||||
|
<Form.Item
|
||||||
|
{...props}
|
||||||
|
valuePropName="fileList"
|
||||||
|
getValueFromEvent={normFile}
|
||||||
|
>
|
||||||
|
<Upload.Dragger
|
||||||
|
name="file"
|
||||||
|
action={api.parse}
|
||||||
|
multiple
|
||||||
|
headers={{ [Authorization]: getAuthorization() }}
|
||||||
|
>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<InboxOutlined />
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">{t('fileManager.uploadTitle')}</p>
|
||||||
|
<p className="ant-upload-hint">
|
||||||
|
{t('fileManager.uploadDescription')}
|
||||||
|
</p>
|
||||||
|
</Upload.Dragger>
|
||||||
|
</Form.Item>
|
||||||
|
),
|
||||||
|
[BeginQueryType.Integer]: (
|
||||||
|
<Form.Item {...props}>
|
||||||
|
<InputNumber></InputNumber>
|
||||||
|
</Form.Item>
|
||||||
|
),
|
||||||
|
[BeginQueryType.Boolean]: (
|
||||||
|
<Form.Item valuePropName={'checked'} {...props}>
|
||||||
|
<Switch></Switch>
|
||||||
|
</Form.Item>
|
||||||
|
),
|
||||||
|
[BeginQueryType.Url]: (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
{...pick(props, ['key', 'label', 'rules'])}
|
||||||
|
required={!q.optional}
|
||||||
|
className={urlList.length > 0 ? 'mb-1' : ''}
|
||||||
|
>
|
||||||
|
<PopoverForm visible={visible} switchVisible={switchVisible}>
|
||||||
|
<Button
|
||||||
|
onClick={handleShowPopover(idx)}
|
||||||
|
className="text-buttonBlueText"
|
||||||
|
>
|
||||||
|
{t('flow.pasteFileLink')}
|
||||||
|
</Button>
|
||||||
|
</PopoverForm>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name={idx} noStyle {...pick(props, ['rules'])} />
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
shouldUpdate={(prevValues, curValues) =>
|
||||||
|
prevValues[idx] !== curValues[idx]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ getFieldValue }) => {
|
||||||
|
const urlInfo: { url: string; result: string }[] =
|
||||||
|
getFieldValue(idx) || [];
|
||||||
|
return urlInfo.length ? (
|
||||||
|
<Flex vertical gap={8} className="mb-3">
|
||||||
|
{urlInfo.map((u, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex items-center justify-between gap-2 hover:bg-slate-100 group"
|
||||||
|
>
|
||||||
|
<Link2 className="size-5"></Link2>
|
||||||
|
<span className="flex-1 truncate"> {u.url}</span>
|
||||||
|
<Trash2
|
||||||
|
className="size-4 invisible group-hover:visible cursor-pointer"
|
||||||
|
onClick={handleRemoveUrl(idx, index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
) : null;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return BeginQueryTypeMap[q.type as BeginQueryType];
|
||||||
|
},
|
||||||
|
[form, handleRemoveUrl, handleShowPopover, switchVisible, t, visible],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatModal!);
|
||||||
|
|
||||||
|
const handleRunAgent = useCallback(
|
||||||
|
(nextValues: Record<string, any>) => {
|
||||||
|
const currentNodes = updateNodeForm('begin', nextValues, ['query']);
|
||||||
|
handleRun(currentNodes);
|
||||||
|
hideModal?.();
|
||||||
|
},
|
||||||
|
[handleRun, hideModal, updateNodeForm],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onOk = useCallback(async () => {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
const nextValues = Object.entries(values).map(([key, value]) => {
|
||||||
|
const item = query[Number(key)];
|
||||||
|
let nextValue = value;
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
nextValue = ``;
|
||||||
|
|
||||||
|
value.forEach((x, idx) => {
|
||||||
|
if (x?.originFileObj instanceof File) {
|
||||||
|
if (idx === 0) {
|
||||||
|
nextValue += `${x.name}\n\n${x.response.data}\n\n`;
|
||||||
|
} else {
|
||||||
|
nextValue += `${x.response.data}\n\n`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (idx === 0) {
|
||||||
|
nextValue += `${x.url}\n\n${x.result}\n\n`;
|
||||||
|
} else {
|
||||||
|
nextValue += `${x.result}\n\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { ...item, value: nextValue };
|
||||||
|
});
|
||||||
|
handleRunAgent(nextValues);
|
||||||
|
}, [form, handleRunAgent, query]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
title={t('flow.testRun')}
|
||||||
|
placement="right"
|
||||||
|
onClose={hideModal}
|
||||||
|
open
|
||||||
|
getContainer={false}
|
||||||
|
width={getDrawerWidth()}
|
||||||
|
mask={false}
|
||||||
|
>
|
||||||
|
<section className={styles.formWrapper}>
|
||||||
|
<Form.Provider
|
||||||
|
onFormFinish={(name, { values, forms }) => {
|
||||||
|
if (name === 'urlForm') {
|
||||||
|
const { basicForm } = forms;
|
||||||
|
const urlInfo = basicForm.getFieldValue(currentRecord) || [];
|
||||||
|
basicForm.setFieldsValue({
|
||||||
|
[currentRecord]: [...urlInfo, values],
|
||||||
|
});
|
||||||
|
hidePopover();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name="basicForm"
|
||||||
|
autoComplete="off"
|
||||||
|
layout={'vertical'}
|
||||||
|
form={form}
|
||||||
|
>
|
||||||
|
{query.map((x, idx) => {
|
||||||
|
return renderWidget(x, idx);
|
||||||
|
})}
|
||||||
|
</Form>
|
||||||
|
</Form.Provider>
|
||||||
|
</section>
|
||||||
|
<Button type={'primary'} block onClick={onOk} disabled={!submittable}>
|
||||||
|
{t('common.next')}
|
||||||
|
</Button>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RunDrawer;
|
74
web/src/pages/flow/run-drawer/popover-form.tsx
Normal file
74
web/src/pages/flow/run-drawer/popover-form.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { useParseDocument } from '@/hooks/document-hooks';
|
||||||
|
import { useResetFormOnCloseModal } from '@/hooks/logic-hooks';
|
||||||
|
import { IModalProps } from '@/interfaces/common';
|
||||||
|
import { Button, Form, Input, Popover } from 'antd';
|
||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const reg =
|
||||||
|
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
|
||||||
|
|
||||||
|
export const PopoverForm = ({
|
||||||
|
children,
|
||||||
|
visible,
|
||||||
|
switchVisible,
|
||||||
|
}: PropsWithChildren<IModalProps<any>>) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const { parseDocument, loading } = useParseDocument();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useResetFormOnCloseModal({
|
||||||
|
form,
|
||||||
|
visible,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
const val = values.url;
|
||||||
|
|
||||||
|
if (reg.test(val)) {
|
||||||
|
const ret = await parseDocument(val);
|
||||||
|
if (ret?.data?.code === 0) {
|
||||||
|
form.setFieldValue('result', ret?.data?.data);
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<Form form={form} name="urlForm">
|
||||||
|
<Form.Item
|
||||||
|
name="url"
|
||||||
|
rules={[{ required: true, type: 'url' }]}
|
||||||
|
className="m-0"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
onPressEnter={(e) => e.preventDefault()}
|
||||||
|
placeholder={t('flow.pasteFileLink')}
|
||||||
|
suffix={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={onOk}
|
||||||
|
size={'small'}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{t('common.submit')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name={'result'} noStyle />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
content={content}
|
||||||
|
open={visible}
|
||||||
|
trigger={'click'}
|
||||||
|
onOpenChange={switchVisible}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
@ -47,7 +47,7 @@ export type RFState = {
|
|||||||
nodeId: string,
|
nodeId: string,
|
||||||
values: any,
|
values: any,
|
||||||
path?: (string | number)[],
|
path?: (string | number)[],
|
||||||
) => void;
|
) => Node[];
|
||||||
onSelectionChange: OnSelectionChangeFunc;
|
onSelectionChange: OnSelectionChangeFunc;
|
||||||
addNode: (nodes: Node) => void;
|
addNode: (nodes: Node) => void;
|
||||||
getNode: (id?: string | null) => Node<NodeData> | undefined;
|
getNode: (id?: string | null) => Node<NodeData> | undefined;
|
||||||
@ -331,27 +331,30 @@ const useGraphStore = create<RFState>()(
|
|||||||
values: any,
|
values: any,
|
||||||
path: (string | number)[] = [],
|
path: (string | number)[] = [],
|
||||||
) => {
|
) => {
|
||||||
set({
|
const nextNodes = get().nodes.map((node) => {
|
||||||
nodes: get().nodes.map((node) => {
|
if (node.id === nodeId) {
|
||||||
if (node.id === nodeId) {
|
let nextForm: Record<string, unknown> = { ...node.data.form };
|
||||||
let nextForm: Record<string, unknown> = { ...node.data.form };
|
if (path.length === 0) {
|
||||||
if (path.length === 0) {
|
nextForm = Object.assign(nextForm, values);
|
||||||
nextForm = Object.assign(nextForm, values);
|
} else {
|
||||||
} else {
|
lodashSet(nextForm, path, values);
|
||||||
lodashSet(nextForm, path, values);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
data: {
|
|
||||||
...node.data,
|
|
||||||
form: nextForm,
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
form: nextForm,
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
set({
|
||||||
|
nodes: nextNodes,
|
||||||
|
});
|
||||||
|
|
||||||
|
return nextNodes;
|
||||||
},
|
},
|
||||||
updateSwitchFormData: (source, sourceHandle, target) => {
|
updateSwitchFormData: (source, sourceHandle, target) => {
|
||||||
const { updateNodeForm } = get();
|
const { updateNodeForm } = get();
|
||||||
|
@ -62,6 +62,7 @@ export default {
|
|||||||
web_crawl: `${api_host}/document/web_crawl`,
|
web_crawl: `${api_host}/document/web_crawl`,
|
||||||
document_infos: `${api_host}/document/infos`,
|
document_infos: `${api_host}/document/infos`,
|
||||||
upload_and_parse: `${api_host}/document/upload_and_parse`,
|
upload_and_parse: `${api_host}/document/upload_and_parse`,
|
||||||
|
parse: `${api_host}/document/parse`,
|
||||||
|
|
||||||
// chat
|
// chat
|
||||||
setDialog: `${api_host}/dialog/set`,
|
setDialog: `${api_host}/dialog/set`,
|
||||||
|
@ -99,8 +99,8 @@ request.interceptors.request.use((url: string, options: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
request.interceptors.response.use(async (response: any, options) => {
|
request.interceptors.response.use(async (response: any, options) => {
|
||||||
if (response?.status === 413) {
|
if (response?.status === 413 || response?.status === 504) {
|
||||||
message.error(RetcodeMessage[413]);
|
message.error(RetcodeMessage[response?.status as ResultCode]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.responseType === 'blob') {
|
if (options.responseType === 'blob') {
|
||||||
|
@ -24,6 +24,7 @@ module.exports = {
|
|||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
background: 'var(--background)',
|
background: 'var(--background)',
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: 'hsl(var(--foreground))',
|
||||||
|
buttonBlueText: 'var(--button-blue-text)',
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
foreground: 'hsl(var(--primary-foreground))',
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
|
@ -37,6 +37,8 @@
|
|||||||
|
|
||||||
--background-inverse-standard: rgba(58, 56, 65, 0.15);
|
--background-inverse-standard: rgba(58, 56, 65, 0.15);
|
||||||
--background-inverse-standard-foreground: rgb(92, 81, 81);
|
--background-inverse-standard-foreground: rgb(92, 81, 81);
|
||||||
|
|
||||||
|
--button-blue-text: rgb(22, 119, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user