feat: 完善工作流 Public API 调用能力

- 支持 JSON 文件 URL 简写与 Multipart 单请求文件上传

- 完善执行拓扑、枚举状态、节点名称、恢复校验和安全错误响应

- 增加临时上传生命周期清理并升级 MinIO SDK

- 重构工作流接口调用说明弹窗的扁平响应式布局
This commit is contained in:
2026-08-09 21:27:30 +08:00
parent 0d14f1c165
commit 54d85ae460
61 changed files with 8131 additions and 161 deletions

View File

@@ -511,16 +511,20 @@ function resolveApiFields(row: any): ApiFieldDoc[] {
})
.filter(Boolean) as ApiFieldDoc[];
}
function buildExampleVariables(row: any) {
function isFileApiField(field: ApiFieldDoc) {
return (
field.type.trim().toLowerCase() === 'file' ||
field.type.trim().toLowerCase() === 'files'
);
}
function buildExampleVariables(row: any, includeFileReferences = true) {
const variables: Record<string, any> = {};
for (const field of resolveApiFields(row)) {
if (field.type === 'file') {
variables[field.key] = [
{
fileName: 'example.pdf',
filePath: 'https://example.com/example.pdf',
},
];
if (isFileApiField(field)) {
if (!includeFileReferences) {
continue;
}
variables[field.key] = 'https://files.example.com/example.pdf';
continue;
}
if (field.type === 'checkbox') {
@@ -541,12 +545,101 @@ function buildRunRequestExample(row: any) {
2,
);
}
function buildRunResponseExample() {
function quoteShellFormValue(value: string) {
return `'${value.replaceAll(`'`, String.raw`'"'"'`)}'`;
}
function buildMultipartCurlExample(row: any, url: string) {
const fileFields = resolveApiFields(row).filter((field) =>
isFileApiField(field),
);
const metadata = JSON.stringify({
id: row?.id,
variables: buildExampleVariables(row, false),
});
const parts = [
`curl --request POST '${url}'`,
` --header 'ApiKey: <your-api-key>'`,
` --form ${quoteShellFormValue(
`metadata=${metadata};type=application/json`,
)}`,
...fileFields.map(
(field) =>
` --form ${quoteShellFormValue(`files.${field.key}=@/path/to/example.pdf;type=application/pdf`)}`,
),
];
return parts
.map((line, index) => (index < parts.length - 1 ? `${line} \\` : line))
.join('\n');
}
function buildRunResponseExample(row: any) {
return JSON.stringify(
{
errorCode: 0,
message: '成功',
data: '执行ID',
workflow: {
workflowId: String(row?.id ?? ''),
alias: row?.alias || null,
title: row?.title || null,
description: row?.description || null,
revision: row?.revision ?? null,
publishedAt: row?.publishedAt || null,
nodes: [
{
nodeId: '<节点 ID>',
nodeType: '<节点类型>',
nodeName: '<节点名称>',
description: null,
parentNodeId: null,
definitionIndex: 0,
topologyIndex: 0,
topologyLevel: 0,
inDegree: 0,
outDegree: 1,
startNode: true,
endNode: false,
predecessorNodeIds: [],
successorNodeIds: ['<后继节点 ID>'],
incomingEdgeIds: [],
outgoingEdgeIds: ['<边 ID>'],
inputParameters: [
{
parameterId: '<参数 ID>',
name: 'documents',
label: '文档',
dataType: 'File',
contentType: 'file',
required: true,
description: null,
multipartPartName: 'files.documents',
},
],
outputParameters: [],
},
],
edges: [
{
edgeId: '<边 ID>',
edgeType: '<边类型>',
label: null,
sourceNodeId: '<源节点 ID>',
targetNodeId: '<目标节点 ID>',
sourceHandle: null,
targetHandle: null,
parentNodeId: null,
definitionIndex: 0,
dangling: false,
},
],
topologicalOrder: ['<开始节点 ID>', '<处理节点 ID>', '<结束节点 ID>'],
topologyLevels: [
['<开始节点 ID>'],
['<处理节点 ID>'],
['<结束节点 ID>'],
],
hasCycle: false,
unresolvedNodeIds: [],
},
},
null,
2,
@@ -578,11 +671,18 @@ function handleApiDocClick(e: MouseEvent) {
if (url) copyApiContent(url);
}
function apiUrlLine(method: string, url: string) {
const escaped = url.replace(/"/g, '&quot;');
return `\`${method}\` \`${url}\` <button class="api-url-copy-btn" data-copy="${escaped}" title="复制">
const escaped = url.replaceAll('"', '&quot;');
return `\`${method}\` \`${url}\` <button type="button" class="api-url-copy-btn" data-copy="${escaped}" title="复制接口地址" aria-label="复制接口地址">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg></button>`;
}
function escapeMarkdownTableCell(value: unknown) {
return String(value ?? '')
.replaceAll('|', String.raw`\|`)
.replaceAll(/\r?\n/g, '<br>');
}
/* eslint-disable unicorn/prefer-single-call -- 文档按接口章节顺序增量组装,可读性优先。 */
const apiDocMarkdown = computed(() => {
const row = apiInstructionRow.value;
if (!row) return '';
@@ -625,12 +725,36 @@ const apiDocMarkdown = computed(() => {
`异步执行工作流,立即返回执行 ID。工作流必须已发布且存在发布快照。`,
);
lines.push(``);
lines.push(`### 请求`);
lines.push(`### JSON 请求`);
lines.push(``);
lines.push(
`> 顶层 \`Content-Type\` 必须为 \`application/json\`。多数 HTTP 客户端在发送 JSON 对象时会自动设置;直接发送文本或文件内容时请显式声明。`,
);
lines.push(``);
lines.push('```json');
lines.push(buildRunRequestExample(row));
lines.push('```');
lines.push(``);
if (fields.some((field) => isFileApiField(field))) {
lines.push(`### Multipart 文件直传`);
lines.push(``);
lines.push(
`文件参数可直接通过 \`multipart/form-data\` 上传,无需先获取文件地址。\`metadata\` 只承载 API 请求元数据;工作流文件统一使用 \`files.<开始节点文件参数名>\`,例如参数 \`documents\` 对应 Part \`files.documents\``,
);
lines.push(``);
lines.push(
`> 请勿手工添加顶层 \`Content-Type: multipart/form-data\`。由 cURL、Postman 或 Hoppscotch 根据请求体自动生成包含 boundary 的请求头;\`metadata\` Part 必须使用 \`application/json\``,
);
lines.push(``);
lines.push('```bash');
lines.push(buildMultipartCurlExample(row, `${baseUrl}/runAsync`));
lines.push('```');
lines.push(``);
lines.push(
`同一文件参数需要上传多个文件时,重复传入同名 \`files.<参数名>\` Part。文件 Part 可声明合法 MIME客户端发送空值、\`Other\` 或非法值时,服务端会按扩展名推断,仍无法识别时使用 \`application/octet-stream\``,
);
lines.push(``);
}
// 入参说明
lines.push(`### 入参说明`);
@@ -649,9 +773,15 @@ const apiDocMarkdown = computed(() => {
lines.push(`| 参数 | 类型 | 必填 | 说明 |`);
lines.push(`| --- | --- | --- | --- |`);
for (const f of fields) {
const desc = [f.label, f.description].filter(Boolean).join(' · ');
const fileDescription = isFileApiField(f)
? 'JSON 可直接填写 HTTP/HTTPS 文件 URL多文件使用 URL 数组。URL 路径需包含文件名和扩展名;继续兼容 fileName + filePath 文件对象'
: '';
const desc = [f.label, f.description, fileDescription]
.filter(Boolean)
.join(' · ');
const type = isFileApiField(f) ? 'string / string[]' : f.type;
lines.push(
`| ${f.key} | ${f.type} | ${f.required ? '是' : '否'} | ${desc} |`,
`| ${escapeMarkdownTableCell(f.key)} | ${escapeMarkdownTableCell(type)} | ${f.required ? '是' : '否'} | ${escapeMarkdownTableCell(desc)} |`,
);
}
lines.push(``);
@@ -660,13 +790,17 @@ const apiDocMarkdown = computed(() => {
lines.push(`### 响应`);
lines.push(``);
lines.push('```json');
lines.push(buildRunResponseExample());
lines.push(buildRunResponseExample(row));
lines.push('```');
lines.push(``);
lines.push(
`\`data\` 为 **执行 ID**executeId后续查询和恢复均需使用此 ID。`,
);
lines.push(``);
lines.push(
`\`workflow.nodes\` 提供全部公开节点信息;\`topologicalOrder\` 提供稳定的节点 ID 拓扑顺序;\`topologyLevels\` 按可并行层级分组。节点配置中的提示词、脚本和连接密钥等运行配置不会返回。`,
);
lines.push(``);
lines.push(`---`);
lines.push(``);
@@ -686,7 +820,7 @@ const apiDocMarkdown = computed(() => {
JSON.stringify(
{
executeId: '<runAsync 返回的执行 ID>',
nodes: [{ nodeId: '<需要查询的节点 ID>' }],
nodes: [{ nodeId: '<runAsync.workflow.nodes[].nodeId>' }],
},
null,
2,
@@ -695,7 +829,7 @@ const apiDocMarkdown = computed(() => {
lines.push('```');
lines.push(``);
lines.push(
`> \`nodes\` 参数可选。不传则只返回工作流整体状态;需要节点详情时传入对象数组,每项至少包含 \`nodeId\``,
`> \`nodes\` 参数可选。不传则只返回工作流整体状态;需要节点详情时,可将 \`runAsync.workflow.nodes\` 中的 \`nodeId\` 组成对象数组传入,服务端会从该执行实例的定义快照补齐 \`nodeName\``,
);
lines.push(``);
lines.push(`### 响应示例`);
@@ -708,10 +842,25 @@ const apiDocMarkdown = computed(() => {
message: '成功',
data: {
executeId: 'abc5358c-a310-4caa-97ec-455062b2235e',
status: 20,
message: null,
result: { output: '工作流执行结果' },
nodes: {},
status: 'failed',
terminal: true,
message: '工作流执行失败,请检查输入或稍后重试',
result: null,
nodes: {
'<节点 ID>': {
nodeId: '<节点 ID>',
nodeName: '文档解析',
status: 'failed',
message: '节点执行失败,请检查输入或稍后重试',
},
},
error: {
code: 'WORKFLOW_EXECUTION_FAILED',
message: '工作流执行失败,请检查输入或稍后重试',
nodeId: '<节点 ID>',
nodeName: '文档解析',
retryable: false,
},
},
},
null,
@@ -722,17 +871,20 @@ const apiDocMarkdown = computed(() => {
lines.push(``);
lines.push(`### 状态值说明`);
lines.push(``);
lines.push(`| 数值 | 状态 | 说明 |`);
lines.push(`| status | terminal | 说明 |`);
lines.push(`| --- | --- | --- |`);
lines.push(`| 0 | READY | 就绪,尚未开始 |`);
lines.push(`| 1 | RUNNING | 执行中 |`);
lines.push(`| 5 | SUSPEND | 挂起,等待确认节点恢复 |`);
lines.push(`| 10 | ERROR | 执行异常,可能仍在重试 |`);
lines.push(`| 20 | SUCCEEDED | 执行成功,终态 |`);
lines.push(`| 21 | FAILED | 执行失败,终态 |`);
lines.push(`| 22 | CANCELLED | 已取消,终态 |`);
lines.push(`| ready | false | 就绪,尚未开始 |`);
lines.push(`| running | false | 执行中 |`);
lines.push(`| suspended | false | 已暂停,等待确认参数恢复 |`);
lines.push(`| error | false | 执行异常,运行时可能仍在处理 |`);
lines.push(`| done | true | 执行成功 |`);
lines.push(`| failed | true | 执行失败 |`);
lines.push(`| cancelled | true | 已取消 |`);
lines.push(`| unknown | false | 无法识别的兼容状态 |`);
lines.push(``);
lines.push(`轮询可在状态值为 \`20\`\`21\`\`22\` 时结束。`);
lines.push(
`调用方应优先根据 \`terminal\` 判断是否结束轮询;值为 \`true\` 时,状态为 \`done\`\`failed\`\`cancelled\``,
);
lines.push(``);
// ---- 3. 恢复执行 ----
@@ -743,7 +895,7 @@ const apiDocMarkdown = computed(() => {
lines.push(apiUrlLine('POST', `${baseUrl}/resume`));
lines.push(``);
lines.push(
`当工作流包含**确认节点**时,执行到该节点后状态变为 \`5SUSPEND\`,需要调用此接口传入确认参数后恢复执行。若工作流不包含确认节点则无需调用。`,
`当工作流包含**确认节点**时,执行到该节点后状态变为 \`suspended\`,需要调用此接口传入确认参数后恢复执行。仅暂停中的执行实例允许恢复;若工作流不包含确认节点则无需调用。`,
);
lines.push(``);
lines.push(`### 请求体`);
@@ -769,21 +921,43 @@ const apiDocMarkdown = computed(() => {
lines.push(`## 错误处理`);
lines.push(``);
lines.push(
`请求失败时\`errorCode\` 不为 0\`message\` 包含错误原因。常见错误:`,
`请求尚未受理时会返回真实的 HTTP 4xx/5xx\`errorCode\` 为稳定业务码\`data.requestId\` 可用于服务端排查。请求已返回 executeId 后的节点失败通过 \`getChainStatus.data.error\` 获取。`,
);
lines.push(``);
lines.push(`| 场景 | 说明 |`);
lines.push(`| --- | --- |`);
lines.push(`| ApiKey 无效或过期 | 检查访问令牌状态与有效期 |`);
lines.push(`| HTTP | errorCode | 场景与处理 |`);
lines.push(`| --- | --- | --- |`);
lines.push(
`| 未授权工作流 API 调用 | 在访问令牌中开启「工作流 API 调用授权」 |`,
`| 400 | 40011 | JSON 请求体无效;文件直传请改用 multipart/form-data |`,
);
lines.push(
`| 工作流尚未发布 | 仅已发布且存在发布快照的工作流可通过 API 调用 |`,
`| 400 | 40012 | Multipart 缺少或无法解析 boundary删除手工设置的顶层 Content-Type |`,
);
lines.push(`| 400 | 40013 | 缺少 metadata Part |`);
lines.push(`| 400 | 40014 | metadata Part 不是有效 JSON |`);
lines.push(`| 400 | 40015 | metadata.id 缺失 |`);
lines.push(`| 400 | 40016 | files.<参数名> 与开始节点文件参数不匹配 |`);
lines.push(
`| 400 | 40017 | 其他工作流运行参数不合法,例如文件 URL 无法识别文件名或扩展名 |`,
);
lines.push(`| 401 | 40101 | 缺少 ApiKey 请求头 |`);
lines.push(`| 401 | 40102 / 40103 | ApiKey 无效、禁用或过期 |`);
lines.push(`| 403 | 40301 / 40302 | 缺少接口权限或工作流调用权限 |`);
lines.push(`| 404 | 40401 | 工作流不存在、未发布或不可公开调用 |`);
lines.push(`| 404 | 40402 | 执行记录不存在、过期或不可访问 |`);
lines.push(`| 409 | 40901 | 当前执行状态不允许恢复 |`);
lines.push(`| 413 | 41301 | 文件、文件数量或请求总量超限 |`);
lines.push(`| 415 | 41501 | 顶层 Content-Type 缺失或不支持 |`);
lines.push(`| 415 | 41502 | metadata Part 未使用 application/json |`);
lines.push(`| 500 | 50001 | 服务端内部错误;携带 requestId 联系管理员 |`);
lines.push(`| 503 | 50301 | 文件存储暂时不可用,可稍后重试 |`);
lines.push(``);
lines.push(
`Hoppscotch 等工具若显示自动生成的 \`multipart/form-data\` 请求头,请保持自动模式,不要覆盖该请求头;覆盖后通常会丢失 boundary。`,
);
return lines.join('\n');
});
/* eslint-enable unicorn/prefer-single-call */
async function submitPublishAction(row: any) {
if (
@@ -1189,11 +1363,26 @@ function handleHeaderButtonClick(data: any) {
/>
<ElDialog
v-model="apiInstructionVisible"
:title="$t('aiWorkflow.apiInstruction')"
width="780px"
width="min(960px, calc(100vw - 32px))"
class="workflow-api-dialog"
:footer="false"
>
<template #header>
<div class="workflow-api-dialog__header">
<h2 class="workflow-api-dialog__title">
{{ $t('aiWorkflow.apiInstruction') }}
</h2>
<p
v-if="apiInstructionRow?.title"
class="workflow-api-dialog__context"
>
{{ apiInstructionRow.title }}
<span v-if="apiInstructionRow.alias">
· {{ apiInstructionRow.alias }}
</span>
</p>
</div>
</template>
<div
v-if="apiInstructionRow"
class="workflow-api-markdown-wrap"
@@ -1654,13 +1843,37 @@ button.workflow-scope-chip:disabled {
box-shadow: 0 18px 34px -28px hsl(var(--foreground) / 20%);
}
.workflow-api-dialog__header {
min-width: 0;
padding-right: 40px;
}
.workflow-api-dialog__title {
margin: 0;
font-size: 18px;
font-weight: 650;
line-height: 1.4;
color: hsl(var(--text-strong));
}
.workflow-api-dialog__context {
margin: var(--space-1) 0 0;
overflow: hidden;
font-size: 13px;
line-height: 1.5;
color: hsl(var(--text-muted));
text-overflow: ellipsis;
white-space: nowrap;
}
.workflow-api-markdown-wrap {
max-height: 65vh;
padding: 0 4px;
max-height: min(76vh, 760px);
padding: var(--space-5) 36px var(--space-8);
overflow-y: auto;
font-size: 14px;
line-height: 1.7;
line-height: 1.75;
color: hsl(var(--foreground));
scrollbar-gutter: stable;
}
.workflow-api-markdown-wrap::-webkit-scrollbar {
@@ -1673,13 +1886,12 @@ button.workflow-scope-chip:disabled {
}
.workflow-api-markdown-wrap :deep(h2) {
padding-bottom: 8px;
margin-top: 28px;
margin-bottom: 16px;
font-size: 18px;
font-weight: 700;
color: hsl(var(--foreground));
border-bottom: 1px solid hsl(var(--border));
margin: 40px 0 14px;
font-size: 20px;
font-weight: 650;
line-height: 1.4;
color: hsl(var(--text-strong));
letter-spacing: -0.01em;
}
.workflow-api-markdown-wrap :deep(h2:first-child) {
@@ -1687,92 +1899,145 @@ button.workflow-scope-chip:disabled {
}
.workflow-api-markdown-wrap :deep(h3) {
margin-top: 20px;
margin-bottom: 10px;
font-size: 15px;
margin: 28px 0 10px;
font-size: 16px;
font-weight: 600;
color: hsl(var(--foreground));
line-height: 1.5;
color: hsl(var(--text-strong));
}
.workflow-api-markdown-wrap :deep(h4) {
margin-top: 16px;
margin-bottom: 8px;
margin: 22px 0 8px;
font-size: 14px;
font-weight: 600;
color: hsl(var(--foreground));
color: hsl(var(--text-strong));
}
.workflow-api-markdown-wrap :deep(hr) {
margin: 24px 0;
border: none;
border-top: 1px solid hsl(var(--border));
display: none;
}
.workflow-api-markdown-wrap :deep(p) {
margin: 8px 0;
margin: 8px 0 12px;
color: hsl(var(--foreground) / 84%);
}
.workflow-api-markdown-wrap :deep(ul),
.workflow-api-markdown-wrap :deep(ol) {
padding-left: 22px;
margin: 10px 0 16px;
}
.workflow-api-markdown-wrap :deep(code) {
padding: 2px 6px;
padding: 2px 5px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 13px;
font-size: 0.92em;
color: hsl(var(--foreground));
background: hsl(var(--muted) / 50%);
border-radius: 4px;
overflow-wrap: anywhere;
background: hsl(var(--surface-contrast-soft));
border-radius: var(--space-1);
}
.workflow-api-markdown-wrap :deep(pre) {
max-height: 280px;
padding: 14px 16px;
margin: 10px 0;
max-height: 340px;
padding: var(--space-4);
margin: 12px 0 20px;
overflow: auto;
background: hsl(220 14% 96%);
border: 1px solid hsl(var(--border));
border-radius: 8px;
line-height: 1.65;
background: hsl(var(--surface-subtle));
border: 0;
border-radius: var(--radius-toolbar);
}
.workflow-api-markdown-wrap :deep(pre code) {
padding: 0;
padding: 0 !important;
font-size: 12.5px;
color: hsl(var(--foreground));
overflow-wrap: normal;
background: transparent;
border-radius: 0;
}
.workflow-api-markdown-wrap :deep(.pre-md) {
background: transparent !important;
border: 0 !important;
border-radius: 0 !important;
box-shadow: none !important;
}
.workflow-api-markdown-wrap :deep(.markdown-elxLanguage-header-div) {
padding: 0 0 var(--space-2) !important;
background: transparent !important;
border: 0 !important;
box-shadow: none !important;
}
.workflow-api-markdown-wrap :deep(table) {
width: 100%;
margin: 10px 0;
margin: 12px 0 22px;
border-collapse: collapse;
border: 0 !important;
border-top: 1px solid hsl(var(--table-row-border)) !important;
}
.workflow-api-markdown-wrap :deep(tr) {
background: transparent !important;
}
.workflow-api-markdown-wrap :deep(th),
.workflow-api-markdown-wrap :deep(td) {
padding: 8px 12px;
padding: 10px 12px;
font-size: 13px;
line-height: 1.55;
text-align: left;
border: 1px solid hsl(var(--border));
vertical-align: top;
border: 0 !important;
border-bottom: 1px solid hsl(var(--table-row-border)) !important;
}
.workflow-api-markdown-wrap :deep(th) {
font-weight: 600;
color: hsl(var(--foreground));
background: hsl(var(--muted) / 40%);
color: hsl(var(--text-strong));
background: hsl(var(--table-header-bg));
}
.workflow-api-markdown-wrap :deep(td) {
color: hsl(var(--foreground) / 85%);
color: hsl(var(--foreground) / 82%);
background: transparent !important;
}
.workflow-api-markdown-wrap :deep(th:nth-child(1)),
.workflow-api-markdown-wrap :deep(td:nth-child(1)) {
width: 22%;
}
.workflow-api-markdown-wrap :deep(th:nth-child(2)),
.workflow-api-markdown-wrap :deep(td:nth-child(2)) {
width: 20%;
white-space: nowrap;
}
.workflow-api-markdown-wrap :deep(th:nth-child(3)),
.workflow-api-markdown-wrap :deep(td:nth-child(3)) {
width: 10%;
white-space: nowrap;
}
.workflow-api-markdown-wrap :deep(blockquote) {
padding: 8px 16px;
margin: 10px 0;
color: hsl(var(--muted-foreground));
border-left: 3px solid hsl(var(--primary) / 40%);
padding: 2px 0 2px var(--space-4);
margin: 12px 0 20px;
color: hsl(var(--text-muted));
border-left: 2px solid hsl(var(--primary) / 55%);
}
.workflow-api-markdown-wrap :deep(blockquote p) {
margin: 0;
color: inherit;
}
.workflow-api-markdown-wrap :deep(strong) {
font-weight: 600;
color: hsl(var(--foreground));
color: hsl(var(--text-strong));
}
.workflow-api-markdown-wrap :deep(.api-url-copy-btn) {
@@ -1787,19 +2052,96 @@ button.workflow-scope-chip:disabled {
vertical-align: middle;
cursor: pointer;
background: transparent;
border: 1px solid hsl(var(--border));
border-radius: 6px;
transition: all 0.15s;
border: 0;
border-radius: var(--space-2);
transition:
color var(--motion-duration-fast) var(--motion-ease-standard),
background-color var(--motion-duration-fast) var(--motion-ease-standard);
}
.workflow-api-markdown-wrap :deep(.api-url-copy-btn:hover) {
color: hsl(var(--primary));
background: hsl(var(--muted) / 50%);
border-color: hsl(var(--primary) / 40%);
background: hsl(var(--surface-contrast-soft));
}
.workflow-api-markdown-wrap :deep(.api-url-copy-btn:focus-visible) {
color: hsl(var(--primary));
outline: 2px solid hsl(var(--primary) / 45%);
outline-offset: 2px;
}
:global(.workflow-api-dialog.el-dialog) {
overflow: hidden;
background: hsl(var(--modal-surface));
border: 1px solid hsl(var(--modal-shell-border-soft));
border-radius: var(--radius-modal);
box-shadow: var(--modal-shadow);
}
:global(.workflow-api-dialog .el-dialog__header) {
padding: var(--space-5) var(--space-6);
margin-right: 0;
border-bottom: 1px solid hsl(var(--modal-divider));
}
:global(.workflow-api-dialog .el-dialog__body) {
padding: 0 20px 20px;
max-height: none;
padding: 0;
overflow: hidden;
background: hsl(var(--modal-content-surface));
}
@media (max-width: 767px) {
:global(.workflow-api-dialog.el-dialog) {
display: flex;
flex-direction: column;
width: calc(100vw - 16px) !important;
max-height: calc(
100vh - var(--easyflow-header-height, 0px) - var(--space-4)
);
margin: calc(var(--easyflow-header-height, 0px) + var(--space-2)) auto
var(--space-2) !important;
}
:global(.workflow-api-dialog .el-dialog__header) {
flex: 0 0 auto;
padding: var(--space-4) 18px;
}
:global(.workflow-api-dialog .el-dialog__body) {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
}
.workflow-api-markdown-wrap {
flex: 1 1 auto;
min-height: 0;
max-height: none;
padding: var(--space-4) 18px var(--space-6);
scrollbar-gutter: auto;
}
.workflow-api-markdown-wrap :deep(h2) {
margin-top: var(--space-8);
font-size: 18px;
}
.workflow-api-markdown-wrap :deep(h3) {
margin-top: var(--space-6);
}
.workflow-api-markdown-wrap :deep(pre) {
max-height: 300px;
padding: var(--space-3);
}
.workflow-api-markdown-wrap :deep(table) {
display: block;
max-width: 100%;
overflow-x: auto;
white-space: nowrap;
}
}
</style>