Przeglądaj źródła

Merge branch 'main' of https://git.shalu.com/Shalu/shalu-agent-workflow

lj1559651600@163.com 5 dni temu
rodzic
commit
b5e45e5601

+ 4 - 0
apps/web/src/assets/icons/code.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <polyline points="16 18 22 12 16 6"></polyline>
+  <polyline points="8 6 2 12 8 18"></polyline>
+</svg>

+ 3 - 0
apps/web/src/assets/icons/shield.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M12 2l7 4v6c0 5-3.5 9.5-7 10-3.5-.5-7-5-7-10V6l7-4z"></path>
+</svg>

+ 6 - 0
apps/web/src/assets/icons/users.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
+  <circle cx="9" cy="7" r="4"></circle>
+  <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+  <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+</svg>

+ 183 - 67
apps/web/src/components/SetterCommon/Code/CodeEditor.vue

@@ -1,76 +1,192 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-28 02:04:56
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-28 16:55:30
- * @Describe: 代码编辑器
--->
-<template>
-    <div class="space-y-4">
-        <!-- <div class="flex items-center justify-between text-gray-800">
-            <span class="w-[60px] text-sm">语法</span>
-            <ElSelect v-model="selectedLanguageVal" @change="changeLanguage">
-                <el-option v-for="li in language" :key="li" :value="li">{{li}}</el-option>
-            </ElSelect>
-        </div> -->
-        <div ref="editorContainer" style="height: 240px;"></div>
-    </div>
-</template>
-
 <script setup lang="ts">
+import type { editor } from 'monaco-editor'
 
-import { ref, onMounted, onUnmounted } from 'vue'
+import { nextTick, onMounted, ref, watch } from 'vue'
 import * as monaco from 'monaco-editor'
-// const language = ref(['javascript','python']);
-// let selectedLanguageVal = ref('javascript')
+import { IconButton } from '@repo/ui'
+
+const props = withDefaults(
+	defineProps<{
+		allowFullscreen?: boolean
+		autoToggleTheme?: boolean
+		bordered?: boolean
+		config?: editor.IStandaloneEditorConstructionOptions
+		language?: string
+		lineNumbers?: 'off' | 'on'
+		modelValue?: any
+		readOnly?: boolean
+		theme?: 'hc-black' | 'vs-dark' | 'vs-light'
+		valueFormat?: string
+		height?: string
+		appendTo?: string | HTMLElement
+	}>(),
+	{
+		allowFullscreen: true,
+		config: () => ({
+			minimap: {
+				enabled: false
+			},
+			selectOnLineNumbers: true
+		}),
+		autoToggleTheme: true,
+		language: 'json',
+		lineNumbers: 'on',
+		readOnly: false,
+		theme: 'vs-light',
+		valueFormat: 'string',
+		height: '120px'
+	}
+)
+
+// todo 动态切换主题
+const theme = ref('dark')
+
+const emit = defineEmits(['update:modelValue', 'update:language'])
+
+const isFullScreen = ref(false)
+
+const fullScreenStyle = `position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2999;`
+
+const editContainer = ref<HTMLElement | null>(null)
+
+let monacoEditor: monaco.editor.IStandaloneCodeEditor | null = null
 
-const editorContainer = ref<HTMLElement | null>(null)
-let editor = ref(null) as any;
+/**
+ * 设置文本
+ * @param text
+ */
+function setValue(text: string) {
+	monacoEditor?.setValue(text || '')
+}
+
+/**
+ * 光标处插入文本
+ * @param text
+ */
+function insertText(text: string) {
+	// 获取光标位置
+	const position = monacoEditor?.getPosition()
+	// 未获取到光标位置信息
+	if (!position) {
+		return
+	}
+	// 插入
+	monacoEditor?.executeEdits('', [
+		{
+			range: new monaco.Range(
+				position.lineNumber,
+				position.column,
+				position.lineNumber,
+				position.column
+			),
+			text
+		}
+	])
+	// 设置新的光标位置
+	monacoEditor?.setPosition({
+		...position,
+		column: position.column + text.length
+	})
+	// 重新聚焦
+	monacoEditor?.focus()
+}
 
 onMounted(() => {
-    try {
-        if (!editorContainer.value) {
-            console.log('编辑器挂载节点没获取到')
-            return
-        }
-        editor.value = monaco.editor.create(editorContainer.value, {
-            value: [
-                'function x() {',
-                '    console.log("Hello world!");',
-                '}'
-            ].join('\n'),
-            language: 'javascript',
-            theme: 'vs-dark',
-            automaticLayout: true,
-            fixedOverflowWidgets: true,
-        });
-        // 防止冒泡
-        editor.value.onKeyDown((e: Event) => {
-            e.stopPropagation();
-        });
-
-    } catch (err) {
-        console.error("Monaco 创建失败:", err);
-    }
-
-});
-
-onUnmounted(() => {
-    if (editor.value) {
-        editor.value.dispose();
-    }
-});
-
-// // 动态设置模型语言
-// const changeLanguage = ()=>{
-// 		console.log('changeLanguage', selectedLanguageVal.value,editor.value );
-// 		if(editor.value){
-// 			const model = editor.value.getModel()
-// 			if (model) {
-// 				monaco.editor.setModelLanguage(model, selectedLanguageVal.value)
-// 			}
-// 		}
-// }
+	monacoEditor = monaco.editor.create(editContainer.value as HTMLElement, {
+		value: getValue(),
+		...props.config,
+		automaticLayout: true,
+		language: props.language,
+		lineNumbers: props.lineNumbers,
+		readOnly: props.readOnly,
+		scrollBeyondLastLine: false,
+		theme: props.theme
+	})
+
+	function handleToggleTheme() {
+		if (theme.value === 'dark') {
+			monaco.editor.setTheme('vs-dark')
+		} else {
+			monaco.editor.setTheme('vs-light')
+		}
+	}
+
+	// 自动切换主题
+	if (props.autoToggleTheme) {
+		watch(
+			() => theme,
+			() => {
+				nextTick(() => handleToggleTheme())
+			},
+			{
+				immediate: true
+			}
+		)
+	}
+
+	// 获取值
+	function getValue() {
+		// valueFormat 为json 格式,需要转换处理
+		if (props.valueFormat === 'json' && props.modelValue) {
+			return JSON.stringify(props.modelValue, null, 2)
+		}
+		return props.modelValue ?? ''
+	}
+
+	// 监听值变化
+	monacoEditor.onDidChangeModelContent(() => {
+		const currenValue = monacoEditor?.getValue()
 
+		// valueFormat 为json 格式,需要转换处理
+		if (props.valueFormat === 'json' && currenValue) {
+			emit('update:modelValue', JSON.parse(currenValue))
+			return
+		}
 
+		emit('update:modelValue', currenValue ?? '')
+	})
+})
+
+defineExpose({
+	insertText,
+	setValue
+})
 </script>
+<template>
+	<Teleport :disabled="!appendTo" :to="appendTo">
+		<div
+			ref="editContainer"
+			:class="{ bordered: props.bordered }"
+			:style="isFullScreen ? fullScreenStyle : { height: props.height }"
+			class="code-editor w-full h-full relative"
+		>
+			<div
+				class="z-999 text-$epic-text-helper absolute right-4 top-2 cursor-pointer text-xl"
+				@click="isFullScreen = !isFullScreen"
+				v-if="props.allowFullscreen"
+			>
+				<IconButton v-if="!isFullScreen" icon="lucide:fullscreen" link />
+				<IconButton v-else icon="material-symbols-light:fullscreen-exit-rounded" link />
+			</div>
+		</div>
+	</Teleport>
+</template>
+<style lang="less" scoped>
+.code-editor {
+	width: 100%;
+	min-height: 150px;
+
+	:deep(.monaco-editor) {
+		height: 100%;
+	}
+
+	&.bordered {
+		border: 1px solid var(--epic-border-color);
+	}
+}
+</style>

+ 46 - 43
apps/web/src/components/setter/CodeSetter.vue

@@ -1,24 +1,28 @@
 <template>
-    <div class="bg-white w-full">
-        <div class="w-full">
-            <!-- Content -->
-            <div class="p-4">
-                <div class="space-y-6">
-                    <!-- 输入变量 -->
-                    <InputVariables v-model="formData.inputVariables" />
+	<div class="bg-white w-full">
+		<div class="w-full">
+			<!-- Content -->
+			<div class="p-4">
+				<div class="space-y-6">
+					<!-- 输入变量 -->
+					<InputVariables v-model="formData.inputVariables" />
 
-                    <!-- 代码编辑器 -->
-                    <CodeEditor v-model="formData.code" v-model:language="formData.language" />
+					<!-- 代码编辑器 -->
+					<CodeEditor
+						v-model="formData.code"
+						v-model:language="formData.language"
+						value-format="javascript"
+					/>
 
-                    <!-- 输出变量 -->
-                    <OutputVariables v-model="formData.outputVariables" />
+					<!-- 输出变量 -->
+					<OutputVariables v-model="formData.outputVariables" />
 
-                    <!-- 测试配置 -->
-                    <TestConfig v-model="formData.testConfig" />
-                </div>
-            </div>
-        </div>
-    </div>
+					<!-- 测试配置 -->
+					<TestConfig v-model="formData.testConfig" />
+				</div>
+			</div>
+		</div>
+	</div>
 </template>
 
 <script setup lang="ts">
@@ -29,19 +33,19 @@ import OutputVariables from '@/components/SetterCommon/Code/OutputVariables.vue'
 import TestConfig from '@/components/SetterCommon/Code/TestConfig.vue'
 
 interface Variable {
-    id: string
-    name: string
+	id: string
+	name: string
 }
 
 interface OutputVariable {
-    id: string
-    name: string
-    type: string
+	id: string
+	name: string
+	type: string
 }
 
 interface TestConfigData {
-    retryEnabled: boolean
-    errorHandling: string
+	retryEnabled: boolean
+	errorHandling: string
 }
 
 // const props = withDefaults(defineProps<{
@@ -52,26 +56,25 @@ interface TestConfigData {
 // }>()
 
 const formData = reactive({
-    // 输入变量, 变量默认值
-    inputVariables: [
-        // { id: 'var_1', name: 'arg1' },
-    ] as Variable[],
-    // 代码内容
-    code: `function main(arg1, arg2) {
+	// 输入变量, 变量默认值
+	inputVariables: [
+		// { id: 'var_1', name: 'arg1' },
+	] as Variable[],
+	// 代码内容
+	code: `function main(arg1, arg2) {
             return {
                 "result": arg1 + arg2,
-            }`,
-    // 编程语言
-    language: 'JavaScript',
-    // 输出变量
-    outputVariables: [
-        // { id: 'output_1', name: 'result', type: 'String' }
-    ] as OutputVariable[],
-    // 测试配置
-    testConfig: {
-        retryEnabled: false,
-        errorHandling: 'none'
-    } as TestConfigData
+}}`,
+	// 编程语言
+	language: 'javascript',
+	// 输出变量
+	outputVariables: [
+		// { id: 'output_1', name: 'result', type: 'String' }
+	] as OutputVariable[],
+	// 测试配置
+	testConfig: {
+		retryEnabled: false,
+		errorHandling: 'none'
+	} as TestConfigData
 })
-
 </script>

+ 4 - 4
apps/web/src/views/About.vue

@@ -21,22 +21,22 @@
 
 			<div class="features-grid">
 				<div class="feature-card">
-					<SvgIcon name="zap" class="feature-icon" />
+					<SvgIcon name="zap" class="feature-icon" size="30"/>
 					<h3>高性能</h3>
 					<p>优化的执行引擎,确保工作流快速可靠运行</p>
 				</div>
 				<div class="feature-card">
-					<SvgIcon name="shield" class="feature-icon" />
+					<SvgIcon name="shield" class="feature-icon" size="30"/>
 					<h3>安全可靠</h3>
 					<p>企业级安全标准,保护你的数据和凭证</p>
 				</div>
 				<div class="feature-card">
-					<SvgIcon name="users" class="feature-icon" />
+					<SvgIcon name="users" class="feature-icon" size="30"/>
 					<h3>团队协作</h3>
 					<p>支持多人协作,共同构建和维护工作流</p>
 				</div>
 				<div class="feature-card">
-					<SvgIcon name="code" class="feature-icon" />
+					<SvgIcon name="code" class="feature-icon" size="30"/>
 					<h3>开放扩展</h3>
 					<p>支持自定义节点和集成,满足个性化需求</p>
 				</div>

+ 4 - 4
apps/web/src/views/Dashboard.vue

@@ -150,7 +150,7 @@
 							<el-card>
 								<div style="display: flex; justify-content: space-between; align-items: center">
 									<div>
-										<div style="font-weight: 700" class="cursor-pointer" @click="toEditor">
+										<div style="font-weight: 700" class="cursor-pointer" @click="toEditor(item.id)">
 											{{ item.title }}
 										</div>
 										<div style="color: #999; font-size: 12px">上次更新时间: {{ item.created }}</div>
@@ -162,7 +162,7 @@
 											<span class="el-dropdown-link cursor-pointer" @click.stop>•••</span>
 											<template #dropdown>
 												<el-dropdown-menu>
-													<el-dropdown-item @click="toEditor">打开</el-dropdown-item>
+													<el-dropdown-item @click="toEditor(item.id)">打开</el-dropdown-item>
 													<el-dropdown-item>分享</el-dropdown-item>
 													<el-dropdown-item>复制</el-dropdown-item>
 													<el-dropdown-item>移动</el-dropdown-item>
@@ -813,8 +813,8 @@ const filteredVariables = computed(() => {
 	return result
 })
 
-const toEditor = () => {
-	$router.push('/workflow/1')
+const toEditor = (id: string) => {
+	$router.push(`/workflow/${id}`)
 }
 </script>
 

+ 12 - 25
apps/web/src/views/Docs.vue

@@ -8,9 +8,7 @@
 				<div class="nav-section">
 					<h3>快速开始</h3>
 					<ul>
-						<li :class="{ active: activeDoc === 'intro' }" @click="activeDoc = 'intro'">
-							介绍
-						</li>
+						<li :class="{ active: activeDoc === 'intro' }" @click="activeDoc = 'intro'">介绍</li>
 						<li
 							:class="{ active: activeDoc === 'installation' }"
 							@click="activeDoc = 'installation'"
@@ -26,10 +24,7 @@
 							工作流
 						</li>
 						<li :class="{ active: activeDoc === 'nodes' }" @click="activeDoc = 'nodes'">节点</li>
-						<li
-							:class="{ active: activeDoc === 'credentials' }"
-							@click="activeDoc = 'credentials'"
-						>
+						<li :class="{ active: activeDoc === 'credentials' }" @click="activeDoc = 'credentials'">
 							凭证管理
 						</li>
 					</ul>
@@ -37,12 +32,8 @@
 				<div class="nav-section">
 					<h3>节点文档</h3>
 					<ul>
-						<li :class="{ active: activeDoc === 'http' }" @click="activeDoc = 'http'">
-							HTTP 节点
-						</li>
-						<li :class="{ active: activeDoc === 'code' }" @click="activeDoc = 'code'">
-							代码节点
-						</li>
+						<li :class="{ active: activeDoc === 'http' }" @click="activeDoc = 'http'">HTTP 节点</li>
+						<li :class="{ active: activeDoc === 'code' }" @click="activeDoc = 'code'">代码节点</li>
 						<li :class="{ active: activeDoc === 'database' }" @click="activeDoc = 'database'">
 							数据库节点
 						</li>
@@ -60,7 +51,8 @@
 					<section class="doc-section">
 						<h2>概述</h2>
 						<p>
-							AI Agent 是一个强大的自动化工作流平台,允许你创建复杂的自动化流程。通过可视化的工作流编辑器,你可以轻松添加各种节点类型,连接各种服务和应用,实现数据的自动化处理和传输。
+							AI Agent
+							是一个强大的自动化工作流平台,允许你创建复杂的自动化流程。通过可视化的工作流编辑器,你可以轻松添加各种节点类型,连接各种服务和应用,实现数据的自动化处理和传输。
 						</p>
 					</section>
 
@@ -68,17 +60,12 @@
 						<h2>主要特性</h2>
 						<ul class="feature-list">
 							<li>
-								<strong>可视化编辑器</strong> - 直观的工作流设计器,支持开始、结束、HTTP请求、条件分支、代码执行、数据查询等多种节点
-							</li>
-							<li>
-								<strong>丰富的节点库</strong> - 支持 HTTP、数据库、代码执行等多种节点类型
-							</li>
-							<li>
-								<strong>实时调试</strong> - 支持单步执行和断点调试
-							</li>
-							<li>
-								<strong>数据转换</strong> - 强大的数据映射和转换能力
+								<strong>可视化编辑器</strong> -
+								直观的工作流设计器,支持开始、结束、HTTP请求、条件分支、代码执行、数据查询等多种节点
 							</li>
+							<li><strong>丰富的节点库</strong> - 支持 HTTP、数据库、代码执行等多种节点类型</li>
+							<li><strong>实时调试</strong> - 支持单步执行和断点调试</li>
+							<li><strong>数据转换</strong> - 强大的数据映射和转换能力</li>
 						</ul>
 					</section>
 
@@ -154,7 +141,7 @@ const docDescription = computed(() => {
 <style lang="less" scoped>
 .docs-container {
 	display: flex;
-	height: calc(100vh - 32px);
+	height: 100vh;
 	background: #f8f9fa;
 
 	.sidebar {