@andrewyang/ai-workflow-editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nuxt/app.config.mjs +18 -0
- package/.nuxt/components.d.ts +910 -0
- package/.nuxt/dev/index.mjs +4103 -0
- package/.nuxt/dev/index.mjs.map +1 -0
- package/.nuxt/dist/server/client.manifest.mjs +4 -0
- package/.nuxt/dist/server/client.precomputed.mjs +1 -0
- package/.nuxt/dist/server/server.mjs +1 -0
- package/.nuxt/i18n-route-resources.mjs +3 -0
- package/.nuxt/imports.d.ts +43 -0
- package/.nuxt/manifest/latest.json +1 -0
- package/.nuxt/manifest/meta/dev.json +1 -0
- package/.nuxt/nitro.json +17 -0
- package/.nuxt/nuxt.d.ts +24 -0
- package/.nuxt/nuxt.json +9 -0
- package/.nuxt/schema/nuxt.schema.d.ts +17 -0
- package/.nuxt/schema/nuxt.schema.json +3 -0
- package/.nuxt/tsconfig.json +234 -0
- package/.nuxt/tsconfig.server.json +185 -0
- package/.nuxt/types/app-defaults.d.ts +7 -0
- package/.nuxt/types/app.config.d.ts +31 -0
- package/.nuxt/types/build.d.ts +29 -0
- package/.nuxt/types/builder-env.d.ts +1 -0
- package/.nuxt/types/components.d.ts +915 -0
- package/.nuxt/types/i18n-plugin.d.ts +123 -0
- package/.nuxt/types/imports.d.ts +993 -0
- package/.nuxt/types/middleware.d.ts +17 -0
- package/.nuxt/types/nitro-config.d.ts +14 -0
- package/.nuxt/types/nitro-imports.d.ts +170 -0
- package/.nuxt/types/nitro-layouts.d.ts +17 -0
- package/.nuxt/types/nitro-nuxt.d.ts +39 -0
- package/.nuxt/types/nitro-routes.d.ts +17 -0
- package/.nuxt/types/nitro.d.ts +3 -0
- package/.nuxt/types/plugins.d.ts +43 -0
- package/.nuxt/types/schema.d.ts +213 -0
- package/.nuxt/types/vue-shim.d.ts +0 -0
- package/.trae/rules/rule.md +38 -0
- package/.vscode/settings.json +5 -0
- package/nuxt.config.ts +38 -0
- package/package.json +42 -0
- package/pnpm-lock.yaml +24 -0
- package/src/app.vue +46 -0
- package/src/components/AiWorkflowEditor.vue +142 -0
- package/src/components/ai/AiChatPanel.vue +135 -0
- package/src/components/ai/AiGenerator.vue +62 -0
- package/src/components/editor/Canvas.vue +175 -0
- package/src/components/editor/FormatPanel.vue +327 -0
- package/src/components/editor/NodePanel.vue +240 -0
- package/src/components/editor/PropertyPanel.vue +348 -0
- package/src/components/editor/Toolbar.vue +49 -0
- package/src/components/editor/nodes/AiNode.vue +77 -0
- package/src/components/editor/nodes/NormalNode.vue +75 -0
- package/src/components/editor/nodes/SysmlBlockNode.vue +72 -0
- package/src/components/editor/nodes/SysmlRequirementNode.vue +72 -0
- package/src/components/editor/nodes/SysmlUseCaseNode.vue +62 -0
- package/src/composables/useAi.ts +82 -0
- package/src/i18n.config.ts +5 -0
- package/src/locales/en.json +106 -0
- package/src/locales/zh.json +106 -0
- package/src/plugins/aiWorkflowEditor.ts +6 -0
- package/src/types/ai.ts +7 -0
- package/src/types/workflow.ts +25 -0
- package/src/utils/llmAdapter.ts +46 -0
- package/tsconfig.json +3 -0
- package/uno.config.ts +15 -0
- package//345/211/215/347/253/257/345/256/232/345/210/266/345/274/200/345/217/221/357/274/232ai-workflow-editor/345/274/200/345/217/221/350/256/241/345/210/222.md +655 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="sysml-req-node group relative min-w-[180px] h-full transition-all" :style="containerStyle">
|
|
3
|
+
<NodeResizer :is-visible="selected" :min-width="120" :min-height="60" />
|
|
4
|
+
<Handle type="target" position="top" class="!bg-gray-700 !w-2.5 !h-2.5 !border-2 !border-white" />
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col h-full">
|
|
7
|
+
<!-- Header -->
|
|
8
|
+
<div class="px-2 pt-1 flex items-center justify-between border-b border-gray-300 bg-yellow-100/50">
|
|
9
|
+
<div class="text-[10px] font-bold text-gray-600">req</div>
|
|
10
|
+
<div class="text-[10px] text-gray-500">Id: 001</div>
|
|
11
|
+
</div>
|
|
12
|
+
<!-- Name -->
|
|
13
|
+
<div v-if="data.isEditing" class="px-2 py-2">
|
|
14
|
+
<input
|
|
15
|
+
v-model="data.label"
|
|
16
|
+
ref="inputRef"
|
|
17
|
+
class="nodrag w-full text-center text-sm font-bold text-gray-800 bg-white/50 border border-yellow-300 rounded px-1 focus:outline-none focus:ring-2 focus:ring-yellow-200"
|
|
18
|
+
:style="data.textStyle"
|
|
19
|
+
@blur="stopEditing"
|
|
20
|
+
@keydown.enter.prevent="stopEditing"
|
|
21
|
+
@vue:mounted="focusInput"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
<div v-else class="px-2 py-2 text-center text-sm font-bold text-gray-800 break-words" :style="data.textStyle">
|
|
25
|
+
{{ data.label }}
|
|
26
|
+
</div>
|
|
27
|
+
<!-- Text -->
|
|
28
|
+
<div class="px-2 pb-2 text-[10px] text-gray-600 italic border-t border-gray-300 pt-1 flex-1">
|
|
29
|
+
The system shall...
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<Handle type="source" position="bottom" class="!bg-gray-700 !w-2.5 !h-2.5 !border-2 !border-white" />
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import { computed, ref, nextTick } from 'vue';
|
|
39
|
+
import { Handle } from '@vue-flow/core';
|
|
40
|
+
import { NodeResizer } from '@vue-flow/node-resizer';
|
|
41
|
+
import '@vue-flow/node-resizer/dist/style.css';
|
|
42
|
+
|
|
43
|
+
const props = defineProps<{
|
|
44
|
+
data: { label: string; style?: any; textStyle?: any; isEditing?: boolean };
|
|
45
|
+
selected?: boolean;
|
|
46
|
+
}>();
|
|
47
|
+
|
|
48
|
+
const inputRef = ref<HTMLInputElement>();
|
|
49
|
+
|
|
50
|
+
const focusInput = () => {
|
|
51
|
+
nextTick(() => {
|
|
52
|
+
inputRef.value?.focus();
|
|
53
|
+
inputRef.value?.select();
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const stopEditing = () => {
|
|
58
|
+
if (props.data) {
|
|
59
|
+
props.data.isEditing = false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const containerStyle = computed(() => ({
|
|
64
|
+
backgroundColor: props.data.style?.backgroundColor || 'rgba(254, 252, 232, 0.3)', // yellow-50/30
|
|
65
|
+
borderColor: props.selected ? '#eab308' : (props.data.style?.borderColor || '#374151'),
|
|
66
|
+
borderWidth: props.data.style?.borderWidth || '2px',
|
|
67
|
+
borderStyle: props.data.style?.borderStyle || 'solid',
|
|
68
|
+
borderRadius: props.data.style?.borderRadius || '0.125rem',
|
|
69
|
+
boxShadow: props.selected ? '0 4px 6px -1px rgba(234, 179, 8, 0.2)' : (props.data.style?.boxShadow || '0 1px 2px 0 rgba(0, 0, 0, 0.05)'),
|
|
70
|
+
opacity: props.data.style?.opacity !== undefined ? props.data.style.opacity : 1,
|
|
71
|
+
}));
|
|
72
|
+
</script>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="sysml-usecase-node group relative min-w-[140px] h-[80px] flex items-center justify-center transition-all" :style="containerStyle">
|
|
3
|
+
<NodeResizer :is-visible="selected" :min-width="100" :min-height="50" />
|
|
4
|
+
<Handle type="target" position="top" class="!bg-gray-700 !w-2.5 !h-2.5 !border-2 !border-white" />
|
|
5
|
+
|
|
6
|
+
<div class="text-center px-4 w-full h-full flex items-center justify-center">
|
|
7
|
+
<textarea
|
|
8
|
+
v-if="data.isEditing"
|
|
9
|
+
v-model="data.label"
|
|
10
|
+
ref="textareaRef"
|
|
11
|
+
class="nodrag w-full text-center bg-white/50 border border-green-300 rounded px-1 text-sm font-bold text-gray-800 focus:outline-none focus:ring-2 focus:ring-green-200 resize-none overflow-hidden"
|
|
12
|
+
:style="data.textStyle"
|
|
13
|
+
rows="1"
|
|
14
|
+
@blur="stopEditing"
|
|
15
|
+
@keydown.enter.prevent="stopEditing"
|
|
16
|
+
@vue:mounted="focusInput"
|
|
17
|
+
/>
|
|
18
|
+
<div v-else class="text-sm font-bold text-gray-800 break-words w-full" :style="data.textStyle">
|
|
19
|
+
{{ data.label }}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<Handle type="source" position="bottom" class="!bg-gray-700 !w-2.5 !h-2.5 !border-2 !border-white" />
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { computed, ref, nextTick } from 'vue';
|
|
29
|
+
import { Handle } from '@vue-flow/core';
|
|
30
|
+
import { NodeResizer } from '@vue-flow/node-resizer';
|
|
31
|
+
import '@vue-flow/node-resizer/dist/style.css';
|
|
32
|
+
|
|
33
|
+
const props = defineProps<{
|
|
34
|
+
data: { label: string; style?: any; textStyle?: any; isEditing?: boolean };
|
|
35
|
+
selected?: boolean;
|
|
36
|
+
}>();
|
|
37
|
+
|
|
38
|
+
const textareaRef = ref<HTMLTextAreaElement>();
|
|
39
|
+
|
|
40
|
+
const focusInput = () => {
|
|
41
|
+
nextTick(() => {
|
|
42
|
+
textareaRef.value?.focus();
|
|
43
|
+
textareaRef.value?.select();
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const stopEditing = () => {
|
|
48
|
+
if (props.data) {
|
|
49
|
+
props.data.isEditing = false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const containerStyle = computed(() => ({
|
|
54
|
+
backgroundColor: props.data.style?.backgroundColor || '#ffffff',
|
|
55
|
+
borderColor: props.selected ? '#22c55e' : (props.data.style?.borderColor || '#374151'),
|
|
56
|
+
borderWidth: props.data.style?.borderWidth || '2px',
|
|
57
|
+
borderStyle: props.data.style?.borderStyle || 'solid',
|
|
58
|
+
borderRadius: props.data.style?.borderRadius || '50%',
|
|
59
|
+
boxShadow: props.selected ? '0 4px 6px -1px rgba(34, 197, 94, 0.2)' : (props.data.style?.boxShadow || '0 1px 2px 0 rgba(0, 0, 0, 0.05)'),
|
|
60
|
+
opacity: props.data.style?.opacity !== undefined ? props.data.style.opacity : 1,
|
|
61
|
+
}));
|
|
62
|
+
</script>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ref } from 'vue';
|
|
2
|
+
import type { AiGenerateWorkflowParams, WorkflowNode, WorkflowEdge } from '@/types/workflow';
|
|
3
|
+
import { LlmClient } from '@/utils/llmAdapter';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
// 定义 AI 返回结构的校验 Schema
|
|
7
|
+
const WorkflowSchema = z.object({
|
|
8
|
+
nodes: z.array(z.object({
|
|
9
|
+
id: z.string(),
|
|
10
|
+
type: z.string(),
|
|
11
|
+
data: z.object({
|
|
12
|
+
label: z.string(),
|
|
13
|
+
}),
|
|
14
|
+
position: z.object({
|
|
15
|
+
x: z.number(),
|
|
16
|
+
y: z.number(),
|
|
17
|
+
}),
|
|
18
|
+
})),
|
|
19
|
+
edges: z.array(z.object({
|
|
20
|
+
id: z.string(),
|
|
21
|
+
source: z.string(),
|
|
22
|
+
target: z.string(),
|
|
23
|
+
})),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* AI 工作流生成 Composable
|
|
28
|
+
*/
|
|
29
|
+
export const useAi = () => {
|
|
30
|
+
const loading = ref(false);
|
|
31
|
+
const error = ref<string | null>(null);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 生成工作流
|
|
35
|
+
* @param params 生成参数
|
|
36
|
+
* @returns 工作流节点和连线数据
|
|
37
|
+
*/
|
|
38
|
+
const generateWorkflow = async (params: AiGenerateWorkflowParams): Promise<{ nodes: WorkflowNode[]; edges: WorkflowEdge[] }> => {
|
|
39
|
+
loading.value = true;
|
|
40
|
+
error.value = null;
|
|
41
|
+
try {
|
|
42
|
+
const llmClient = new LlmClient(params.llmConfig);
|
|
43
|
+
// 构造 Prompt(引导 AI 返回结构化 JSON)
|
|
44
|
+
const prompt = `
|
|
45
|
+
根据以下描述生成工作流的 JSON 结构:${params.prompt}
|
|
46
|
+
要求:
|
|
47
|
+
1. 仅返回 JSON,不要其他文字,不要Markdown代码块标记;
|
|
48
|
+
2. JSON 包含 nodes 和 edges 两个数组;
|
|
49
|
+
3. nodes 中每个节点包含 id、type(可选:custom-ai、custom-normal)、data(包含 label)、position(x/y 为数字);
|
|
50
|
+
4. edges 中每个连线包含 id、source(源节点 ID)、target(目标节点 ID);
|
|
51
|
+
5. 节点 position 分布合理,避免重叠;
|
|
52
|
+
`;
|
|
53
|
+
const response = await llmClient.invoke(prompt);
|
|
54
|
+
|
|
55
|
+
// 简单的 JSON 提取逻辑
|
|
56
|
+
let jsonString = response.trim();
|
|
57
|
+
const firstBrace = jsonString.indexOf('{');
|
|
58
|
+
const lastBrace = jsonString.lastIndexOf('}');
|
|
59
|
+
if (firstBrace !== -1 && lastBrace !== -1) {
|
|
60
|
+
jsonString = jsonString.substring(firstBrace, lastBrace + 1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 解析并校验返回结果
|
|
64
|
+
const workflow = JSON.parse(jsonString);
|
|
65
|
+
const validatedWorkflow = WorkflowSchema.parse(workflow);
|
|
66
|
+
|
|
67
|
+
// 转换为应用内部类型
|
|
68
|
+
return validatedWorkflow as unknown as { nodes: WorkflowNode[]; edges: WorkflowEdge[] };
|
|
69
|
+
} catch (err) {
|
|
70
|
+
error.value = (err as Error).message;
|
|
71
|
+
throw err;
|
|
72
|
+
} finally {
|
|
73
|
+
loading.value = false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
loading,
|
|
79
|
+
error,
|
|
80
|
+
generateWorkflow,
|
|
81
|
+
};
|
|
82
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"loading": "Loading Editor...",
|
|
4
|
+
"generating": "Generating Workflow..."
|
|
5
|
+
},
|
|
6
|
+
"toolbar": {
|
|
7
|
+
"draw": "Draw",
|
|
8
|
+
"shape": "Shape",
|
|
9
|
+
"style": "Style",
|
|
10
|
+
"insert": "Insert",
|
|
11
|
+
"save": "Save",
|
|
12
|
+
"import": "Import",
|
|
13
|
+
"undo": "Undo",
|
|
14
|
+
"redo": "Redo",
|
|
15
|
+
"zoomOut": "Zoom Out",
|
|
16
|
+
"zoomIn": "Zoom In",
|
|
17
|
+
"fitView": "Fit View",
|
|
18
|
+
"untitled": "Untitled Workflow"
|
|
19
|
+
},
|
|
20
|
+
"nodePanel": {
|
|
21
|
+
"search": "Search shapes...",
|
|
22
|
+
"general": "General",
|
|
23
|
+
"sysml": "SysML",
|
|
24
|
+
"advanced": "Advanced",
|
|
25
|
+
"moreShapes": "More Shapes",
|
|
26
|
+
"noAdvanced": "No advanced shapes yet",
|
|
27
|
+
"nodes": {
|
|
28
|
+
"ai": "AI",
|
|
29
|
+
"aiFull": "AI Node",
|
|
30
|
+
"action": "Action",
|
|
31
|
+
"actionFull": "Action Node",
|
|
32
|
+
"square": "Square",
|
|
33
|
+
"circle": "Circle",
|
|
34
|
+
"diamond": "Diamond",
|
|
35
|
+
"triangle": "Triangle",
|
|
36
|
+
"cylinder": "Cylinder",
|
|
37
|
+
"file": "File",
|
|
38
|
+
"sysmlBlock": "Block",
|
|
39
|
+
"sysmlReq": "Requirement",
|
|
40
|
+
"sysmlUseCase": "Use Case",
|
|
41
|
+
"sysmlBlockFull": "SysML Block",
|
|
42
|
+
"sysmlReqFull": "SysML Requirement",
|
|
43
|
+
"sysmlUseCaseFull": "SysML Use Case"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"aiChat": {
|
|
47
|
+
"title": "AI Assistant",
|
|
48
|
+
"new": "NEW",
|
|
49
|
+
"v2": "Workflow Gen V2",
|
|
50
|
+
"desc": "Describe your business logic naturally. Supports complex branching and error handling.",
|
|
51
|
+
"createTitle": "Create with AI",
|
|
52
|
+
"createDesc": "Describe the workflow you want to build or upload an image to replicate.",
|
|
53
|
+
"quickStarts": "Quick Starts",
|
|
54
|
+
"docProcess": {
|
|
55
|
+
"title": "Doc Processing",
|
|
56
|
+
"desc": "Parse PDF/JSON files automatically"
|
|
57
|
+
},
|
|
58
|
+
"userAuth": {
|
|
59
|
+
"title": "User Auth",
|
|
60
|
+
"desc": "Registration & Verification flow"
|
|
61
|
+
},
|
|
62
|
+
"etl": {
|
|
63
|
+
"title": "ETL Pipeline",
|
|
64
|
+
"desc": "Extract, Transform, Load data"
|
|
65
|
+
},
|
|
66
|
+
"inputPlaceholder": "Describe your workflow...",
|
|
67
|
+
"generate": "Generate",
|
|
68
|
+
"quickInputs": {
|
|
69
|
+
"doc": "Create a document processing workflow",
|
|
70
|
+
"auth": "Create a user registration flow with email verification",
|
|
71
|
+
"etl": "Create a data ETL pipeline"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"aiGenerator": {
|
|
75
|
+
"title": "Generate Workflow with AI",
|
|
76
|
+
"placeholder": "Describe your workflow logic here...",
|
|
77
|
+
"cancel": "Cancel",
|
|
78
|
+
"generate": "Generate"
|
|
79
|
+
},
|
|
80
|
+
"propertyPanel": {
|
|
81
|
+
"title": "Format",
|
|
82
|
+
"noSelection": "No Selection",
|
|
83
|
+
"tabs": {
|
|
84
|
+
"style": "Style",
|
|
85
|
+
"text": "Text",
|
|
86
|
+
"arrange": "Arrange"
|
|
87
|
+
},
|
|
88
|
+
"style": {
|
|
89
|
+
"fill": "Fill",
|
|
90
|
+
"stroke": "Stroke",
|
|
91
|
+
"opacity": "Opacity",
|
|
92
|
+
"rounded": "Rounded",
|
|
93
|
+
"shadow": "Shadow"
|
|
94
|
+
},
|
|
95
|
+
"text": {
|
|
96
|
+
"content": "Content",
|
|
97
|
+
"color": "Text Color"
|
|
98
|
+
},
|
|
99
|
+
"arrange": {
|
|
100
|
+
"front": "Bring to Front",
|
|
101
|
+
"back": "Send to Back",
|
|
102
|
+
"size": "Size",
|
|
103
|
+
"position": "Position"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"loading": "加载编辑器...",
|
|
4
|
+
"generating": "生成工作流..."
|
|
5
|
+
},
|
|
6
|
+
"toolbar": {
|
|
7
|
+
"draw": "绘制",
|
|
8
|
+
"shape": "图形",
|
|
9
|
+
"style": "样式",
|
|
10
|
+
"insert": "插入",
|
|
11
|
+
"save": "保存",
|
|
12
|
+
"import": "导入",
|
|
13
|
+
"undo": "撤销",
|
|
14
|
+
"redo": "重做",
|
|
15
|
+
"zoomOut": "缩小",
|
|
16
|
+
"zoomIn": "放大",
|
|
17
|
+
"fitView": "适应视图",
|
|
18
|
+
"untitled": "未命名工作流"
|
|
19
|
+
},
|
|
20
|
+
"nodePanel": {
|
|
21
|
+
"search": "搜索图形...",
|
|
22
|
+
"general": "通用",
|
|
23
|
+
"sysml": "SysML",
|
|
24
|
+
"advanced": "高级",
|
|
25
|
+
"moreShapes": "更多图形",
|
|
26
|
+
"noAdvanced": "暂无高级图形",
|
|
27
|
+
"nodes": {
|
|
28
|
+
"ai": "AI",
|
|
29
|
+
"aiFull": "AI 节点",
|
|
30
|
+
"action": "动作",
|
|
31
|
+
"actionFull": "动作节点",
|
|
32
|
+
"square": "正方形",
|
|
33
|
+
"circle": "圆形",
|
|
34
|
+
"diamond": "菱形",
|
|
35
|
+
"triangle": "三角形",
|
|
36
|
+
"cylinder": "圆柱体",
|
|
37
|
+
"file": "文件",
|
|
38
|
+
"sysmlBlock": "SysML 块",
|
|
39
|
+
"sysmlReq": "需求",
|
|
40
|
+
"sysmlUseCase": "用例",
|
|
41
|
+
"sysmlBlockFull": "SysML 块定义",
|
|
42
|
+
"sysmlReqFull": "SysML 需求",
|
|
43
|
+
"sysmlUseCaseFull": "SysML 用例"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"aiChat": {
|
|
47
|
+
"title": "AI 助手",
|
|
48
|
+
"new": "新",
|
|
49
|
+
"v2": "工作流生成 V2",
|
|
50
|
+
"desc": "自然语言描述业务逻辑,支持复杂分支和错误处理。",
|
|
51
|
+
"createTitle": "AI 创建",
|
|
52
|
+
"createDesc": "描述您想构建的工作流,或上传图片进行复刻。",
|
|
53
|
+
"quickStarts": "快速开始",
|
|
54
|
+
"docProcess": {
|
|
55
|
+
"title": "文档处理",
|
|
56
|
+
"desc": "自动解析 PDF/JSON 文件"
|
|
57
|
+
},
|
|
58
|
+
"userAuth": {
|
|
59
|
+
"title": "用户认证",
|
|
60
|
+
"desc": "注册与验证流程"
|
|
61
|
+
},
|
|
62
|
+
"etl": {
|
|
63
|
+
"title": "ETL 管道",
|
|
64
|
+
"desc": "提取、转换、加载数据"
|
|
65
|
+
},
|
|
66
|
+
"inputPlaceholder": "描述您的工作流...",
|
|
67
|
+
"generate": "生成",
|
|
68
|
+
"quickInputs": {
|
|
69
|
+
"doc": "创建一个文档处理工作流",
|
|
70
|
+
"auth": "创建一个带有邮箱验证的用户注册流程",
|
|
71
|
+
"etl": "创建一个数据 ETL 管道"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"aiGenerator": {
|
|
75
|
+
"title": "AI 生成工作流",
|
|
76
|
+
"placeholder": "在此描述您的工作流逻辑...",
|
|
77
|
+
"cancel": "取消",
|
|
78
|
+
"generate": "生成"
|
|
79
|
+
},
|
|
80
|
+
"propertyPanel": {
|
|
81
|
+
"title": "格式",
|
|
82
|
+
"noSelection": "未选中节点",
|
|
83
|
+
"tabs": {
|
|
84
|
+
"style": "样式",
|
|
85
|
+
"text": "文本",
|
|
86
|
+
"arrange": "调整图形"
|
|
87
|
+
},
|
|
88
|
+
"style": {
|
|
89
|
+
"fill": "填充",
|
|
90
|
+
"stroke": "线条",
|
|
91
|
+
"opacity": "不透明度",
|
|
92
|
+
"rounded": "圆角",
|
|
93
|
+
"shadow": "阴影"
|
|
94
|
+
},
|
|
95
|
+
"text": {
|
|
96
|
+
"content": "内容",
|
|
97
|
+
"color": "字体颜色"
|
|
98
|
+
},
|
|
99
|
+
"arrange": {
|
|
100
|
+
"front": "移至最前",
|
|
101
|
+
"back": "移至最后",
|
|
102
|
+
"size": "大小",
|
|
103
|
+
"position": "位置"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/types/ai.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { LlmConfig } from './ai';
|
|
2
|
+
|
|
3
|
+
export interface WorkflowNode {
|
|
4
|
+
id: string;
|
|
5
|
+
type: string;
|
|
6
|
+
data: {
|
|
7
|
+
label: string;
|
|
8
|
+
props?: Record<string, any>;
|
|
9
|
+
llmConfig?: LlmConfig;
|
|
10
|
+
};
|
|
11
|
+
position: { x: number; y: number };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface WorkflowEdge {
|
|
15
|
+
id: string;
|
|
16
|
+
source: string;
|
|
17
|
+
target: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AiGenerateWorkflowParams {
|
|
22
|
+
prompt: string;
|
|
23
|
+
llmConfig: LlmConfig;
|
|
24
|
+
nodeTypes?: string[];
|
|
25
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
2
|
+
import { ChatOllama } from '@langchain/community/chat_models/ollama';
|
|
3
|
+
import type { LlmConfig } from '@/types/ai';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* LLM 客户端适配器
|
|
7
|
+
* 统一封装 OpenAI 和 Ollama 的调用接口
|
|
8
|
+
*/
|
|
9
|
+
export class LlmClient {
|
|
10
|
+
public client: ChatOpenAI | ChatOllama;
|
|
11
|
+
|
|
12
|
+
constructor(config: LlmConfig) {
|
|
13
|
+
if (config.provider === 'openai') {
|
|
14
|
+
this.client = new ChatOpenAI({
|
|
15
|
+
apiKey: config.apiKey,
|
|
16
|
+
modelName: config.model,
|
|
17
|
+
temperature: config.temperature,
|
|
18
|
+
configuration: {
|
|
19
|
+
baseURL: config.baseUrl,
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
} else if (config.provider === 'ollama') {
|
|
23
|
+
this.client = new ChatOllama({
|
|
24
|
+
model: config.model,
|
|
25
|
+
temperature: config.temperature,
|
|
26
|
+
baseUrl: config.baseUrl || 'http://localhost:11434',
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
throw new Error('Unsupported LLM provider');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 统一调用方法
|
|
35
|
+
* @param prompt 提示词
|
|
36
|
+
* @returns AI 响应内容
|
|
37
|
+
*/
|
|
38
|
+
async invoke(prompt: string): Promise<string> {
|
|
39
|
+
const response = await this.client.invoke(prompt);
|
|
40
|
+
// 确保返回字符串
|
|
41
|
+
if (typeof response.content === 'string') {
|
|
42
|
+
return response.content;
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify(response.content);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/tsconfig.json
ADDED
package/uno.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig, presetUno, presetIcons, presetAttributify } from 'unocss'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
presets: [
|
|
5
|
+
presetUno(),
|
|
6
|
+
presetIcons({
|
|
7
|
+
cdn: 'https://esm.sh/',
|
|
8
|
+
extraProperties: {
|
|
9
|
+
'display': 'inline-block',
|
|
10
|
+
'vertical-align': 'middle',
|
|
11
|
+
},
|
|
12
|
+
}),
|
|
13
|
+
presetAttributify(),
|
|
14
|
+
],
|
|
15
|
+
})
|