├── .cursorignore ├── .github ├── FUNDING.yml └── workflows │ └── publish.yml ├── .gitignore ├── Authors.txt ├── LICENSE ├── NOTICE.txt ├── README.md ├── README_CN.md ├── __init__.py ├── assets ├── Framework.png ├── GenLabHistory.png ├── comfycopilot_nodes_recommend.gif ├── comfycopilot_nodes_search.gif ├── comfyui_manager.png ├── comfyui_manager_install.png ├── comfyui_ui_icon.png ├── discordqrcode.png ├── keygen.png ├── logo.png ├── qrcode.jpg ├── qrcode.png ├── start.png └── 工作流检索.png ├── command ├── dist └── copilot_web │ ├── App-C63C0sLF.js │ ├── ParameterDebugInterfaceV2-QYVwPrST.js │ ├── WorkflowOption-DF-pdh_V.js │ ├── assets │ ├── cc-icon-DfxqqsZi.svg │ ├── input-BFxtFr8c.css │ └── logo-BTZhX0BN.png │ ├── fonts.css │ ├── input.js │ ├── message-components-C_KwPGE8.js │ ├── vendor-markdown-Dek94WS0.js │ ├── vendor-react-V04_Axys.js │ ├── vendor-ui-Dzvx2fKw.js │ └── workflowChat-C3ZWZR1l.js ├── entry ├── comfyui-bridge.js └── entry.js ├── public └── workflows │ ├── basic_image_gen.json │ ├── default.png │ ├── face_restore.json │ └── upscale.json ├── pyproject.toml ├── scripts └── setupGitHooks.js ├── service ├── conversation_service.py └── node_service.py └── ui ├── .eslintrc.cjs ├── .npmrc ├── .prettierrc ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── scripts └── post-build.js ├── src ├── Api.ts ├── App.tsx ├── apis │ ├── comfyApiCustom.ts │ └── workflowChatApi.ts ├── components │ ├── chat │ │ ├── ApiKeyModal.tsx │ │ ├── ChatHeader.tsx │ │ ├── ChatInput.tsx │ │ ├── Icons.tsx │ │ ├── MessageList.tsx │ │ ├── SelectedNodeInfo.tsx │ │ └── messages │ │ │ ├── AIMessage.tsx │ │ │ ├── BaseMessage.tsx │ │ │ ├── DownstreamSubgraphs.tsx │ │ │ ├── LoadingMessage.tsx │ │ │ ├── NodeInstallGuide.tsx │ │ │ ├── NodeSearch.tsx │ │ │ ├── UserMessage.tsx │ │ │ └── WorkflowOption.tsx │ ├── debug │ │ ├── ParameterDebugInterfaceNew.tsx │ │ ├── ParameterDebugInterfaceV2.tsx │ │ ├── README.md │ │ ├── index.ts │ │ ├── modals │ │ │ ├── AIWritingModal.tsx │ │ │ └── ImageModal.tsx │ │ ├── screens │ │ │ ├── ConfigureParameterScreen.tsx │ │ │ ├── ConfirmConfigurationScreen.tsx │ │ │ ├── HistoryItemScreen.tsx │ │ │ ├── HistoryScreen.tsx │ │ │ ├── InitialScreen.tsx │ │ │ ├── ProcessingScreen.tsx │ │ │ └── ResultGalleryScreen.tsx │ │ ├── styles │ │ │ └── highlightPulseStyle.ts │ │ ├── types │ │ │ └── parameterDebugTypes.ts │ │ └── utils │ │ │ ├── aiTextUtils.ts │ │ │ ├── historyUtils.ts │ │ │ ├── imageGenerationUtils.ts │ │ │ ├── interfaceUtils.ts │ │ │ ├── localStorageUtils.ts │ │ │ ├── modalUtils.ts │ │ │ ├── navigationUtils.ts │ │ │ ├── parameterUtils.ts │ │ │ ├── searchUtils.ts │ │ │ ├── stateManagementUtils.ts │ │ │ └── textInputUtils.ts │ ├── markdown.tsx │ └── markdown │ │ └── markdownComponents.ts ├── config.ts ├── const.ts ├── constants │ └── events.ts ├── context │ └── ChatContext.tsx ├── fonts.css ├── fonts │ ├── cc-icon.eot │ ├── cc-icon.svg │ ├── cc-icon.ttf │ └── cc-icon.woff ├── hooks │ ├── useDraggable.ts │ ├── useMousePosition.ts │ ├── useNodeSelection.ts │ ├── useNodeSelection.tsx │ └── useResizable.ts ├── index.css ├── input.css ├── main.tsx ├── output.css ├── scoped-tailwind.css ├── types │ ├── comfy.d.ts │ ├── dbTypes.ts │ ├── litegraph.d.ts │ └── types.ts ├── utils │ ├── OsPathUtils.ts │ ├── civitUtils.ts │ ├── comfyapp.ts │ ├── crypto.ts │ ├── deepJsonDiffCheck.ts │ ├── downloadJsonFile.ts │ ├── downloadWorkflowsZip.ts │ ├── encryptUtils.ts │ ├── findSfwImage.ts │ ├── graphUtils.ts │ ├── jsonUtils.ts │ ├── mediaMetadataUtils.ts │ ├── privacyUtils.ts │ ├── queuePrompt.ts │ ├── saveShareKey.ts │ ├── showAlert.css │ ├── showAlert.ts │ └── uuid.ts ├── vite-env.d.ts └── workflowChat │ └── workflowChat.tsx ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.cursorignore: -------------------------------------------------------------------------------- 1 | # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) 2 | dist/ 3 | entry/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [aib] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Comfy registry 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | paths: 9 | - "pyproject.toml" 10 | 11 | permissions: 12 | issues: write 13 | 14 | jobs: 15 | publish-node: 16 | name: Publish Custom Node to registry 17 | runs-on: ubuntu-latest 18 | if: ${{ github.repository_owner == 'AIDC-AI' }} 19 | steps: 20 | - name: Check out code 21 | uses: actions/checkout@v4 22 | with: 23 | submodules: true 24 | - name: Publish Custom Node 25 | uses: Comfy-Org/publish-node-action@v1 26 | with: 27 | ## Add your own personal access token to your Github Repository secrets and reference it here. 28 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | hash 3 | .env 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | pnpm-debug.log* 11 | lerna-debug.log* 12 | 13 | ui/node_modules 14 | # ui/src/utils/encryptUtils.ts 15 | db 16 | dist-ssr 17 | *.local 18 | backup 19 | # Editor directories and files 20 | .vscode/* 21 | !.vscode/extensions.json 22 | .idea 23 | .DS_Store 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | .cache 30 | # dev 31 | ui/yarn.lock -------------------------------------------------------------------------------- /Authors.txt: -------------------------------------------------------------------------------- 1 | # Name or Organization 2 | 3 | AIDC-AI 4 | Christian Byrne -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2025 AIDC-AI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | 中文 | [English](./README.md) 2 | 3 |
4 | 5 | # 🎯 ComfyUI-Copilot: ComfyUI 智能助手 6 | 7 |

8 | 9 |
10 | 版本 11 | 许可证 12 | 星标 13 | 问题 14 | Python 15 | 16 |

17 | 18 | 👾 _**阿里巴巴国际数字商业集团**_ 👾 19 | 20 |

21 | :octocat: Github   |    💬 微信   22 |

23 | 24 |
25 | 26 | https://github.com/user-attachments/assets/0372faf4-eb64-4aad-82e6-5fd69f349c2c 27 | 28 | ## 🌟 介绍 29 | 30 | 欢迎使用 **ComfyUI-Copilot**,这是一个基于 ComfyUI 框架构建的智能助手,通过自然语言交互简化并增强 AI 算法调试和部署过程。 31 | 32 | 无论是生成文本、图像还是音频,ComfyUI-Copilot 都提供直观的节点推荐、工作流构建辅助和模型查询服务,以简化您的开发过程。 33 | 34 |
35 | 36 |
37 | 38 | --- 39 | 40 | ## 🤔 为什么选择 ComfyUI-Copilot? 41 | 42 | - 🍀 **易于使用**:通过自然语言交互降低入门门槛,使 Comfy-UI 即便对初学者也能轻松上手。 43 | - 🍀 **智能推荐**:利用 AI 驱动的节点建议和工作流实现来提高开发效率。 44 | - 🍀 **实时帮助**:享受全天候互动支持,以解决开发过程中遇到的任何问题。 45 | 46 | --- 47 | ## 🚀 Updates 48 | ### 2025.05.16 发布 49 | #### ✨ 新功能 50 | - GenLab历史:支持参数探索后历史结果的查询。 51 | - 个性化工作流程生成:用户可以输入自己的需求,大语言模型为他们生成定制的工作流程。 52 | 53 |
54 | 55 |
56 | 57 | 58 | ### 2025.04.28 发布 59 | #### ✨ 新功能 60 | - 我们的前端用户界面嵌入在ComfyUI界面中。只需点击左侧栏中的ComfyUI-Copilot图标即可启动我们的服务。 61 | - 界面会自动适应ComfyUI的黑/白主题,背景颜色会相应切换。 62 | 63 | 64 | 65 | ### 2025.04.08 发布 66 | 67 | #### ✨ 新功能 68 | 69 | ##### 1. 🎉 GenLab 上线 70 | 我们很高兴地宣布 GenLab 现已正式上线,带来两个强大的新功能: 71 | 72 | ###### a. 🔍 参数探索:对参数进行遍历,生成的结果在视觉层面进行比较,帮助您快速找到最佳参数配置 73 | - 使用方法: 74 | - 1、点击您希望探索的节点 75 | - 2、选择要探索的参数 76 | - 3、设置参数值范围 77 | - 4、系统将自动批量执行不同的参数组合 78 | 79 | https://github.com/user-attachments/assets/8069744a-411e-4a25-b1a5-4503a303bc6c 80 | 81 | ###### b. ✏️ 提示词重写助手:新功能帮助用户生成丰富、高质量的"咒语" 82 | 83 | https://github.com/user-attachments/assets/85decdbf-9ae5-4c78-818b-8db444ed4e7b 84 | 85 | #### 🛠️ 错误修复 86 | - 🐛 修复了多个已知问题,提高了系统稳定性 87 | 88 | ### 2025.02.27 版本更新 89 | * **多模型支持**:增加集成DeepSeek AI和Qwen-plus模型; 90 | * **节点安装体验优化**:增加当用户尝试加载未安装的节点时,智能跳转至 GitHub 仓库或相关谷歌搜索结果的功能。 91 | * **提示词生成改进**:增强 SD 提示生成能力,改进了错误日志分析能力。 92 | * **性能优化**:解决了在使用 Copilot 时在 GitHub Issue中报告的延迟问题。 93 | * **多语种优化**:修复节点信息查询实现了多语言环境响应,增加节点查询的上下文响应。 94 | * **子图推荐优化**:重新设计下游子图生成逻辑,以过滤冗余子图,移除过大的子图推荐,并过滤上游节点以改善用户体验。 95 | * **模型数据库迭代**:新增覆盖60000+个LoRA和Checkpoint数据。 96 | --- 97 | 98 | ## 🔥 核心功能(V1.0.0) 99 | 100 | - 💎 **互动问答机器人**:访问强大的问答平台,用户可以轻松询问模型细节、节点详情和参数使用。 101 | - 💎 **自然语言节点建议**:使用我们先进的搜索机制快速找到所需节点,提高工作流构建效率。 102 | 103 | 104 | - 💎 **节点查询系统**:深入探索节点,查看其说明、参数定义、使用技巧和下游工作流推荐。 105 | 106 | 107 | - 💎 **智能工作流助手**:自动识别开发者需求,推荐并构建合适的工作流框架,减少手动设置时间。 108 | - 💎 **模型查询**:根据需求提示 Copilot 查找基础模型和 'lora'。 109 | - 💎 **即将推出的功能**: 110 | 111 | - **自动参数调整**:利用机器学习算法无缝分析和优化关键工作流参数。 112 | - **错误诊断和修复建议**:获取全面的错误见解和修正建议,以快速定位和解决问题。 113 | 114 | --- 115 | 116 | ## 🚀 快速开始 117 | 118 | **仓库概览**:访问 [GitHub 仓库](https://github.com/AIDC-AI/ComfyUI-Copilot) 以获取完整代码库。 119 | 120 | 1. **安装**: 121 | 122 | ```bash 123 | cd ComfyUI/custom_nodes 124 | git clone git@github.com:AIDC-AI/ComfyUI-Copilot.git 125 | ``` 126 | 127 | 或 128 | 129 | ```bash 130 | cd ComfyUI/custom_nodes 131 | git clone https://github.com/AIDC-AI/ComfyUI-Copilot 132 | ``` 133 | 134 | 或 135 | 136 | **使用 ComfyUI 管理器**:打开 ComfyUI 管理器,点击自定义节点管理器,搜索 ComfyUI-Copilot,并点击安装按钮。 137 | 138 | 139 | 140 | 2. **激活**:在运行 ComfyUI 项目后,在面板右上角找到 Copilot 激活按钮以启动其服务。 141 | 142 | 143 | 3. **API Key 生成**:在链接中输入您的电子邮件地址,API Key 将稍后自动发送到您的电子邮件地址。 144 | 145 | 146 | 4. **注意**:本项目处于早期阶段。请更新到最新代码以获取新功能。您可以使用 git pull 获取最新代码,或在 ComfyUI Manager 插件中点击“更新”。 147 | 148 | --- 149 | 150 | ## 🤝 贡献 151 | 152 | 我们欢迎任何形式的贡献!可以随时提出 Issues、提交 Pull Request 或建议新功能。 153 | 154 | 155 | ## 📞 联系我们 156 | 157 | 如有任何疑问或建议,请随时联系:ComfyUI-Copilot@service.alibaba.com。 158 | 159 | 微信服务群: 160 |
161 | 162 |
163 | 164 | ## 📚 许可证 165 | 166 | 该项目采用 MIT 许可证 - 有关详情,请参阅 [LICENSE](https://opensource.org/licenses/MIT) 文件。 167 | 168 | ## ⭐ 星标历史 169 | 170 | [![星标历史图](https://api.star-history.com/svg?repos=AIDC-AI/ComfyUI-Copilot&type=Date)](https://star-history.com/#AIDC-AI/ComfyUI-Copilot&Date) 171 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2025 AIDC-AI 2 | # Licensed under the MIT License. 3 | 4 | import asyncio 5 | import server 6 | from aiohttp import web 7 | import folder_paths 8 | from .service.conversation_service import * 9 | 10 | WEB_DIRECTORY = "entry" 11 | NODE_CLASS_MAPPINGS = {} 12 | __all__ = ['NODE_CLASS_MAPPINGS'] 13 | version = "V2.1.0" 14 | 15 | workspace_path = os.path.join(os.path.dirname(__file__)) 16 | comfy_path = os.path.dirname(folder_paths.__file__) 17 | db_dir_path = os.path.join(workspace_path, "db") 18 | 19 | dist_path = os.path.join(workspace_path, 'dist/copilot_web') 20 | if os.path.exists(dist_path): 21 | server.PromptServer.instance.app.add_routes([ 22 | web.static('/copilot_web/', dist_path), 23 | ]) 24 | else: 25 | print(f"🦄🦄🔴🔴Error: Web directory not found: {dist_path}") 26 | -------------------------------------------------------------------------------- /assets/Framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/Framework.png -------------------------------------------------------------------------------- /assets/GenLabHistory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/GenLabHistory.png -------------------------------------------------------------------------------- /assets/comfycopilot_nodes_recommend.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/comfycopilot_nodes_recommend.gif -------------------------------------------------------------------------------- /assets/comfycopilot_nodes_search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/comfycopilot_nodes_search.gif -------------------------------------------------------------------------------- /assets/comfyui_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/comfyui_manager.png -------------------------------------------------------------------------------- /assets/comfyui_manager_install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/comfyui_manager_install.png -------------------------------------------------------------------------------- /assets/comfyui_ui_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/comfyui_ui_icon.png -------------------------------------------------------------------------------- /assets/discordqrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/discordqrcode.png -------------------------------------------------------------------------------- /assets/keygen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/keygen.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/logo.png -------------------------------------------------------------------------------- /assets/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/qrcode.jpg -------------------------------------------------------------------------------- /assets/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/qrcode.png -------------------------------------------------------------------------------- /assets/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/start.png -------------------------------------------------------------------------------- /assets/工作流检索.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/assets/工作流检索.png -------------------------------------------------------------------------------- /command: -------------------------------------------------------------------------------- 1 | mkdir -p static/workflows -------------------------------------------------------------------------------- /dist/copilot_web/App-C63C0sLF.js: -------------------------------------------------------------------------------- 1 | function getImportPath(filename) { 2 | return `./${filename}`; 3 | } 4 | const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["copilot_web/workflowChat-C3ZWZR1l.js","copilot_web/input.js","copilot_web/vendor-markdown-Dek94WS0.js","copilot_web/vendor-react-V04_Axys.js","copilot_web/message-components-C_KwPGE8.js","copilot_web/assets/input-BFxtFr8c.css","copilot_web/fonts.css"].map(path => { 5 | const apiBase = window.comfyAPI?.api?.api?.api_base; 6 | if (apiBase) { 7 | // 有 API base 时,使用完整路径 8 | return `${apiBase.substring(1)}/${path}`; 9 | } else { 10 | // 没有 API base 时,使用相对路径 11 | return `./${path}`; 12 | } 13 | }))))=>i.map(i=>d[i]); 14 | import{_ as a}from"./input.js";import{j as t}from"./vendor-markdown-Dek94WS0.js";import{r,R as i}from"./vendor-react-V04_Axys.js";import{C as n}from"./message-components-C_KwPGE8.js";/* empty css */const o={EXPLAIN_NODE:"copilot:explain-node"},d=i.lazy(()=>a(()=>import(getImportPath("workflowChat-C3ZWZR1l.js")).then(e=>e.w),__vite__mapDeps([0,1,2,3,4,5,6])).then(e=>({default:e.default})));function p(){const[e,s]=r.useState(!1);return r.useEffect(()=>{const l=()=>{s(!0)};return window.addEventListener(o.EXPLAIN_NODE,l),()=>window.removeEventListener(o.EXPLAIN_NODE,l)},[]),t.jsx(n,{children:t.jsx("div",{className:"h-full w-full flex flex-col",children:t.jsx(r.Suspense,{fallback:t.jsx("div",{className:"h-full w-full flex items-center justify-center",children:"Loading..."}),children:t.jsx(d,{visible:!0,triggerUsage:e,onUsageTriggered:()=>s(!1)})})})})}export{p as default}; 15 | -------------------------------------------------------------------------------- /dist/copilot_web/WorkflowOption-DF-pdh_V.js: -------------------------------------------------------------------------------- 1 | function getImportPath(filename) { 2 | return `./${filename}`; 3 | } 4 | import{j as t}from"./vendor-markdown-Dek94WS0.js";import{W as p,g as u,a as d}from"./message-components-C_KwPGE8.js";import{r as N}from"./vendor-react-V04_Axys.js";function O({content:v,name:C="Assistant",avatar:_,latestInput:y,installedNodes:k,onAddMessage:g}){const h=JSON.parse(v),w=h.ext?.find(e=>e.type==="workflow")?.data||[],[i,c]=N.useState({}),b=async e=>{if(!e.id){console.error("No workflow id provided");return}const r=String(e.id);if(!i[r]){c(o=>({...o,[r]:!0})),p.trackEvent({event_type:"workflow_accept",message_type:"workflow",message_id:h.message_id,data:{workflow_id:e.id,workflow_name:e.name}});try{const o=await p.getOptimizedWorkflow(e.id,y);if(o.workflow){const a=new Set;if(o.workflow.nodes)for(const s of o.workflow.nodes)a.add(s.type);else for(const s of Object.values(o.workflow))a.add(s.class_type);const n=Array.from(a).filter(s=>!k.includes(s));if(console.log("[WorkflowOption] Missing node types:",n),n.length>0){try{console.log("[WorkflowOption] Fetching info for missing nodes");const s=await p.batchGetNodeInfo(n);console.log("[WorkflowOption] Received node infos:",s);const m={text:"",ext:[{type:"node_install_guide",data:s.map(l=>({name:l.name,repository_url:l.github_url}))}]},f={id:u(),role:"ai",content:JSON.stringify(m),format:"markdown",name:"Assistant",metadata:{pendingWorkflow:o.workflow,optimizedParams:o.optimized_params}};g?.(f)}catch(s){console.error("[WorkflowOption] Error fetching node info:",s),alert("Error checking required nodes. Please try again.")}finally{c(s=>({...s,[r]:!1}))}return}j(o.workflow,o.optimized_params)}}catch(o){console.error("Failed to optimize workflow:",o),alert("Failed to optimize workflow. Please try again.")}finally{c(o=>({...o,[r]:!1}))}}},j=(e,r)=>{e.nodes?d.loadGraphData(e):d.loadApiJson(e);for(const[a,n,s,m,f]of r){const l=d.graph._nodes_by_id[a].widgets;for(const x of l)x.name===m&&(x.value=f)}d.graph.setDirtyCanvas(!1,!0);const o={id:u(),role:"tool",content:JSON.stringify({text:"The workflow has been successfully loaded to the canvas",ext:[]}),format:"markdown",name:"Assistant"};g?.(o)};return t.jsx("div",{className:"space-y-3",children:w.length>0&&t.jsx("div",{className:"flex flex-col space-y-4",children:w.map((e,r)=>{const o=e.id?String(e.id):"";return t.jsxs("div",{className:"flex items-center gap-4 p-4 rounded-lg border border-gray-200 hover:bg-gray-50",children:[e.image&&t.jsx("img",{src:e.image,alt:e.name,className:"w-14 h-14 object-cover rounded-lg",onError:a=>{const n=a.target;n.onerror=null,n.src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='56' height='56' viewBox='0 0 56 56' fill='none'%3E%3Crect width='56' height='56' fill='%23F3F4F6'/%3E%3Cpath d='M28 28C30.2091 28 32 26.2091 32 24C32 21.7909 30.2091 20 28 20C25.7909 20 24 21.7909 24 24C24 26.2091 25.7909 28 28 28Z' fill='%239CA3AF'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M18.7253 37.6307C19.8278 35.1533 22.6897 33.6 26 33.6H30C33.3103 33.6 36.1722 35.1533 37.2747 37.6307C37.6419 38.4561 37.0611 39.2 36.1694 39.2H19.8306C18.9389 39.2 18.3581 38.4561 18.7253 37.6307Z' fill='%239CA3AF'/%3E%3C/svg%3E"}}),t.jsxs("div",{className:"flex-1 break-words flex flex-col h-[4.5rem] justify-between",children:[t.jsx("div",{children:t.jsx("h3",{className:"font-medium text-sm line-clamp-2 h-10 overflow-hidden",children:e.name})}),t.jsxs("div",{className:"flex justify-between items-center mt-1",children:[e.description&&t.jsxs("div",{className:"relative group",children:[t.jsx("div",{className:"w-5 h-5 flex items-center justify-center text-gray-500 cursor-help",children:t.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:1.5,stroke:"currentColor",className:"w-4 h-4",children:t.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"})})}),t.jsx("div",{className:"absolute bottom-full left-0 mb-2 hidden group-hover:block",children:t.jsx("div",{className:"bg-gray-900 text-white text-xs rounded-md py-2 px-3 min-w-[400px] whitespace-normal break-words",children:e.description})})]}),t.jsx("button",{onClick:()=>b(e),disabled:i[o],className:`px-3 py-1.5 ${i[o]?"bg-gray-400 cursor-not-allowed":"bg-blue-500 hover:bg-blue-600"} text-white rounded-md transition-colors text-xs`,children:i[o]?"Loading...":"Accept"})]})]})]},r)})})})}export{O as WorkflowOption}; 5 | -------------------------------------------------------------------------------- /dist/copilot_web/assets/cc-icon-DfxqqsZi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /dist/copilot_web/assets/logo-BTZhX0BN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/dist/copilot_web/assets/logo-BTZhX0BN.png -------------------------------------------------------------------------------- /dist/copilot_web/input.js: -------------------------------------------------------------------------------- 1 | function getImportPath(filename) { 2 | return `./${filename}`; 3 | } 4 | const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["copilot_web/App-C63C0sLF.js","copilot_web/vendor-markdown-Dek94WS0.js","copilot_web/vendor-react-V04_Axys.js","copilot_web/message-components-C_KwPGE8.js","copilot_web/fonts.css"].map(path => { 5 | const apiBase = window.comfyAPI?.api?.api?.api_base; 6 | if (apiBase) { 7 | // 有 API base 时,使用完整路径 8 | return `${apiBase.substring(1)}/${path}`; 9 | } else { 10 | // 没有 API base 时,使用相对路径 11 | return `./${path}`; 12 | } 13 | }))))=>i.map(i=>d[i]); 14 | import{j as s}from"./vendor-markdown-Dek94WS0.js";import{a as w,R as p,r as x}from"./vendor-react-V04_Axys.js";import{w as v,a as g}from"./message-components-C_KwPGE8.js";/* empty css */const R="modulepreload",b=function(e){return"/"+e},m={},C=function(c,a,P){let d=Promise.resolve();if(a&&a.length>0){document.getElementsByTagName("link");const r=document.querySelector("meta[property=csp-nonce]"),t=r?.nonce||r?.getAttribute("nonce");d=Promise.allSettled(a.map(o=>{if(o=b(o),o in m)return;m[o]=!0;const i=o.endsWith(".css"),h=i?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${o}"]${h}`))return;const n=document.createElement("link");if(n.rel=i?"stylesheet":R,i||(n.as="script"),n.crossOrigin="",n.href=o,t&&n.setAttribute("nonce",t),document.head.appendChild(n),i)return new Promise((y,E)=>{n.addEventListener("load",y),n.addEventListener("error",()=>E(new Error(`Unable to preload CSS for ${o}`)))})}))}function u(r){const t=new Event("vite:preloadError",{cancelable:!0});if(t.payload=r,window.dispatchEvent(t),!t.defaultPrevented)throw r}return d.then(r=>{for(const t of r||[])t.status==="rejected"&&u(t.reason);return c().catch(u)})};var l={},f=w;l.createRoot=f.createRoot,l.hydrateRoot=f.hydrateRoot;const S=p.lazy(()=>C(async()=>{const{default:e}=await import(getImportPath("App-C63C0sLF.js"));return{default:e}},__vite__mapDeps([0,1,2,3,4])).then(({default:e})=>({default:e})));function j(){return new Promise(e=>{if(document.body)return e(document.body);document.addEventListener("DOMContentLoaded",()=>{e(document.body)})})}j().then(()=>v()).then(()=>{g.extensionManager.registerSidebarTab({id:"comfyui-copilot",icon:"cc-icon-logo",title:"ComfyUI Copilot",tooltip:"ComfyUI Copilot",type:"custom",render:e=>{const c=document.createElement("div");c.id="comfyui-copilot-plugin",c.className="h-full w-full flex flex-col",e.style.height="100%",e.appendChild(c),l.createRoot(c).render(s.jsx(p.StrictMode,{children:s.jsx(x.Suspense,{fallback:s.jsx("div",{className:"h-full w-full flex items-center justify-center",children:"Loading..."}),children:s.jsx(S,{})})}))}})});export{C as _}; 15 | -------------------------------------------------------------------------------- /dist/copilot_web/vendor-ui-Dzvx2fKw.js: -------------------------------------------------------------------------------- 1 | function getImportPath(filename) { 2 | return `./${filename}`; 3 | } 4 | import"./vendor-react-V04_Axys.js"; 5 | -------------------------------------------------------------------------------- /entry/comfyui-bridge.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | 4 | import { app } from "../../scripts/app.js"; 5 | import { api } from "../../scripts/api.js"; 6 | 7 | // 定义事件常量 8 | const COPILOT_EVENTS = { 9 | EXPLAIN_NODE: 'copilot:explain-node' 10 | }; 11 | 12 | function addExtraMenuOptions(nodeType, nodeData, app) { 13 | const original_getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; 14 | nodeType.prototype.getExtraMenuOptions = function (_, options) { 15 | original_getExtraMenuOptions?.apply(this, arguments); 16 | options.push({ 17 | content: "Explain with Copilot", 18 | callback: async () => { 19 | const nodeTypeUniqueId = nodeType?.comfyClass; 20 | // 触发自定义事件 21 | window.dispatchEvent(new CustomEvent(COPILOT_EVENTS.EXPLAIN_NODE, { 22 | detail: { nodeType: nodeTypeUniqueId } 23 | })); 24 | } 25 | }) 26 | } 27 | } 28 | 29 | app.registerExtension({ 30 | name: "ComfyUI-Copilot-Bridge", 31 | async beforeRegisterNodeDef(nodeType, nodeData, app) { 32 | addExtraMenuOptions(nodeType, nodeData, app); 33 | }, 34 | }) -------------------------------------------------------------------------------- /entry/entry.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | 4 | //@ts-ignore 5 | import { api } from "../../scripts/api.js"; 6 | 7 | setTimeout(() => { 8 | import(api.api_base + "/copilot_web/input.js"); 9 | const fontsLink = document.createElement("link"); 10 | fontsLink.rel = "stylesheet"; 11 | fontsLink.href = api.api_base + "/copilot_web/fonts.css"; 12 | document.head.appendChild(fontsLink); 13 | }, 500); 14 | -------------------------------------------------------------------------------- /public/workflows/basic_image_gen.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 9, 3 | "last_link_id": 9, 4 | "nodes": [ 5 | { 6 | "id": 7, 7 | "type": "CLIPTextEncode", 8 | "pos": [413, 389], 9 | "size": { "0": 425.27801513671875, "1": 180.6060791015625 }, 10 | "flags": {}, 11 | "order": 3, 12 | "mode": 0, 13 | "inputs": [{ "name": "clip", "type": "CLIP", "link": 5 }], 14 | "outputs": [ 15 | { 16 | "name": "CONDITIONING", 17 | "type": "CONDITIONING", 18 | "links": [6], 19 | "slot_index": 0 20 | } 21 | ], 22 | "properties": {}, 23 | "widgets_values": ["text, watermark"] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /public/workflows/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIDC-AI/ComfyUI-Copilot/b171e4b8eb4c2c3dd20fa9d7e41c85f3cdf7eec6/public/workflows/default.png -------------------------------------------------------------------------------- /public/workflows/face_restore.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 5, 3 | "last_link_id": 4, 4 | "nodes": [ 5 | { 6 | "id": 1, 7 | "type": "LoadImage", 8 | "pos": [200, 200] 9 | }, 10 | { 11 | "id": 2, 12 | "type": "FaceRestore", 13 | "pos": [500, 200] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /public/workflows/upscale.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 4, 3 | "last_link_id": 3, 4 | "nodes": [ 5 | { 6 | "id": 1, 7 | "type": "LoadImage", 8 | "pos": [200, 200] 9 | }, 10 | { 11 | "id": 2, 12 | "type": "ImageUpscale", 13 | "pos": [500, 200] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "ComfyUI-Copilot" 3 | description = "Your Intelligent Assistant for Comfy-UI." 4 | version = "1.3.3" 5 | license = {file = "LICENSE"} 6 | 7 | [project.urls] 8 | Repository = "https://github.com/AIDC-AI/ComfyUI-Copilot" 9 | # Used by Comfy Registry https://comfyregistry.org 10 | 11 | [tool.comfy] 12 | PublisherId = "yx9966" 13 | DisplayName = "ComfyUI-Copilot" 14 | Icon = "" 15 | -------------------------------------------------------------------------------- /scripts/setupGitHooks.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | 4 | 5 | const fs = require('fs'); 6 | const { exec } = require('child_process'); 7 | const path = require('path'); 8 | 9 | const files = [ 10 | { path: '../.git/info/exclude.dist', content: '/dist' }, 11 | { 12 | path: '../.git/hooks/post-checkout', 13 | content: ` 14 | #!/bin/sh 15 | 16 | # Get the current branch name 17 | BRANCH_NAME=$(git rev-parse --symbolic-full-name --abbrev-ref HEAD) 18 | 19 | # Remove previously set exclude files 20 | rm -f .git/info/exclude 21 | 22 | # When not in the beta/main branch, add additional files that need to be ignored 23 | if [ "$BRANCH_NAME" != "beta" ] && [ "$BRANCH_NAME" != "main" ]; then 24 | cp .git/info/exclude.dist .git/info/exclude 25 | # elif [ "$BRANCH_NAME" = "xxx" ]; then 26 | # cp .git/info/exclude.xxx .git/info/exclude 27 | fi`, 28 | }, 29 | ]; 30 | 31 | function createFile(filePath, content) { 32 | fs.writeFile(filePath, content, (err) => { 33 | if (err) { 34 | console.error(`An error occurred while creating file ${filePath}:`, err); 35 | } 36 | }); 37 | } 38 | 39 | function executeCommand(command, workingDirectory = '../') { 40 | exec(command, { cwd: path.resolve(__dirname, workingDirectory) }, (error, stdout, stderr) => { 41 | if (error) { 42 | console.error(`An error occurred while executing command "${command}":`, error); 43 | return; 44 | } 45 | if (stderr) { 46 | console.error(`Command error "${stderr}"`); 47 | return; 48 | } 49 | }); 50 | } 51 | 52 | function main() { 53 | files.forEach((file) => { 54 | createFile(file.path, file.content); 55 | }); 56 | executeCommand('git config advice.ignoredHook false'); 57 | executeCommand('chmod +x .git/hooks/post-checkout'); 58 | console.log('Custom git hooks installation completed') 59 | } 60 | 61 | main(); 62 | -------------------------------------------------------------------------------- /service/node_service.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2025 AIDC-AI 2 | # Licensed under the MIT License. 3 | 4 | import inspect 5 | import json 6 | import os 7 | import subprocess 8 | from nodes import NODE_CLASS_MAPPINGS 9 | import server 10 | from aiohttp import web 11 | BUILT_IN_NODE_TYPES = { 12 | "BasicScheduler", 13 | "CLIPLoader", 14 | "CLIPMergeSimple", 15 | "CLIPSave", 16 | "CLIPSetLastLayer", 17 | "CLIPTextEncode", 18 | "CLIPTextEncodeSDXL", 19 | "CLIPTextEncodeSDXLRefiner", 20 | "CLIPVisionEncode", 21 | "CLIPVisionLoader", 22 | "Canny", 23 | "CheckpointLoader", 24 | "CheckpointLoaderSimple", 25 | "CheckpointSave", 26 | "ConditioningAverage", 27 | "ConditioningCombine", 28 | "ConditioningConcat", 29 | "ConditioningSetArea", 30 | "ConditioningSetAreaPercentage", 31 | "ConditioningSetAreaStrength", 32 | "ConditioningSetMask", 33 | "ConditioningSetTimestepRange", 34 | "ConditioningZeroOut", 35 | "ControlNetApply", 36 | "ControlNetApplyAdvanced", 37 | "ControlNetLoader", 38 | "CropMask", 39 | "DiffControlNetLoader", 40 | "DiffusersLoader", 41 | "DualCLIPLoader", 42 | "EmptyImage", 43 | "EmptyLatentImage", 44 | "ExponentialScheduler", 45 | "FeatherMask", 46 | "FlipSigmas", 47 | "FreeU", 48 | "FreeU_V2", 49 | "GLIGENLoader", 50 | "GLIGENTextBoxApply", 51 | "GrowMask", 52 | "HyperTile", 53 | "HypernetworkLoader", 54 | "ImageBatch", 55 | "ImageBlend", 56 | "ImageBlur", 57 | "ImageColorToMask", 58 | "ImageCompositeMasked", 59 | "ImageCrop", 60 | "ImageInvert", 61 | "ImageOnlyCheckpointLoader", 62 | "ImageOnlyCheckpointSave", 63 | "ImagePadForOutpaint", 64 | "ImageQuantize", 65 | "ImageScale", 66 | "ImageScaleBy", 67 | "ImageScaleToTotalPixels", 68 | "ImageSharpen", 69 | "ImageToMask", 70 | "ImageUpscaleWithModel", 71 | "InpaintModelConditioning", 72 | "InvertMask", 73 | "JoinImageWithAlpha", 74 | "KSampler", 75 | "KSamplerAdvanced", 76 | "KSamplerSelect", 77 | "KarrasScheduler", 78 | "LatentAdd", 79 | "LatentBatch", 80 | "LatentBatchSeedBehavior", 81 | "LatentBlend", 82 | "LatentComposite", 83 | "LatentCompositeMasked", 84 | "LatentCrop", 85 | "LatentFlip", 86 | "LatentFromBatch", 87 | "LatentInterpolate", 88 | "LatentMultiply", 89 | "LatentRotate", 90 | "LatentSubtract", 91 | "LatentUpscale", 92 | "LatentUpscaleBy", 93 | "LoadImage", 94 | "LoadImageMask", 95 | "LoadLatent", 96 | "LoraLoader", 97 | "LoraLoaderModelOnly", 98 | "MaskComposite", 99 | "MaskToImage", 100 | "ModelMergeAdd", 101 | "ModelMergeBlocks", 102 | "ModelMergeSimple", 103 | "ModelMergeSubtract", 104 | "ModelSamplingContinuousEDM", 105 | "ModelSamplingDiscrete", 106 | "PatchModelAddDownscale", 107 | "PerpNeg", 108 | "PhotoMakerEncode", 109 | "PhotoMakerLoader", 110 | "PolyexponentialScheduler", 111 | "PorterDuffImageComposite", 112 | "PreviewImage", 113 | "RebatchImages", 114 | "RebatchLatents", 115 | "RepeatImageBatch", 116 | "RepeatLatentBatch", 117 | "RescaleCFG", 118 | "SDTurboScheduler", 119 | "SD_4XUpscale_Conditioning", 120 | "SVD_img2vid_Conditioning", 121 | "SamplerCustom", 122 | "SamplerDPMPP_2M_SDE", 123 | "SamplerDPMPP_SDE", 124 | "SaveAnimatedPNG", 125 | "SaveAnimatedWEBP", 126 | "SaveImage", 127 | "SaveLatent", 128 | "SelfAttentionGuidance", 129 | "SetLatentNoiseMask", 130 | "SolidMask", 131 | "SplitImageWithAlpha", 132 | "SplitSigmas", 133 | "StableZero123_Conditioning", 134 | "StableZero123_Conditioning_Batched", 135 | "StyleModelApply", 136 | "StyleModelLoader", 137 | "TomePatchModel", 138 | "UNETLoader", 139 | "UpscaleModelLoader", 140 | "VAEDecode", 141 | "VAEDecodeTiled", 142 | "VAEEncode", 143 | "VAEEncodeForInpaint", 144 | "VAEEncodeTiled", 145 | "VAELoader", 146 | "VAESave", 147 | "VPScheduler", 148 | "VideoLinearCFGGuidance", 149 | "unCLIPCheckpointLoader", 150 | "unCLIPConditioning" 151 | } 152 | 153 | def get_git_repo(node_type: str): 154 | if node_type not in NODE_CLASS_MAPPINGS: 155 | return None 156 | if node_type in BUILT_IN_NODE_TYPES: 157 | return None 158 | 159 | cls = NODE_CLASS_MAPPINGS[node_type] 160 | source_file = inspect.getfile(cls) 161 | directory = os.path.dirname(source_file) 162 | 163 | # Attempt to get the remote repository URL directly 164 | try: 165 | git_repo = subprocess.check_output(["git", "-C", directory, "config", "--get", "remote.origin.url"], text=True).strip() 166 | except subprocess.CalledProcessError: 167 | return None 168 | 169 | # Attempt to get the current commit hash 170 | try: 171 | commit_hash = subprocess.check_output(["git", "-C", directory, "rev-parse", "HEAD"], text=True).strip() 172 | except subprocess.CalledProcessError: 173 | return None 174 | if git_repo.endswith(".git"): 175 | git_repo = git_repo[:-4] 176 | username = git_repo.split("/")[-2] 177 | repo_name = git_repo.split("/")[-1] 178 | return {"gitRepo": f"{username}/{repo_name}", "commitHash": commit_hash} 179 | 180 | @server.PromptServer.instance.routes.post("/workspace/fetch_node_repos") # Handle POST requests 181 | async def fetch_node_repos(request): 182 | data = await request.json() 183 | nodetypes = data.get("nodes") 184 | if not nodetypes: 185 | return web.Response(status=400, text="NodeTypes parameter is required and should be a list of node types.") 186 | repos_mapping = {} 187 | for nodetype in nodetypes: 188 | try: 189 | repo = get_git_repo(nodetype) 190 | if repo: 191 | repos_mapping[nodetype] = repo 192 | except Exception as e: 193 | print(f"Error fetching repo for {nodetype}: {e}") 194 | return web.Response(text=json.dumps(repos_mapping), content_type='application/json') 195 | 196 | -------------------------------------------------------------------------------- /ui/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | 'prettier', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parser: '@typescript-eslint/parser', 12 | plugins: ['react-refresh', '@typescript-eslint', 'prettier'], 13 | rules: { 14 | 'react-refresh/only-export-components': [ 15 | 'warn', 16 | { allowConstantExport: true }, 17 | ], 18 | 'prettier/prettier': ['error'], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /ui/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ -------------------------------------------------------------------------------- /ui/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "htmlWhitespaceSensitivity": "css", 4 | "insertPragma": false, 5 | "jsxBracketSameLine": false, 6 | "printWidth": 80, 7 | "proseWrap": "always", 8 | "quoteProps": "as-needed", 9 | "requirePragma": false, 10 | "semi": true, 11 | "tabWidth": 2, 12 | "trailingComma": "all", 13 | "useTabs": false, 14 | "endOfLine": "auto" 15 | } 16 | -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "comfyui-workspace-manager", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build && node scripts/post-build.js", 9 | "build:css": "tailwindcss -i ./src/scoped-tailwind.css -o ./src/output.css --watch", 10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview", 12 | "setupGithooks": "node ../scripts/setupGitHooks", 13 | "build:pre": "npx tailwindcss -i ./src/scoped-tailwind.css -o ./src/output.css && VITE_API_BASE_URL=https://comfyui-copilot-server-pre.onrender.com vite build && node scripts/post-build.js", 14 | "build:prod": "npx tailwindcss -i ./src/scoped-tailwind.css -o ./src/output.css && VITE_API_BASE_URL=https://comfyui-copilot-server.onrender.com vite build && node scripts/post-build.js" 15 | }, 16 | "dependencies": { 17 | "@heroicons/react": "^2.2.0", 18 | "@tabler/icons-react": "^2.42.0", 19 | "dexie": "^3.2.4", 20 | "dexie-react-hooks": "^1.1.7", 21 | "fuse.js": "^7.0.0", 22 | "glob": "^8.1.0", 23 | "jsencrypt": "^3.3.2", 24 | "lucide-react": "^0.475.0", 25 | "marked": "^12.0.2", 26 | "nanoid": "^5.0.6", 27 | "react": "^18.2.0", 28 | "react-dom": "^18.2.0", 29 | "react-markdown": "^9.0.1", 30 | "rehype-external-links": "^3.0.0", 31 | "rehype-katex": "^7.0.1", 32 | "remark-gfm": "^4.0.0", 33 | "remark-math": "^6.0.0", 34 | "vis-network": "^9.1.9" 35 | }, 36 | "devDependencies": { 37 | "@comfyorg/comfyui-frontend-types": "^1.15.8", 38 | "@tailwindcss/typography": "^0.5.15", 39 | "@types/lodash": "^4.17.15", 40 | "@types/node": "^22.10.0", 41 | "@types/react": "^18.2.37", 42 | "@types/react-dom": "^18.2.15", 43 | "@types/uuid": "^9.0.7", 44 | "@vitejs/plugin-react": "^4.2.0", 45 | "autoprefixer": "^10.4.20", 46 | "concurrently": "^9.1.0", 47 | "eslint": "^8.53.0", 48 | "eslint-config-prettier": "^9.1.0", 49 | "eslint-plugin-prettier": "^5.1.3", 50 | "eslint-plugin-react-hooks": "^4.6.0", 51 | "eslint-plugin-react-refresh": "^0.4.4", 52 | "postcss": "^8.4.49", 53 | "postcss-nesting": "^13.0.1", 54 | "prettier": "^3.2.4", 55 | "rollup-plugin-watch": "^1.0.4", 56 | "tailwindcss": "^3.4.15", 57 | "typescript": "^5.2.2", 58 | "vite": "^5.4.14" 59 | }, 60 | "overrides": { 61 | "cross-spawn": ">=7.0.5", 62 | "rollup": ">=4.22.4", 63 | "pagefinder": ">=1.1.1", 64 | "terser": ">=5.16.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | 'tailwindcss/nesting': {}, 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /ui/scripts/post-build.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ai-business-hql qingli.hql@alibaba-inc.com 3 | * @Date: 2025-02-17 20:53:45 4 | * @LastEditors: ai-business-hql qingli.hql@alibaba-inc.com 5 | * @LastEditTime: 2025-06-05 11:18:48 6 | * @FilePath: /comfyui_copilot/ui/scripts/post-build.js 7 | * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 8 | */ 9 | import fs from 'fs'; 10 | import path from 'path'; 11 | import glob from 'glob'; 12 | import { fileURLToPath } from 'url'; 13 | import { dirname } from 'path'; 14 | 15 | // 获取 __dirname 的等效值 16 | const __filename = fileURLToPath(import.meta.url); 17 | const __dirname = dirname(__filename); 18 | 19 | // 在 dist/copilot_web 目录下查找所有 JS 文件 20 | const distDir = path.resolve(__dirname, '../../dist/copilot_web'); 21 | const files = glob.sync(`${distDir}/**/*.js`); 22 | 23 | files.forEach(file => { 24 | console.log('start modify', file) 25 | let content = fs.readFileSync(file, 'utf-8'); 26 | 27 | // 检查是否已经有 getImportPath 函数(function 或 const 形式) 28 | // if (!content.includes('getImportPath')) { 29 | // content = `function getImportPath(filename) { 30 | // const apiBase = window.comfyAPI?.api?.api?.api_base; 31 | // if (apiBase) { 32 | // // 有 API base 时,使用完整路径 33 | // return \`\${apiBase.substring(1)}/copilot_web/\${filename}\`; 34 | // } else { 35 | // // 没有 API base 时,使用相对路径(因为所有文件都在同一目录) 36 | // return \`./\${filename}\`; 37 | // } 38 | // } 39 | // ` + content; 40 | // } 41 | if (!content.includes('getImportPath')) { 42 | content = `function getImportPath(filename) { 43 | return \`./\${filename}\`; 44 | } 45 | ` + content; 46 | } 47 | 48 | if (content.includes('__vite__mapDeps')) { 49 | // 尝试两种不同的格式 50 | let depsMatch = content.match(/const __vite__mapDeps=\(.*?m\.f=\[(.*?)\]/); 51 | 52 | // 如果第一种格式不匹配,尝试第二种格式(用于 input.js) 53 | if (!depsMatch) { 54 | depsMatch = content.match(/const __vite__mapDeps=\(.*?m\.f\|\|\(m\.f=\[(.*?)\]\)\)\)/); 55 | } 56 | 57 | if (depsMatch && depsMatch[1]) { 58 | const originalDeps = depsMatch[1]; 59 | 60 | // 如果文件中还没有路径转换逻辑,则添加 61 | if (!content.includes('.map(path =>')) { 62 | // 替换整个 __vite__mapDeps 函数,处理两种可能的格式 63 | content = content.replace( 64 | /const __vite__mapDeps=\([^;]+\);/, 65 | `const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=[${originalDeps}].map(path => { 66 | const apiBase = window.comfyAPI?.api?.api?.api_base; 67 | if (apiBase) { 68 | // 有 API base 时,使用完整路径 69 | return \`\${apiBase.substring(1)}/\${path}\`; 70 | } else { 71 | // 没有 API base 时,使用相对路径 72 | return \`./\${path}\`; 73 | } 74 | }))))=>i.map(i=>d[i]);` 75 | ); 76 | } 77 | console.log(`Modified ${path.basename(file)} - __vite__mapDeps`); 78 | } else { 79 | console.log(`No deps pattern found in ${path.basename(file)}`); 80 | } 81 | } else { 82 | console.log(`No __vite__mapDeps found in ${path.basename(file)}`); 83 | } 84 | 85 | // 处理 import("filename") 形式的动态导入 86 | const dynamicImportPattern = /import\("([^"]+\.js)"\)/g; 87 | if (dynamicImportPattern.test(content)) { 88 | content = content.replace(dynamicImportPattern, (match, filename) => { 89 | // 只处理相对路径的导入 90 | if (filename.startsWith('./') || !filename.includes('/')) { 91 | const cleanFilename = filename.startsWith('./') ? filename.substring(2) : filename; 92 | return `import(getImportPath("${cleanFilename}"))`; 93 | } 94 | return match; 95 | }); 96 | } 97 | 98 | fs.writeFileSync(file, content, 'utf-8'); 99 | console.log(`Modified ${path.basename(file)}`); 100 | }); 101 | 102 | // 不需要单独处理 input.js,因为它已经在上面的循环中被处理了 -------------------------------------------------------------------------------- /ui/src/Api.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | 4 | import { Table } from "./db-tables/WorkspaceDB"; 5 | import type { ModelsListRespItem } from "./model-manager/types"; 6 | import { api } from "./utils/comfyapp"; 7 | 8 | export function fetchApi( 9 | route: string, 10 | options?: RequestInit, 11 | ): Promise { 12 | if (api == null) { 13 | console.error("api is null!"); 14 | throw new Error("api is null!"); 15 | } 16 | return api.fetchApi(route, options); 17 | } 18 | 19 | export async function getDB(table: Table): Promise { 20 | console.warn("[workspace deprecated] getDB is deprecated", table); 21 | try { 22 | const response = await fetchApi(`/workspace/get_db?table=${table}`); 23 | if (!response.ok) { 24 | return undefined; 25 | } 26 | const data = await response.json(); 27 | return data; 28 | } catch (error) { 29 | console.error("Error fetching workspace:", error); 30 | return undefined; 31 | } 32 | } 33 | 34 | export async function fetchMyWorkflowsDir() { 35 | const resp = await fetchApi("/workspace/get_my_workflows_dir"); 36 | const res = (await resp.json()) as { 37 | path?: string; 38 | error?: string; 39 | os: "win32" | "darwin" | "linux"; 40 | }; 41 | if (res.error) { 42 | alert(`Failed to fetch my workflows path: ${res.error}`); 43 | } 44 | return res; 45 | } 46 | -------------------------------------------------------------------------------- /ui/src/App.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ai-business-hql ai.bussiness.hql@gmail.com 3 | * @Date: 2025-04-21 11:01:32 4 | * @LastEditors: ai-business-hql ai.bussiness.hql@gmail.com 5 | * @LastEditTime: 2025-04-21 11:44:43 6 | * @FilePath: /comfyui_copilot/ui/src/App.tsx 7 | * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 8 | */ 9 | // Copyright (C) 2025 AIDC-AI 10 | // Licensed under the MIT License. 11 | 12 | import React, { useState, useEffect, Suspense } from "react"; 13 | import { COPILOT_EVENTS } from "./constants/events"; 14 | import { ChatProvider } from './context/ChatContext'; 15 | 16 | const WorkflowChat = React.lazy(() => import("./workflowChat/workflowChat").then(module => ({ 17 | default: module.default 18 | }))); 19 | 20 | export default function App() { 21 | const [shouldTriggerUsage, setShouldTriggerUsage] = useState(false); 22 | 23 | useEffect(() => { 24 | const handleExplainNode = () => { 25 | setShouldTriggerUsage(true); 26 | }; 27 | 28 | window.addEventListener(COPILOT_EVENTS.EXPLAIN_NODE, handleExplainNode); 29 | return () => window.removeEventListener(COPILOT_EVENTS.EXPLAIN_NODE, handleExplainNode); 30 | }, []); 31 | 32 | return ( 33 | 34 |
35 | Loading...
}> 36 | setShouldTriggerUsage(false)} 40 | /> 41 | 42 | 43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /ui/src/apis/comfyApiCustom.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: ai-business-hql ai.bussiness.hql@gmail.com 3 | * @Date: 2025-02-17 20:53:45 4 | * @LastEditors: ai-business-hql ai.bussiness.hql@gmail.com 5 | * @LastEditTime: 2025-05-08 17:28:21 6 | * @FilePath: /comfyui_copilot/ui/src/apis/comfyApiCustom.ts 7 | * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 8 | */ 9 | // Copyright (C) 2025 AIDC-AI 10 | // Licensed under the MIT License. 11 | // Copyright (C) 2025 ComfyUI-Copilot Authors 12 | // Licensed under the MIT License. 13 | 14 | import { api } from "../utils/comfyapp"; 15 | import type { ComfyApi } from "@comfyorg/comfyui-frontend-types"; 16 | 17 | type ObjectInfo = ReturnType; 18 | 19 | export async function getObjectInfo(): Promise { 20 | try { 21 | const response = await api.fetchApi("/object_info", { method: "GET" }); 22 | return await response.json(); 23 | } catch (error) { 24 | console.error("Error fetching object info:", error); 25 | throw error; 26 | } 27 | } 28 | 29 | export async function getInstalledNodes() { 30 | const objectInfos = await getObjectInfo(); 31 | return Object.keys(objectInfos); 32 | } 33 | 34 | export async function runPrompt(json_data: any): Promise { 35 | const response = await api.fetchApi("/prompt", { 36 | method: "POST", 37 | body: JSON.stringify(json_data), 38 | }); 39 | return await response.json(); 40 | } 41 | 42 | /** 43 | * Clear the prompt queue or delete specific queue items 44 | * 45 | * @param options Configuration options 46 | * @param options.clear If true, clears the entire queue 47 | * @param options.delete Array of prompt IDs to delete from the queue 48 | * @returns Promise with the response 49 | * 50 | * @example 51 | * // Clear the entire queue 52 | * await manageQueue({ clear: true }); 53 | * 54 | * // Delete specific prompts from the queue 55 | * await manageQueue({ delete: ["prompt-123", "prompt-456"] }); 56 | * 57 | * // Both clear the queue and delete specific prompts 58 | * await manageQueue({ clear: true, delete: ["prompt-789"] }); 59 | */ 60 | export async function manageQueue(options: { 61 | clear?: boolean; 62 | delete?: string[]; 63 | }): Promise { 64 | try { 65 | const response = await api.fetchApi("/queue", { 66 | method: "POST", 67 | body: JSON.stringify(options), 68 | }); 69 | 70 | if (!response.ok) { 71 | throw new Error(`Failed to manage queue: ${response.status} ${response.statusText}`); 72 | } 73 | 74 | return response; 75 | } catch (error) { 76 | console.error("Error managing queue:", error); 77 | throw error; 78 | } 79 | } 80 | 81 | 82 | /** 83 | * Interrupts the current processing/generation 84 | * 85 | * This function sends a request to the server to stop the current 86 | * processing operation. It's useful for canceling ongoing image generations. 87 | * 88 | * @returns Promise with the response 89 | * 90 | * @example 91 | * // Interrupt the current generation process 92 | * await interruptProcessing(); 93 | */ 94 | export async function interruptProcessing(): Promise { 95 | try { 96 | const response = await api.fetchApi("/interrupt", { 97 | method: "POST", 98 | }); 99 | 100 | if (!response.ok) { 101 | throw new Error(`Failed to interrupt processing: ${response.status} ${response.statusText}`); 102 | } 103 | 104 | return response; 105 | } catch (error) { 106 | console.error("Error interrupting processing:", error); 107 | throw error; 108 | } 109 | } 110 | 111 | 112 | export async function getHistory(promptId: string): Promise { 113 | try { 114 | const response = await api.fetchApi(`/history/${promptId}`, { 115 | method: "GET" 116 | }); 117 | 118 | if (!response.ok) { 119 | throw new Error(`Failed to fetch history: ${response.status} ${response.statusText}`); 120 | } 121 | 122 | return await response.json(); 123 | } catch (error) { 124 | console.error("Error fetching history:", error); 125 | throw error; 126 | } 127 | } 128 | 129 | -------------------------------------------------------------------------------- /ui/src/components/chat/ChatHeader.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 晴知 qingli.hql@alibaba-inc.com 3 | * @Date: 2024-11-28 10:19:07 4 | * @LastEditors: ai-business-hql qingli.hql@alibaba-inc.com 5 | * @LastEditTime: 2025-06-05 15:01:55 6 | * @FilePath: /comfyui_copilot/ui/src/components/chat/ChatHeader.tsx 7 | * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 8 | */ 9 | // Copyright (C) 2025 AIDC-AI 10 | // Licensed under the MIT License. 11 | 12 | import { useState, useEffect } from 'react'; 13 | import { XIcon, TrashIcon, CogIcon } from './Icons'; 14 | import { ApiKeyModal } from './ApiKeyModal'; 15 | import logoImage from '../../../../assets/logo.png'; 16 | 17 | interface ChatHeaderProps { 18 | onClose?: () => void; 19 | onClear?: () => void; 20 | hasMessages: boolean; 21 | onHeightResize?: (deltaY: number) => void; 22 | title?: string; 23 | } 24 | 25 | export function ChatHeader({ 26 | onClose, 27 | onClear, 28 | hasMessages, 29 | onHeightResize, 30 | title = "ComfyUI-Copilot" 31 | }: ChatHeaderProps) { 32 | const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false); 33 | const [isResizing, setIsResizing] = useState(false); 34 | 35 | const handleApiKeyClick = () => { 36 | setIsApiKeyModalOpen(true); 37 | }; 38 | 39 | const handleSaveApiKey = (apiKey: string) => { 40 | localStorage.setItem('chatApiKey', apiKey); 41 | }; 42 | 43 | const handleMouseDown = (e: React.MouseEvent) => { 44 | setIsResizing(true); 45 | e.preventDefault(); 46 | }; 47 | 48 | useEffect(() => { 49 | const handleMouseMove = (e: MouseEvent) => { 50 | if (!isResizing) return; 51 | const deltaY = e.movementY; 52 | onHeightResize?.(deltaY); 53 | }; 54 | 55 | const handleMouseUp = () => { 56 | setIsResizing(false); 57 | }; 58 | 59 | if (isResizing) { 60 | document.addEventListener('mousemove', handleMouseMove); 61 | document.addEventListener('mouseup', handleMouseUp); 62 | } 63 | 64 | return () => { 65 | document.removeEventListener('mousemove', handleMouseMove); 66 | document.removeEventListener('mouseup', handleMouseUp); 67 | }; 68 | }, [isResizing, onHeightResize]); 69 | 70 | return ( 71 | <> 72 |
74 |
75 | ComfyUI-Copilot Logo 80 |

{title}

81 | 87 |
88 |
89 | 96 |
97 |
98 | 99 | setIsApiKeyModalOpen(false)} 102 | onSave={handleSaveApiKey} 103 | initialApiKey={localStorage.getItem('chatApiKey') || ''} 104 | /> 105 | 106 | ); 107 | } -------------------------------------------------------------------------------- /ui/src/components/chat/Icons.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | export { 4 | Send as SendIcon, 5 | X as XIcon, 6 | Trash as TrashIcon, 7 | Upload as UploadIcon, 8 | Image as ImageIcon, 9 | Plus as PlusIcon, 10 | Settings as CogIcon 11 | } from 'lucide-react'; -------------------------------------------------------------------------------- /ui/src/components/chat/SelectedNodeInfo.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 晴知 qingli.hql@alibaba-inc.com 3 | * @Date: 2025-02-17 20:53:45 4 | * @LastEditors: ai-business-hql ai.bussiness.hql@gmail.com 5 | * @LastEditTime: 2025-04-22 16:43:57 6 | * @FilePath: /comfyui_copilot/ui/src/components/chat/SelectedNodeInfo.tsx 7 | * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 8 | */ 9 | // Copyright (C) 2025 AIDC-AI 10 | // Licensed under the MIT License. 11 | 12 | import { app } from "../../utils/comfyapp"; 13 | 14 | interface SelectedNodeInfoProps { 15 | nodeInfo: any[]; 16 | onSendWithIntent: (intent: string, ext?: any) => void; 17 | loading: boolean; 18 | onSendWithContent: (text: string) => void; 19 | } 20 | 21 | 22 | function getDownstreamSubgraphExt(nodeInfo: any[]) { 23 | const selectedNode = nodeInfo[0]; 24 | const nodeTypeSet = new Set(); 25 | 26 | function findUpstreamNodes(node: any, depth: number) { 27 | if (!node || depth >= 1) return; 28 | 29 | if (node.inputs) { 30 | for (const input of Object.values(node.inputs)) { 31 | const linkId = (input as any).link; 32 | if (linkId && app.graph.links[linkId]) { 33 | const originId = app.graph.links[linkId].origin_id; 34 | const originNode = app.graph._nodes_by_id[originId]; 35 | if (originNode) { 36 | nodeTypeSet.add(originNode.type); 37 | findUpstreamNodes(originNode, depth + 1); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | if (selectedNode) { 45 | findUpstreamNodes(selectedNode, 0); 46 | return [{"type": "upstream_node_types", "data": Array.from(nodeTypeSet)}]; 47 | } 48 | 49 | return null; 50 | } 51 | 52 | export function SelectedNodeInfo({ nodeInfo, onSendWithIntent, loading, onSendWithContent }: SelectedNodeInfoProps) { 53 | console.log('SelectedNodeInfo nodeInfo:', nodeInfo[0]); 54 | return ( 55 |
56 |
57 |

Selected node: {nodeInfo[0].type}

58 |
59 | 66 | 73 | 80 |
81 |
82 |
83 | ); 84 | } -------------------------------------------------------------------------------- /ui/src/components/chat/messages/BaseMessage.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | 4 | interface BaseMessageProps { 5 | name: string; 6 | isUser?: boolean; 7 | children: React.ReactNode; 8 | } 9 | 10 | export function BaseMessage({ name, isUser = false, children }: BaseMessageProps) { 11 | return ( 12 |
13 |
14 | {children} 15 |
16 |
17 | ); 18 | } -------------------------------------------------------------------------------- /ui/src/components/chat/messages/LoadingMessage.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | 4 | import { BaseMessage } from "./BaseMessage"; 5 | 6 | export function LoadingMessage() { 7 | return ( 8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 | AI is thinking... 16 |
17 |
18 | ); 19 | } -------------------------------------------------------------------------------- /ui/src/components/chat/messages/NodeInstallGuide.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 晴知 qingli.hql@alibaba-inc.com 3 | * @Date: 2024-12-26 17:16:51 4 | * @LastEditors: ai-business-hql ai.bussiness.hql@gmail.com 5 | * @LastEditTime: 2025-05-08 17:27:46 6 | * @FilePath: /comfyui_copilot/ui/src/components/chat/messages/NodeInstallGuide.tsx 7 | * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE 8 | */ 9 | // Copyright (C) 2025 AIDC-AI 10 | // Licensed under the MIT License. 11 | // Copyright (C) 2025 ComfyUI-Copilot Authors 12 | // Licensed under the MIT License. 13 | 14 | import React from 'react'; 15 | 16 | interface NodeInstallGuideProps { 17 | content: string; 18 | onLoadSubgraph?: () => void; 19 | } 20 | 21 | export function NodeInstallGuide({ content, onLoadSubgraph }: NodeInstallGuideProps) { 22 | const response = JSON.parse(content); 23 | const nodeInfos = response.ext?.find((item: { type: string }) => item.type === 'node_install_guide')?.data || []; 24 | 25 | const uniqueNodeInfos = nodeInfos.reduce((acc: any[], node) => { 26 | if (!acc.some(n => n.name === node.name)) { 27 | acc.push(node); 28 | } 29 | return acc; 30 | }, []); 31 | 32 | return ( 33 |
34 |

Before loading the graph to canvas, the following nodes need to be installed. Please visit the corresponding GitHub repositories to install them:

35 |
36 | {uniqueNodeInfos.map((node: any, index: number) => ( 37 |
42 |
43 |

44 | {node.name} 45 |

46 |
47 | {node.repository_url ? ( 48 | 56 | 57 | 58 | 59 | Download 60 | 61 | ) : ( 62 | 69 | 70 | 71 | 72 | Search on Google 73 | 74 | )} 75 |
76 |
77 |
78 | ))} 79 |
80 |

81 |

After installation, please click the continue loading graph button to load the graph to the canvas:

82 |
83 | 90 |
91 |
92 | ); 93 | } -------------------------------------------------------------------------------- /ui/src/components/chat/messages/UserMessage.tsx: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (C) 2025 AIDC-AI 3 | // Licensed under the MIT License. 4 | 5 | import { BaseMessage } from './BaseMessage'; 6 | import { InformationCircleIcon } from '@heroicons/react/24/outline'; 7 | import { useState } from 'react'; 8 | 9 | interface UserMessageProps { 10 | content: string; 11 | trace_id?: string; 12 | } 13 | 14 | export function UserMessage({ content, trace_id }: UserMessageProps) { 15 | const [showTooltip, setShowTooltip] = useState(false); 16 | const [copied, setCopied] = useState(false); 17 | 18 | const handleCopyTraceId = async () => { 19 | if (trace_id) { 20 | await navigator.clipboard.writeText(trace_id); 21 | setCopied(true); 22 | setTimeout(() => setCopied(false), 2000); 23 | } 24 | }; 25 | 26 | return ( 27 | 28 |
29 |

{content}

30 | {trace_id && ( 31 |
setShowTooltip(true)} 34 | onMouseLeave={() => setShowTooltip(false)} 35 | onClick={handleCopyTraceId} 36 | > 37 | 38 | 39 | {/* Tooltip */} 40 | {showTooltip && ( 41 |
42 | {copied ? 'Copied!' : `Copy trace ID`} 43 |
44 | )} 45 |
46 | )} 47 |
48 |
49 | ); 50 | } -------------------------------------------------------------------------------- /ui/src/components/debug/ParameterDebugInterfaceV2.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | 4 | import React, { useState, useEffect } from 'react'; 5 | import { ParameterDebugInterface as OriginalParameterDebugInterface } from './ParameterDebugInterfaceNew'; 6 | import { ParameterDebugInterfaceProps } from './types/parameterDebugTypes'; 7 | import { useChatContext } from '../../context/ChatContext'; 8 | import { loadStateFromLocalStorage, clearStateFromLocalStorage } from './utils/localStorageUtils'; 9 | 10 | /** 11 | * Enhanced version of ParameterDebugInterface with improved architecture 12 | * 13 | * This component: 14 | * 1. Handles loading/persisting state to/from localStorage 15 | * 2. Manages context interactions 16 | * 3. Delegates rendering to the original component 17 | */ 18 | export const ParameterDebugInterface: React.FC = (props) => { 19 | const { dispatch } = useChatContext(); 20 | const [isInitialized, setIsInitialized] = useState(false); 21 | 22 | // Handle initialization - load saved state if available 23 | useEffect(() => { 24 | if (props.visible && !isInitialized) { 25 | // Attempt to load saved state 26 | const savedState = loadStateFromLocalStorage(); 27 | if (savedState) { 28 | // Update context with screen state if available 29 | if (savedState.screenState) { 30 | dispatch({ 31 | type: 'SET_SCREEN_STATE', 32 | payload: { 33 | currentScreen: savedState.currentScreen, 34 | isProcessing: savedState.isProcessing, 35 | isCompleted: savedState.isCompleted 36 | } 37 | }); 38 | } 39 | } 40 | setIsInitialized(true); 41 | } 42 | }, [props.visible, isInitialized, dispatch]); 43 | 44 | // Clean up when component unmounts 45 | useEffect(() => { 46 | return () => { 47 | if (!props.visible) { 48 | dispatch({ type: 'SET_SCREEN_STATE', payload: null }); 49 | } 50 | }; 51 | }, [props.visible, dispatch]); 52 | 53 | // The original component handles most of the actual work 54 | return ; 55 | }; -------------------------------------------------------------------------------- /ui/src/components/debug/README.md: -------------------------------------------------------------------------------- 1 | # Parameter Debug Interface 2 | 3 | This directory contains the Parameter Debug Interface components for ComfyUI Copilot. 4 | 5 | ## Directory Structure 6 | 7 | - `ParameterDebugInterfaceNew.tsx` - Original large component (refactored to use extracted utilities) 8 | - `ParameterDebugInterfaceV2.tsx` - Enhanced wrapper component with better architecture 9 | - `index.ts` - Central export file for all components and utilities 10 | - `types/` - Type definitions extracted from the original component 11 | - `parameterDebugTypes.ts` - Common type definitions 12 | - `utils/` - Utility functions extracted from the original component 13 | - `localStorageUtils.ts` - LocalStorage related utilities 14 | - `textInputUtils.ts` - Text input handling utilities 15 | - `parameterUtils.ts` - Parameter processing utilities 16 | - `interfaceUtils.ts` - UI interaction utilities 17 | - `searchUtils.ts` - Search and filter utilities 18 | - `styles/` - Style definitions 19 | - `highlightPulseStyle.ts` - CSS animation styles 20 | - `screens/` - Screen components used by the interface 21 | - `modals/` - Modal components used by the interface 22 | 23 | ## Refactoring Strategy 24 | 25 | The original `ParameterDebugInterfaceNew.tsx` file was very large and complex. Our strategy was to: 26 | 27 | 1. Extract independent parts (types, utilities, styles) without changing the core logic 28 | 2. Create a wrapper (`ParameterDebugInterfaceV2.tsx`) that uses the original component but leverages the extracted parts 29 | 3. Refactor the original component to use the extracted utility functions 30 | 4. Update the export structure to expose a clean API 31 | 32 | ## Benefits of the New Architecture 33 | 34 | 1. **Modularity**: Core functions are organized by purpose in separate files 35 | 2. **Reusability**: Utilities can be easily reused across components 36 | 3. **Maintainability**: Smaller files are easier to understand and modify 37 | 4. **Performance**: Cleaner code structure may lead to better performance 38 | 5. **Testing**: Isolated utilities are easier to test 39 | 40 | ## Future Improvements 41 | 42 | 1. Continue splitting functionality into more specialized files 43 | 2. Convert screen components to use the extracted utilities 44 | 3. Add proper typing to all function parameters 45 | 4. Implement unit tests for utilities -------------------------------------------------------------------------------- /ui/src/components/debug/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | 4 | // Re-export components 5 | export { ParameterDebugInterface } from './ParameterDebugInterfaceV2'; 6 | 7 | // Re-export types 8 | export * from './types/parameterDebugTypes'; 9 | 10 | // Re-export utilities 11 | export * from './utils/localStorageUtils'; 12 | export * from './utils/textInputUtils'; 13 | export * from './utils/parameterUtils'; 14 | export * from './utils/interfaceUtils'; 15 | export * from './utils/searchUtils'; -------------------------------------------------------------------------------- /ui/src/components/debug/modals/AIWritingModal.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 AIDC-AI 2 | // Licensed under the MIT License. 3 | 4 | import React from 'react'; 5 | 6 | interface AIWritingModalProps { 7 | visible: boolean; 8 | aiWritingModalText: string; 9 | setAiWritingModalText: React.Dispatch>; 10 | aiGeneratedTexts: string[]; 11 | aiSelectedTexts: {[key: string]: boolean}; 12 | aiWritingLoading: boolean; 13 | aiWritingError: string | null; 14 | handleAiWriting: () => void; 15 | toggleTextSelection: (textKey: string) => void; 16 | addSelectedTexts: () => void; 17 | onClose: () => void; 18 | } 19 | 20 | export const AIWritingModal: React.FC = ({ 21 | visible, 22 | aiWritingModalText, 23 | setAiWritingModalText, 24 | aiGeneratedTexts, 25 | aiSelectedTexts, 26 | aiWritingLoading, 27 | aiWritingError, 28 | handleAiWriting, 29 | toggleTextSelection, 30 | addSelectedTexts, 31 | onClose 32 | }) => { 33 | if (!visible) return null; 34 | 35 | return ( 36 |
37 |
38 |
39 |

AI Writing Assistant

40 | 48 |
49 | 50 |
51 |
52 |

Enter text and AI will generate variations

53 |