(feature)(webapp) modify model to view (#719)

This commit is contained in:
williamhliu
2024-02-04 20:58:33 +08:00
committed by GitHub
parent da5e7b9b75
commit e801c448be
17 changed files with 67 additions and 145 deletions

View File

@@ -28,7 +28,7 @@ export type ModelInfoType = {
}; };
export type EntityInfoType = { export type EntityInfoType = {
modelInfo: ModelInfoType; viewInfo: ModelInfoType;
dimensions: FieldType[]; dimensions: FieldType[];
metrics: FieldType[]; metrics: FieldType[];
entityId: number; entityId: number;
@@ -83,7 +83,7 @@ export type ChatContextType = {
aggType: string; aggType: string;
modelId: number; modelId: number;
modelName: string; modelName: string;
model: ModelType; view: ModelType;
dateInfo: DateInfoType; dateInfo: DateInfoType;
dimensions: FieldType[]; dimensions: FieldType[];
metrics: FieldType[]; metrics: FieldType[];

View File

@@ -89,7 +89,7 @@ const ParseTip: React.FC<Props> = ({
const { const {
modelId, modelId,
model, view,
dimensions, dimensions,
metrics, metrics,
aggType, aggType,
@@ -148,12 +148,8 @@ const ParseTip: React.FC<Props> = ({
</div> </div>
) : ( ) : (
<div className={`${prefixCls}-tip-item`}> <div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div> <div className={`${prefixCls}-tip-item-name`}></div>
<div className={itemValueClass}> <div className={itemValueClass}>{view?.name}</div>
{model?.modelNames?.length === 1
? model.modelNames[0]
: model?.modelNames?.map(modelName => <Tag key={modelName}>{modelName}</Tag>)}
</div>
</div> </div>
)} )}
{(queryType === 'METRIC' || queryType === 'METRIC_TAG' || queryType === 'TAG') && ( {(queryType === 'METRIC' || queryType === 'METRIC_TAG' || queryType === 'TAG') && (

View File

@@ -84,7 +84,7 @@ const ChatItem: React.FC<Props> = ({
let data: MsgDataType | undefined = undefined; let data: MsgDataType | undefined = undefined;
const { queryColumns, queryResults, queryState, queryMode, response, chatContext } = const { queryColumns, queryResults, queryState, queryMode, response, chatContext } =
res.data || {}; res.data || {};
if (res.code === 401 || res.code === 412) { if (res.code === 400 || res.code === 401 || res.code === 412) {
tip = res.msg; tip = res.msg;
} else if (res.code !== 200) { } else if (res.code !== 200) {
tip = SEARCH_EXCEPTION_TIP; tip = SEARCH_EXCEPTION_TIP;

View File

@@ -141,7 +141,7 @@ const BarChart: React.FC<Props> = ({ data, triggerResize, loading, onApplyAuth }
if (metricColumn && !metricColumn?.authorized) { if (metricColumn && !metricColumn?.authorized) {
return ( return (
<NoPermissionChart <NoPermissionChart
model={entityInfo?.modelInfo.name || ''} model={entityInfo?.viewInfo.name || ''}
chartType="barChart" chartType="barChart"
onApplyAuth={onApplyAuth} onApplyAuth={onApplyAuth}
/> />

View File

@@ -48,7 +48,7 @@ const MetricCard: React.FC<Props> = ({ data, loading, onApplyAuth }) => {
<Spin spinning={loading}> <Spin spinning={loading}>
<div className={`${prefixCls}-indicator`}> <div className={`${prefixCls}-indicator`}>
{indicatorColumn && !indicatorColumn?.authorized ? ( {indicatorColumn && !indicatorColumn?.authorized ? (
<ApplyAuth model={entityInfo?.modelInfo.name || ''} onApplyAuth={onApplyAuth} /> <ApplyAuth model={entityInfo?.viewInfo.name || ''} onApplyAuth={onApplyAuth} />
) : ( ) : (
<div style={{ display: 'flex', alignItems: 'flex-end' }}> <div style={{ display: 'flex', alignItems: 'flex-end' }}>
<div className={`${prefixCls}-indicator-value`}> <div className={`${prefixCls}-indicator-value`}>

View File

@@ -109,7 +109,7 @@ const MetricTrend: React.FC<Props> = ({
/> />
) : ( ) : (
<MetricTrendChart <MetricTrendChart
model={entityInfo?.modelInfo.name} model={entityInfo?.viewInfo.name}
dateColumnName={dateColumnName} dateColumnName={dateColumnName}
categoryColumnName={categoryColumnName} categoryColumnName={categoryColumnName}
metricField={currentMetricField} metricField={currentMetricField}

View File

@@ -26,7 +26,7 @@ const Table: React.FC<Props> = ({ data, size, loading, onApplyAuth }) => {
title: name || nameEn, title: name || nameEn,
render: (value: string | number) => { render: (value: string | number) => {
if (!authorized) { if (!authorized) {
return <ApplyAuth model={entityInfo?.modelInfo.name || ''} onApplyAuth={onApplyAuth} />; return <ApplyAuth model={entityInfo?.viewInfo.name || ''} onApplyAuth={onApplyAuth} />;
} }
if (dataFormatType === 'percent') { if (dataFormatType === 'percent') {
return ( return (

View File

@@ -1,6 +1,6 @@
const { createProxyMiddleware } = require('http-proxy-middleware'); const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) { module.exports = function (app) {
app.use( app.use(
'/api', '/api',
createProxyMiddleware({ createProxyMiddleware({
@@ -15,4 +15,4 @@ module.exports = function(app) {
changeOrigin: true, changeOrigin: true,
}) })
); );
}; };

View File

@@ -28,7 +28,7 @@
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less", "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
"precommit": "lint-staged", "precommit": "lint-staged",
"prettier": "prettier -c --write \"src/**/*\"", "prettier": "prettier -c --write \"src/**/*\"",
"start": "npm run start:osdev", "start": "NODE_OPTIONS=--openssl-legacy-provider npm run start:osdev",
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none APP_TARGET=inner umi dev", "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none APP_TARGET=inner umi dev",
"start:osdev": "cross-env REACT_APP_ENV=dev PORT=9000 MOCK=none APP_TARGET=opensource umi dev", "start:osdev": "cross-env REACT_APP_ENV=dev PORT=9000 MOCK=none APP_TARGET=opensource umi dev",
"start:no-mock": "cross-env MOCK=none umi dev", "start:no-mock": "cross-env MOCK=none umi dev",
@@ -147,4 +147,4 @@
"@types/react": "17.0.0" "@types/react": "17.0.0"
}, },
"__npminstall_done": false "__npminstall_done": false
} }

View File

@@ -135,18 +135,6 @@ const AgentsSection: React.FC<Props> = ({
<PlusOutlined /> <PlusOutlined />
</Button> </Button>
<div className={styles.switchShowType}>
<span className={styles.switchShowTypeLabel}></span>
<Switch
size="small"
checked={showType === 'card'}
onChange={(value) => {
const showTypeValue = value ? 'card' : 'list';
setShowType(showTypeValue);
localStorage.setItem('AGENT_SHOW_TYPE', showTypeValue);
}}
/>
</div>
</div> </div>
{showType === 'list' ? ( {showType === 'list' ? (
<Table columns={columns} dataSource={showAgents} /> <Table columns={columns} dataSource={showAgents} />

View File

@@ -1,18 +1,16 @@
import { Form, Modal, Input, Select, Button } from 'antd'; import { Form, Modal, Input, Select, Button, TreeSelect } from 'antd';
import { import {
AgentToolType, AgentToolType,
AgentToolTypeEnum, AgentToolTypeEnum,
AGENT_TOOL_TYPE_LIST, AGENT_TOOL_TYPE_LIST,
MetricOptionType, MetricOptionType,
MetricType,
ModelType,
QUERY_MODE_LIST, QUERY_MODE_LIST,
} from './type'; } from './type';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import styles from './style.less'; import styles from './style.less';
import { getLeafList, uuid } from '@/utils/utils'; import { traverseTree, uuid } from '@/utils/utils';
import { getMetricList, getModelList } from './service'; import { getModelList } from './service';
import { PluginType } from '../ChatPlugin/type'; import { PluginType } from '../ChatPlugin/type';
import { getPluginList } from '../ChatPlugin/service'; import { getPluginList } from '../ChatPlugin/service';
@@ -26,17 +24,22 @@ type Props = {
const ToolModal: React.FC<Props> = ({ editTool, onSaveTool, onCancel }) => { const ToolModal: React.FC<Props> = ({ editTool, onSaveTool, onCancel }) => {
const [toolType, setToolType] = useState<AgentToolTypeEnum>(); const [toolType, setToolType] = useState<AgentToolTypeEnum>();
const [modelList, setModelList] = useState<ModelType[]>([]); const [modelList, setModelList] = useState<any[]>([]);
const [saveLoading, setSaveLoading] = useState(false); const [saveLoading, setSaveLoading] = useState(false);
const [examples, setExamples] = useState<{ id: string; question?: string }[]>([]); const [examples, setExamples] = useState<{ id: string; question?: string }[]>([]);
const [metricOptions, setMetricOptions] = useState<MetricOptionType[]>([]); const [metricOptions, setMetricOptions] = useState<MetricOptionType[]>([]);
const [modelMetricList, setModelMetricList] = useState<MetricType[]>([]);
const [plugins, setPlugins] = useState<PluginType[]>([]); const [plugins, setPlugins] = useState<PluginType[]>([]);
const [form] = Form.useForm(); const [form] = Form.useForm();
const initModelList = async () => { const initModelList = async () => {
const res = await getModelList(); const res = await getModelList();
setModelList([{ id: -1, name: '默认' }, ...getLeafList(res.data)]); const treeData = traverseTree(res.data, (node: any) => {
node.title = node.name;
node.value = node.type === 'DOMAIN' ? `DOMAIN_${node.id}` : node.id;
node.checkable =
node.type === 'VIEW' || (node.type === 'DOMAIN' && node.children?.length > 0);
});
setModelList([{ title: '默认', value: -1, type: 'VIEW' }, ...treeData]);
}; };
const initPluginList = async () => { const initPluginList = async () => {
@@ -49,11 +52,6 @@ const ToolModal: React.FC<Props> = ({ editTool, onSaveTool, onCancel }) => {
initPluginList(); initPluginList();
}, []); }, []);
const initModelMetrics = async (params: any) => {
const res = await getMetricList(params[0].modelId);
setModelMetricList(res.data.list);
};
useEffect(() => { useEffect(() => {
if (editTool) { if (editTool) {
form.setFieldsValue({ ...editTool, plugins: editTool.plugins?.[0] }); form.setFieldsValue({ ...editTool, plugins: editTool.plugins?.[0] });
@@ -62,9 +60,6 @@ const ToolModal: React.FC<Props> = ({ editTool, onSaveTool, onCancel }) => {
(editTool.exampleQuestions || []).map((item) => ({ id: uuid(), question: item })), (editTool.exampleQuestions || []).map((item) => ({ id: uuid(), question: item })),
); );
setMetricOptions(editTool.metricOptions || []); setMetricOptions(editTool.metricOptions || []);
if (editTool.metricOptions && editTool.metricOptions.length > 0) {
initModelMetrics(editTool.metricOptions || []);
}
} else { } else {
form.resetFields(); form.resetFields();
} }
@@ -88,11 +83,6 @@ const ToolModal: React.FC<Props> = ({ editTool, onSaveTool, onCancel }) => {
setSaveLoading(false); setSaveLoading(false);
}; };
const updateMetricList = async (value: number) => {
const res = await getMetricList(value);
setModelMetricList(res.data.list);
};
return ( return (
<Modal <Modal
open open
@@ -115,11 +105,13 @@ const ToolModal: React.FC<Props> = ({ editTool, onSaveTool, onCancel }) => {
</FormItem> </FormItem>
{(toolType === AgentToolTypeEnum.NL2SQL_RULE || {(toolType === AgentToolTypeEnum.NL2SQL_RULE ||
toolType === AgentToolTypeEnum.NL2SQL_LLM) && ( toolType === AgentToolTypeEnum.NL2SQL_LLM) && (
<FormItem name="modelIds" label="主题域"> <FormItem name="viewIds" label="视图">
<Select <TreeSelect
options={modelList.map((model) => ({ label: model.name, value: model.id }))} treeData={modelList}
placeholder="请选择主题域" placeholder="请选择视图"
mode="multiple" multiple
treeCheckable
allowClear
/> />
</FormItem> </FormItem>
)} )}
@@ -159,70 +151,6 @@ const ToolModal: React.FC<Props> = ({ editTool, onSaveTool, onCancel }) => {
</div> </div>
</FormItem> </FormItem>
)} )}
{toolType === AgentToolTypeEnum.ANALYTICS && (
<>
<FormItem name="modelId" label="主题域">
<Select
options={modelList.map((model) => ({ label: model.name, value: model.id }))}
showSearch
filterOption={(input, option) =>
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
}
placeholder="请选择主题域"
onChange={(value) => {
setMetricOptions([...metricOptions]);
updateMetricList(value);
}}
/>
</FormItem>
<FormItem name="params" label="指标">
<div className={styles.paramsSection}>
{metricOptions.map((filter: any) => {
return (
<div className={styles.filterRow} key={filter.id}>
<Select
placeholder="请选择指标,需先选择主题域"
options={(modelMetricList || []).map((metric) => ({
label: metric.name,
value: `${metric.id}`,
}))}
showSearch
className={styles.filterParamValueField}
filterOption={(input, option) =>
((option?.label ?? '') as string)
.toLowerCase()
.includes(input.toLowerCase())
}
allowClear
value={filter.metricId}
onChange={(value) => {
filter.metricId = value;
setMetricOptions([...metricOptions]);
}}
/>
<DeleteOutlined
onClick={() => {
setMetricOptions(metricOptions.filter((item) => item.id !== filter.id));
}}
/>
</div>
);
})}
<Button
onClick={() => {
setMetricOptions([
...metricOptions,
{ id: uuid(), metricId: undefined, modelId: undefined },
]);
}}
>
<PlusOutlined />
</Button>
</div>
</FormItem>
</>
)}
{toolType === AgentToolTypeEnum.PLUGIN && ( {toolType === AgentToolTypeEnum.PLUGIN && (
<FormItem name="plugins" label="插件"> <FormItem name="plugins" label="插件">
<Select <Select

View File

@@ -19,7 +19,7 @@ export function deleteAgent(id: number) {
} }
export function getModelList() { export function getModelList() {
return request<Result<ModelType[]>>('/api/chat/conf/viewList', { return request<Result<ModelType[]>>('/api/chat/conf/getDomainViewTree', {
method: 'GET', method: 'GET',
}); });
} }

View File

@@ -8,7 +8,6 @@ export enum AgentToolTypeEnum {
NL2SQL_RULE = 'NL2SQL_RULE', NL2SQL_RULE = 'NL2SQL_RULE',
NL2SQL_LLM = 'NL2SQL_LLM', NL2SQL_LLM = 'NL2SQL_LLM',
PLUGIN = 'PLUGIN', PLUGIN = 'PLUGIN',
ANALYTICS = 'ANALYTICS',
} }
export const AGENT_TOOL_TYPE_LIST = [ export const AGENT_TOOL_TYPE_LIST = [
@@ -76,6 +75,7 @@ export type ModelType = {
parentId: number; parentId: number;
name: string; name: string;
bizName: string; bizName: string;
type: 'DOMAIN' | 'VIEW';
}; };
export type MetricType = { export type MetricType = {

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Modal, Select, Form, Input, InputNumber, message, Button, Radio } from 'antd'; import { Modal, Select, Form, Input, InputNumber, message, Button, Radio, TreeSelect } from 'antd';
import { getDimensionList, getModelList, savePlugin } from './service'; import { getDimensionList, getModelList, savePlugin } from './service';
import { import {
DimensionType, DimensionType,
@@ -10,7 +10,7 @@ import {
FunctionParamFormItemType, FunctionParamFormItemType,
PluginTypeEnum, PluginTypeEnum,
} from './type'; } from './type';
import { getLeafList, uuid } from '@/utils/utils'; import { getLeafList, traverseTree, uuid } from '@/utils/utils';
import styles from './style.less'; import styles from './style.less';
import { PLUGIN_TYPE_MAP } from './constants'; import { PLUGIN_TYPE_MAP } from './constants';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
@@ -38,7 +38,13 @@ const DetailModal: React.FC<Props> = ({ detail, onSubmit, onCancel }) => {
const initModelList = async () => { const initModelList = async () => {
const res = await getModelList(); const res = await getModelList();
setModelList([{ id: -1, name: '默认' }, ...getLeafList(res.data)]); const treeData = traverseTree(res.data, (node: any) => {
node.title = node.name;
node.value = node.type === 'DOMAIN' ? `DOMAIN_${node.id}` : node.id;
node.checkable =
node.type === 'VIEW' || (node.type === 'DOMAIN' && node.children?.length > 0);
});
setModelList([{ title: '默认', value: -1, type: 'VIEW' }, ...treeData]);
}; };
useEffect(() => { useEffect(() => {
@@ -181,18 +187,12 @@ const DetailModal: React.FC<Props> = ({ detail, onSubmit, onCancel }) => {
onCancel={onCancel} onCancel={onCancel}
> >
<Form {...layout} form={form} style={{ maxWidth: 820 }}> <Form {...layout} form={form} style={{ maxWidth: 820 }}>
<FormItem name="modelList" label="主题域"> <FormItem name="viewList" label="视图">
<Select <TreeSelect
placeholder="请选择主题域" treeData={modelList}
options={modelList.map((model) => ({ placeholder="请选择视图"
label: model.name, multiple
value: model.id, treeCheckable
}))}
showSearch
filterOption={(input, option) =>
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
}
mode="multiple"
allowClear allowClear
/> />
</FormItem> </FormItem>

View File

@@ -3,11 +3,11 @@ import { PlusOutlined } from '@ant-design/icons';
import { Button, Input, message, Popconfirm, Select, Table, Tag } from 'antd'; import { Button, Input, message, Popconfirm, Select, Table, Tag } from 'antd';
import moment from 'moment'; import moment from 'moment';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { PARSE_MODE_MAP, PLUGIN_TYPE_MAP } from './constants'; import { PLUGIN_TYPE_MAP } from './constants';
import DetailModal from './DetailModal'; import DetailModal from './DetailModal';
import { deletePlugin, getModelList, getPluginList } from './service'; import { deletePlugin, getModelList, getPluginList } from './service';
import styles from './style.less'; import styles from './style.less';
import { ModelType, ParseModeEnum, PluginType, PluginTypeEnum } from './type'; import { ModelType, PluginType, PluginTypeEnum } from './type';
const { Search } = Input; const { Search } = Input;
@@ -57,9 +57,9 @@ const PluginManage = () => {
key: 'name', key: 'name',
}, },
{ {
title: '主题域', title: '视图',
dataIndex: 'modelList', dataIndex: 'viewList',
key: 'modelList', key: 'viewList',
width: 200, width: 200,
render: (value: number[]) => { render: (value: number[]) => {
if (value?.includes(-1)) { if (value?.includes(-1)) {
@@ -67,7 +67,7 @@ const PluginManage = () => {
} }
return value ? ( return value ? (
<div className={styles.modelColumn}> <div className={styles.modelColumn}>
{value.map((id, index) => { {value.map((id) => {
const name = modelList.find((model) => model.id === +id)?.name; const name = modelList.find((model) => model.id === +id)?.name;
return name ? <Tag key={id}>{name}</Tag> : null; return name ? <Tag key={id}>{name}</Tag> : null;
})} })}

View File

@@ -22,7 +22,7 @@ export function deletePlugin(id: number) {
} }
export function getModelList() { export function getModelList() {
return request<Result<ModelType[]>>('/api/chat/conf/viewList', { return request<Result<ModelType[]>>('/api/chat/conf/getDomainViewTree', {
method: 'GET', method: 'GET',
}); });
} }

View File

@@ -371,7 +371,7 @@ function getLeafNodes(treeNodes: any[]): any[] {
return leafNodes; return leafNodes;
} }
function buildTree(nodes: any[]): any[] { export function buildTree(nodes: any[]): any[] {
const map: Record<number, any> = {}; const map: Record<number, any> = {};
const roots: any[] = []; const roots: any[] = [];
@@ -400,6 +400,16 @@ export function getLeafList(flatNodes: any[]): any[] {
return leafNodes; return leafNodes;
} }
export function traverseTree(treeData: any[], callback: (node: any) => void) {
treeData.forEach((node) => {
callback(node);
if (node.children?.length > 0) {
traverseTree(node.children, callback);
}
});
return treeData;
}
export function traverseRoutes(routes, env: string, result: any[] = []) { export function traverseRoutes(routes, env: string, result: any[] = []) {
if (!Array.isArray(routes)) { if (!Array.isArray(routes)) {
return result; return result;