@@ -5,11 +5,15 @@ import type {
ActionButton ,
CardPrimaryAction ,
} from '#/components/page/CardList.vue' ;
import type { OfflineImpactCheck } from '#/views/ai/shared/offline-impact' ;
import { computed , h , markRaw , onMounted , ref } from 'vue' ;
import ElXMarkdown from 'vue-element-plus-x/es/XMarkdown/index.js' ;
import { useAccess } from '@easyflow/access' ;
import { EasyFlowFormModal } from '@easyflow/common-ui' ;
import { useAppConfig } from '@easyflow/hooks' ;
import {
Check ,
@@ -17,6 +21,7 @@ import {
Delete ,
Download ,
Edit ,
Link ,
Lock ,
OfficeBuilding ,
Plus ,
@@ -26,6 +31,7 @@ import {
VideoPlay ,
} from '@element-plus/icons-vue' ;
import {
ElDialog ,
ElForm ,
ElFormItem ,
ElIcon ,
@@ -49,10 +55,7 @@ import { $t } from '#/locales';
import { router } from '#/router' ;
import { useDictStore } from '#/store' ;
import AiResourceCornerMeta from '#/views/ai/shared/AiResourceCornerMeta.vue' ;
import {
buildOfflineImpactMessage ,
type OfflineImpactCheck ,
} from '#/views/ai/shared/offline-impact' ;
import { buildOfflineImpactMessage } from '#/views/ai/shared/offline-impact' ;
import {
canAiResourceDelete ,
canAiResourceOffline ,
@@ -64,6 +67,8 @@ import {
import WorkflowModal from './WorkflowModal.vue' ;
const { apiURL } = useAppConfig ( import . meta . env , import . meta . env . PROD ) ;
interface FieldDefinition {
// 字段名称
prop : string ;
@@ -79,6 +84,15 @@ interface FieldDefinition {
type VisibilityScope = 'DEPT' | 'PRIVATE' | 'PUBLIC' ;
interface ApiFieldDoc {
key : string ;
label : string ;
type : string ;
required : boolean ;
description : string ;
placeholder : string ;
}
const primaryAction : CardPrimaryAction = {
icon : DesignIcon ,
text : $t ( 'button.design' ) ,
@@ -94,6 +108,8 @@ const canManageWorkflow = computed(() =>
) ;
const updatingScopeId = ref < null | number | string > ( null ) ;
const visibilityScopePopoverRefs = ref < Record < string , any > > ( { } ) ;
const apiInstructionVisible = ref ( false ) ;
const apiInstructionRow = ref < any > ( null ) ;
const visibilityScopeMeta = computed ( ( ) => ( {
PRIVATE : {
label : $t ( 'aiWorkflow.visibilityScopePrivate' ) ,
@@ -158,6 +174,14 @@ const actions: ActionButton[] = [
} ) ;
} ,
} ,
{
icon : Link ,
text : $t ( 'aiWorkflow.apiInstruction' ) ,
placement : 'menu' ,
onClick : ( row : any ) => {
showApiInstruction ( row ) ;
} ,
} ,
{
icon : Download ,
text : $t ( 'button.export' ) ,
@@ -199,7 +223,8 @@ const actions: ActionButton[] = [
text : $t ( 'button.offline' ) ,
permission : '/api/v1/workflow/save' ,
placement : 'menu' ,
visible : ( row : any ) => canAiResourceOffline ( row . displayPublishStatus , row . publishStatus ) ,
visible : ( row : any ) =>
canAiResourceOffline ( row . displayPublishStatus , row . publishStatus ) ,
onClick : ( row : any ) => {
submitOfflineAction ( row ) ;
} ,
@@ -210,7 +235,8 @@ const actions: ActionButton[] = [
tone : 'danger' ,
permission : '/api/v1/workflow/remove' ,
placement : 'menu' ,
visible : ( row : any ) => canAiResourceDelete ( row . displayPublishStatus , row . publishStatus ) ,
visible : ( row : any ) =>
canAiResourceDelete ( row . displayPublishStatus , row . publishStatus ) ,
onClick : ( row : any ) => {
submitDeleteApproval ( row ) ;
} ,
@@ -306,6 +332,321 @@ function resolveNavTitle(row: any) {
function isRepublishAction ( row : any ) {
return canAiResourceRepublish ( row . displayPublishStatus , row . publishStatus ) ;
}
function isWorkflowPublished ( row : any ) {
return (
resolveAiResourceDisplayStatus (
row . displayPublishStatus ,
row . publishStatus ,
) === 'PUBLISHED'
) ;
}
function showApiInstruction ( row : any ) {
if ( ! isWorkflowPublished ( row ) || ! getPublishedWorkflowContent ( row ) ) {
ElMessage . warning ( $t ( 'aiWorkflow.apiInstructionPublishRequired' ) ) ;
return ;
}
apiInstructionRow . value = row ;
apiInstructionVisible . value = true ;
}
function getPublishedWorkflowContent ( row : any ) {
const snapshot = row ? . publishedSnapshotJson ;
if ( snapshot && typeof snapshot === 'object' && snapshot . content ) {
return snapshot . content ;
}
return null ;
}
function parseWorkflowContent ( row : any ) {
const content = getPublishedWorkflowContent ( row ) ;
if ( ! content ) {
return null ;
}
if ( typeof content === 'object' ) {
return content ;
}
try {
return JSON . parse ( String ( content ) ) ;
} catch {
return null ;
}
}
function normalizeApiOrigin ( value ? : string ) {
const rawValue = String ( value || '' ) . trim ( ) ;
if ( ! rawValue ) {
return window . location . origin ;
}
if ( /^https?:\/\//i . test ( rawValue ) ) {
return rawValue . replace ( /\/+$/ , '' ) ;
}
const normalizedPath = rawValue . startsWith ( '/' ) ? rawValue : ` / ${ rawValue } ` ;
return ` ${ window . location . origin } ${ normalizedPath } ` . replace ( /\/+$/ , '' ) ;
}
function resolveApiBaseUrl ( ) {
return ` ${ normalizeApiOrigin ( apiURL ) } /public-api/workflow ` ;
}
function resolveStartNodeData ( row : any ) {
const workflow = parseWorkflowContent ( row ) ;
const nodes = Array . isArray ( workflow ? . nodes ) ? workflow . nodes : [ ] ;
const startNode = nodes . find ( ( node : any ) => node ? . type === 'startNode' ) ;
return startNode ? . data || { } ;
}
function resolveApiFields ( row : any ) : ApiFieldDoc [ ] {
const startNodeData = resolveStartNodeData ( row ) ;
const schema = Array . isArray ( startNodeData . startFormSchema )
? startNodeData . startFormSchema
: [ ] ;
const parameters = Array . isArray ( startNodeData . parameters )
? startNodeData . parameters
: [ ] ;
const source = schema . length > 0 ? schema : parameters ;
return source
. map ( ( item : any ) => {
const key = String ( item ? . key || item ? . name || '' ) . trim ( ) ;
if ( ! key ) {
return null ;
}
return {
key ,
label : String ( item ? . label || item ? . title || item ? . name || key ) ,
type : String ( item ? . type || item ? . contentType || 'text' ) ,
required : Boolean ( item ? . required ) ,
description : String ( item ? . description || item ? . formDescription || '' ) ,
placeholder : String ( item ? . placeholder || item ? . formPlaceholder || '' ) ,
} ;
} )
. filter ( Boolean ) as ApiFieldDoc [ ] ;
}
function buildExampleVariables ( row : any ) {
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' ,
} ,
] ;
continue ;
}
if ( field . type === 'checkbox' ) {
variables [ field . key ] = [ ] ;
continue ;
}
variables [ field . key ] = field . placeholder || field . label ;
}
return variables ;
}
function buildRunRequestExample ( row : any ) {
return JSON . stringify (
{
id : row ? . id ,
variables : buildExampleVariables ( row ) ,
} ,
null ,
2 ,
) ;
}
function buildRunResponseExample ( ) {
return JSON . stringify (
{
errorCode : 0 ,
message : '成功' ,
data : '执行ID' ,
} ,
null ,
2 ,
) ;
}
function buildResumeRequestExample ( ) {
return JSON . stringify (
{
executeId : '执行ID' ,
confirmParams : {
confirm : true ,
} ,
} ,
null ,
2 ,
) ;
}
async function copyApiContent ( content : string ) {
try {
await navigator . clipboard . writeText ( content ) ;
ElMessage . success ( $t ( 'message.copySuccess' ) ) ;
} catch {
ElMessage . error ( $t ( 'message.copyFail' ) ) ;
}
}
function handleApiDocClick ( e : MouseEvent ) {
const target = ( e . target as HTMLElement ) . closest ( '.api-url-copy-btn' ) ;
if ( ! target ) return ;
const url = ( target as HTMLElement ) . dataset . copy ;
if ( url ) copyApiContent ( url ) ;
}
function apiUrlLine ( method : string , url : string ) {
const escaped = url . replace ( /"/g , '"' ) ;
return ` \` ${ method } \` \` ${ url } \` <button class="api-url-copy-btn" data-copy=" ${ escaped } " title="复制">
<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> ` ;
}
const apiDocMarkdown = computed ( ( ) => {
const row = apiInstructionRow . value ;
if ( ! row ) return '' ;
const baseUrl = resolveApiBaseUrl ( ) ;
const fields = resolveApiFields ( row ) ;
const lines : string [ ] = [ ] ;
// ---- 概述 ----
lines . push ( ` ## 概述 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` 通过 Public API 可异步执行已发布的工作流并获取执行结果。 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` **调用流程**:发起执行 → 轮询查询执行结果 → (若有确认节点)恢复执行 ` ) ;
lines . push ( ` ` ) ;
// ---- 鉴权 ----
lines . push ( ` ## 鉴权 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` 所有请求需在 Header 中携带访问令牌,访问令牌需开启 **工作流 API 调用授权**。 ` ) ;
lines . push ( ` ` ) ;
lines . push ( '```' ) ;
lines . push ( ` ApiKey: <your-api-key> ` ) ;
lines . push ( '```' ) ;
lines . push ( ` ` ) ;
lines . push ( ` --- ` ) ;
lines . push ( ` ` ) ;
// ---- 1. 发起执行 ----
lines . push ( ` ## 1. 发起执行 ` ) ;
lines . push ( ` ` ) ;
lines . push ( apiUrlLine ( 'POST' , ` ${ baseUrl } /runAsync ` ) ) ;
lines . push ( ` ` ) ;
lines . push ( ` 异步执行工作流,立即返回执行 ID。工作流必须已发布且存在发布快照。 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` ### 请求体 ` ) ;
lines . push ( ` ` ) ;
lines . push ( '```json' ) ;
lines . push ( buildRunRequestExample ( row ) ) ;
lines . push ( '```' ) ;
lines . push ( ` ` ) ;
// 入参说明
lines . push ( ` ### 入参说明 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` | 参数 | 类型 | 必填 | 说明 | ` ) ;
lines . push ( ` | --- | --- | --- | --- | ` ) ;
lines . push ( ` | id | string | 是 | 工作流 ID | ` ) ;
lines . push ( ` | variables | object | 否 | 运行参数,字段说明见下方 | ` ) ;
lines . push ( ` ` ) ;
if ( fields . length > 0 ) {
lines . push ( ` #### variables 字段明细 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` 以下字段基于当前发布快照的开始节点配置生成。 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` | 参数 | 类型 | 必填 | 说明 | ` ) ;
lines . push ( ` | --- | --- | --- | --- | ` ) ;
for ( const f of fields ) {
const desc = [ f . label , f . description ] . filter ( Boolean ) . join ( ' · ' ) ;
lines . push ( ` | ${ f . key } | ${ f . type } | ${ f . required ? '是' : '否' } | ${ desc } | ` ) ;
}
lines . push ( ` ` ) ;
}
lines . push ( ` ### 响应 ` ) ;
lines . push ( ` ` ) ;
lines . push ( '```json' ) ;
lines . push ( buildRunResponseExample ( ) ) ;
lines . push ( '```' ) ;
lines . push ( ` ` ) ;
lines . push ( ` \` data \` 为 **执行 ID**( executeId) , 后续查询和恢复均需使用此 ID。 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` --- ` ) ;
lines . push ( ` ` ) ;
// ---- 2. 查询执行结果 ----
lines . push ( ` ## 2. 查询执行结果 ` ) ;
lines . push ( ` ` ) ;
lines . push ( apiUrlLine ( 'POST' , ` ${ baseUrl } /getChainStatus ` ) ) ;
lines . push ( ` ` ) ;
lines . push ( ` 查询工作流执行的整体状态与各节点执行详情。工作流为异步执行,建议**轮询**该接口直到状态为终态。 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` ### 请求体 ` ) ;
lines . push ( ` ` ) ;
lines . push ( '```json' ) ;
lines . push ( JSON . stringify ( { executeId : '<runAsync 返回的执行 ID>' } , null , 2 ) ) ;
lines . push ( '```' ) ;
lines . push ( ` ` ) ;
lines . push ( ` > \` nodes \` 参数可选。不传则只返回工作流整体状态;传入节点 ID 数组可额外获取对应节点的执行详情。 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` ### 响应示例 ` ) ;
lines . push ( ` ` ) ;
lines . push ( '```json' ) ;
lines . push ( JSON . stringify ( {
errorCode : 0 ,
message : '成功' ,
data : {
executeId : 'abc5358c-a310-4caa-97ec-455062b2235e' ,
status : 'FINISHED' ,
message : null ,
result : { output : '工作流执行结果' } ,
nodes : { } ,
} ,
} , null , 2 ) ) ;
lines . push ( '```' ) ;
lines . push ( ` ` ) ;
lines . push ( ` ### 状态值说明 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` | 状态 | 说明 | ` ) ;
lines . push ( ` | --- | --- | ` ) ;
lines . push ( ` | READY | 就绪,尚未开始 | ` ) ;
lines . push ( ` | RUNNING | 执行中 | ` ) ;
lines . push ( ` | SUSPEND | 挂起,等待确认节点恢复 | ` ) ;
lines . push ( ` | FINISHED | 执行完成 | ` ) ;
lines . push ( ` | FAILED | 执行失败 | ` ) ;
lines . push ( ` | ERROR | 执行异常 | ` ) ;
lines . push ( ` ` ) ;
// ---- 3. 恢复执行 ----
lines . push ( ` --- ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` ## 3. 恢复执行(确认节点) ` ) ;
lines . push ( ` ` ) ;
lines . push ( apiUrlLine ( 'POST' , ` ${ baseUrl } /resume ` ) ) ;
lines . push ( ` ` ) ;
lines . push ( ` 当工作流包含**确认节点**时,执行到该节点后状态变为 \` SUSPEND \` ,需要调用此接口传入确认参数后恢复执行。若工作流不包含确认节点则无需调用。 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` ### 请求体 ` ) ;
lines . push ( ` ` ) ;
lines . push ( '```json' ) ;
lines . push ( buildResumeRequestExample ( ) ) ;
lines . push ( '```' ) ;
lines . push ( ` ` ) ;
lines . push ( ` ### 响应 ` ) ;
lines . push ( ` ` ) ;
lines . push ( '```json' ) ;
lines . push ( JSON . stringify ( { errorCode : 0 , message : '成功' , data : null } , null , 2 ) ) ;
lines . push ( '```' ) ;
lines . push ( ` ` ) ;
lines . push ( ` 恢复后可继续轮询 \` getChainStatus \` 获取后续执行状态。 ` ) ;
lines . push ( ` ` ) ;
// ---- 错误码 ----
lines . push ( ` --- ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` ## 错误处理 ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` 当请求失败时, \` errorCode \` 不为 0, \` message \` 包含错误原因。常见错误: ` ) ;
lines . push ( ` ` ) ;
lines . push ( ` | 场景 | 说明 | ` ) ;
lines . push ( ` | --- | --- | ` ) ;
lines . push ( ` | ApiKey 无效或过期 | 检查访问令牌状态与有效期 | ` ) ;
lines . push ( ` | 未授权工作流 API 调用 | 在访问令牌中开启「工作流 API 调用授权」 | ` ) ;
lines . push ( ` | 工作流尚未发布 | 仅已发布且存在发布快照的工作流可通过 API 调用 | ` ) ;
return lines . join ( '\n' ) ;
} ) ;
async function submitPublishAction ( row : any ) {
if (
@@ -329,12 +670,9 @@ async function submitPublishAction(row: any) {
} catch {
return ;
}
const res = await api . post (
'/api/v1/workflow/submitPublishApproval' ,
{
id : row . id ,
} ,
) ;
const res = await api . post ( '/api/v1/workflow/submitPublishApproval' , {
id : row . id ,
} ) ;
if ( res . errorCode === 0 ) {
ElMessage . success ( res . message || $t ( 'message.saveOkMessage' ) ) ;
pageDataRef . value ? . reload ? . ( ) ;
@@ -350,17 +688,15 @@ async function submitOfflineAction(row: any) {
const impactRes = await api . get < {
data : OfflineImpactCheck ;
errorCode : number ;
} > (
'/api/v1/workflow/offlineImpactCheck' ,
{
params : { id : row . id } ,
} ,
) ;
} > ( '/api/v1/workflow/offlineImpactCheck' , {
params : { id : row . id } ,
} ) ;
if ( impactRes . errorCode !== 0 ) {
return ;
}
try {
const sections = [ ] ;
let offlineImpactFooter = $t ( 'aiWorkflow.offlineImpactBoundBotsFooter' ) ;
if ( impactRes . data ? . hasBotBindings ) {
sections . push (
buildOfflineImpactMessage (
@@ -383,32 +719,23 @@ async function submitOfflineAction(row: any) {
) ,
) ;
}
if ( impactRes . data ? . hasBotBindings && impactRes . data ? . hasPluginBindings ) {
offlineImpactFooter = $t ( 'aiWorkflow.offlineImpactBoundMixedFooter' ) ;
} else if ( impactRes . data ? . hasPluginBindings ) {
offlineImpactFooter = $t ( 'aiWorkflow.offlineImpactBoundPluginsFooter' ) ;
}
const impactMessage =
sections . length > 0
? h ( 'div' , [
... sections ,
h (
'p' ,
{
style : 'margin-top: 12px;' ,
} ,
impactRes . data ? . hasBotBindings && impactRes . data ? . hasPluginBindings
? $t ( 'aiWorkflow.offlineImpactBoundMixedFooter' )
: impactRes . data ? . hasPluginBindings
? $t ( 'aiWorkflow.offlineImpactBoundPluginsFooter' )
: $t ( 'aiWorkflow.offlineImpactBoundBotsFooter' ) ,
) ,
h ( 'p' , { style : 'margin-top: 12px;' } , offlineImpactFooter ) ,
] )
: $t ( 'aiWorkflow.submitOfflineApprovalConfirm' ) ;
await ElMessageBox . confirm (
impactMessage ,
$t ( 'message.noticeTitle ') ,
{
confirmButtonText : $t ( 'button.confirm' ) ,
cancelButtonText : $t ( 'button.cancel' ) ,
type : 'warning' ,
} ,
) ;
await ElMessageBox . confirm ( impactMessage , $t ( 'message.noticeTitle' ) , {
confirmButtonText : $t ( 'button.confirm' ) ,
cancelButtonText : $t ( 'button.cancel ') ,
type : 'warning' ,
} ) ;
} catch {
return ;
}
@@ -459,18 +786,18 @@ function resolvePublishStatusMetaByInstance(
tone : 'danger' ,
} ;
}
case 'OFFLINE_PENDING' : {
return {
label : $t ( 'aiWorkflow.publishStatusOfflinePending' ) ,
tone : 'pending' ,
} ;
}
case 'OFFLINE' : {
return {
label : $t ( 'aiWorkflow.publishStatusOffline' ) ,
tone : 'draft' ,
} ;
}
case 'OFFLINE_PENDING' : {
return {
label : $t ( 'aiWorkflow.publishStatusOfflinePending' ) ,
tone : 'pending' ,
} ;
}
case 'PUBLISH_PENDING' : {
return {
label : $t ( 'aiWorkflow.publishStatusPublishPending' ) ,
@@ -668,6 +995,25 @@ function handleHeaderButtonClick(data: any) {
< template >
< div class = "flex h-full flex-col gap-6 p-6" >
< WorkflowModal ref = "saveDialog" @reload ="reset" / >
< ElDialog
v-model = "apiInstructionVisible"
:title = "$t('aiWorkflow.apiInstruction')"
width = "780px"
class = "workflow-api-dialog"
:footer = "false"
>
< div
v-if = "apiInstructionRow"
class = "workflow-api-markdown-wrap"
@click ="handleApiDocClick"
>
< ElXMarkdown
:markdown = "apiDocMarkdown"
:allow-html = "true"
:sanitize = "false"
/ >
< / div >
< / ElDialog >
< HeaderSearch
:buttons = "headerButtons"
@search ="handleSearch"
@@ -705,7 +1051,10 @@ function handleHeaderButtonClick(data: any) {
>
< span class = "workflow-publish-chip__dot" > < / span >
< span > { {
resolvePublishStatusMetaByInstance ( item . displayPublishStatus , item . publishStatus ) . label
resolvePublishStatusMetaByInstance (
item . displayPublishStatus ,
item . publishStatus ,
) . label
} } < / span >
< / div >
< / template >
@@ -1102,4 +1451,153 @@ button.workflow-scope-chip:disabled {
border - radius : 16 px ;
box - shadow : 0 18 px 34 px - 28 px hsl ( var ( -- foreground ) / 20 % ) ;
}
. workflow - api - markdown - wrap {
max - height : 65 vh ;
padding : 0 4 px ;
overflow - y : auto ;
font - size : 14 px ;
line - height : 1.7 ;
color : hsl ( var ( -- foreground ) ) ;
}
. workflow - api - markdown - wrap : : - webkit - scrollbar {
width : 6 px ;
}
. workflow - api - markdown - wrap : : - webkit - scrollbar - thumb {
background - color : hsl ( var ( -- muted - foreground ) / 20 % ) ;
border - radius : 3 px ;
}
. workflow - api - markdown - wrap : deep ( h2 ) {
padding - bottom : 8 px ;
margin - top : 28 px ;
margin - bottom : 16 px ;
font - size : 18 px ;
font - weight : 700 ;
color : hsl ( var ( -- foreground ) ) ;
border - bottom : 1 px solid hsl ( var ( -- border ) ) ;
}
. workflow - api - markdown - wrap : deep ( h2 : first - child ) {
margin - top : 0 ;
}
. workflow - api - markdown - wrap : deep ( h3 ) {
margin - top : 20 px ;
margin - bottom : 10 px ;
font - size : 15 px ;
font - weight : 600 ;
color : hsl ( var ( -- foreground ) ) ;
}
. workflow - api - markdown - wrap : deep ( h4 ) {
margin - top : 16 px ;
margin - bottom : 8 px ;
font - size : 14 px ;
font - weight : 600 ;
color : hsl ( var ( -- foreground ) ) ;
}
. workflow - api - markdown - wrap : deep ( hr ) {
margin : 24 px 0 ;
border : none ;
border - top : 1 px solid hsl ( var ( -- border ) ) ;
}
. workflow - api - markdown - wrap : deep ( p ) {
margin : 8 px 0 ;
}
. workflow - api - markdown - wrap : deep ( code ) {
padding : 2 px 6 px ;
font - family : ui - monospace , SFMono - Regular , Menlo , Monaco , Consolas , monospace ;
font - size : 13 px ;
color : hsl ( var ( -- foreground ) ) ;
background : hsl ( var ( -- muted ) / 50 % ) ;
border - radius : 4 px ;
}
. workflow - api - markdown - wrap : deep ( pre ) {
max - height : 280 px ;
padding : 14 px 16 px ;
margin : 10 px 0 ;
overflow : auto ;
background : hsl ( 220 14 % 96 % ) ;
border : 1 px solid hsl ( var ( -- border ) ) ;
border - radius : 8 px ;
}
. workflow - api - markdown - wrap : deep ( pre code ) {
padding : 0 ;
font - size : 12.5 px ;
color : hsl ( var ( -- foreground ) ) ;
background : transparent ;
border - radius : 0 ;
}
. workflow - api - markdown - wrap : deep ( table ) {
width : 100 % ;
margin : 10 px 0 ;
border - collapse : collapse ;
}
. workflow - api - markdown - wrap : deep ( th ) ,
. workflow - api - markdown - wrap : deep ( td ) {
padding : 8 px 12 px ;
font - size : 13 px ;
text - align : left ;
border : 1 px solid hsl ( var ( -- border ) ) ;
}
. workflow - api - markdown - wrap : deep ( th ) {
font - weight : 600 ;
color : hsl ( var ( -- foreground ) ) ;
background : hsl ( var ( -- muted ) / 40 % ) ;
}
. workflow - api - markdown - wrap : deep ( td ) {
color : hsl ( var ( -- foreground ) / 85 % ) ;
}
. workflow - api - markdown - wrap : deep ( blockquote ) {
padding : 8 px 16 px ;
margin : 10 px 0 ;
color : hsl ( var ( -- muted - foreground ) ) ;
border - left : 3 px solid hsl ( var ( -- primary ) / 40 % ) ;
}
. workflow - api - markdown - wrap : deep ( strong ) {
font - weight : 600 ;
color : hsl ( var ( -- foreground ) ) ;
}
. workflow - api - markdown - wrap : deep ( . api - url - copy - btn ) {
display : inline - flex ;
align - items : center ;
justify - content : center ;
width : 24 px ;
height : 24 px ;
padding : 0 ;
margin - left : 6 px ;
color : hsl ( var ( -- muted - foreground ) ) ;
vertical - align : middle ;
cursor : pointer ;
background : transparent ;
border : 1 px solid hsl ( var ( -- border ) ) ;
border - radius : 6 px ;
transition : all 0.15 s ;
}
. 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 % ) ;
}
: global ( . workflow - api - dialog . el - dialog _ _body ) {
padding : 0 20 px 20 px ;
overflow : hidden ;
}
< / style >