feat: 工作流适配数据中枢查询节点
- 新增查询数据与写入数据节点并移除旧数据中心节点入口 - 将查询数据节点切换为连接服务加 SQL 的执行模型 - 同步更新工作流校验、提示词上下文与设计器交互
This commit is contained in:
@@ -20,7 +20,6 @@ import WorkflowForm from '#/views/ai/workflow/components/WorkflowForm.vue';
|
||||
import WorkflowSteps from '#/views/ai/workflow/components/WorkflowSteps.vue';
|
||||
|
||||
import {getCustomNode} from './customNode/index';
|
||||
import nodeNames from './customNode/nodeNames';
|
||||
|
||||
import '@tinyflow-ai/vue/dist/index.css';
|
||||
|
||||
@@ -59,17 +58,35 @@ const codeEngineList = ref<any[]>([
|
||||
available: true,
|
||||
},
|
||||
]);
|
||||
|
||||
function escapeHtmlAttr(value?: string) {
|
||||
return String(value || '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>');
|
||||
}
|
||||
|
||||
function buildModelIconMarkup(icon?: string, providerType?: string) {
|
||||
const normalized = String(icon || '').trim();
|
||||
if (normalized) {
|
||||
if (normalized.startsWith('<svg') || normalized.startsWith('<img')) {
|
||||
return normalized;
|
||||
}
|
||||
return `<img src="${escapeHtmlAttr(normalized)}" alt="" style="width:100%; height:100%; object-fit:contain;" />`;
|
||||
}
|
||||
if (!providerType) {
|
||||
return undefined;
|
||||
}
|
||||
return getIconByValue(providerType) || undefined;
|
||||
}
|
||||
|
||||
const provider = computed(() => ({
|
||||
llm: () => llmList.value.map((item: any) => {
|
||||
let iconStr = undefined;
|
||||
if (item.modelProvider?.icon) {
|
||||
iconStr = `<img src="${item.modelProvider.icon}" style="width:100%; height:100%; object-fit:contain;" />`;
|
||||
} else if (item.modelProvider?.providerType) {
|
||||
const svgStr = getIconByValue(item.modelProvider.providerType);
|
||||
if (svgStr) {
|
||||
iconStr = svgStr;
|
||||
}
|
||||
}
|
||||
const iconStr = buildModelIconMarkup(
|
||||
item.modelProvider?.icon,
|
||||
item.modelProvider?.providerType,
|
||||
);
|
||||
|
||||
// Extract brand and model name directly from the title if it contains '/'
|
||||
let displayTitle = item.title || '';
|
||||
@@ -330,15 +347,15 @@ async function runCheck(stage: WorkflowCheckStage, silentPass: boolean = false)
|
||||
stage,
|
||||
});
|
||||
checkResult.value = res.data;
|
||||
const issues = Array.isArray(res.data?.issues) ? res.data.issues : [];
|
||||
checkIssuesVisible.value = issues.length > 0;
|
||||
if (!res.data?.passed) {
|
||||
checkIssuesVisible.value = true;
|
||||
ElMessage.error($t('aiWorkflow.checkFailed'));
|
||||
return false;
|
||||
}
|
||||
checkIssuesVisible.value = false;
|
||||
focusedIssueKey.value = '';
|
||||
issueFocusActive.value = false;
|
||||
if (!silentPass) {
|
||||
if (!silentPass && issues.length === 0) {
|
||||
ElMessage.success($t('aiWorkflow.checkPassed'));
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,573 @@
|
||||
import type {
|
||||
DatasetRefPayload,
|
||||
ManagedDatasetOption,
|
||||
ManagedDatasetSourceOption,
|
||||
} from './datasetOptions';
|
||||
import huaweiIcon from '#/assets/datacenter/huawei-icon.svg';
|
||||
import mysqlIcon from '#/assets/datacenter/mysql-icon.svg';
|
||||
import postgresqlIcon from '#/assets/datacenter/postgresql-icon.svg';
|
||||
import {
|
||||
groupManagedDatasetOptions,
|
||||
loadManagedDatasetOptions,
|
||||
} from './datasetOptions';
|
||||
|
||||
const SOURCE_LOGO_MAP: Record<string, string> = {
|
||||
EXCEL: 'excel',
|
||||
EXCEL_MATERIALIZED: 'excel',
|
||||
GAUSSDB_NATIVE: 'gaussdb',
|
||||
GBASE_8A: 'gbase',
|
||||
GBASE_8S: 'gbase',
|
||||
MYSQL: 'mysql',
|
||||
ORACLE: 'oracle',
|
||||
POSTGRESQL: 'postgresql',
|
||||
PROJECT_MYSQL: 'mysql',
|
||||
};
|
||||
|
||||
type NodeLike = {
|
||||
id: string;
|
||||
data?: Record<string, any>;
|
||||
};
|
||||
|
||||
type UpdateNodeData = (
|
||||
nodeId: string,
|
||||
data: Record<string, any> | ((node: Record<string, any>) => Record<string, any>),
|
||||
) => void;
|
||||
|
||||
type FlowInstance = {
|
||||
updateNodeData: UpdateNodeData;
|
||||
};
|
||||
|
||||
type RenderContext = FlowInstance | undefined;
|
||||
|
||||
type RendererState = {
|
||||
pickerOpen: boolean;
|
||||
loadingOptions: boolean;
|
||||
optionsLoaded: boolean;
|
||||
options: ManagedDatasetOption[];
|
||||
sources: ManagedDatasetSourceOption[];
|
||||
tableSearchText: string;
|
||||
updateNodeData?: UpdateNodeData;
|
||||
};
|
||||
|
||||
function getState(parent: HTMLElement): RendererState {
|
||||
const holder = parent as HTMLElement & { __datasetState?: RendererState };
|
||||
if (!holder.__datasetState) {
|
||||
holder.__datasetState = {
|
||||
pickerOpen: false,
|
||||
loadingOptions: false,
|
||||
optionsLoaded: false,
|
||||
options: [],
|
||||
sources: [],
|
||||
tableSearchText: '',
|
||||
};
|
||||
}
|
||||
return holder.__datasetState;
|
||||
}
|
||||
|
||||
function getUpdateNodeData(parent: HTMLElement, flowInstance?: RenderContext) {
|
||||
const state = getState(parent);
|
||||
if (flowInstance?.updateNodeData) {
|
||||
state.updateNodeData = flowInstance.updateNodeData.bind(flowInstance);
|
||||
}
|
||||
return state.updateNodeData;
|
||||
}
|
||||
|
||||
function getDatasetKey(datasetRef?: DatasetRefPayload | null) {
|
||||
return datasetRef?.tableId == null ? '' : String(datasetRef.tableId);
|
||||
}
|
||||
|
||||
function getSourceKey(datasetRef?: DatasetRefPayload | null) {
|
||||
return datasetRef?.sourceId == null ? '' : String(datasetRef.sourceId);
|
||||
}
|
||||
|
||||
function createSourceOnlyDatasetRef(source: ManagedDatasetSourceOption): DatasetRefPayload {
|
||||
return {
|
||||
sourceId: source.sourceId,
|
||||
catalogId: null,
|
||||
catalogName: '',
|
||||
tableId: null,
|
||||
tableName: '',
|
||||
versionId: null,
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtml(value?: string | number | null) {
|
||||
return String(value ?? '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function getSourceLogoType(sourceType?: string) {
|
||||
return SOURCE_LOGO_MAP[sourceType || ''] || 'default';
|
||||
}
|
||||
|
||||
function buildSourceLogo(sourceType?: string) {
|
||||
const logoType = getSourceLogoType(sourceType);
|
||||
if (logoType === 'mysql') {
|
||||
return `<img class="dataset-node-brand-image" src="${mysqlIcon}" alt="" />`;
|
||||
}
|
||||
if (logoType === 'postgresql') {
|
||||
return `<img class="dataset-node-brand-image" src="${postgresqlIcon}" alt="" />`;
|
||||
}
|
||||
if (logoType === 'gaussdb') {
|
||||
return `<img class="dataset-node-brand-image" src="${huaweiIcon}" alt="" />`;
|
||||
}
|
||||
if (logoType === 'oracle') {
|
||||
return `
|
||||
<svg class="dataset-node-brand-svg" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect x="4.6" y="7.2" width="14.8" height="9.6" rx="4.8" fill="#ea4335" />
|
||||
<rect x="7.1" y="9.35" width="9.8" height="5.3" rx="2.65" fill="white" />
|
||||
<rect x="8.4" y="10.65" width="7.2" height="2.7" rx="1.35" fill="#ea4335" />
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
if (logoType === 'gbase') {
|
||||
return `
|
||||
<svg class="dataset-node-brand-svg" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="8" fill="#10b981" />
|
||||
<path
|
||||
d="M15.85 9.2C15.1 8.02 13.8 7.3 12.38 7.3C9.94 7.3 8.1 9.14 8.1 11.98C8.1 14.87 10.05 16.7 12.56 16.7C13.88 16.7 15.03 16.18 15.88 15.15V12.55H12.45"
|
||||
stroke="white"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.7"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
if (logoType === 'excel') {
|
||||
return `
|
||||
<svg class="dataset-node-brand-svg" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect x="4.4" y="4.2" width="15.2" height="15.6" rx="3.2" fill="#16a34a" />
|
||||
<path
|
||||
d="M9 9L11.1 12L9 15M15 9L12.9 12L15 15"
|
||||
stroke="white"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.65"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
return `
|
||||
<svg class="dataset-node-brand-svg" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<ellipse cx="12" cy="6.5" rx="5.75" ry="2.5" fill="#94a3b8" />
|
||||
<path d="M6.25 6.5V12.2C6.25 13.58 8.82 14.7 12 14.7C15.18 14.7 17.75 13.58 17.75 12.2V6.5" fill="#cbd5e1" />
|
||||
<path d="M6.25 12.2V17.35C6.25 18.72 8.82 19.85 12 19.85C15.18 19.85 17.75 18.72 17.75 17.35V12.2" fill="#e2e8f0" />
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
|
||||
function buildStyles() {
|
||||
return `
|
||||
<style>
|
||||
.dataset-node-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.dataset-node-label {
|
||||
font-size: 12px;
|
||||
color: var(--tf-text-muted);
|
||||
}
|
||||
.dataset-node-picker-anchor {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.dataset-node-picker-trigger {
|
||||
width: 100%;
|
||||
min-height: 38px;
|
||||
border: 1px solid var(--tf-border-color);
|
||||
border-radius: 8px;
|
||||
padding: 7px 10px 7px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
background: var(--tf-bg-surface);
|
||||
color: var(--tf-text-primary);
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.dataset-node-picker-trigger:hover {
|
||||
border-color: var(--tf-border-color-strong);
|
||||
}
|
||||
.dataset-node-picker-trigger.is-open {
|
||||
border-color: var(--tf-color-primary);
|
||||
}
|
||||
.dataset-node-picker-value {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.dataset-node-picker-placeholder {
|
||||
font-size: 12px;
|
||||
color: var(--tf-text-secondary);
|
||||
}
|
||||
.dataset-node-picker-text {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.dataset-node-picker-main {
|
||||
min-width: 0;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.dataset-node-picker-arrow {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--tf-text-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dataset-node-picker {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
border: 1px solid var(--tf-border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--tf-bg-surface);
|
||||
box-shadow: var(--tf-shadow-medium);
|
||||
overflow: hidden;
|
||||
z-index: 40;
|
||||
}
|
||||
.dataset-node-picker-body,
|
||||
.dataset-node-list {
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
max-height: 240px;
|
||||
overflow: auto;
|
||||
}
|
||||
.dataset-node-picker-item {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 6px;
|
||||
padding: 9px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
}
|
||||
.dataset-node-picker-item:hover {
|
||||
background: var(--tf-bg-muted);
|
||||
}
|
||||
.dataset-node-picker-item.is-active {
|
||||
background: var(--tf-bg-muted);
|
||||
color: var(--tf-color-primary);
|
||||
}
|
||||
.dataset-node-picker-item-inner {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.dataset-node-picker-item-main {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.dataset-node-picker-item-title {
|
||||
font-size: 12px;
|
||||
color: inherit;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.dataset-node-picker-item-meta {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
color: var(--tf-text-secondary);
|
||||
}
|
||||
.dataset-node-empty {
|
||||
font-size: 12px;
|
||||
color: var(--tf-text-secondary);
|
||||
}
|
||||
.dataset-node-brand {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dataset-node-brand-image,
|
||||
.dataset-node-brand-svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
.dataset-node-list-box {
|
||||
border: 1px solid var(--tf-border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--tf-bg-surface);
|
||||
overflow: hidden;
|
||||
}
|
||||
.dataset-node-search {
|
||||
width: 100%;
|
||||
border: 1px solid var(--tf-border-color);
|
||||
border-radius: 8px;
|
||||
padding: 7px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: var(--tf-text-primary);
|
||||
background: var(--tf-bg-surface);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.dataset-node-search:focus {
|
||||
outline: none;
|
||||
border-color: var(--tf-color-primary);
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
function filterTableOptions(options: ManagedDatasetOption[], searchText: string) {
|
||||
const keyword = searchText.trim().toLowerCase();
|
||||
if (!keyword) {
|
||||
return options;
|
||||
}
|
||||
return options.filter((option) => option.keywords.includes(keyword));
|
||||
}
|
||||
|
||||
function ensureOptionsLoaded(
|
||||
state: RendererState,
|
||||
parent: HTMLElement,
|
||||
node: NodeLike,
|
||||
flowInstance: RenderContext,
|
||||
rerender: (parent: HTMLElement, node: NodeLike, flowInstance?: RenderContext) => void,
|
||||
) {
|
||||
if (state.loadingOptions || state.optionsLoaded) {
|
||||
return;
|
||||
}
|
||||
state.loadingOptions = true;
|
||||
loadManagedDatasetOptions()
|
||||
.then((options) => {
|
||||
state.options = options;
|
||||
state.sources = groupManagedDatasetOptions(options);
|
||||
state.optionsLoaded = true;
|
||||
})
|
||||
.finally(() => {
|
||||
state.loadingOptions = false;
|
||||
rerender(parent, node, flowInstance);
|
||||
});
|
||||
}
|
||||
|
||||
function buildPickerListItem(
|
||||
title: string,
|
||||
meta: string,
|
||||
active: boolean,
|
||||
action: string,
|
||||
extraAttr: string,
|
||||
iconHtml: string = '',
|
||||
) {
|
||||
return `
|
||||
<button
|
||||
type="button"
|
||||
class="dataset-node-picker-item nopan nodrag nowheel ${active ? 'is-active' : ''}"
|
||||
data-action="${action}"
|
||||
${extraAttr}
|
||||
>
|
||||
<span class="dataset-node-picker-item-inner">
|
||||
${iconHtml ? `<span class="dataset-node-brand">${iconHtml}</span>` : ''}
|
||||
<span class="dataset-node-picker-item-main">
|
||||
<span class="dataset-node-picker-item-title">${escapeHtml(title)}</span>
|
||||
${meta ? `<span class="dataset-node-picker-item-meta">${escapeHtml(meta)}</span>` : ''}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
function buildSourceList(options: ManagedDatasetSourceOption[], activeKey: string, emptyText: string) {
|
||||
if (!options.length) {
|
||||
return `<div class="dataset-node-empty">${emptyText}</div>`;
|
||||
}
|
||||
return options
|
||||
.map((option) =>
|
||||
buildPickerListItem(
|
||||
option.sourceName,
|
||||
`${option.tables.length} 张表`,
|
||||
activeKey === String(option.sourceId),
|
||||
'select-source',
|
||||
`data-source-id="${escapeHtml(option.sourceId)}"`,
|
||||
buildSourceLogo(option.sourceType),
|
||||
),
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
|
||||
function buildTableList(options: ManagedDatasetOption[], activeKey: string, emptyText: string) {
|
||||
if (!options.length) {
|
||||
return `<div class="dataset-node-empty">${emptyText}</div>`;
|
||||
}
|
||||
return options
|
||||
.map((option) =>
|
||||
buildPickerListItem(
|
||||
option.tableName,
|
||||
`${option.sourceName} / ${option.catalogName}`,
|
||||
activeKey === String(option.datasetRef.tableId),
|
||||
'select-dataset',
|
||||
`data-table-id="${escapeHtml(option.datasetRef.tableId)}"`,
|
||||
buildSourceLogo(option.sourceType),
|
||||
),
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
|
||||
function buildSearchSummary(currentSource?: ManagedDatasetSourceOption) {
|
||||
if (!currentSource) {
|
||||
return '<span class="dataset-node-picker-placeholder">请选择连接服务</span>';
|
||||
}
|
||||
return `
|
||||
<span class="dataset-node-brand">${buildSourceLogo(currentSource.sourceType)}</span>
|
||||
<span class="dataset-node-picker-text">
|
||||
<span class="dataset-node-picker-main">${escapeHtml(currentSource.sourceName)}</span>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
|
||||
function bindInteractiveElements(parent: HTMLElement) {
|
||||
parent.querySelectorAll<HTMLElement>('button, input, select, textarea').forEach((element) => {
|
||||
element.onpointerdown = (event) => event.stopPropagation();
|
||||
element.onmousedown = (event) => event.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
export function rerenderSearchNode(parent: HTMLElement, node: NodeLike, flowInstance?: RenderContext) {
|
||||
const state = getState(parent);
|
||||
const updateNodeData = getUpdateNodeData(parent, flowInstance);
|
||||
ensureOptionsLoaded(state, parent, node, flowInstance, rerenderSearchNode);
|
||||
|
||||
const datasetRef = (node.data?.datasetRef || null) as DatasetRefPayload | null;
|
||||
const sourceKey = getSourceKey(datasetRef);
|
||||
const currentSource = state.sources.find((item) => String(item.sourceId) === sourceKey);
|
||||
|
||||
parent.innerHTML = `
|
||||
${buildStyles()}
|
||||
<div class="dataset-node-section">
|
||||
<div class="dataset-node-label">连接服务</div>
|
||||
<div class="dataset-node-picker-anchor">
|
||||
<button type="button" class="dataset-node-picker-trigger nopan nodrag nowheel ${state.pickerOpen ? 'is-open' : ''}" data-action="toggle-picker">
|
||||
<span class="dataset-node-picker-value">${buildSearchSummary(currentSource)}</span>
|
||||
<span class="dataset-node-picker-arrow">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M11.9999 13.1714L16.9497 8.22168L18.3639 9.63589L11.9999 15.9999L5.63599 9.63589L7.0502 8.22168L11.9999 13.1714Z"></path></svg>
|
||||
</span>
|
||||
</button>
|
||||
${state.pickerOpen ? `
|
||||
<div class="dataset-node-picker">
|
||||
<div class="dataset-node-picker-body">
|
||||
${state.loadingOptions ? '<div class="dataset-node-empty">正在加载连接服务...</div>' : buildSourceList(state.sources, sourceKey, '暂无可用连接服务')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
bindInteractiveElements(parent);
|
||||
|
||||
parent.querySelector<HTMLElement>('[data-action="toggle-picker"]')?.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
state.pickerOpen = !state.pickerOpen;
|
||||
rerenderSearchNode(parent, node, flowInstance);
|
||||
});
|
||||
|
||||
parent.querySelectorAll<HTMLElement>('[data-action="select-source"]').forEach((element) => {
|
||||
element.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const sourceId = element.dataset.sourceId;
|
||||
const source = state.sources.find((item) => String(item.sourceId) === String(sourceId));
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
state.pickerOpen = false;
|
||||
const nextDatasetRef = createSourceOnlyDatasetRef(source);
|
||||
node.data = {
|
||||
...(node.data || {}),
|
||||
datasetRef: nextDatasetRef,
|
||||
sourceName: source.sourceName,
|
||||
sourceType: source.sourceType,
|
||||
};
|
||||
updateNodeData?.(node.id, {
|
||||
datasetRef: nextDatasetRef,
|
||||
sourceName: source.sourceName,
|
||||
sourceType: source.sourceType,
|
||||
});
|
||||
rerenderSearchNode(parent, node, flowInstance);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function rerenderSaveNode(parent: HTMLElement, node: NodeLike, flowInstance?: RenderContext) {
|
||||
const state = getState(parent);
|
||||
const updateNodeData = getUpdateNodeData(parent, flowInstance);
|
||||
ensureOptionsLoaded(state, parent, node, flowInstance, rerenderSaveNode);
|
||||
|
||||
const datasetRef = (node.data?.datasetRef || null) as DatasetRefPayload | null;
|
||||
const activeKey = getDatasetKey(datasetRef);
|
||||
const currentOption = state.options.find((item) => String(item.datasetRef.tableId) === activeKey);
|
||||
const filtered = filterTableOptions(state.options, state.tableSearchText);
|
||||
|
||||
parent.innerHTML = `
|
||||
${buildStyles()}
|
||||
<div class="dataset-node-section">
|
||||
<div class="dataset-node-label">已接入表</div>
|
||||
<input class="dataset-node-search nopan nodrag nowheel" data-role="table-search" placeholder="搜索连接 / 库 / 表" value="${escapeHtml(state.tableSearchText)}" />
|
||||
<div class="dataset-node-list-box">
|
||||
<div class="dataset-node-list">
|
||||
${state.loadingOptions ? '<div class="dataset-node-empty">正在加载已接入表...</div>' : buildTableList(filtered, activeKey, '没有匹配的已接入表')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
bindInteractiveElements(parent);
|
||||
|
||||
parent.querySelector<HTMLInputElement>('[data-role="table-search"]')?.addEventListener('input', (event) => {
|
||||
event.stopPropagation();
|
||||
state.tableSearchText = (event.currentTarget as HTMLInputElement).value || '';
|
||||
rerenderSaveNode(parent, node, flowInstance);
|
||||
});
|
||||
|
||||
parent.querySelectorAll<HTMLElement>('[data-action="select-dataset"]').forEach((element) => {
|
||||
element.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const tableId = element.dataset.tableId;
|
||||
const option = state.options.find((item) => String(item.datasetRef.tableId) === String(tableId));
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
node.data = {
|
||||
...(node.data || {}),
|
||||
datasetRef: option.datasetRef,
|
||||
sourceName: option.sourceName,
|
||||
sourceType: option.sourceType,
|
||||
};
|
||||
updateNodeData?.(node.id, {
|
||||
datasetRef: option.datasetRef,
|
||||
sourceName: option.sourceName,
|
||||
sourceType: option.sourceType,
|
||||
});
|
||||
rerenderSaveNode(parent, node, flowInstance);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { api } from '#/api/request';
|
||||
|
||||
export interface DatasetRefPayload {
|
||||
sourceId: number | string | null;
|
||||
catalogId?: number | string | null;
|
||||
catalogName?: string;
|
||||
tableId: number | string | null;
|
||||
tableName: string;
|
||||
versionId?: number | string | null;
|
||||
}
|
||||
|
||||
export interface ManagedDatasetFieldOption {
|
||||
fieldName: string;
|
||||
fieldDesc?: string;
|
||||
fieldType?: string;
|
||||
}
|
||||
|
||||
export interface ManagedDatasetSchema {
|
||||
tableName?: string;
|
||||
tableDesc?: string;
|
||||
fields: ManagedDatasetFieldOption[];
|
||||
}
|
||||
|
||||
export interface ManagedDatasetOption {
|
||||
label: string;
|
||||
value: number | string;
|
||||
keywords: string;
|
||||
sourceName: string;
|
||||
sourceType?: string;
|
||||
catalogName: string;
|
||||
tableName: string;
|
||||
datasetRef: DatasetRefPayload;
|
||||
}
|
||||
|
||||
export interface ManagedDatasetSourceOption {
|
||||
sourceId: number | string;
|
||||
sourceName: string;
|
||||
sourceType?: string;
|
||||
label: string;
|
||||
keywords: string;
|
||||
tables: ManagedDatasetOption[];
|
||||
}
|
||||
|
||||
const SOURCE_MISSING_MESSAGE = '连接不存在';
|
||||
const SOURCE_UNAVAILABLE_MESSAGE = '当前连接不可用,请检查连接配置后重试';
|
||||
|
||||
function shouldSkipSourceError(error: any) {
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const message = String(responseData?.message ?? error?.message ?? '');
|
||||
|
||||
return (
|
||||
message.includes(SOURCE_MISSING_MESSAGE) ||
|
||||
message.includes(SOURCE_UNAVAILABLE_MESSAGE)
|
||||
);
|
||||
}
|
||||
|
||||
function dedupeManagedDatasetOptions(options: ManagedDatasetOption[]) {
|
||||
const uniqueOptions = new Map<string, ManagedDatasetOption>();
|
||||
for (const option of options || []) {
|
||||
const key = option.datasetRef?.tableId != null
|
||||
? String(option.datasetRef.tableId)
|
||||
: [
|
||||
option.datasetRef?.sourceId ?? '',
|
||||
option.datasetRef?.catalogId ?? '',
|
||||
option.tableName ?? '',
|
||||
].join(':');
|
||||
if (!uniqueOptions.has(key)) {
|
||||
uniqueOptions.set(key, option);
|
||||
}
|
||||
}
|
||||
return Array.from(uniqueOptions.values());
|
||||
}
|
||||
|
||||
export async function loadManagedDatasetOptions(): Promise<ManagedDatasetOption[]> {
|
||||
const sourceRes = await api.get('/api/v1/datacenterSource/page', {
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 200,
|
||||
},
|
||||
});
|
||||
const sources = sourceRes.data?.records || [];
|
||||
const options: ManagedDatasetOption[] = [];
|
||||
for (const source of sources) {
|
||||
try {
|
||||
const catalogRes = await api.get('/api/v1/datacenterSource/catalogs', {
|
||||
params: {
|
||||
sourceId: source.id,
|
||||
},
|
||||
});
|
||||
const catalogs = catalogRes.data || [];
|
||||
for (const catalog of catalogs) {
|
||||
const tableRes = await api.get('/api/v1/datacenterDataset/managedTables', {
|
||||
params: {
|
||||
sourceId: source.id,
|
||||
catalogId: catalog.id,
|
||||
},
|
||||
});
|
||||
const tables = tableRes.data || [];
|
||||
for (const table of tables) {
|
||||
const label = `${source.sourceName} / ${catalog.catalogName} / ${table.tableName}`;
|
||||
options.push({
|
||||
label,
|
||||
value: table.id,
|
||||
keywords: `${source.sourceName} ${catalog.catalogName} ${table.tableName}`.toLowerCase(),
|
||||
sourceName: source.sourceName,
|
||||
sourceType: source.sourceType,
|
||||
catalogName: catalog.catalogName,
|
||||
tableName: table.tableName,
|
||||
datasetRef: {
|
||||
sourceId: source.id,
|
||||
catalogId: catalog.id,
|
||||
catalogName: catalog.catalogName,
|
||||
tableId: table.id,
|
||||
tableName: table.tableName,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (shouldSkipSourceError(error)) {
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return dedupeManagedDatasetOptions(options);
|
||||
}
|
||||
|
||||
export function groupManagedDatasetOptions(
|
||||
options: ManagedDatasetOption[],
|
||||
): ManagedDatasetSourceOption[] {
|
||||
const grouped = new Map<number | string, ManagedDatasetSourceOption>();
|
||||
for (const option of dedupeManagedDatasetOptions(options || [])) {
|
||||
const sourceId = option.datasetRef?.sourceId;
|
||||
if (sourceId == null) {
|
||||
continue;
|
||||
}
|
||||
if (!grouped.has(sourceId)) {
|
||||
grouped.set(sourceId, {
|
||||
sourceId,
|
||||
sourceName: option.sourceName,
|
||||
sourceType: option.sourceType,
|
||||
label: option.sourceName,
|
||||
keywords: option.sourceName.toLowerCase(),
|
||||
tables: [],
|
||||
});
|
||||
}
|
||||
grouped.get(sourceId)!.tables.push(option);
|
||||
}
|
||||
return Array.from(grouped.values()).map((item) => ({
|
||||
...item,
|
||||
keywords: `${item.sourceName} ${item.tables
|
||||
.map((table) => `${table.catalogName} ${table.tableName}`)
|
||||
.join(' ')}`.toLowerCase(),
|
||||
tables: item.tables.sort((a, b) => a.label.localeCompare(b.label)),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function loadManagedDatasetSchema(
|
||||
datasetRef?: DatasetRefPayload | null,
|
||||
): Promise<ManagedDatasetSchema> {
|
||||
if (!datasetRef?.tableId) {
|
||||
return {
|
||||
tableName: datasetRef?.tableName,
|
||||
fields: [],
|
||||
};
|
||||
}
|
||||
const res = await api.get('/api/v1/datacenterDataset/schema', {
|
||||
params: datasetRef,
|
||||
});
|
||||
const data = res.data || {};
|
||||
const fields = Array.isArray(data.fields)
|
||||
? data.fields.map((field: any) => ({
|
||||
fieldName: field.fieldName,
|
||||
fieldDesc: field.fieldDesc,
|
||||
fieldType: field.jdbcType || field.fieldType,
|
||||
}))
|
||||
: [];
|
||||
return {
|
||||
tableName: data.table?.tableName || datasetRef.tableName,
|
||||
tableDesc: data.table?.tableDesc,
|
||||
fields,
|
||||
};
|
||||
}
|
||||
@@ -3,9 +3,8 @@ import downloadNode from './downloadNode';
|
||||
import makeFileNode from './makeFileNode';
|
||||
import nodeNames from './nodeNames';
|
||||
import { PluginNode } from './pluginNode';
|
||||
import { SaveToDatacenterNode } from './saveToDatacenter';
|
||||
import { SearchDatacenterNode } from './searchDatacenter';
|
||||
import sqlNode from './sqlNode';
|
||||
import { SaveDatasetNode } from './saveDataset';
|
||||
import { SearchDatasetNode } from './searchDataset';
|
||||
import { WorkflowNode } from './workflowNode';
|
||||
|
||||
export interface CustomNodeOptions {
|
||||
@@ -14,16 +13,15 @@ export interface CustomNodeOptions {
|
||||
export const getCustomNode = async (options: CustomNodeOptions) => {
|
||||
const pluginNode = PluginNode({ onChosen: options.handleChosen });
|
||||
const workflowNode = WorkflowNode({ onChosen: options.handleChosen });
|
||||
const searchDatacenterNode = await SearchDatacenterNode();
|
||||
const saveToDatacenterNode = await SaveToDatacenterNode();
|
||||
const searchDatasetNode = await SearchDatasetNode();
|
||||
const saveDatasetNode = await SaveDatasetNode();
|
||||
return {
|
||||
...docNode,
|
||||
...makeFileNode,
|
||||
...downloadNode,
|
||||
...sqlNode,
|
||||
[nodeNames.pluginNode]: pluginNode,
|
||||
[nodeNames.workflowNode]: workflowNode,
|
||||
[nodeNames.searchDatacenterNode]: searchDatacenterNode,
|
||||
[nodeNames.saveToDatacenterNode]: saveToDatacenterNode,
|
||||
[nodeNames.searchDatasetNode]: searchDatasetNode,
|
||||
[nodeNames.saveDatasetNode]: saveDatasetNode,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,9 +2,8 @@ export default {
|
||||
documentNode: 'document-node',
|
||||
makeFileNode: 'make-file',
|
||||
downloadNode: 'download-node',
|
||||
sqlNode: 'sql-node',
|
||||
pluginNode: 'plugin-node',
|
||||
workflowNode: 'workflow-node',
|
||||
searchDatacenterNode: 'search-datacenter-node',
|
||||
saveToDatacenterNode: 'save-to-datacenter-node',
|
||||
searchDatasetNode: 'search-dataset-node',
|
||||
saveDatasetNode: 'save-dataset-node',
|
||||
};
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { rerenderSaveNode } from './datasetNodeRenderer';
|
||||
|
||||
export const SaveDatasetNode = async () => {
|
||||
return {
|
||||
title: $t('aiWorkflow.saveDataset'),
|
||||
group: 'base',
|
||||
description: $t('aiWorkflow.descriptions.saveDataset'),
|
||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M5 3H19C20.1046 3 21 3.89543 21 5V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V5C3 3.89543 3.89543 3 5 3ZM7 5V9H17V5H7ZM7 13V19H17V13H7Z"></path></svg>',
|
||||
sortNo: 812,
|
||||
parametersAddEnable: false,
|
||||
outputDefsAddEnable: false,
|
||||
parameters: [
|
||||
{
|
||||
name: 'saveList',
|
||||
title: $t('aiWorkflow.dataToBeSaved'),
|
||||
dataType: 'Array',
|
||||
dataTypeDisabled: true,
|
||||
required: true,
|
||||
parametersAddEnable: false,
|
||||
description: $t('aiWorkflow.descriptions.dataToBeSaved'),
|
||||
deleteDisabled: true,
|
||||
nameDisabled: true,
|
||||
},
|
||||
],
|
||||
outputDefs: [
|
||||
{
|
||||
name: 'successRows',
|
||||
title: $t('aiWorkflow.successInsertedRecords'),
|
||||
dataType: 'Number',
|
||||
dataTypeDisabled: true,
|
||||
required: true,
|
||||
parametersAddEnable: false,
|
||||
description: $t('aiWorkflow.successInsertedRecords'),
|
||||
deleteDisabled: true,
|
||||
nameDisabled: true,
|
||||
},
|
||||
],
|
||||
render: rerenderSaveNode,
|
||||
onUpdate: rerenderSaveNode,
|
||||
};
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
import { getOptions } from '@easyflow/utils';
|
||||
|
||||
import { api } from '#/api/request';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export const SaveToDatacenterNode = async () => {
|
||||
const res = await api.get('/api/v1/datacenterTable/list');
|
||||
|
||||
return {
|
||||
title: $t('aiWorkflow.saveData'),
|
||||
group: 'base',
|
||||
description: $t('aiWorkflow.descriptions.saveData'),
|
||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M11 19V9H4V19H11ZM11 7V4C11 3.44772 11.4477 3 12 3H21C21.5523 3 22 3.44772 22 4V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V8C2 7.44772 2.44772 7 3 7H11ZM13 5V19H20V5H13ZM5 16H10V18H5V16ZM14 16H19V18H14V16ZM14 13H19V15H14V13ZM14 10H19V12H14V10ZM5 13H10V15H5V13Z"></path></svg>',
|
||||
sortNo: 812,
|
||||
parametersAddEnable: false,
|
||||
outputDefsAddEnable: false,
|
||||
parameters: [
|
||||
{
|
||||
name: 'saveList',
|
||||
title: $t('aiWorkflow.dataToBeSaved'),
|
||||
dataType: 'Array',
|
||||
dataTypeDisabled: true,
|
||||
required: true,
|
||||
parametersAddEnable: false,
|
||||
description: $t('aiWorkflow.descriptions.dataToBeSaved'),
|
||||
deleteDisabled: true,
|
||||
nameDisabled: true,
|
||||
},
|
||||
],
|
||||
outputDefs: [
|
||||
{
|
||||
name: 'successRows',
|
||||
title: $t('aiWorkflow.successInsertedRecords'),
|
||||
dataType: 'Number',
|
||||
dataTypeDisabled: true,
|
||||
required: true,
|
||||
parametersAddEnable: false,
|
||||
description: $t('aiWorkflow.successInsertedRecords'),
|
||||
deleteDisabled: true,
|
||||
nameDisabled: true,
|
||||
},
|
||||
],
|
||||
forms: [
|
||||
{
|
||||
type: 'heading',
|
||||
label: $t('aiWorkflow.dataTable'),
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
label: '',
|
||||
description: $t('aiWorkflow.descriptions.dataTable'),
|
||||
name: 'tableId',
|
||||
defaultValue: '',
|
||||
options: getOptions('tableName', 'id', res.data),
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
import { getOptions } from '@easyflow/utils';
|
||||
|
||||
import { api } from '#/api/request';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export const SearchDatacenterNode = async () => {
|
||||
const res = await api.get('/api/v1/datacenterTable/list');
|
||||
|
||||
return {
|
||||
title: $t('aiWorkflow.queryData'),
|
||||
group: 'base',
|
||||
description: $t('aiWorkflow.descriptions.queryData'),
|
||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M11 2C15.968 2 20 6.032 20 11C20 15.968 15.968 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2ZM11 18C14.8675 18 18 14.8675 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18ZM19.4853 18.0711L22.3137 20.8995L20.8995 22.3137L18.0711 19.4853L19.4853 18.0711Z"></path></svg>',
|
||||
sortNo: 813,
|
||||
parametersAddEnable: true,
|
||||
outputDefsAddEnable: false,
|
||||
parameters: [],
|
||||
outputDefs: [
|
||||
{
|
||||
name: 'rows',
|
||||
title: $t('aiWorkflow.queryResult'),
|
||||
dataType: 'Array',
|
||||
dataTypeDisabled: true,
|
||||
required: true,
|
||||
parametersAddEnable: false,
|
||||
description: $t('aiWorkflow.queryResult'),
|
||||
deleteDisabled: true,
|
||||
nameDisabled: false,
|
||||
},
|
||||
],
|
||||
forms: [
|
||||
{
|
||||
type: 'heading',
|
||||
label: $t('aiWorkflow.dataTable'),
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
label: '',
|
||||
description: $t('aiWorkflow.descriptions.dataTable'),
|
||||
name: 'tableId',
|
||||
defaultValue: '',
|
||||
options: getOptions('tableName', 'id', res.data),
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
label: $t('aiWorkflow.filterConditions'),
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
templateSupport: true,
|
||||
label: "如:name='张三' and age=21 or field = {{流程变量}}",
|
||||
description: '',
|
||||
name: 'where',
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
label: $t('aiWorkflow.limit'),
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: '',
|
||||
description: '',
|
||||
name: 'limit',
|
||||
defaultValue: '10',
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { rerenderSearchNode } from './datasetNodeRenderer';
|
||||
|
||||
export const SearchDatasetNode = async () => {
|
||||
return {
|
||||
title: $t('aiWorkflow.queryDataset'),
|
||||
group: 'base',
|
||||
description: $t('aiWorkflow.descriptions.queryDataset'),
|
||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4H20V6H4V4ZM4 9H20V11H4V9ZM4 14H14V16H4V14ZM4 19H14V21H4V19ZM17 14H22V21H17V14Z"></path></svg>',
|
||||
sortNo: 813,
|
||||
parametersAddEnable: true,
|
||||
outputDefsAddEnable: false,
|
||||
renderFirst: true,
|
||||
parameters: [],
|
||||
forms: [
|
||||
{
|
||||
name: 'querySql',
|
||||
type: 'textarea',
|
||||
templateSupport: true,
|
||||
label: 'SQL',
|
||||
placeholder: $t('aiWorkflow.descriptions.enterSQL'),
|
||||
attrs: {
|
||||
rows: 6,
|
||||
},
|
||||
},
|
||||
],
|
||||
outputDefs: [
|
||||
{
|
||||
name: 'data',
|
||||
title: 'data',
|
||||
dataType: 'Array',
|
||||
dataTypeDisabled: true,
|
||||
required: true,
|
||||
parametersAddEnable: false,
|
||||
description: 'data',
|
||||
deleteDisabled: true,
|
||||
nameDisabled: true,
|
||||
},
|
||||
],
|
||||
render: rerenderSearchNode,
|
||||
onUpdate: rerenderSearchNode,
|
||||
};
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import nodeNames from './nodeNames';
|
||||
|
||||
export default {
|
||||
[nodeNames.sqlNode]: {
|
||||
title: $t('aiWorkflow.sqlQuery'),
|
||||
group: 'base',
|
||||
description: $t('aiWorkflow.descriptions.sqlQuery'),
|
||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgba(37,99,235,1)"><path d="M5 12.5C5 12.8134 5.46101 13.3584 6.53047 13.8931C7.91405 14.5849 9.87677 15 12 15C14.1232 15 16.0859 14.5849 17.4695 13.8931C18.539 13.3584 19 12.8134 19 12.5V10.3287C17.35 11.3482 14.8273 12 12 12C9.17273 12 6.64996 11.3482 5 10.3287V12.5ZM19 15.3287C17.35 16.3482 14.8273 17 12 17C9.17273 17 6.64996 16.3482 5 15.3287V17.5C5 17.8134 5.46101 18.3584 6.53047 18.8931C7.91405 19.5849 9.87677 20 12 20C14.1232 20 16.0859 19.5849 17.4695 18.8931C18.539 18.3584 19 17.8134 19 17.5V15.3287ZM3 17.5V7.5C3 5.01472 7.02944 3 12 3C16.9706 3 21 5.01472 21 7.5V17.5C21 19.9853 16.9706 22 12 22C7.02944 22 3 19.9853 3 17.5ZM12 10C14.1232 10 16.0859 9.58492 17.4695 8.89313C18.539 8.3584 19 7.81342 19 7.5C19 7.18658 18.539 6.6416 17.4695 6.10687C16.0859 5.41508 14.1232 5 12 5C9.87677 5 7.91405 5.41508 6.53047 6.10687C5.46101 6.6416 5 7.18658 5 7.5C5 7.81342 5.46101 8.3584 6.53047 8.89313C7.91405 9.58492 9.87677 10 12 10Z"></path></svg>',
|
||||
sortNo: 803,
|
||||
parametersAddEnable: true,
|
||||
outputDefsAddEnable: true,
|
||||
parameters: [],
|
||||
forms: [
|
||||
{
|
||||
name: 'sql',
|
||||
type: 'textarea',
|
||||
templateSupport: true,
|
||||
label: 'SQL',
|
||||
placeholder: $t('aiWorkflow.descriptions.enterSQL'),
|
||||
},
|
||||
],
|
||||
outputDefs: [
|
||||
{
|
||||
name: 'queryData',
|
||||
title: $t('aiWorkflow.queryResult'),
|
||||
dataType: 'Array',
|
||||
dataTypeDisabled: true,
|
||||
required: true,
|
||||
parametersAddEnable: false,
|
||||
description: $t('aiWorkflow.descriptions.queryResultJson'),
|
||||
deleteDisabled: true,
|
||||
nameDisabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user