feat(Workflow): 新增工作流节点组件
This commit is contained in:
parent
43e29a267e
commit
94952fb924
23
app.vue
23
app.vue
@ -8,6 +8,9 @@
|
||||
|
||||
<!-- 应用引导-款式融合-->
|
||||
<StyleFusionGuide/>
|
||||
|
||||
<!-- 工作流节点-Markdown转Html-->
|
||||
<Markdown2Html v-model:data="data"/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@ -39,4 +42,24 @@ provide<GlobalInjectWorkflowAppStatus>(GlobalInjectKeyConst.AllWorkFlowApps, {
|
||||
});
|
||||
|
||||
|
||||
|
||||
const data = ref({
|
||||
_meta: {
|
||||
title: "markdown转html"
|
||||
},
|
||||
inputs: {},
|
||||
data: {
|
||||
method: 'POST', // 请求方法
|
||||
url: 'http://localhost:3200/plugins/api/markdown2html', // 请求地址,节点的业务逻辑,需要在接口中完成
|
||||
body: {
|
||||
markdown: '1' // 接口请求体内容,输入的markdown内容
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// 依据接口自行扩展,认证等信息
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
74
components/Markdown2Html/index.vue
Normal file
74
components/Markdown2Html/index.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import type { IExecutionNodeStatusItem } from "~/composables/worklfow/node/node.interface";
|
||||
|
||||
const data = defineModel<INodeData<NodeTypeEnum.Markdown2Html>>("data");
|
||||
|
||||
const props = defineProps<{
|
||||
nodeResult: IExecutionNodeStatusItem
|
||||
}>(); // 节点执行产出结果
|
||||
|
||||
/**
|
||||
* 文本内容
|
||||
*/
|
||||
const textContent = computed(() => {
|
||||
return props.nodeResult?.result?.output_content?.filter(
|
||||
item => item.type === "text"
|
||||
);
|
||||
});
|
||||
|
||||
const initInputs = () => {
|
||||
if(!data.value?.inputs){
|
||||
Object.assign(data.value.inputs, { markdown: "" });
|
||||
}
|
||||
};
|
||||
|
||||
initInputs();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="w-full p-2 min-w-96"
|
||||
@keydown.stop
|
||||
>
|
||||
<div>
|
||||
<!-- 需要输入的变量 -->
|
||||
<a-form
|
||||
layout="vertical"
|
||||
:model="data.data.body"
|
||||
>
|
||||
<div class="relative">
|
||||
<a-form-item
|
||||
label="markdown内容"
|
||||
name="markdown"
|
||||
:rules="[{ required: true, message: `请输入内容` }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="data.data.body.markdown"
|
||||
placeholder='请输入markdown'
|
||||
class="border"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div
|
||||
v-if="data.inputs.markdown"
|
||||
class="absolute inset-0 z-10 flex items-center justify-center bg-gray-500/40 rounded"
|
||||
>
|
||||
<div
|
||||
class="bg-gray-500/60 text-white text-xs rounded px-2 py-0.5 shadow"
|
||||
>
|
||||
已关联节点 #{{ data.inputs.markdown[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
<div v-show="textContent?.length">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="转换结果">
|
||||
<a-textarea v-for="(output, index) in textContent || []" :value="output.content" readonly class="border"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
52
composables/worklfow/node/Markdown2HtmlNode.ts
Normal file
52
composables/worklfow/node/Markdown2HtmlNode.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { NodeCategoryEnum, PluginBaseNode } from "./PluginBaseNode";
|
||||
import {
|
||||
Markdown2Html
|
||||
} from '#components'
|
||||
|
||||
import { NodeTypeEnum } from "~/composables/worklfow/node/node.interface";
|
||||
|
||||
export default class Markdown2HtmlNode extends PluginBaseNode {
|
||||
static override nodeType = NodeTypeEnum.Markdown2Html // 节点类型
|
||||
|
||||
static override getNodeList() {
|
||||
return [
|
||||
{
|
||||
type: Markdown2HtmlNode.nodeType, // 节点类型
|
||||
label: 'Markdown转HTML', // 标签
|
||||
description: 'Markdown转HTML', // 描述
|
||||
category: NodeCategoryEnum.BASE, // 分类
|
||||
icon: 'material-symbols-light:markdown-paste' // 图标
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
initData() {
|
||||
// 数据类型 IApiPluginNodeData
|
||||
return {
|
||||
method: 'POST', // 请求方法
|
||||
url: 'http://localhost:3200/plugins/api/markdown2html', // 请求地址,节点的业务逻辑,需要在接口中完成
|
||||
body: {
|
||||
markdown: '' // 接口请求体内容,输入的markdown内容
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// 依据接口自行扩展,认证等信息
|
||||
}
|
||||
} // 初始化数据
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入信息关联父节点产出的数据类型及数据路径
|
||||
*/
|
||||
override createOutputSpec(): INodeOutputSpec {
|
||||
return {
|
||||
type: 'text', // 文本类型
|
||||
defaultPath: ['output_content', '*', 'content'] // 数据路径,文本为 ['output_content', '*', 'content'],媒体类型为 ['output_content', '*', 'url']
|
||||
}
|
||||
}
|
||||
|
||||
// 画布节点 UI
|
||||
static override renderNode() {
|
||||
return Markdown2Html // 节点组件
|
||||
}
|
||||
}
|
||||
79
composables/worklfow/node/PluginBaseNode.ts
Normal file
79
composables/worklfow/node/PluginBaseNode.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { type INodeData, type INodeOutputSpec, NodeTypeEnum } from "~/composables/worklfow/node/node.interface";
|
||||
|
||||
export interface NodeOptions {
|
||||
id?: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export const NodeCategoryEnum = {
|
||||
BASE: '基础',
|
||||
FUNCTIONAL: '功能',
|
||||
APP: '应用',
|
||||
Agent: '智能体'
|
||||
} as const
|
||||
|
||||
export type NodeCategoryEnum =
|
||||
(typeof NodeCategoryEnum)[keyof typeof NodeCategoryEnum]
|
||||
|
||||
export interface NodeViewData {
|
||||
type: NodeTypeEnum
|
||||
label: string
|
||||
description?: string
|
||||
category: NodeCategoryEnum
|
||||
id?: string // appId / agentId // component
|
||||
previewUrl?: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
export abstract class PluginBaseNode {
|
||||
id?: string
|
||||
title: string
|
||||
data: Record<string, unknown>
|
||||
|
||||
static nodeType = ''
|
||||
static getNodeList(): NodeViewData[] {
|
||||
return []
|
||||
}
|
||||
|
||||
constructor(options: NodeOptions) {
|
||||
this.id = options.id
|
||||
this.title = options.title
|
||||
this.data = this.initData(options)
|
||||
}
|
||||
|
||||
/** 初始化节点数据 */
|
||||
abstract initData(options?: NodeOptions): Record<string, unknown>
|
||||
|
||||
create(title: string, id?: string): INodeData {
|
||||
return {
|
||||
class_type: this.getNodeType() as NodeTypeEnum,
|
||||
plugin_type: NodeTypeEnum.ApiPlugin, // 远程插件节点必填项
|
||||
_meta: {
|
||||
title
|
||||
},
|
||||
inputs: {},
|
||||
data: this.data,
|
||||
outputSpec: this.createOutputSpec(id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 节点类型 */
|
||||
getNodeType() {
|
||||
return (this.constructor as typeof PluginBaseNode).nodeType
|
||||
}
|
||||
|
||||
/** 输出规格(可选) */
|
||||
createOutputSpec(_id?: string): undefined | INodeOutputSpec {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** 节点本体 UI */
|
||||
static renderNode(): Component {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
/** 节点属性面板 UI(可选) */
|
||||
static renderProperties(): Component {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
288
composables/worklfow/node/node.interface.ts
Normal file
288
composables/worklfow/node/node.interface.ts
Normal file
@ -0,0 +1,288 @@
|
||||
/**
|
||||
* 节点类型枚举
|
||||
*/
|
||||
export enum NodeTypeEnum {
|
||||
API = 'API',
|
||||
POLL_API = 'POLL_API',
|
||||
OUTPUT = 'OUTPUT',
|
||||
INPUT = 'INPUT',
|
||||
URL2FILE = 'URL2FILE',
|
||||
OpenAIImage = 'OpenAIImage',
|
||||
DrawApp = 'DrawApp',
|
||||
Agent = 'Agent',
|
||||
Preview = 'Preview',
|
||||
ComponentInput = 'ComponentInput',
|
||||
SAVETOLOCALFILE = 'SaveLocalFile',
|
||||
ApiPlugin='ApiPlugin',
|
||||
Markdown2Html='Markdown2Html'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 节点元数据
|
||||
*/
|
||||
export interface INodeMeta {
|
||||
title: string
|
||||
description?: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点数据接口
|
||||
*/
|
||||
export interface INodeData<T extends NodeTypeEnum = NodeTypeEnum> {
|
||||
class_type: T
|
||||
plugin_type?: NodeTypeEnum.ApiPlugin // 插件节点,必填
|
||||
_meta: INodeMeta
|
||||
inputs?: {
|
||||
[key: string]: any
|
||||
}
|
||||
output?: any
|
||||
data?: NodeDataMap[T]
|
||||
outputSpec?: INodeOutputSpec
|
||||
}
|
||||
|
||||
export interface INodeOutputSpec {
|
||||
type: FileType
|
||||
defaultPath: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点数据映射类型
|
||||
* @type {NodeDataMap}
|
||||
* @property {IAPINodeData} API - API 节点数据
|
||||
* @property {IPollAPINodeData} POLL_API - 轮询 API 节点数据
|
||||
* @property {IInputNodeData} INPUT - 输入节点数据
|
||||
* @property {IOutputNodeData} OUTPUT - 输出节点数据
|
||||
* @property {IUrl2FileNodeData} URL2FILE - URL转文件节点数据
|
||||
* @property {IOpenAIImageNodeData} OpenAIImage - 云平台模型接口
|
||||
* @property {IDrawAppNodeData} DrawApp - 绘图应用
|
||||
* @property {IAgentNodeData} Agent - 智能体
|
||||
* @property {IPreviewNodeData} Preview - 预览节点
|
||||
*/
|
||||
export type NodeDataMap = {
|
||||
[NodeTypeEnum.API]: IAPINodeData
|
||||
[NodeTypeEnum.POLL_API]: IPollAPINodeData
|
||||
[NodeTypeEnum.INPUT]: IInputNodeData
|
||||
[NodeTypeEnum.OUTPUT]: IOutputNodeData
|
||||
[NodeTypeEnum.URL2FILE]: IUrl2FileNodeData
|
||||
[NodeTypeEnum.OpenAIImage]: IOpenAIImageNodeData
|
||||
[NodeTypeEnum.DrawApp]: IDrawAppNodeData
|
||||
[NodeTypeEnum.Agent]: IAgentNodeData
|
||||
[NodeTypeEnum.Preview]: IPreviewNodeData
|
||||
[NodeTypeEnum.ComponentInput]: IComponentInputData
|
||||
[NodeTypeEnum.ApiPlugin]: IApiPluginNodeData;
|
||||
[NodeTypeEnum.Markdown2Html]: IApiPluginNodeData
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* API 节点数据类型
|
||||
* @interface IAPINodeData
|
||||
* @param userId - 用户 ID
|
||||
* @param successCondition - 成功条件
|
||||
* @param pollTime - 轮询时间
|
||||
* @param pollTimeout - 轮询超时时间
|
||||
* @param response - 响应数据
|
||||
*/
|
||||
export interface IAPINodeData {
|
||||
headers?: { [key: string]: string }
|
||||
params?: { [key: string]: string }
|
||||
pathParams?: { [key: string]: string }
|
||||
body?: { [key: string]: any }
|
||||
successCondition?: { key: string[], value: string | number | boolean }[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询 API 节点数据类型
|
||||
* @interface IPollAPINodeData
|
||||
* @param headers - 请求头
|
||||
* @param body - 请求体
|
||||
* @param params - 请求参数
|
||||
* @param pathParams - 请求路径参数
|
||||
* @param successCondition - 成功条件
|
||||
* @param pollTime - 轮询时间
|
||||
* @param pollTimeout - 轮询超时时间
|
||||
*/
|
||||
export interface IPollAPINodeData {
|
||||
headers?: { [key: string]: string }
|
||||
body?: { [key: string]: any }
|
||||
params?: { [key: string]: string }
|
||||
pathParams?: { [key: string]: string }
|
||||
successCondition?: { key: string[], value: string | number | boolean }[]
|
||||
pollTime?: number
|
||||
pollTimeout?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入字段类型枚举
|
||||
* @enum {string} InputFieldTypeEnum
|
||||
* @property {string} boolean - 布尔类型
|
||||
* @property {string} string - 字符串类型
|
||||
* @property {string} number - 数字类型
|
||||
*/
|
||||
export enum InputFieldTypeEnum {
|
||||
boolean = 'boolean',
|
||||
string = 'string',
|
||||
number = 'number'
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入节点数据类型
|
||||
* @interface IInputNodeData
|
||||
* @param schema - 输入数据结构
|
||||
*/
|
||||
export interface IInputNodeData {
|
||||
schema: IInputNodeDataSchema
|
||||
}
|
||||
|
||||
export interface IInputNodeDataSchema {
|
||||
[key: string]: {
|
||||
type: InputFieldTypeEnum
|
||||
default?: any
|
||||
required?: boolean
|
||||
description?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface IOutputNodeDataSchema {
|
||||
[key: string]: {
|
||||
outputType?: OutputType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出节点数据类型
|
||||
* @interface IOutputNodeData
|
||||
* @param schema - 输出数据
|
||||
*/
|
||||
export interface IOutputNodeData {
|
||||
schema: IOutputNodeDataSchema
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出节点数据类型
|
||||
* @interface IUrl2FileNodeData
|
||||
* @param output - 输出数据
|
||||
*/
|
||||
export interface IUrl2FileNodeData {
|
||||
[key: string]: File
|
||||
}
|
||||
|
||||
export type openAIImageNodeType =
|
||||
| 'editImage'
|
||||
| 'generateImage'
|
||||
| 'generateVideo'
|
||||
| 'multiViewGenerate3D'
|
||||
| 'imageGenerate3D'
|
||||
| 'textGenerate3D'
|
||||
|
||||
export interface IOpenAIImageNodeData extends INodeDataCondition {
|
||||
type: openAIImageNodeType
|
||||
}
|
||||
|
||||
export interface IDrawAppNodeData extends INodeDataCondition {
|
||||
options: unknown
|
||||
}
|
||||
|
||||
export interface IAgentNodeData extends INodeDataCondition {
|
||||
agentId: string
|
||||
model: string
|
||||
systemPrompt: string // 系统提示词中含有变量
|
||||
userPrompt: string // 用户提示词(变量)
|
||||
params: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface IPreviewNodeData {
|
||||
sourceNode: {
|
||||
id: string
|
||||
}[]
|
||||
}
|
||||
export interface IComponentInputData {
|
||||
component: string
|
||||
params: {
|
||||
value: string
|
||||
type: FileType
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 插件需要继承 IApiPluginNodeData
|
||||
// */
|
||||
// export interface IMarkdown2HtmlData extends IApiPluginNodeData {
|
||||
// input: ''
|
||||
// output: ''
|
||||
// }
|
||||
export interface IApiPluginNodeData extends INodeDataCondition {
|
||||
method: 'POST' // 请求方法
|
||||
url: string; // 请求地址
|
||||
body: Record<string, unknown> // 请求体
|
||||
headers: { [key: string]: string } // 请求头
|
||||
}
|
||||
|
||||
|
||||
export type INodeDataCondition = Partial<
|
||||
INodeDataSuccessCondition & INodeDataFailureCondition
|
||||
>
|
||||
|
||||
export interface INodeDataSuccessCondition {
|
||||
successCondition?: { key: string[], value: string | number | boolean }[]
|
||||
}
|
||||
|
||||
export interface INodeDataFailureCondition {
|
||||
failureCondition: { key: string[], value: string | number | boolean }[]
|
||||
}
|
||||
|
||||
export type FileType = 'image' | 'video' | 'audio' | '3d' | 'text' | 'file'
|
||||
export type OutputType = 'image' | 'video' | 'text' | 'audio' | '3d'
|
||||
|
||||
|
||||
export interface GeneralOutput {
|
||||
/**
|
||||
* The generated output type, defaults to `image`,support `image` and `video` etc.
|
||||
*/
|
||||
type?: FileType;
|
||||
/**
|
||||
* The base64-encoded JSON of the generated image. Default value for `gpt-image-1`,
|
||||
* and only present if `response_format` is set to `b64_json` for `dall-e-2` and
|
||||
* `dall-e-3`.
|
||||
*/
|
||||
b64_json?: string;
|
||||
|
||||
/**
|
||||
* For `dall-e-3` only, the revised prompt that was used to generate the image.
|
||||
*/
|
||||
revised_prompt?: string;
|
||||
|
||||
/**
|
||||
* When using `dall-e-2` or `dall-e-3`, the URL of the generated image if
|
||||
* `response_format` is set to `url` (default value). Unsupported for
|
||||
* `gpt-image-1`.
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* The generated content.
|
||||
*/
|
||||
content?: string;
|
||||
/**
|
||||
* The role of the generated content.
|
||||
*/
|
||||
role?: string;
|
||||
|
||||
/** buffer */
|
||||
buffer?: Buffer;
|
||||
}
|
||||
|
||||
export interface IExecutionNodeStatusItem {
|
||||
status: 'started' | 'process' | 'success' | 'failed'
|
||||
result: unknown
|
||||
message?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 节点输出类型
|
||||
*/
|
||||
export interface NodeOutput {
|
||||
output_content: GeneralOutput[]
|
||||
}
|
||||
|
||||
84
docs/DEVELOPERS-WORKFLOWNODE.md
Normal file
84
docs/DEVELOPERS-WORKFLOWNODE.md
Normal file
@ -0,0 +1,84 @@
|
||||
# 工作流节点开发文档
|
||||
> 参考示例:Markdown2HTML节点
|
||||
|
||||
## 节点入口文件
|
||||
> composables/worklfow/node/Markdown2HtmlNode.ts
|
||||
> 此文件定义了节点所需要的所有内容
|
||||
- nodeType 节点类型
|
||||
- getNodeList 节点清单,对应添加节点时,显示的内容
|
||||
- initData 初始化数据,当前的插件节点,后端逻辑都是通过接口处理的
|
||||
- 示例接口:`server/api/markdown2html.post.ts` **注意返回结果类型:NodeOutput**
|
||||
- createOutputSpec 定义参数关联节点产出的路径
|
||||
- 文本类型为 ['output_content', '*', 'content']
|
||||
- 媒体类型为 ['output_content', '*', 'url']
|
||||
- renderNode: 节点对应的前端组件`components/Markdown2Html/index.vue`
|
||||
|
||||
```typescript
|
||||
import { NodeCategoryEnum, PluginBaseNode } from "./PluginBaseNode";
|
||||
import {
|
||||
Markdown2Html
|
||||
} from '#components'
|
||||
|
||||
import { NodeTypeEnum } from "~/composables/worklfow/node/node.interface";
|
||||
|
||||
export default class Markdown2HtmlNode extends PluginBaseNode {
|
||||
static override nodeType = NodeTypeEnum.Markdown2Html // 节点类型
|
||||
|
||||
static override getNodeList() {
|
||||
return [
|
||||
{
|
||||
type: Markdown2HtmlNode.nodeType, // 节点类型
|
||||
label: 'Markdown转HTML', // 标签
|
||||
description: 'Markdown转HTML', // 描述
|
||||
category: NodeCategoryEnum.BASE, // 分类
|
||||
icon: 'material-symbols-light:markdown-paste' // 图标
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
initData() {
|
||||
// 数据类型 IApiPluginNodeData
|
||||
return {
|
||||
method: 'POST', // 请求方法
|
||||
url: 'http://localhost:3200/plugins/api/markdown2html', // 请求地址,节点的业务逻辑,需要在接口中完成
|
||||
body: {
|
||||
markdown: '' // 接口请求体内容,输入的markdown内容
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// 依据接口自行扩展,认证等信息
|
||||
}
|
||||
} // 初始化数据
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入信息关联父节点产出的数据类型及数据路径
|
||||
*/
|
||||
override createOutputSpec(): INodeOutputSpec {
|
||||
return {
|
||||
type: 'text', // 文本类型
|
||||
defaultPath: ['output_content', '*', 'content'] // 数据路径,文本为 ['output_content', '*', 'content'],媒体类型为 ['output_content', '*', 'url']
|
||||
}
|
||||
}
|
||||
|
||||
// 画布节点 UI
|
||||
static override renderNode() {
|
||||
return Markdown2Html // 节点组件
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 维护组件信息文件:
|
||||
> manifest/Markdown2HtmlNode.ts
|
||||
> 需要参照IComponentMateInfo类型约束文件进行定义
|
||||
|
||||
|
||||
## 组件效果
|
||||
|
||||
1. 添加节点
|
||||

|
||||
2. 关联节点
|
||||

|
||||
|
||||
|
||||
BIN
docs/images/WorkflowNode-AddNode.png
Normal file
BIN
docs/images/WorkflowNode-AddNode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 589 KiB |
BIN
docs/images/WorkflowNode-Associated.png
Normal file
BIN
docs/images/WorkflowNode-Associated.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 618 KiB |
13
manifest/Markdown2HtmlNode.ts
Normal file
13
manifest/Markdown2HtmlNode.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 页面插件示例
|
||||
*/
|
||||
|
||||
import {ComponentSceneConst, type IComponentMateInfo} from "~/composables";
|
||||
export default {
|
||||
name: "Markdown2Html", // 插件名称 备注:带上特殊标识,不要和已有插件重名,比如Remote+插件名
|
||||
path: "./composables/worklfow/node/Markdown2HtmlNode.ts", // 插件路径
|
||||
scenes: ComponentSceneConst.Workflow, // 组件场景
|
||||
description: "",
|
||||
data: undefined,
|
||||
} satisfies IComponentMateInfo<typeof ComponentSceneConst.Workflow>;
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"ant-design-vue": "4.2.6",
|
||||
"markdown-it": "^14.1.0",
|
||||
"nuxt": "^4.0.3",
|
||||
"vue": "^3.5.14",
|
||||
"vue-router": "^4.5.1"
|
||||
|
||||
@ -23,9 +23,15 @@ importers:
|
||||
ant-design-vue:
|
||||
specifier: 4.2.6
|
||||
version: 4.2.6(vue@3.5.18(typescript@5.9.2))
|
||||
markdown-it:
|
||||
specifier: ^14.1.0
|
||||
version: 14.1.0
|
||||
nuxt:
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@24.3.0)(@vue/compiler-sfc@3.5.18)(db0@0.3.2)(eslint@9.33.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.77.7)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.77.7)(terser@5.43.1)(yaml@2.8.1))(yaml@2.8.1)
|
||||
prismjs:
|
||||
specifier: ^1.30.0
|
||||
version: 1.30.0
|
||||
vue:
|
||||
specifier: ^3.5.14
|
||||
version: 3.5.18(typescript@5.9.2)
|
||||
@ -3892,6 +3898,9 @@ packages:
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
linkify-it@5.0.0:
|
||||
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
|
||||
|
||||
listhen@1.9.0:
|
||||
resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==}
|
||||
hasBin: true
|
||||
@ -3978,6 +3987,10 @@ packages:
|
||||
magicast@0.3.5:
|
||||
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
|
||||
|
||||
markdown-it@14.1.0:
|
||||
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||
hasBin: true
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -3991,6 +4004,9 @@ packages:
|
||||
mdn-data@2.12.2:
|
||||
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
|
||||
|
||||
mdurl@2.0.0:
|
||||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||
|
||||
media-typer@0.3.0:
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -4936,6 +4952,10 @@ packages:
|
||||
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
|
||||
engines: {node: ^14.13.1 || >=16.0.0}
|
||||
|
||||
prismjs@1.30.0:
|
||||
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
@ -4957,6 +4977,10 @@ packages:
|
||||
pump@3.0.3:
|
||||
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
|
||||
|
||||
punycode.js@2.3.1:
|
||||
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@ -5535,6 +5559,9 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
uc.micro@2.1.0:
|
||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||
|
||||
ufo@1.6.1:
|
||||
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
|
||||
|
||||
@ -9865,6 +9892,10 @@ snapshots:
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
linkify-it@5.0.0:
|
||||
dependencies:
|
||||
uc.micro: 2.1.0
|
||||
|
||||
listhen@1.9.0:
|
||||
dependencies:
|
||||
'@parcel/watcher': 2.5.1
|
||||
@ -9971,6 +10002,15 @@ snapshots:
|
||||
'@babel/types': 7.28.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
markdown-it@14.1.0:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
entities: 4.5.0
|
||||
linkify-it: 5.0.0
|
||||
mdurl: 2.0.0
|
||||
punycode.js: 2.3.1
|
||||
uc.micro: 2.1.0
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
mdn-data@2.0.14: {}
|
||||
@ -9979,6 +10019,8 @@ snapshots:
|
||||
|
||||
mdn-data@2.12.2: {}
|
||||
|
||||
mdurl@2.0.0: {}
|
||||
|
||||
media-typer@0.3.0: {}
|
||||
|
||||
merge-options@3.0.4:
|
||||
@ -11075,6 +11117,8 @@ snapshots:
|
||||
|
||||
pretty-bytes@6.1.1: {}
|
||||
|
||||
prismjs@1.30.0: {}
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
|
||||
process@0.11.10: {}
|
||||
@ -11093,6 +11137,8 @@ snapshots:
|
||||
end-of-stream: 1.4.5
|
||||
once: 1.4.0
|
||||
|
||||
punycode.js@2.3.1: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qs@6.14.0:
|
||||
@ -11735,6 +11781,8 @@ snapshots:
|
||||
|
||||
typescript@5.9.2: {}
|
||||
|
||||
uc.micro@2.1.0: {}
|
||||
|
||||
ufo@1.6.1: {}
|
||||
|
||||
ultrahtml@1.6.0: {}
|
||||
|
||||
38
server/api/markdown2html.post.ts
Normal file
38
server/api/markdown2html.post.ts
Normal file
@ -0,0 +1,38 @@
|
||||
// server/api/markdown2html.post.ts
|
||||
import { defineEventHandler, readBody } from "h3";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import type { NodeOutput } from "~/composables/worklfow/node/node.interface";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// 获取请求体
|
||||
const body = await readBody<{ markdown: string }>(event);
|
||||
|
||||
if (!body?.markdown) {
|
||||
return { error: "Missing markdown content" };
|
||||
}
|
||||
|
||||
// 初始化 Markdown-it
|
||||
const md = new MarkdownIt({
|
||||
html: true, // 支持 HTML 标签
|
||||
linkify: true, // 自动识别 URL
|
||||
typographer: true // 美化引号、破折号等
|
||||
});
|
||||
|
||||
// 转换
|
||||
const html = md.render(body.markdown);
|
||||
|
||||
/**
|
||||
* 节点输出类型标准数据结构
|
||||
*/
|
||||
return {
|
||||
output_content: [{
|
||||
type: "text",
|
||||
content: html
|
||||
}]
|
||||
} as NodeOutput;
|
||||
} catch (err) {
|
||||
console.error("Markdown to HTML conversion failed:", err);
|
||||
return { error: "Conversion failed", details: err.message };
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user