From a2272919332898535e31bac9983f1338b5cc98bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=84=E7=8E=84?= Date: Thu, 7 Aug 2025 10:57:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9EAPI/MCP/LLM/RAG=E5=9B=9B?= =?UTF-8?q?=E4=B8=AAnode=E7=9A=84=E6=A0=B7=E5=BC=8F=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=8F=8A=E9=83=A8=E5=88=86style=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/VariableRichTextEditor.vue | 2642 +++++++++-------- src/i18n/lang/en.ts | 97 +- src/i18n/lang/zh-cn.ts | 96 +- src/views/createapp/components/types.ts | 11 + src/views/createapp/components/workFlow.vue | 656 ++-- .../workFlowConfig/ApiCallDrawer.vue | 830 ++++++ .../components/workFlowConfig/LLMDrawer.vue | 581 ++++ .../components/workFlowConfig/MCPDrawer.vue | 384 +++ .../components/workFlowConfig/RAGDrawer.vue | 538 ++++ vite.config.ts | 8 +- 10 files changed, 4228 insertions(+), 1615 deletions(-) create mode 100644 src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue create mode 100644 src/views/createapp/components/workFlowConfig/LLMDrawer.vue create mode 100644 src/views/createapp/components/workFlowConfig/MCPDrawer.vue create mode 100644 src/views/createapp/components/workFlowConfig/RAGDrawer.vue diff --git a/src/components/VariableRichTextEditor.vue b/src/components/VariableRichTextEditor.vue index 10b6ba5c..c53ca849 100644 --- a/src/components/VariableRichTextEditor.vue +++ b/src/components/VariableRichTextEditor.vue @@ -7,44 +7,53 @@ 回复
-
- + + > {x} - - - - -
+ + + +
- + />
-
+
- @@ -53,10 +62,10 @@
- +
-
- +
- - @@ -2268,11 +2402,11 @@ onUnmounted(() => { user-select: none !important; vertical-align: middle !important; pointer-events: auto !important; // 确保可以响应hover事件 - + &:hover { opacity: 0.8 !important; } - + // 无效变量的警告样式 &.variable-tag-invalid { background: linear-gradient(135deg, #f56565 0%, #e53e3e 100%) !important; @@ -2280,17 +2414,17 @@ onUnmounted(() => { cursor: help !important; // 使用help光标提示有tooltip pointer-events: all !important; // 强制允许所有指针事件 user-select: auto !important; // 允许选择以支持tooltip - + &:hover { opacity: 0.9 !important; } - + // 添加警告图标 &::after { - content: " ⚠️"; + content: ' ⚠️'; font-size: 10px; margin-left: 2px; } } } - \ No newline at end of file + diff --git a/src/i18n/lang/en.ts b/src/i18n/lang/en.ts index 5b6e518a..bbb5cab3 100644 --- a/src/i18n/lang/en.ts +++ b/src/i18n/lang/en.ts @@ -35,7 +35,7 @@ export default { }, }, home: { - name: 'openEuler Intelligence', + name: 'HuaKun Copilot', }, tabs: { work: 'Work', @@ -58,8 +58,11 @@ export default { create_plugin: 'Create Plugin', semantic_interface_center: 'Semantic interface center', all_select: 'All', - interface_name: 'Interface name', - interface_introduction: 'Interface introduction', + component_name: 'Interface name', + component_introduction: 'Interface introduction', + rag_query_variables: 'query variables', + selectedModel: 'model', + interface_url: 'URL', username: 'Username', interface_search: 'Search', interface_upload: 'Upload', @@ -91,6 +94,9 @@ export default { baseMessage: 'Base Message', copyFailed: 'Copy failed', checkFormat: 'Please check the format', + key: 'key', + value: 'value', + no_model_error: 'No model instances added yet. Go to settings to add one!', }, plugin_center: { plugin_name: 'Plugin Name', @@ -194,19 +200,18 @@ export default { selected: 'Selected', }, main: { - start: 'Start', - end: 'End', - describe1: "Hi! I'm", + describe1: "Hi! I'm KunZai", describe2: ", and I'm happy to be of service.", + describe3: "Hi! I'm", left_describe: 'Popular Apps', - os_knowledge: 'CVE Hotfix Assistant', - os_knowledge_describe: 'CVE Hotfix', - openEuler_expertise: 'Diagnostics Assistant', - openEuler_expertise_describe: 'Intelligent Diagnostics', - beyond_openEuler: 'Tuning Assistant', - beyond_openEuler_describe: 'Intelligent Tuning', - openEuler_use_cases: 'Container Stack', - openEuler_use_cases_describe: 'AI Container Stack assistant', + os_knowledge: 'Intelligent Official Document Writing', + os_knowledge_describe: 'Official Document Writing', + openEuler_expertise: 'Government Knowledge Center', + openEuler_expertise_describe: 'Government Knowledge', + beyond_openEuler: 'Intelligent Bidding Assistant', + beyond_openEuler_describe: 'Intelligent Bidding', + openEuler_use_cases: 'Intelligent Meeting Assistant Meeting', + openEuler_use_cases_describe: 'Intelligent', question: 'Recommendation qustions', addQuestion: 'Add question', addFiveQuestions: 'Up to 5 questions can be added', @@ -217,14 +222,14 @@ export default { query_interpretation: 'Query Interpretation', Automatic: 'Automatic', ask_me_anything: - 'Ask me anything about openEuler. Press Shift+Enter to start a new line.', + 'Ask me anything about HuaKun. Press Shift+Enter to start a new line.', you_might_want_to_know: 'You might want to know:', close: 'Close', confirm: 'Confirm', email1: 'Email:', - email2: 'contact@openeuler.io', + email2: 'https://www.schkzy.com/about_us/contact_us', opinions: - 'AI-generated responses are for reference only and do not reflect the opinions of openEuler.', + 'AI-generated responses are for reference only and do not reflect the opinions of HuaKun.', service_agreement: 'Service Agreement', privacy_policy: 'Privacy Policy', contact_us: 'Contact Us', @@ -257,7 +262,7 @@ export default { time_filter_last_6_months: 'Last 6 months', links: 'Links', hiss_basic_software_service_capability_platform: - 'HiSS Basic Software Service Capability Platform', // 注意:这里我保留了原始的大小写,但通常我们会将首字母大写 + 'HuaKunZhenYu Official Website', // 注意:这里我保留了原始的大小写,但通常我们会将首字母大写 collapse: 'Collapse', no_chat_history: 'No chat history available.', // 假设需要一个字段来表示没有历史对话的情况 expand: 'Expand', // 假设界面中有展开历史对话的功能 @@ -268,13 +273,13 @@ export default { feedbackSuccesful: 'Feedback succeeded.', regenerate: 'Regenerate', // 这里我保留了原样,因为通常键名不加双引号 try_ask_me: 'Try ask me:', - eulercopilot_is_thinking: 'openEuler Intelligence is thinking…', + eulercopilot_is_thinking: 'KunZai is thinking…', generation_stopped: 'Generation stopped.', stop: 'Stop', stopSuccessful: 'Stop Successfully', systemBusy: 'The system is busy. Please try again later.', onlySupport: - "I'm sorry, but for now, we only support questions related to the fields of openEuler and Linux.", + "I'm sorry, but for now, we only support questions related to the fields of HuaKun and Linux.", copy: 'Copy', copied_successfully: 'Copied successfully.', copied_failed: 'Copied failed', @@ -294,6 +299,7 @@ export default { report: 'Report', reason_for_reporting: 'Reason for Reporting', enter_a_description_for_your_report: 'Enter a description for your report.', + network_error: 'Network connection issue. Please try again later.', }, Report: { pornographic_content: 'Pornographic content', @@ -315,6 +321,7 @@ export default { Login: { login: 'Log In', logout: 'Log Out', + logout_failed: 'Log Out Failed', login_now: 'Log In Now', // 假设这里“立即登录”需要更明确的英文表达 account: 'Account', enter_account: 'Please enter your account', @@ -331,43 +338,47 @@ export default { unauthorized: 'Unauthorized page, please login first', }, question: { - open_euler_community_edition_categories: 'Community Edition Categories', + open_euler_community_edition_categories: + 'How to apply for a replacement ID card if it is lost?', lts_release_cycle_and_support: - 'LTS Release Cycle and Community Support Duration', + 'What is the process for applying for a residence permit?', innovation_release_cycle_and_support: - 'Innovation Release Cycle and Community Support Duration', + 'What documents are required for business tax registration?', container_cloud_platform_solution: - 'Container Cloud Platform Solution (CCPS) of the openEuler Community', - sec_gear_main_functions: 'Main Functions of secGear', - dde_description: 'What is DDE?', - lustre_description: 'What is Lustre?', + 'How to check the latest policies and adjustments for public transportation?', + sec_gear_main_functions: + 'What government subsidies are available,and how can I apply for them?', + dde_description: + 'How to apply for an electronic ID card or digital signature?', + lustre_description: 'How to check and pay social insurance fees?', open_euler_testing_management_platform: - 'Testing Management Platform of the openEuler Community', - open_euler_pkgship: 'What is pkgship in openEuler?', + 'What is the process for reporting municipal facility repairs?', + open_euler_pkgship: + 'Steps and requirements for applying for a passport online?', open_euler_software_package_introduction_principles: - 'Introduction Principles of openEuler Software Packages', - download_rpm_without_installing: - 'How to download an RPM package to the local system without installing it in openEuler?', + 'How to obtain business information disclosure through government service platforms?', + dowwnload_rpm_without_installing: + 'How to quickly query and process public administrative approval applications?', count_the_occurrences_of_the_hello: - "Generate a shell command to count the occurrences of the 'hello' string in the 'test.txt' file.", + 'How to upload and manage policy and regulation documents on the government platform?', convert_uppercase_to_lowercase: - 'Give me a shell command to convert uppercase letters to lowercase in text files in the current directory and its subdirectories', + 'How to ensure the security and data protection of the government service platform?', list_files_with_specific_permissions: - 'Give me a shell command to find and list files with specific permissions in the current directory', + 'What are the evaluation criteria and procedures for government procurement projects?', search_error_keyword_with_context: - "Give me a shell command to search for the keyword 'error' in text files in the /home directory and its subdirectories, and output the matching lines along with the 3 lines before and after them to a file named 'result.txt'", + "How to connect and update government data with other departments' systems?", clear_dependencies_for_software_package: - 'How to clear dependencies for software packages in openEuler?', + 'How to use big data analysis to optimize government service efficiency?', gpgcheck_purpose_in_dnf: - 'What is the purpose of the gpgcheck parameter in DNF in openEuler?', + 'How to review and manage the use and allocation of public funds?', installonly_limit_function_in_dnf: - 'What is the function of the installonly_limit parameter in DNF in openEuler?', + 'How to handle the performance assessment and evaluation of civil servants?', clean_requirement_on_remove_function_in_dnf: - 'What is the function of the clean_requirement_on_remove parameter in DNF in openEuler?', + 'How to publish and manage government announcements and notifications on the platform?', hunan_tobacco_monopoly_applications_on_openeuler: - 'What are the applications of Hunan Tobacco Monopoly based on openEuler?', + 'How to use the reporting generation and data export functions of the government platform?', xsky_applications_on_openeuler: - 'What are the applications of XSKY based on openEuler?', + 'How to improve the transparency of government work and public participation?', }, upload: { upload_tip_text: @@ -416,6 +427,8 @@ export default { choose_flow: 'Please choose a workflow', debug: 'Debug', enterWorkflowName: 'Please enter workflow name', + please_input: 'please input', + please_input_with_variables: "please input, press '/' to insert variables", default: '', success: 'successful', error: 'failed', diff --git a/src/i18n/lang/zh-cn.ts b/src/i18n/lang/zh-cn.ts index 2af28873..2eca4557 100644 --- a/src/i18n/lang/zh-cn.ts +++ b/src/i18n/lang/zh-cn.ts @@ -35,7 +35,7 @@ export default { }, }, home: { - name: 'openEuler 智能化解决方案', + name: '华鲲小智', }, tabs: { work: '作品', @@ -58,8 +58,11 @@ export default { create_plugin: '创建插件', semantic_interface_center: '语义接口中心', all_select: '全部', - interface_name: '接口名称', - interface_introduction: '接口简介', + component_name: '组件名称', + component_introduction: '组件简介', + rag_query_variables: '查询变量', + selectedModel: '模型', + interface_url: 'URL', username: '用户名称', interface_search: '搜索', interface_upload: '上传', @@ -90,6 +93,9 @@ export default { publish: '发布', copyFailed: '复制失败', checkFormat: '请检查格式', + key: '键', + value: '值', + no_model_error: '你还没有添加大模型实例,快去设置里添加一个吧!', }, plugin_center: { plugin_name: '插件名称', @@ -194,22 +200,23 @@ export default { main: { start: '开始', end: '结束', - describe1: '你好,我是', + describe1: '你好,我是鲲仔', describe2: ',很高兴为你服务', + describe3: '你好,我是', left_describe: '热门应用', - os_knowledge: 'CVE热修复智能助手', - os_knowledge_describe: 'CVE热修复', - openEuler_expertise: '智能诊断智能助手', - openEuler_expertise_describe: '智能诊断', - beyond_openEuler: '智能调优智能助手', - beyond_openEuler_describe: '智能调优', - openEuler_use_cases: 'AI容器栈智能助手', - openEuler_use_cases_describe: 'AI容器栈', + os_knowledge: '智慧公文撰写', + os_knowledge_describe: '公文撰写', + openEuler_expertise: '政务知识中心', + openEuler_expertise_describe: '政务知识', + beyond_openEuler: '智能投标助手', + beyond_openEuler_describe: '智能投标', + openEuler_use_cases: '智能会议助手', + openEuler_use_cases_describe: '智能会议', question: '推荐问题', addQuestion: '添加问题', addFiveQuestions: '最多添加5个问题', smart_shell_describe: - '欢迎探索首款自然语言交互的智能操作系统:一句话,即享智能诊断与优化', + '欢迎探索首款自然语言交互的智能操作系统:一句话,即享政务知识与优化', try_app: '进入应用中心', refresh: '换一换', query_interpretation: '请选择识别方式', @@ -219,7 +226,7 @@ export default { close: '关闭', confirm: '确定', email1: '联系邮箱', - email2: 'contact@openeuler.io', + email2: 'https://www.schkzy.com/about_us/contact_us', opinions: '所有内容均由人工智能输出,仅供参考,不代表我们的态度和观点', service_agreement: '服务协议', privacy_policy: '隐私政策', @@ -252,7 +259,7 @@ export default { time_filter_last_30_days: '最近30天', time_filter_last_6_months: '最近半年', links: '友情链接', - hiss_basic_software_service_capability_platform: '基础软件服务能力平台', + hiss_basic_software_service_capability_platform: '华鲲振宇官网', collapse: '收起', no_chat_history: '暂无历史对话', expand: '展开', @@ -263,12 +270,12 @@ export default { feedbackSuccesful: '反馈成功', regenerate: '重新生成', try_ask_me: '你可以继续问我:', - eulercopilot_is_thinking: 'openEuler Intelligence 正在生成回答...', + eulercopilot_is_thinking: '鲲仔正在生成回答...', generation_stopped: '回答已停止生成', stop: '停止回答', stopSuccessful: '暂停成功', systemBusy: '系统繁忙,请稍后再试', - onlySupport: '很抱歉,暂时只支持问题 openEuler 和 Linux 领域相关的问题', + onlySupport: '很抱歉,暂时只支持问题 华鲲振宇 和 Linux 领域相关的问题', copy: '复制', copied_successfully: '复制成功', copied_failed: '复制失败', @@ -286,6 +293,7 @@ export default { report: '举报', reason_for_reporting: '选择举报类型', enter_a_description_for_your_report: '请输入举报描述', + network_error: '当前网络故障,请稍后再试', }, Report: { pornographic_content: '低俗色情', @@ -307,6 +315,7 @@ export default { Login: { login: '登录', logout: '退出登录', + logout_failed: '退出登录失败', login_now: '立即登录', account: '账号', enter_account: '请输入账号', @@ -322,43 +331,34 @@ export default { unauthorized: '页面未授权,请先登录', }, question: { - open_euler_community_edition_categories: 'openEuler 社区版本有哪些分类?', - lts_release_cycle_and_support: - 'openEuler 长期支持版本的发布间隔周期和社区支持各是多久?', - innovation_release_cycle_and_support: - 'openEuler 社区创新版本的发布间隔周期和社区支持各是多久?', - container_cloud_platform_solution: - 'openEuler 社区的容器云管理平台解决方案(CCPS)是什么?', - sec_gear_main_functions: 'secGear 主要提供哪三大能力?', - dde_description: 'DDE 是一款什么组件?', - lustre_description: 'Lustre 是什么?', - open_euler_testing_management_platform: - 'openEuler 社区的测试管理平台是什么?', - open_euler_pkgship: 'openEuler 的 pkgship 是什么?', + open_euler_community_edition_categories: '如何办理身份证丢失补办手续?', + lts_release_cycle_and_support: '申请居住证的流程是什么?', + innovation_release_cycle_and_support: '企业税务登记需要哪些材料?', + container_cloud_platform_solution: '如何查询公共交通的最新政策和调整?', + sec_gear_main_functions: '政府补贴有哪些,如何申请?', + dde_description: '如何申请电子身份证或电子签名?', + lustre_description: '如何查询和缴纳社会保险费用?', + open_euler_testing_management_platform: '市政设施维修报修流程是什么?', + open_euler_pkgship: '在线办理护照的步骤和要求?', open_euler_software_package_introduction_principles: - 'openEuler 软件包引入原则是什么?', - download_rpm_without_installing: - 'openEuler 系统如何将一个RPM包下载到本地而不安装?', + '如何通过政府服务平台获取企业信息公开?', + download_rpm_without_installing: '如何快速查询并处理公众的行政审批申请?', count_the_occurrences_of_the_hello: - '请给我一个 shell 命令,实现以下功能:计算 test.txt 文件中 hello 字符串的出现次数', - convert_uppercase_to_lowercase: - '给我一个 shell 命令,实现以下功能:linux 命令将本目录及子目录文本文件中的大写字母修改成小写字母', + '如何上传并管理政策法规文件到政府平台?', + convert_uppercase_to_lowercase: '如何确保政务服务平台的安全性与数据保护?', list_files_with_specific_permissions: - '给我一个 shell 命令,实现以下功能:shell 命令查找当前目录下权限符合的文件并列出', + '政府采购项目的评审标准与流程是什么?', search_error_keyword_with_context: - '给我一个 shell 命令,实现以下功能:在 /home 目录及其子目录中查找关键字“error”的文本文件,并将匹配行以及它们前后的3行内容输出到名为“result.txt”的文件中', + '如何对接和更新政务数据与其他部门的系统?', clear_dependencies_for_software_package: - 'openEuler 系统如何清除软件源的依赖?', - gpgcheck_purpose_in_dnf: - 'openEuler 系统 DNF 中的 gpgcheck 参数是用来做什么的?', - installonly_limit_function_in_dnf: - 'openEuler 系统 DNF 中的 installonly_limit 参数的作用是?', + '如何利用大数据分析优化政府服务效率?', + gpgcheck_purpose_in_dnf: '如何审核和管理公共资金的使用与分配?', + installonly_limit_function_in_dnf: '如何处理公务员的绩效考核与评价?', clean_requirement_on_remove_function_in_dnf: - 'openEuler 系统 DNF 中的 clean_requirement_on_remove 参数具有什么功能?', + '如何在平台上发布并管理政府公告和通知?', hunan_tobacco_monopoly_applications_on_openeuler: - '湖南省烟草专卖局基于 openeuler 系统有哪些应用?', - xsky_applications_on_openeuler: - 'XSKY星辰天合公司基于 openeuler 系统有哪些应用?', + '如何使用政务平台的报表生成与数据导出功能?', + xsky_applications_on_openeuler: '如何提升政务工作透明度与公众参与度?', }, upload: { upload_tip_text: @@ -404,6 +404,8 @@ export default { choose_flow: '请选择工作流', debug: '调试', enterWorkflowName: '请输入工作流名称', + please_input: '请输入', + please_input_with_variables: "请输入,键入'/'快速插入变量", default: '', success: '运行成功', error: '运行失败', diff --git a/src/views/createapp/components/types.ts b/src/views/createapp/components/types.ts index 425721a8..6cdb5cff 100644 --- a/src/views/createapp/components/types.ts +++ b/src/views/createapp/components/types.ts @@ -148,3 +148,14 @@ export interface ListItem { id: string; value: string | null; } + +export const NodeType = { + RAG: 'rag', + API: 'api', + LLM: 'llm', + MCP: 'mcp', + CHOICE: 'choice', + CODE: 'code', + DIRECTREPLY: 'directreply', + PLUGIN: 'api', +}; diff --git a/src/views/createapp/components/workFlow.vue b/src/views/createapp/components/workFlow.vue index f4a9d9cb..3b97cd64 100644 --- a/src/views/createapp/components/workFlow.vue +++ b/src/views/createapp/components/workFlow.vue @@ -25,6 +25,10 @@ import { IconPlusCircle, } from '@computing/opendesign-icons'; +import ApiCallDrawer from './workFlowConfig/ApiCallDrawer.vue'; +import LLMDrawer from './workFlowConfig/LLMDrawer.vue'; +import MCPDrawer from './workFlowConfig/MCPDrawer.vue'; +import RAGDrawer from './workFlowConfig/RAGDrawer.vue'; import EditYamlDrawer from './workFlowConfig/yamlEditDrawer.vue'; import VariableBasedStartNodeDrawer from './workFlowConfig/VariableBasedStartNodeDrawer.vue'; import CodeNodeDrawer from './workFlowConfig/CodeNodeDrawer.vue'; @@ -42,7 +46,7 @@ import CustomLoading from '../../customLoading/index.vue'; import EditFlowName from './workFlowConfig/editFlowName.vue'; import NodeListPanel from './workFlowConfig/NodeListPanel.vue'; import EnvironmentVariableDrawer from './workFlowConfig/EnvironmentVariableDrawer.vue'; - +import { NodeType } from './types'; const { t } = useI18n(); const copilotAside = ref(); const isCopilotAsideVisible = ref(false); @@ -54,6 +58,10 @@ const editFlowNameId = ref(); const editData = ref(); const dialogType = ref(''); const isEditYaml = ref(false); +const isEditApiCall = ref(false); +const isEditLLM = ref(false); +const isEditRAG = ref(false); +const isEditMCP = ref(false); const isEditStartNode = ref(false); const isEditCodeNode = ref(false); const isEditDirectReplyNode = ref(false); @@ -74,7 +82,13 @@ const emits = defineEmits(['updateFlowsDebug']); const route = useRoute(); const workFlowList = ref([]); const props = defineProps(['flowList']); -const flowObj = ref<{flowId?: string, debug?: boolean, name?: string, nodes?: any[], edges?: any[]}>({}); +const flowObj = ref<{ + flowId?: string; + debug?: boolean; + name?: string; + nodes?: any[]; + edges?: any[]; +}>({}); const nodes = ref([]); const debugStatus = ref(''); const debugTime = ref(''); @@ -101,7 +115,7 @@ const insertMenuData = ref({ visible: false, position: { x: 0, y: 0 }, edgeInfo: null, - direction: 'right' as 'left' | 'right' + direction: 'right' as 'left' | 'right', }); const hanleAsideVisible = () => { @@ -170,7 +184,6 @@ onConnect((e) => { }); }); - // 打开新增工作流弹窗 const addWorkFlow = () => { // 待增加新增弹窗 @@ -183,7 +196,9 @@ const handleClose = (flowId?: string) => { api.querySingleAppData({ id: route.query.appId as string }).then((res) => { //workflowList 数据更新 workFlowList.value = res[1]?.result.workflows; - const foundFlow = workFlowList.value.find((item: any) => item.id === flowId); + const foundFlow = workFlowList.value.find( + (item: any) => item.id === flowId, + ); if (foundFlow) { choiceFlowId(foundFlow); } @@ -201,7 +216,7 @@ const delNode = (id) => { }; // 处理变量更新事件 - 仅重新加载对话变量用于开始节点展示 -const handleVariablesUpdated = async () => { +const handleVariablesUpdated = async () => { // 延迟加载,确保后端数据已经同步 setTimeout(async () => { await loadConversationVariablesForDisplay(); @@ -214,16 +229,16 @@ const loadConversationVariablesForDisplay = async () => { console.warn('没有flowId,跳过变量加载'); return; } - + variablesLoading.value = true; - - try { + + try { // 只加载对话变量(不带current_step_id,因为是给开始节点展示用的) - const convVars: any = await listVariables({ - scope: 'conversation', - flow_id: flowObj.value.flowId + const convVars: any = await listVariables({ + scope: 'conversation', + flow_id: flowObj.value.flowId, }); - + // 修复:支持多种API响应结构 let variables: any[] = []; if (convVars?.result?.variables) { @@ -236,13 +251,12 @@ const loadConversationVariablesForDisplay = async () => { // 结构3: 直接返回数组 variables = convVars; } - + if (variables && Array.isArray(variables)) { conversationVariablesForDisplay.value = variables; } else { conversationVariablesForDisplay.value = []; } - } catch (error) { console.error('❌ 加载对话变量失败:', error); ElMessage.error('加载对话变量失败'); @@ -251,8 +265,6 @@ const loadConversationVariablesForDisplay = async () => { } }; - - // 验证节点是否都连接 const nodeAndLineConnection = () => { // 获取当前所有节点和边 @@ -290,83 +302,120 @@ const nodeAndLineConnection = () => { }; // 编辑yaml const editYamlDrawer = (name, desc, yamlCode, nodeId) => { - // 查找当前节点 const currentNode = findNode(nodeId); - - // 检查是否为Code类型节点 - if (currentNode && currentNode.data.callId === 'Code') { - // 打开代码节点编辑器 - currentCodeNodeData.value = { - name: currentNode.data.name, - description: currentNode.data.description, - callId: currentNode.data.callId, - - // 代码节点自身属性 - code: currentNode.data.code || '', - codeType: currentNode.data.codeType || 'python', - securityLevel: currentNode.data.securityLevel || 'low', - timeoutSeconds: currentNode.data.timeoutSeconds || 30, - memoryLimitMb: currentNode.data.memoryLimitMb || 128, - cpuLimit: currentNode.data.cpuLimit || 0.5, - - // 用户定义的输入输出参数 - input_parameters: currentNode.data.parameters?.input_parameters || {}, - output_parameters: currentNode.data.parameters?.output_parameters || {}, - }; - nodeYamlId.value = nodeId; - selectedNodeId.value = nodeId; - isEditCodeNode.value = true; - } else if (currentNode && currentNode.data.callId === 'DirectReply') { - // 打开直接回复节点编辑器 - currentDirectReplyNodeData.value = { - name: currentNode.data.name, - description: currentNode.data.description, - callId: currentNode.data.callId, - parameters: { - input_parameters: { - answer: currentNode.data.parameters?.input_parameters?.answer || '' + console.log(currentNode?.data.callId); + switch (currentNode?.data.callId) { + case 'Code': + // 打开代码节点编辑器 + currentCodeNodeData.value = { + name: currentNode.data.name, + description: currentNode.data.description, + callId: currentNode.data.callId, + + // 代码节点自身属性 + code: currentNode.data.code || '', + codeType: currentNode.data.codeType || 'python', + securityLevel: currentNode.data.securityLevel || 'low', + timeoutSeconds: currentNode.data.timeoutSeconds || 30, + memoryLimitMb: currentNode.data.memoryLimitMb || 128, + cpuLimit: currentNode.data.cpuLimit || 0.5, + + // 用户定义的输入输出参数 + input_parameters: currentNode.data.parameters?.input_parameters || {}, + output_parameters: currentNode.data.parameters?.output_parameters || {}, + }; + isEditCodeNode.value = true; + break; + case 'DirectReply': + // 打开直接回复节点编辑器 + currentDirectReplyNodeData.value = { + name: currentNode.data.name, + description: currentNode.data.description, + callId: currentNode.data.callId, + parameters: { + input_parameters: { + answer: currentNode.data.parameters?.input_parameters?.answer || '', + }, + output_parameters: + currentNode.data.parameters?.output_parameters || {}, }, - output_parameters: currentNode.data.parameters?.output_parameters || {} - } - }; - nodeYamlId.value = nodeId; - selectedNodeId.value = nodeId; - isEditDirectReplyNode.value = true; - } else if (currentNode && currentNode.data.callId === 'Choice') { - // 打开条件分支节点编辑器 - currentChoiceBranchNodeData.value = { - name: currentNode.data.name, - description: currentNode.data.description, - callId: currentNode.data.callId, - parameters: currentNode.data.parameters || { - input_parameters: { choices: [] }, - output_parameters: { - branch_id: { - type: 'string', - description: '选中的分支ID' - } -} - } - }; - nodeYamlId.value = nodeId; - selectedNodeId.value = nodeId; - isEditChoiceBranchNode.value = true; - } else { - // 打开YAML编辑器(其他节点类型) - yamlContent.value = yamlCode; - nodeName.value = name; - nodeDesc.value = desc; - isEditYaml.value = true; - nodeYamlId.value = nodeId; - selectedNodeId.value = nodeId; + }; + isEditDirectReplyNode.value = true; + break; + case 'Choice': + // 打开条件分支节点编辑器 + currentChoiceBranchNodeData.value = { + name: currentNode.data.name, + description: currentNode.data.description, + callId: currentNode.data.callId, + parameters: currentNode.data.parameters || { + input_parameters: { choices: [] }, + output_parameters: { + branch_id: { + type: 'string', + description: '选中的分支ID', + }, + }, + }, + }; + isEditChoiceBranchNode.value = true; + break; + case 'API': + isEditApiCall.value = true; + break; + case 'LLM': + isEditLLM.value = true; + break; + case 'RAG': + isEditRAG.value = true; + break; + case 'MCP': + isEditMCP.value = true; + break; + default: + isEditYaml.value = true; + break; } - - // 编辑时,需要debug 后才可发布 + + yamlContent.value = yamlCode; + nodeName.value = name; + nodeDesc.value = desc; + nodeYamlId.value = nodeId; + selectedNodeId.value = nodeId; + // ↓ 调用节点需要debug 后才可发布 emits('updateFlowsDebug', false); }; // 关闭抽屉 -const closeDrawer = () => { - isEditYaml.value = false; +const closeDrawer = (node_type) => { + switch (node_type) { + case NodeType.API: + isEditApiCall.value = false; + break; + case NodeType.LLM: + isEditLLM.value = false; + break; + case NodeType.RAG: + isEditRAG.value = false; + break; + case NodeType.MCP: + isEditMCP.value = false; + break; + case NodeType.CODE: + isEditCodeNode.value = false; + currentCodeNodeData.value = {}; + break; + case NodeType.DIRECTREPLY: + isEditDirectReplyNode.value = false; + currentDirectReplyNodeData.value = {}; + break; + case NodeType.CHOICE: + isEditChoiceBranchNode.value = false; + currentChoiceBranchNodeData.value = {}; + break; + default: + isEditYaml.value = false; + } + // 清除选中状态 selectedNodeId.value = ''; }; @@ -385,13 +434,13 @@ const saveCodeNode = (nodeData, nodeId) => { id: nodeId, ...nodeData, }; - + // 调用保存接口 saveFlow(updateNodeParameter); - + // 关闭抽屉 closeCodeNodeDrawer(); - + ElMessage.success('代码节点保存成功'); }; @@ -409,13 +458,13 @@ const saveDirectReplyNode = (nodeData, nodeId) => { id: nodeId, ...nodeData, }; - + // 调用保存接口 saveFlow(updateNodeParameter); - + // 关闭抽屉 closeDirectReplyDrawer(); - + ElMessage.success('直接回复节点保存成功'); }; @@ -433,13 +482,13 @@ const saveChoiceBranchNode = (nodeData, nodeId) => { id: nodeId, ...nodeData, }; - + // 调用保存接口 saveFlow(updateNodeParameter); - + // 关闭抽屉 closeChoiceBranchDrawer(); - + ElMessage.success('条件分支节点保存成功'); }; @@ -468,7 +517,7 @@ const editStartNodeDrawer = async (name, desc, yamlCode, nodeId) => { nodeYamlId.value = nodeId; // 设置选中的节点 selectedNodeId.value = nodeId; - + isEditStartNode.value = true; // 编辑 yaml 时,需要debug 后才可发布 emits('updateFlowsDebug', false); @@ -534,7 +583,7 @@ onMounted(() => { }) .then((res) => { const services = res[1]?.result.services || []; - + apiServiceList.value = services; allApiServiceList.value = services; apiLoading.value = false; @@ -548,8 +597,6 @@ onUnmounted(() => { sessionStorage.setItem('workflowViewPortY', ''); }); - - // 处理节点拖拽开始事件 const handleNodeDragStart = (event: DragEvent, node: any) => { onDragStart(event, node.type, { @@ -621,7 +668,7 @@ const getCreatedFlow = async (createdFlowObj) => { workFlowItemName.value = createdFlowObj.name; // 回显工作流节点和边 redrageFlow(createdFlowObj?.nodes, createdFlowObj?.edges); - + // 重新加载对话变量,确保显示当前flow的正确变量状态 await loadConversationVariablesForDisplay(); } @@ -672,11 +719,11 @@ const editFlow = async (item) => { appId: route.query?.appId, flowId: item.id, }); - + if (res[1]?.result?.flow) { flowObj.value = res[1].result.flow; redrageFlow(flowObj.value.nodes || [], flowObj.value.edges || []); - + // 加载对话变量用于开始节点展示 await loadConversationVariablesForDisplay(); } @@ -758,13 +805,15 @@ const redrageFlow = (nodesList, edgesList) => { newNode.deletable = false; } else if (node.callId === 'Choice') { newNode.type = 'Choice'; - + // 处理Choice节点的参数,确保包含ELSE分支 const choices = node.parameters?.input_parameters?.choices || []; - + // 检查是否已有默认分支 - const hasDefaultBranch = choices.some(choice => choice.is_default === true); - + const hasDefaultBranch = choices.some( + (choice) => choice.is_default === true, + ); + // 如果没有默认分支,添加一个ELSE分支 if (!hasDefaultBranch) { choices.push({ @@ -772,30 +821,30 @@ const redrageFlow = (nodesList, edgesList) => { name: 'ELSE', is_default: true, conditions: [], - logic: 'and' + logic: 'and', }); } - + newNode.data = { ...newNode.data, parameters: { - input_parameters: { - choices: choices + input_parameters: { + choices: choices, }, - output_parameters: node.parameters?.output_parameters || { + output_parameters: node.parameters?.output_parameters || { branch_id: { type: 'string', - description: '选中的分支ID' - } - } - } + description: '选中的分支ID', + }, + }, + }, }; } else if (node.callId === 'Code') { // Code节点特殊处理:从parameters中提取特有属性并添加到data中 newNode.type = 'custom'; newNode.data = { ...newNode.data, - nodeId: 'Code', // 设置正确的nodeId + nodeId: 'Code', // 设置正确的nodeId // 从parameters中提取Code节点特有的配置属性 code: node.parameters?.code || '', codeType: node.parameters?.codeType || 'python', @@ -840,43 +889,43 @@ const handleInsertNode = (edgeInfo) => { if (debugDialogVisible.value) { return; // 调试模式下不允许插入节点 } - + // 获取Vue Flow画布的viewport信息 const viewport = getViewport(); - + // 获取Vue Flow容器的位置信息 const vueFlowElement = document.querySelector('.vue-flow__viewport'); const containerRect = vueFlowElement?.getBoundingClientRect(); - + if (!containerRect) { console.error('无法获取Vue Flow容器位置'); return; } - + // 将画布坐标转换为相对于Vue Flow容器的坐标 const containerX = edgeInfo.midX * viewport.zoom + viewport.x; const containerY = edgeInfo.midY * viewport.zoom + viewport.y; - + // 计算建议的菜单方向(不做位置调整,只做方向建议) const menuWidth = 400; let direction: 'left' | 'right' = 'right'; - + // 简单的方向建议:如果右侧空间不足,建议左侧显示 if (containerX + menuWidth + 20 > containerRect.width) { direction = 'left'; } else { direction = 'right'; } - + // 更新插入菜单数据(直接传递容器坐标,让子组件处理边界) insertMenuData.value = { visible: true, position: { x: containerX, - y: containerY + y: containerY, }, edgeInfo, - direction + direction, }; }; @@ -894,26 +943,28 @@ const executeInsertNode = (nodeMetaData) => { console.error('没有边信息,无法插入节点'); return; } - + const edgeInfo = insertMenuData.value.edgeInfo; - + // 生成新节点ID const newNodeId = `node_${Date.now()}`; - + // 找到源边 - const sourceEdge = getEdges.value.find(edge => edge.id === edgeInfo.edgeId); + const sourceEdge = getEdges.value.find( + (edge) => edge.id === edgeInfo.edgeId, + ); if (!sourceEdge) { console.error('找不到要插入的边'); return; } - + // 创建新节点 const newNode = { id: newNodeId, type: nodeMetaData.callId === 'Choice' ? 'Choice' : 'custom', position: { x: edgeInfo.midX - 100, // 节点宽度的一半 - y: edgeInfo.midY - 40 // 节点高度的一半 + y: edgeInfo.midY - 40, // 节点高度的一半 }, data: { name: nodeMetaData.name, @@ -921,44 +972,51 @@ const executeInsertNode = (nodeMetaData) => { nodeId: nodeMetaData.nodeId, callId: nodeMetaData.callId, serviceId: nodeMetaData.serviceId || 'default', - parameters: nodeMetaData.callId === 'Choice' ? { - input_parameters: { - choices: [ - { - branch_id: `else_${newNodeId}`, - name: 'ELSE', - is_default: true, - conditions: [], - logic: 'and' + parameters: + nodeMetaData.callId === 'Choice' + ? { + input_parameters: { + choices: [ + { + branch_id: `else_${newNodeId}`, + name: 'ELSE', + is_default: true, + conditions: [], + logic: 'and', + }, + ], + }, + output_parameters: { + branch_id: { + type: 'string', + description: '选中的分支ID', + }, + }, } - ] - }, - output_parameters: { - branch_id: { - type: 'string', - description: '选中的分支ID' - } - } - } : nodeMetaData.callId === 'DirectReply' ? { - input_parameters: { - answer: '' // 确保新建的DirectReply节点内容为空 - }, - output_parameters: {} - } : { - input_parameters: {}, - output_parameters: {} - } + : nodeMetaData.callId === 'DirectReply' + ? { + input_parameters: { + answer: '', // 确保新建的DirectReply节点内容为空 + }, + output_parameters: {}, + } + : { + input_parameters: {}, + output_parameters: {}, + }, }, - deletable: true + deletable: true, }; - + // 添加新节点 const currentNodes = [...getNodes.value, newNode]; setNodes(currentNodes); - + // 删除原来的边 - const currentEdges = getEdges.value.filter(edge => edge.id !== edgeInfo.edgeId); - + const currentEdges = getEdges.value.filter( + (edge) => edge.id !== edgeInfo.edgeId, + ); + // 创建新的边:源节点 -> 新节点 -> 目标节点 const newEdge1 = { id: `edge_${Date.now()}_1`, @@ -968,35 +1026,34 @@ const executeInsertNode = (nodeMetaData) => { type: 'normal', data: { sourceStatus: 'default', - targetStatus: 'default' - } + targetStatus: 'default', + }, }; - + const newEdge2 = { - id: `edge_${Date.now()}_2`, + id: `edge_${Date.now()}_2`, source: newNodeId, target: sourceEdge.target, targetHandle: sourceEdge.targetHandle, type: 'normal', data: { sourceStatus: 'default', - targetStatus: 'default' - } + targetStatus: 'default', + }, }; - + // 设置新的边 setEdges([...currentEdges, newEdge1, newEdge2]); - + // 触发工作流状态更新 emits('updateFlowsDebug', false); updateFlowsDebugStatus.value = false; nodeAndLineConnection(); - + // 关闭菜单 closeInsertNodeMenu(); - + ElMessage.success(`${nodeMetaData.name} 节点插入成功`); - } catch (error) { console.error('插入节点失败:', error); ElMessage.error('插入节点失败'); @@ -1131,10 +1188,10 @@ const saveFlow = (updateNodeParameter?, debug?) => { apiId: item.data.nodeId, serviceId: item.data.serviceId, stepId: item.id, - nodeId: item.data.nodeId, // 添加nodeId字段 + nodeId: item.data.nodeId, // 添加nodeId字段 type: item.data.nodeId, }; - + // 对于Code节点,需要特殊处理parameters结构 if (item.data.callId === 'Code') { // Code节点:将所有配置放在parameters中 @@ -1152,7 +1209,7 @@ const saveFlow = (updateNodeParameter?, debug?) => { timeoutSeconds: item.data.timeoutSeconds || 30, memoryLimitMb: item.data.memoryLimitMb || 128, cpuLimit: item.data.cpuLimit || 0.5, - } + }, }; } else { // 其他节点:使用原有逻辑 @@ -1174,10 +1231,12 @@ const saveFlow = (updateNodeParameter?, debug?) => { } else if (item.type === 'branch') { // 处理分支节点保存,确保包含ELSE分支 const choices = item.data.parameters?.input_parameters?.choices || []; - + // 检查是否已有默认分支 - const hasDefaultBranch = choices.some(choice => choice.is_default === true); - + const hasDefaultBranch = choices.some( + (choice) => choice.is_default === true, + ); + // 如果没有默认分支,添加一个ELSE分支 if (!hasDefaultBranch) { choices.push({ @@ -1185,30 +1244,32 @@ const saveFlow = (updateNodeParameter?, debug?) => { name: 'ELSE', is_default: true, conditions: [], - logic: 'and' + logic: 'and', }); } - + newItem = { ...newItem, callId: 'Choice', parameters: { input_parameters: { choices: choices }, - output_parameters: item.data.parameters?.output_parameters || { + output_parameters: item.data.parameters?.output_parameters || { branch_id: { type: 'string', - description: '选中的分支ID' - } - } + description: '选中的分支ID', + }, + }, }, }; } else if (item.type === 'Choice') { // 处理Choice节点保存,确保包含ELSE分支 const choices = item.data.parameters?.input_parameters?.choices || []; - + // 检查是否已有默认分支 - const hasDefaultBranch = choices.some(choice => choice.is_default === true); - + const hasDefaultBranch = choices.some( + (choice) => choice.is_default === true, + ); + // 如果没有默认分支,添加一个ELSE分支 if (!hasDefaultBranch) { choices.push({ @@ -1216,21 +1277,21 @@ const saveFlow = (updateNodeParameter?, debug?) => { name: 'ELSE', is_default: true, conditions: [], - logic: 'and' + logic: 'and', }); } - + newItem = { ...newItem, callId: 'Choice', parameters: { input_parameters: { choices: choices }, - output_parameters: item.data.parameters?.output_parameters || { + output_parameters: item.data.parameters?.output_parameters || { branch_id: { type: 'string', - description: '选中的分支ID' - } - } + description: '选中的分支ID', + }, + }, }, }; } @@ -1252,28 +1313,37 @@ const saveFlow = (updateNodeParameter?, debug?) => { updateNodes.forEach((item) => { if (item.stepId === updateNodeParameter.id) { if (item.type === 'Code') { - item.parameters.input_parameters = updateNodeParameter.parameters.input_parameters; - item.parameters.output_parameters = updateNodeParameter.parameters.output_parameters; + item.parameters.input_parameters = + updateNodeParameter.parameters.input_parameters; + item.parameters.output_parameters = + updateNodeParameter.parameters.output_parameters; item.parameters.code = updateNodeParameter.parameters.code; item.parameters.codeType = updateNodeParameter.parameters.codeType; - item.parameters.securityLevel = updateNodeParameter.parameters.securityLevel; - item.parameters.timeoutSeconds = updateNodeParameter.parameters.timeoutSeconds; - item.parameters.memoryLimitMb = updateNodeParameter.parameters.memoryLimitMb; + item.parameters.securityLevel = + updateNodeParameter.parameters.securityLevel; + item.parameters.timeoutSeconds = + updateNodeParameter.parameters.timeoutSeconds; + item.parameters.memoryLimitMb = + updateNodeParameter.parameters.memoryLimitMb; item.parameters.cpuLimit = updateNodeParameter.parameters.cpuLimit; } else if (item.callId === 'DirectReply') { // 确保parameters对象存在 if (!item.parameters) { item.parameters = {}; } - item.parameters.input_parameters = updateNodeParameter.parameters.input_parameters; - item.parameters.output_parameters = updateNodeParameter.parameters.output_parameters; + item.parameters.input_parameters = + updateNodeParameter.parameters.input_parameters; + item.parameters.output_parameters = + updateNodeParameter.parameters.output_parameters; } else if (item.callId === 'Choice') { // 条件分支节点 if (!item.parameters) { item.parameters = {}; } - item.parameters.input_parameters = updateNodeParameter.parameters.input_parameters; - item.parameters.output_parameters = updateNodeParameter.parameters.output_parameters; + item.parameters.input_parameters = + updateNodeParameter.parameters.input_parameters; + item.parameters.output_parameters = + updateNodeParameter.parameters.output_parameters; } else if (item.type === 'start') { item.variables == updateNodeParameter.variables; } else if (item.inputStream !== undefined) { @@ -1281,17 +1351,6 @@ const saveFlow = (updateNodeParameter?, debug?) => { if (!item.parameters) { item.parameters = {}; } - // 当Node以yaml编辑器形式修改了参数 - // 检查updateNodeParameter.inputStream是否包含新的数据结构 - if (updateNodeParameter.inputStream.input_parameters !== undefined && - updateNodeParameter.inputStream.output_parameters !== undefined) { - - item.parameters.input_parameters = updateNodeParameter.inputStream.input_parameters; - item.parameters.output_parameters = updateNodeParameter.inputStream.output_parameters; - } else { - // 旧格式:兼容处理 - item.parameters.input_parameters = updateNodeParameter.inputStream; - } } item.name = updateNodeParameter.name; item.description = updateNodeParameter.description; @@ -1353,12 +1412,17 @@ defineExpose({
@@ -1366,7 +1430,7 @@ defineExpose({
拖拉拽新建节点
- +
@@ -1379,14 +1443,10 @@ defineExpose({
-
- 节点 -
-
- 应用 -
+
节点
+
应用
- +
- - + +
@@ -1594,7 +1662,9 @@ defineExpose({
-
{{ $t(`flow.${StatusInfoTitle[debugStatus]}`) }}
+
+ {{ $t(`flow.${StatusInfoTitle[debugStatus]}`) }} +
+ + + + + + + + + + + + - + - + - + - + - + - + \ No newline at end of file + diff --git a/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue b/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue new file mode 100644 index 00000000..5a29edc9 --- /dev/null +++ b/src/views/createapp/components/workFlowConfig/ApiCallDrawer.vue @@ -0,0 +1,830 @@ + + + + + diff --git a/src/views/createapp/components/workFlowConfig/LLMDrawer.vue b/src/views/createapp/components/workFlowConfig/LLMDrawer.vue new file mode 100644 index 00000000..7391aa56 --- /dev/null +++ b/src/views/createapp/components/workFlowConfig/LLMDrawer.vue @@ -0,0 +1,581 @@ + + + + + diff --git a/src/views/createapp/components/workFlowConfig/MCPDrawer.vue b/src/views/createapp/components/workFlowConfig/MCPDrawer.vue new file mode 100644 index 00000000..bab04234 --- /dev/null +++ b/src/views/createapp/components/workFlowConfig/MCPDrawer.vue @@ -0,0 +1,384 @@ + + + + + diff --git a/src/views/createapp/components/workFlowConfig/RAGDrawer.vue b/src/views/createapp/components/workFlowConfig/RAGDrawer.vue new file mode 100644 index 00000000..d30a9548 --- /dev/null +++ b/src/views/createapp/components/workFlowConfig/RAGDrawer.vue @@ -0,0 +1,538 @@ + + + + + diff --git a/vite.config.ts b/vite.config.ts index 685ac906..667eb230 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,7 +20,7 @@ import type { UserConfigExport } from 'vite'; // https://vitejs.dev/config/ export default ({ mode }): UserConfigExport => { const env = loadEnv(mode, process.cwd()); - + // 开发模式使用 '/',生产模式使用 './' const baseUrl = mode === 'development' ? '/' : './'; return defineConfig({ @@ -72,10 +72,10 @@ export default ({ mode }): UserConfigExport => { }, server: { - host: '10.211.55.10', + host: 'localhost', hmr: true, - port: 8080, - origin: 'http://10.211.55.10:8080', + port: 3000, + origin: 'http://localhost:3000', headers: { 'Access-Control-Allow-Origin': '*', }, -- Gitee