feat: 开放所有apikey

This commit is contained in:
ai-business-hql
2025-10-09 11:31:21 +08:00
parent 4dd19c588b
commit 6efde26efd
17 changed files with 448 additions and 346 deletions
+7 -1
View File
@@ -239,6 +239,9 @@ async def invoke_chat(request):
"workflow_checkpoint_id": workflow_checkpoint_id,
"openai_api_key": request.headers.get('Openai-Api-Key'),
"openai_base_url": request.headers.get('Openai-Base-Url'),
# Workflow LLM settings (optional, used by tools/agents that need a different LLM)
"workflow_llm_api_key": request.headers.get('Workflow-LLM-Api-Key'),
"workflow_llm_base_url": request.headers.get('Workflow-LLM-Base-Url'),
"model_select": next((x['data'][0] for x in ext if x['type'] == 'model_select' and x.get('data')), None)
}
@@ -504,7 +507,10 @@ async def invoke_debug(request):
"session_id": session_id,
"model": "gemini-2.5-flash", # Default model for debug agents
"openai_api_key": request.headers.get('Openai-Api-Key'),
"openai_base_url": request.headers.get('Openai-Base-Url', 'https://api.openai.com/v1'),
"openai_base_url": request.headers.get('Openai-Base-Url'),
# Workflow LLM settings (optional)
"workflow_llm_api_key": request.headers.get('Workflow-LLM-Api-Key'),
"workflow_llm_base_url": request.headers.get('Workflow-LLM-Base-Url'),
}
# 获取当前语言
+11 -12
View File
@@ -1,6 +1,7 @@
'''
Debug Agent for ComfyUI Workflow Error Analysis
'''
from ..utils.key_utils import workflow_config_adapt
from ..agent_factory import create_agent
from agents.items import ItemHelpers
from agents.run import Runner
@@ -205,6 +206,7 @@ async def debug_workflow_errors(workflow_data: Dict[str, Any]):
# Get session_id and config from request context
session_id = get_session_id()
config = get_config()
workflow_config_adapt(config)
if not session_id:
session_id = str(uuid.uuid4()) # Fallback if no context
@@ -253,7 +255,8 @@ Start by validating the workflow to see its current state.""",
model=WORKFLOW_MODEL_NAME,
tools=[run_workflow, analyze_error_type, save_current_workflow],
config={
"max_tokens": 8192
"max_tokens": 8192,
**config
}
)
@@ -302,7 +305,8 @@ Start by validating the workflow to see its current state.""",
tools=[get_current_workflow, get_node_info, update_workflow],
handoffs=[agent],
config={
"max_tokens": 8192
"max_tokens": 8192,
**config
}
)
@@ -393,7 +397,8 @@ Start by validating the workflow to see its current state.""",
get_current_workflow, get_node_info],
handoffs=[agent],
config={
"max_tokens": 8192
"max_tokens": 8192,
**config
}
)
@@ -487,7 +492,8 @@ Start by validating the workflow to see its current state.""",
suggest_model_download, update_workflow_parameter, get_current_workflow],
handoffs=[agent],
config={
"max_tokens": 8192
"max_tokens": 8192,
**config
}
)
@@ -692,16 +698,9 @@ Start by validating the workflow to see its current state.""",
except Exception as e:
log.error(f"Error in debug_workflow_errors: {str(e)}")
error_message = current_text + f"\n\n× Error occurred during debugging: {str(e)}\n\n"
error_message = f"\n\n× Error occurred during debugging: {str(e)}\n\n"
# Include workflow_update ext if captured from tools before the error
final_error_ext = None
if 'workflow_update_ext' in locals() and workflow_update_ext:
final_error_ext = [workflow_update_ext]
log.info(f"-- Including latest workflow_update ext in error response")
ext_with_finished = {
"data": final_error_ext,
"finished": True
}
yield (error_message, ext_with_finished)
+7 -3
View File
@@ -2,7 +2,7 @@
Author: ai-business-hql qingli.hql@alibaba-inc.com
Date: 2025-07-24 17:10:23
LastEditors: ai-business-hql ai.bussiness.hql@gmail.com
LastEditTime: 2025-08-28 11:15:49
LastEditTime: 2025-09-30 11:03:37
FilePath: /comfyui_copilot/backend/service/workflow_rewrite_agent.py
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
'''
@@ -14,11 +14,13 @@ import uuid
import os
from typing import Dict, Any
from ..utils.key_utils import workflow_config_adapt
from ..dao.expert_table import list_rewrite_experts_short, get_rewrite_expert_by_name_list
from ..agent_factory import create_agent
from ..utils.globals import WORKFLOW_MODEL_NAME, get_language
from ..utils.request_context import get_session_id
from ..utils.request_context import get_config, get_session_id
from ..service.workflow_rewrite_tools import *
@@ -42,7 +44,9 @@ def create_workflow_rewrite_agent():
language = get_language()
session_id = get_session_id() or "unknown_session"
config = get_config()
workflow_config_adapt(config)
return create_agent(
name="Workflow Rewrite Agent",
model=WORKFLOW_MODEL_NAME,
@@ -11,11 +11,12 @@ import json
from typing import Dict, Any, Optional
import asyncio
from pydantic import BaseModel
from ..utils.key_utils import workflow_config_adapt
from openai import OpenAI
from ..agent_factory import create_agent
from ..utils.globals import WORKFLOW_MODEL_NAME, get_comfyui_copilot_api_key, LLM_DEFAULT_BASE_URL
from ..utils.request_context import get_rewrite_context, RewriteContext
from ..utils.request_context import get_config, get_rewrite_context, RewriteContext
from ..utils.logger import log
@@ -81,11 +82,12 @@ ComfyUI API格式工作流是一个JSON对象,其中:
"workflow_data": "这里是完整的工作流JSON字符串"
}
"""
config = get_config()
workflow_config_adapt(config)
# 创建OpenAI客户端
client = OpenAI(
base_url = LLM_DEFAULT_BASE_URL,
api_key = get_comfyui_copilot_api_key() or ""
base_url = config.get("openai_base_url") or LLM_DEFAULT_BASE_URL,
api_key = config.get("openai_api_key") or get_comfyui_copilot_api_key() or ""
)
# 调用LLM
+12 -8
View File
@@ -2,11 +2,7 @@
Author: ai-business-hql qingli.hql@alibaba-inc.com
Date: 2025-08-08 17:14:52
LastEditors: ai-business-hql ai.bussiness.hql@gmail.com
<<<<<<< HEAD
LastEditTime: 2025-08-25 17:41:14
=======
LastEditTime: 2025-08-29 11:25:52
>>>>>>> mcp
LastEditTime: 2025-09-30 10:18:44
FilePath: /comfyui_copilot/backend/utils/globals.py
Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
'''
@@ -15,8 +11,16 @@ Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查
Global utilities for managing application-wide state and configuration.
"""
import os
import threading
from typing import Optional, Dict, Any
from pathlib import Path
from dotenv import load_dotenv
# Load .env file if it exists
env_path = Path(__file__).parent.parent.parent / '.env'
if env_path.exists():
load_dotenv(env_path)
class GlobalState:
"""Thread-safe global state manager for application-wide configuration."""
@@ -95,11 +99,11 @@ def set_comfyui_copilot_api_key(api_key: str) -> None:
_global_state.set('comfyui_copilot_api_key', api_key)
BACKEND_BASE_URL = "https://comfyui-copilot-server.onrender.com"
BACKEND_BASE_URL = os.getenv("BACKEND_BASE_URL", "https://comfyui-copilot-server.onrender.com")
LMSTUDIO_DEFAULT_BASE_URL = "http://localhost:1234/v1"
WORKFLOW_MODEL_NAME = "us.anthropic.claude-sonnet-4-20250514-v1:0"
WORKFLOW_MODEL_NAME = os.getenv("WORKFLOW_MODEL_NAME", "us.anthropic.claude-sonnet-4-20250514-v1:0")
# WORKFLOW_MODEL_NAME = "gpt-5-2025-08-07-GlobalStandard"
LLM_DEFAULT_BASE_URL = BACKEND_BASE_URL + "/v1"
LLM_DEFAULT_BASE_URL = os.getenv("LLM_DEFAULT_BASE_URL", BACKEND_BASE_URL + "/v1")
def is_lmstudio_url(base_url: str) -> bool:
+10
View File
@@ -0,0 +1,10 @@
def workflow_config_adapt(config: dict):
if config:
if config.get("workflow_llm_api_key"):
config["openai_api_key"] = config.get("workflow_llm_api_key")
config["workflow_llm_api_key"] = None
if config.get("workflow_llm_base_url"):
config["openai_base_url"] = config.get("workflow_llm_base_url")
config["workflow_llm_base_url"] = None
@@ -1,7 +1,7 @@
function getImportPath(filename) {
return `./${filename}`;
}
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["copilot_web/workflowChat-C7at41Bc.js","copilot_web/vendor-markdown-B4Av9P7l.js","copilot_web/vendor-react-ByKzdfMq.js","copilot_web/message-components-VDKdm9Kg.js","copilot_web/input.js","copilot_web/assets/input-UcJyCCDl.css","copilot_web/fonts.css"].map(path => {
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["copilot_web/workflowChat-B4zoMsdC.js","copilot_web/vendor-markdown-B4Av9P7l.js","copilot_web/vendor-react-ByKzdfMq.js","copilot_web/message-components-C_zeUc_b.js","copilot_web/input.js","copilot_web/assets/input-UcJyCCDl.css","copilot_web/fonts.css"].map(path => {
const apiBase = window.comfyAPI?.api?.api?.api_base;
if (apiBase) {
// 有 API base 时,使用完整路径
@@ -11,4 +11,4 @@ function getImportPath(filename) {
return `./${path}`;
}
}))))=>i.map(i=>d[i]);
import{_ as a}from"./input.js";import{j as o}from"./vendor-markdown-B4Av9P7l.js";import{r as t,R as i}from"./vendor-react-ByKzdfMq.js";import{C as n}from"./message-components-VDKdm9Kg.js";const s={EXPLAIN_NODE:"copilot:explain-node",TOOLBOX_USAGE:"copilot:toolbox-usage",TOOLBOX_PARAMETERS:"copilot:toolbox-parameters",TOOLBOX_DOWNSTREAMNODES:"copilot:toolbox-downstreamnodes"},d=i.lazy(()=>a(()=>import(getImportPath("workflowChat-C7at41Bc.js")).then(e=>e.w),__vite__mapDeps([0,1,2,3,4,5,6])).then(e=>({default:e.default})));function c(){const[e,r]=t.useState(!1);return t.useEffect(()=>{const l=()=>{r(!0)};return window.addEventListener(s.EXPLAIN_NODE,l),()=>window.removeEventListener(s.EXPLAIN_NODE,l)},[]),o.jsx(n,{children:o.jsx("div",{className:"h-full w-full flex flex-col",children:o.jsx(t.Suspense,{fallback:o.jsx("div",{className:"h-full w-full flex items-center justify-center",children:"Loading..."}),children:o.jsx(d,{visible:!0,triggerUsage:e,onUsageTriggered:()=>r(!1)})})})})}const O=Object.freeze(Object.defineProperty({__proto__:null,default:c},Symbol.toStringTag,{value:"Module"}));export{O as A,s as C};
import{_ as a}from"./input.js";import{j as o}from"./vendor-markdown-B4Av9P7l.js";import{r as t,R as i}from"./vendor-react-ByKzdfMq.js";import{C as n}from"./message-components-C_zeUc_b.js";const s={EXPLAIN_NODE:"copilot:explain-node",TOOLBOX_USAGE:"copilot:toolbox-usage",TOOLBOX_PARAMETERS:"copilot:toolbox-parameters",TOOLBOX_DOWNSTREAMNODES:"copilot:toolbox-downstreamnodes"},d=i.lazy(()=>a(()=>import(getImportPath("workflowChat-B4zoMsdC.js")).then(e=>e.w),__vite__mapDeps([0,1,2,3,4,5,6])).then(e=>({default:e.default})));function c(){const[e,r]=t.useState(!1);return t.useEffect(()=>{const l=()=>{r(!0)};return window.addEventListener(s.EXPLAIN_NODE,l),()=>window.removeEventListener(s.EXPLAIN_NODE,l)},[]),o.jsx(n,{children:o.jsx("div",{className:"h-full w-full flex flex-col",children:o.jsx(t.Suspense,{fallback:o.jsx("div",{className:"h-full w-full flex items-center justify-center",children:"Loading..."}),children:o.jsx(d,{visible:!0,triggerUsage:e,onUsageTriggered:()=>r(!1)})})})})}const O=Object.freeze(Object.defineProperty({__proto__:null,default:c},Symbol.toStringTag,{value:"Module"}));export{O as A,s as C};
@@ -1,5 +1,5 @@
function getImportPath(filename) {
return `./${filename}`;
}
import{j as s}from"./vendor-markdown-B4Av9P7l.js";import{r as i}from"./vendor-react-ByKzdfMq.js";import{u as A,p as E,R as D,o as m,W as f,a as b}from"./message-components-VDKdm9Kg.js";function P({content:h,name:k="Assistant",avatar:O,onAddMessage:y,onUpdateMessage:c,messageId:_}){const{dispatch:u,abortControllerRef:l}=A(),[d,w]=i.useState(!1),[T,G]=i.useState(""),[J,S]=i.useState(!1),[j,v]=i.useState(null),r=i.useMemo(()=>{try{return JSON.parse(h)}catch(e){return console.error("Failed to parse message content:",e),null}},[h]),x=i.useMemo(()=>{if(!r||!r.ext)return null;const e=r.ext.find(t=>t.type==="debug_start_checkpoint"||t.type==="debug_checkpoint"&&t.data?.checkpoint_type==="debug_start");return e&&e.data&&e.data.checkpoint_id?e.data.checkpoint_id:null},[r])||j,N=async()=>{if(d)return;const e=m(),t={id:e,role:"ai",content:JSON.stringify({text:`🔍 Starting workflow analysis...
import{j as s}from"./vendor-markdown-B4Av9P7l.js";import{r as i}from"./vendor-react-ByKzdfMq.js";import{u as A,p as E,R as D,o as m,W as f,a as b}from"./message-components-C_zeUc_b.js";function P({content:h,name:k="Assistant",avatar:O,onAddMessage:y,onUpdateMessage:c,messageId:_}){const{dispatch:u,abortControllerRef:l}=A(),[d,w]=i.useState(!1),[T,G]=i.useState(""),[J,S]=i.useState(!1),[j,v]=i.useState(null),r=i.useMemo(()=>{try{return JSON.parse(h)}catch(e){return console.error("Failed to parse message content:",e),null}},[h]),x=i.useMemo(()=>{if(!r||!r.ext)return null;const e=r.ext.find(t=>t.type==="debug_start_checkpoint"||t.type==="debug_checkpoint"&&t.data?.checkpoint_type==="debug_start");return e&&e.data&&e.data.checkpoint_id?e.data.checkpoint_id:null},[r])||j,N=async()=>{if(d)return;const e=m(),t={id:e,role:"ai",content:JSON.stringify({text:`🔍 Starting workflow analysis...
`,ext:[]}),format:"markdown",name:"Assistant",finished:!1,debugGuide:!0};y?.(t);try{f.trackEvent({event_type:"debug_trigger",message_type:"debug",message_id:e,data:{}}),await C(),u({type:"SET_LOADING",payload:!0}),await I(e)}finally{w(!1)}},C=async()=>{try{const e=await b.graphToPrompt(),t=localStorage.getItem("sessionId")||"";if(t&&e){const n=await f.saveWorkflowCheckpoint(t,e.output,e.workflow,"debug_start");if(v(n.version_id),console.log(`Saved debug start checkpoint: ${n.version_id}`),c&&r){const o=[...r.ext||[]].filter(g=>g.type!=="debug_start_checkpoint"&&!(g.type==="debug_checkpoint"&&g.data?.checkpoint_type==="debug_start"));o.push({type:"debug_start_checkpoint",data:{checkpoint_id:n.version_id,checkpoint_type:"debug_start"}});const p={id:_||m(),role:"ai",content:JSON.stringify({text:r.text,ext:o}),format:"debug_guide",name:"Assistant"};c(p)}}}catch(e){console.error("Failed to save checkpoint before debug:",e)}},I=async e=>{try{const t=await b.graphToPrompt();let n="",a=null;l&&(l.current=new AbortController);for await(const o of f.streamDebugAgent(t,l?.current?.signal||void 0))if(o.text){n=o.text,o.ext&&(a=o.ext);const p={id:e,role:"ai",content:JSON.stringify({text:n,ext:a||[]}),format:"markdown",name:"Assistant",finished:!1,debugGuide:!0};c?.(p)}c?.({id:e,role:"ai",content:JSON.stringify({text:n,ext:a||[]}),format:"markdown",name:"Assistant",finished:!0,debugGuide:!0}),u({type:"SET_LOADING",payload:!1}),l.current=null}catch(t){console.error("Error calling debug agent:",t),S(!1);const a={id:e,role:"ai",content:JSON.stringify({text:`I encountered an error while analyzing your workflow: ${t.message||"Unknown error"}`,ext:[]}),format:"markdown",name:"Assistant",finished:!0};c?.(a),u({type:"SET_LOADING",payload:!1}),l.current=null}};return r?s.jsx(E,{name:k,children:s.jsxs("div",{className:"bg-gray-100 pt-4 px-4 pb-3 rounded-lg",children:[s.jsx("div",{className:"flex justify-between items-start",children:s.jsx("p",{className:"text-gray-700 text-sm flex-1",children:r.text})}),s.jsx("div",{className:"flex justify-end mt-4",children:s.jsx("button",{onClick:N,disabled:d,className:`debug-btn bg-debug-btn text-sm text-[#fff] rounded-lg px-4 py-2 ${d?"cursor-not-allowed":"cursor-pointer"}`,children:d?"Analyzing...":"Debug Errors"})}),s.jsx("div",{className:"flex justify-end mt-2",children:!!x&&s.jsx("div",{className:"ml-2 flex-shrink-0",children:s.jsx(D,{checkpointId:x,onRestore:()=>{console.log("Workflow restored from checkpoint")}})})})]})}):null}export{P as DebugGuide};
@@ -1,4 +1,4 @@
function getImportPath(filename) {
return `./${filename}`;
}
import{j as e}from"./vendor-markdown-B4Av9P7l.js";import{b as u,c as w,p as g,G as k,R as j}from"./message-components-VDKdm9Kg.js";import{r as p}from"./vendor-react-ByKzdfMq.js";import{B as v,M as N}from"./workflowChat-C7at41Bc.js";import"./input.js";/* empty css */import"./App-SJGu_4x8.js";const y=n=>{const{title:a="",isWorkflowUpdate:f=!1,className:d="",children:c}=n,[i,r]=p.useState(!1);return e.jsx(v,{className:"rounded-lg ",borderClassName:"rounded-lg",children:e.jsxs("div",{className:`flex flex-col ${d} ${i?"":"max-h-[200px]"}`,children:[e.jsxs("div",{className:"flex justify-between items-center pb-2 border-b border-gray-100",children:[e.jsx("div",{children:typeof a=="string"?e.jsx("h3",{className:"text-sm text-[#fff] font-medium",children:a}):a}),e.jsx("button",{onClick:()=>{r(!i)},children:i?e.jsx(u,{className:"text-gray-700"}):e.jsx(w,{className:"text-gray-700"})})]}),e.jsx("div",{className:"overflow-hidden flex-1",children:c})]})})};function E({content:n,name:a="Assistant",avatar:f,format:d="markdown"}){const c=p.useRef(null),i=()=>{let r=null,l=!1,o,x=[];try{if(o=JSON.parse(n),o.ext){let t=o.ext.find(s=>s.type==="workflow_rewrite_checkpoint"||s.type==="debug_checkpoint"&&s.data?.checkpoint_type==="workflow_rewrite_start");t&&t.data&&t.data.checkpoint_id?(r=t.data.checkpoint_id,l=!0):(t=o.ext.find(s=>s.type==="workflow_rewrite_complete"),t&&t.data&&t.data.version_id?(r=t.data.version_id,l=!0):(t=o.ext.find(s=>s.type==="debug_checkpoint"),t&&t.data&&t.data.checkpoint_id&&(r=t.data.checkpoint_id,l=!1))),o.ext.find(s=>s.type==="workflow_update")&&(l=!0),x=o.ext.find(s=>s.type==="param_update")?.data?.model_suggest||[]}}catch(t){return console.error("Failed to parse DebugResult content:",t),null}const h=l?e.jsxs("div",{className:"flex items-center text-green-500",children:[e.jsx("svg",{className:"w-5 h-5 mr-2",fill:"currentColor",viewBox:"0 0 20 20",children:e.jsx("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z",clipRule:"evenodd"})}),e.jsx("h4",{className:"font-bold text-xl",children:"Workflow Updated Successfully"})]}):e.jsxs("div",{className:"flex items-center text-gray-900",children:[e.jsx("svg",{className:"w-5 h-5 mr-2",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg","p-id":"28108",fill:"currentColor",children:e.jsx("path",{d:"M401.048025 844.855924c0 20.341281 16.643052 36.984333 36.984333 36.984333l147.936307 0c20.341281 0 36.984333-16.643052 36.984333-36.984333L622.952998 807.872614 401.048025 807.872614 401.048025 844.855924zM512 142.159744c-142.943596 0-258.888282 115.944686-258.888282 258.888282 0 88.021729 44.011376 165.503405 110.951975 212.288964l0 83.58365c0 20.341281 16.643052 36.984333 36.984333 36.984333l221.903949 0c20.341281 0 36.984333-16.643052 36.984333-36.984333l0-83.58365c66.941622-46.784536 110.951975-124.266212 110.951975-212.288964C770.888282 258.104429 654.943596 142.159744 512 142.159744zM617.588827 552.682561l-31.621185 22.005176 0 85.248569L438.031335 659.936307l0-85.063351L406.41015 552.866756c-49.743938-34.764781-79.33079-91.350544-79.33079-151.634536 0-101.890598 83.029018-184.92064 184.92064-184.92064s184.92064 83.029018 184.92064 184.92064C696.919617 461.332017 667.332764 517.91778 617.588827 552.682561z","p-id":"28277"})}),e.jsx("h4",{className:"font-bold text-xl",children:"Workflow Updated Finished"})]}),m=l?e.jsx("div",{className:"mt-3 text-xs text-gray-700",children:"💡 If you're not satisfied with the changes, click the restore button to revert to the previous version."}):null;return e.jsxs("div",{ref:c,className:"sticky top-0 left-0 w-full bg-gray-100 p-4 rounded-lg overflow-hidden",children:[e.jsx(y,{title:h,isWorkflowUpdate:l,className:"p-4",children:e.jsxs("div",{className:"prose prose-sm max-w-none",children:[d==="markdown"?e.jsx(k,{response:o||{}}):e.jsx("pre",{className:"whitespace-pre-wrap text-gray-700 text-sm leading-relaxed h-full",children:o?.text||""}),m]})}),x?.length>0&&e.jsx(N,{modelList:x,showPagination:!1}),e.jsx("div",{className:"flex justify-end mt-2",children:!!r&&e.jsx("div",{className:"ml-2 flex-shrink-0",children:e.jsx(j,{checkpointId:r,onRestore:()=>{console.log(`Workflow restored from ${l?"workflow update":"debug"} checkpoint`)},title:l?`Restore to this version (Version ${r})`:`Restore checkpoint ${r}`})})})]})};return e.jsx(g,{name:a,children:i()})}export{E as DebugResult};
import{j as e}from"./vendor-markdown-B4Av9P7l.js";import{b as u,c as w,p as g,G as k,R as j}from"./message-components-C_zeUc_b.js";import{r as p}from"./vendor-react-ByKzdfMq.js";import{B as v,M as N}from"./workflowChat-B4zoMsdC.js";import"./input.js";/* empty css */import"./App-BpKMzle9.js";const y=n=>{const{title:a="",isWorkflowUpdate:f=!1,className:d="",children:c}=n,[i,r]=p.useState(!1);return e.jsx(v,{className:"rounded-lg ",borderClassName:"rounded-lg",children:e.jsxs("div",{className:`flex flex-col ${d} ${i?"":"max-h-[200px]"}`,children:[e.jsxs("div",{className:"flex justify-between items-center pb-2 border-b border-gray-100",children:[e.jsx("div",{children:typeof a=="string"?e.jsx("h3",{className:"text-sm text-[#fff] font-medium",children:a}):a}),e.jsx("button",{onClick:()=>{r(!i)},children:i?e.jsx(u,{className:"text-gray-700"}):e.jsx(w,{className:"text-gray-700"})})]}),e.jsx("div",{className:"overflow-hidden flex-1",children:c})]})})};function E({content:n,name:a="Assistant",avatar:f,format:d="markdown"}){const c=p.useRef(null),i=()=>{let r=null,l=!1,o,x=[];try{if(o=JSON.parse(n),o.ext){let t=o.ext.find(s=>s.type==="workflow_rewrite_checkpoint"||s.type==="debug_checkpoint"&&s.data?.checkpoint_type==="workflow_rewrite_start");t&&t.data&&t.data.checkpoint_id?(r=t.data.checkpoint_id,l=!0):(t=o.ext.find(s=>s.type==="workflow_rewrite_complete"),t&&t.data&&t.data.version_id?(r=t.data.version_id,l=!0):(t=o.ext.find(s=>s.type==="debug_checkpoint"),t&&t.data&&t.data.checkpoint_id&&(r=t.data.checkpoint_id,l=!1))),o.ext.find(s=>s.type==="workflow_update")&&(l=!0),x=o.ext.find(s=>s.type==="param_update")?.data?.model_suggest||[]}}catch(t){return console.error("Failed to parse DebugResult content:",t),null}const h=l?e.jsxs("div",{className:"flex items-center text-green-500",children:[e.jsx("svg",{className:"w-5 h-5 mr-2",fill:"currentColor",viewBox:"0 0 20 20",children:e.jsx("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z",clipRule:"evenodd"})}),e.jsx("h4",{className:"font-bold text-xl",children:"Workflow Updated Successfully"})]}):e.jsxs("div",{className:"flex items-center text-gray-900",children:[e.jsx("svg",{className:"w-5 h-5 mr-2",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg","p-id":"28108",fill:"currentColor",children:e.jsx("path",{d:"M401.048025 844.855924c0 20.341281 16.643052 36.984333 36.984333 36.984333l147.936307 0c20.341281 0 36.984333-16.643052 36.984333-36.984333L622.952998 807.872614 401.048025 807.872614 401.048025 844.855924zM512 142.159744c-142.943596 0-258.888282 115.944686-258.888282 258.888282 0 88.021729 44.011376 165.503405 110.951975 212.288964l0 83.58365c0 20.341281 16.643052 36.984333 36.984333 36.984333l221.903949 0c20.341281 0 36.984333-16.643052 36.984333-36.984333l0-83.58365c66.941622-46.784536 110.951975-124.266212 110.951975-212.288964C770.888282 258.104429 654.943596 142.159744 512 142.159744zM617.588827 552.682561l-31.621185 22.005176 0 85.248569L438.031335 659.936307l0-85.063351L406.41015 552.866756c-49.743938-34.764781-79.33079-91.350544-79.33079-151.634536 0-101.890598 83.029018-184.92064 184.92064-184.92064s184.92064 83.029018 184.92064 184.92064C696.919617 461.332017 667.332764 517.91778 617.588827 552.682561z","p-id":"28277"})}),e.jsx("h4",{className:"font-bold text-xl",children:"Workflow Updated Finished"})]}),m=l?e.jsx("div",{className:"mt-3 text-xs text-gray-700",children:"💡 If you're not satisfied with the changes, click the restore button to revert to the previous version."}):null;return e.jsxs("div",{ref:c,className:"sticky top-0 left-0 w-full bg-gray-100 p-4 rounded-lg overflow-hidden",children:[e.jsx(y,{title:h,isWorkflowUpdate:l,className:"p-4",children:e.jsxs("div",{className:"prose prose-sm max-w-none",children:[d==="markdown"?e.jsx(k,{response:o||{}}):e.jsx("pre",{className:"whitespace-pre-wrap text-gray-700 text-sm leading-relaxed h-full",children:o?.text||""}),m]})}),x?.length>0&&e.jsx(N,{modelList:x,showPagination:!1}),e.jsx("div",{className:"flex justify-end mt-2",children:!!r&&e.jsx("div",{className:"ml-2 flex-shrink-0",children:e.jsx(j,{checkpointId:r,onRestore:()=>{console.log(`Workflow restored from ${l?"workflow update":"debug"} checkpoint`)},title:l?`Restore to this version (Version ${r})`:`Restore checkpoint ${r}`})})})]})};return e.jsx(g,{name:a,children:i()})}export{E as DebugResult};
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "ComfyUI-Copilot"
description = "Your Intelligent Assistant for Comfy-UI."
version = "2.0.15"
version = "2.0.16"
license = {file = "LICENSE"}
[project.urls]
+40 -79
View File
@@ -2,7 +2,7 @@
* @Author: ai-business-hql qingli.hql@alibaba-inc.com
* @Date: 2025-06-24 16:29:05
* @LastEditors: ai-business-hql ai.bussiness.hql@gmail.com
* @LastEditTime: 2025-09-02 17:08:42
* @LastEditTime: 2025-09-29 17:43:30
* @FilePath: /comfyui_copilot/ui/src/apis/workflowChatApi.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
@@ -34,13 +34,17 @@ const setApiKey = (apiKey: string) => {
// Get OpenAI configuration from localStorage
const getOpenAiConfig = () => {
const openaiApiKey = localStorage.getItem('openaiApiKey');
const openaiBaseUrl = localStorage.getItem('openaiBaseUrl') || 'https://api.openai.com/v1';
const openaiBaseUrl = localStorage.getItem('openaiBaseUrl');
const rsaPublicKey = localStorage.getItem('rsaPublicKey');
const workflowLLMApiKey = localStorage.getItem('workflowLLMApiKey');
const workflowLLMBaseUrl = localStorage.getItem('workflowLLMBaseUrl');
return {
openaiApiKey: openaiApiKey || '',
openaiBaseUrl,
rsaPublicKey
openaiBaseUrl: openaiBaseUrl || '',
rsaPublicKey,
workflowLLMApiKey: workflowLLMApiKey || '',
workflowLLMBaseUrl: workflowLLMBaseUrl || '',
};
};
@@ -141,7 +145,7 @@ export namespace WorkflowChatAPI {
try {
const apiKey = getApiKey();
const browserLanguage = app.extensionManager.setting.get('Comfy.Locale');
const { openaiApiKey, openaiBaseUrl, rsaPublicKey } = getOpenAiConfig();
const { openaiApiKey, openaiBaseUrl, rsaPublicKey, workflowLLMApiKey, workflowLLMBaseUrl } = getOpenAiConfig();
// Generate a unique message ID for this chat request
const messageId = generateUUID();
@@ -258,6 +262,13 @@ export namespace WorkflowChatAPI {
console.error('Error encrypting OpenAI API key:', error);
}
}
// Add Workflow LLM headers if available
if (workflowLLMBaseUrl) {
headers['Workflow-LLM-Base-Url'] = workflowLLMBaseUrl;
}
if (workflowLLMApiKey) {
headers['Workflow-LLM-Api-Key'] = workflowLLMApiKey;
}
// Create controller and combine with external signal if provided
const controller = new AbortController();
@@ -350,7 +361,7 @@ export namespace WorkflowChatAPI {
try {
const apiKey = getApiKey();
const browserLanguage = getBrowserLanguage();
const { openaiApiKey, openaiBaseUrl, rsaPublicKey } = getOpenAiConfig();
const { openaiApiKey, openaiBaseUrl, rsaPublicKey, workflowLLMApiKey, workflowLLMBaseUrl } = getOpenAiConfig();
// Prepare headers
const headers: Record<string, string> = {
@@ -372,6 +383,13 @@ export namespace WorkflowChatAPI {
console.error('Error encrypting OpenAI API key:', error);
}
}
// Add Workflow LLM headers if available
if (workflowLLMBaseUrl) {
headers['Workflow-LLM-Base-Url'] = workflowLLMBaseUrl;
}
if (workflowLLMApiKey) {
headers['Workflow-LLM-Api-Key'] = workflowLLMApiKey;
}
const response = await fetch(`${BASE_URL}/api/chat/get_optimized_workflow`, {
method: 'POST',
@@ -402,7 +420,7 @@ export namespace WorkflowChatAPI {
export async function batchGetNodeInfo(nodeTypes: string[]): Promise<any> {
const apiKey = getApiKey();
const browserLanguage = getBrowserLanguage();
const { openaiApiKey, openaiBaseUrl, rsaPublicKey } = getOpenAiConfig();
const { openaiApiKey, openaiBaseUrl, rsaPublicKey, workflowLLMApiKey, workflowLLMBaseUrl } = getOpenAiConfig();
// Prepare headers
const headers: Record<string, string> = {
@@ -422,6 +440,13 @@ export namespace WorkflowChatAPI {
console.error('Error encrypting OpenAI API key:', error);
}
}
// Add Workflow LLM headers if available
if (workflowLLMBaseUrl) {
headers['Workflow-LLM-Base-Url'] = workflowLLMBaseUrl;
}
if (workflowLLMApiKey) {
headers['Workflow-LLM-Api-Key'] = workflowLLMApiKey;
}
const response = await fetch(`${BASE_URL}/api/chat/get_node_info_by_types`, {
method: 'POST',
@@ -451,65 +476,6 @@ export namespace WorkflowChatAPI {
return JSON.parse(cachedMessages) as Message[];
}
return [];
const apiKey = getApiKey();
const browserLanguage = getBrowserLanguage();
const { openaiApiKey, openaiBaseUrl, rsaPublicKey } = getOpenAiConfig();
// Prepare headers
const headers: Record<string, string> = {
'accept': 'application/json',
'Authorization': `Bearer ${apiKey}`,
'trace-id': generateUUID(),
'Accept-Language': browserLanguage,
};
// Add OpenAI configuration headers if available
if (openaiApiKey && openaiApiKey.trim() !== '' && rsaPublicKey) {
try {
const encryptedApiKey = await encryptWithRsaPublicKey(openaiApiKey as string, rsaPublicKey as string);
headers['Encrypted-Openai-Api-Key'] = encryptedApiKey;
headers['Openai-Base-Url'] = openaiBaseUrl;
} catch (error) {
console.error('Error encrypting OpenAI API key:', error);
}
}
const response = await fetch(`${BASE_URL}/api/chat/get_messages_by_session_id?session_id=${sessionId}`, {
method: 'GET',
headers,
});
checkAndSaveApiKey(response);
const result = await response.json();
if (!result.success) {
const message = result.message || 'Failed to fetch messages';
alert(message + ', please refresh the page and try again.');
throw new Error(message);
}
const messages = result.data.map((msg: any) => {
console.log("msg.ext", msg.ext);
return {
id: msg.id.toString(),
content: msg.role === 'ai' ? JSON.stringify({
session_id: sessionId,
text: msg.content,
finished: msg.finished || false,
format: msg.format || 'markdown',
ext: msg.ext
}) : msg.content,
role: msg.role,
name: msg.role === 'ai' ? 'Assistant' : undefined,
format: msg.format || 'markdown',
finished: msg.finished
}
});
// Cache the messages in localStorage
localStorage.setItem(`messages_${sessionId}`, JSON.stringify(messages));
return messages;
} catch (error) {
console.error('Error fetching messages:', error);
alert(error instanceof Error ? error.message : 'Failed to fetch messages' + ', please refresh the page and try again.');
@@ -521,7 +487,6 @@ export namespace WorkflowChatAPI {
try {
const apiKey = getApiKey();
const browserLanguage = getBrowserLanguage();
const { openaiApiKey, openaiBaseUrl, rsaPublicKey } = getOpenAiConfig();
// Prepare headers
const headers: Record<string, string> = {
@@ -531,17 +496,6 @@ export namespace WorkflowChatAPI {
'Accept-Language': browserLanguage,
};
// Add OpenAI configuration headers if available
if (openaiApiKey && openaiApiKey.trim() !== '' && rsaPublicKey) {
try {
const encryptedApiKey = await encryptWithRsaPublicKey(openaiApiKey as string, rsaPublicKey as string);
headers['Encrypted-Openai-Api-Key'] = encryptedApiKey;
headers['Openai-Base-Url'] = openaiBaseUrl;
} catch (error) {
console.error('Error encrypting OpenAI API key:', error);
}
}
const response = await fetch(`${BASE_URL}/api/chat/announcement`, {
method: 'GET',
headers,
@@ -651,7 +605,7 @@ export namespace WorkflowChatAPI {
abortSignal?: AbortSignal
): AsyncGenerator<ChatResponse> {
try {
const { openaiApiKey, openaiBaseUrl } = getOpenAiConfig();
const { openaiApiKey, openaiBaseUrl, workflowLLMApiKey, workflowLLMBaseUrl } = getOpenAiConfig();
const browserLanguage = app.extensionManager.setting.get('Comfy.Locale');
const session_id = localStorage.getItem("sessionId") || null;
const apiKey = getApiKey();
@@ -670,6 +624,13 @@ export namespace WorkflowChatAPI {
headers['Openai-Api-Key'] = openaiApiKey;
headers['Openai-Base-Url'] = openaiBaseUrl;
}
// Add Workflow LLM headers if available
if (workflowLLMBaseUrl) {
headers['Workflow-LLM-Base-Url'] = workflowLLMBaseUrl;
}
if (workflowLLMApiKey) {
headers['Workflow-LLM-Api-Key'] = workflowLLMApiKey;
}
// Create controller and combine with external signal if provided
const controller = new AbortController();
+111 -8
View File
@@ -45,12 +45,17 @@ export function ApiKeyModal({ isOpen, onClose, onSave, initialApiKey = '', onCon
// OpenAI configuration
const [openaiApiKey, setOpenaiApiKey] = useState('');
const [openaiBaseUrl, setOpenaiBaseUrl] = useState('https://api.openai.com/v1');
const [openaiBaseUrl, setOpenaiBaseUrl] = useState('');
const [showOpenaiApiKey, setShowOpenaiApiKey] = useState(false);
const [verifyingKey, setVerifyingKey] = useState(false);
const [verificationResult, setVerificationResult] = useState<{success: boolean, message: string} | null>(null);
const [rsaPublicKey, setRsaPublicKey] = useState<string | null>(null);
// Workflow LLM configuration
const [workflowLLMApiKey, setWorkflowLLMApiKey] = useState('');
const [workflowLLMBaseUrl, setWorkflowLLMBaseUrl] = useState('');
const [showWorkflowLLMApiKey, setShowWorkflowLLMApiKey] = useState(false);
const { apikeymodel_title } = useLanguage();
useEffect(() => {
@@ -59,6 +64,8 @@ export function ApiKeyModal({ isOpen, onClose, onSave, initialApiKey = '', onCon
// Load OpenAI configuration from localStorage
const savedOpenaiApiKey = localStorage.getItem('openaiApiKey');
const savedOpenaiBaseUrl = localStorage.getItem('openaiBaseUrl');
const savedWorkflowLLMApiKey = localStorage.getItem('workflowLLMApiKey');
const savedWorkflowLLMBaseUrl = localStorage.getItem('workflowLLMBaseUrl');
if (savedOpenaiApiKey) {
setOpenaiApiKey(savedOpenaiApiKey);
@@ -67,6 +74,12 @@ export function ApiKeyModal({ isOpen, onClose, onSave, initialApiKey = '', onCon
if (savedOpenaiBaseUrl) {
setOpenaiBaseUrl(savedOpenaiBaseUrl);
}
if (savedWorkflowLLMApiKey) {
setWorkflowLLMApiKey(savedWorkflowLLMApiKey);
}
if (savedWorkflowLLMBaseUrl) {
setWorkflowLLMBaseUrl(savedWorkflowLLMBaseUrl);
}
// Fetch RSA public key
const fetchPublicKey = async () => {
@@ -173,22 +186,44 @@ export function ApiKeyModal({ isOpen, onClose, onSave, initialApiKey = '', onCon
// Check if OpenAI configuration has changed
const previousOpenaiApiKey = localStorage.getItem('openaiApiKey') || '';
const previousOpenaiBaseUrl = localStorage.getItem('openaiBaseUrl') || 'https://api.openai.com/v1';
const hasOpenaiConfigChanged = openaiApiKey.trim() !== previousOpenaiApiKey || openaiBaseUrl !== previousOpenaiBaseUrl;
const previousOpenaiBaseUrl = localStorage.getItem('openaiBaseUrl') || '';
const hasOpenaiConfigChanged = openaiApiKey.trim() !== previousOpenaiApiKey || openaiBaseUrl.trim() !== previousOpenaiBaseUrl;
// Always persist base URL (needed for LMStudio which may not require API key)
localStorage.setItem('openaiBaseUrl', openaiBaseUrl);
// Check if Workflow LLM configuration has changed
const previousWorkflowLLMApiKey = localStorage.getItem('workflowLLMApiKey') || '';
const previousWorkflowLLMBaseUrl = localStorage.getItem('workflowLLMBaseUrl') || '';
const hasWorkflowConfigChanged = workflowLLMApiKey.trim() !== previousWorkflowLLMApiKey || workflowLLMBaseUrl.trim() !== previousWorkflowLLMBaseUrl;
// Save or clear OpenAI base URL
if (openaiBaseUrl.trim()) {
localStorage.setItem('openaiBaseUrl', openaiBaseUrl.trim());
} else {
localStorage.removeItem('openaiBaseUrl');
}
// Save or clear Workflow LLM base URL
if (workflowLLMBaseUrl.trim()) {
localStorage.setItem('workflowLLMBaseUrl', workflowLLMBaseUrl.trim());
} else {
localStorage.removeItem('workflowLLMBaseUrl');
}
// Save or clear OpenAI API key in localStorage
if (openaiApiKey.trim()) {
localStorage.setItem('openaiApiKey', openaiApiKey);
localStorage.setItem('openaiApiKey', openaiApiKey.trim());
} else {
// If the OpenAI API key is empty, remove only the key and keep base URL
localStorage.removeItem('openaiApiKey');
}
// Save or clear Workflow LLM API key
if (workflowLLMApiKey.trim()) {
localStorage.setItem('workflowLLMApiKey', workflowLLMApiKey);
} else {
localStorage.removeItem('workflowLLMApiKey');
}
// Call configuration updated callback if OpenAI config has changed
if (hasOpenaiConfigChanged && onConfigurationUpdated) {
if ((hasOpenaiConfigChanged || hasWorkflowConfigChanged) && onConfigurationUpdated) {
onConfigurationUpdated();
}
@@ -377,6 +412,74 @@ export function ApiKeyModal({ isOpen, onClose, onSave, initialApiKey = '', onCon
)}
</div>
</CollapsibleCard>
{/* Workflow LLM Configuration (Optional) */}
<CollapsibleCard
title={<h3 className="text-sm text-gray-900 dark:text-white font-medium">Workflow LLM Configuration (Optional)</h3>}
className='mb-4'
>
<div>
{/* Workflow LLM API Key */}
<div className="mb-4">
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-2">
Workflow LLM API Key
</label>
<div className="relative">
<input
type={showWorkflowLLMApiKey ? "text" : "password"}
value={workflowLLMApiKey}
onChange={(e) => setWorkflowLLMApiKey(e.target.value)}
placeholder="Enter workflow LLM API key (if required)"
className="w-full px-4 py-3 border border-gray-200 dark:border-gray-600 rounded-lg pr-12 text-xs
bg-gray-50 dark:bg-gray-700
text-gray-900 dark:text-white
placeholder-gray-500 dark:placeholder-gray-400
focus:border-blue-500 dark:focus:border-blue-400
focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20
focus:outline-none"
/>
<button
type="button"
className="absolute inset-y-0 right-0 pr-4 flex items-center text-gray-500 dark:text-gray-400
hover:text-gray-700 dark:hover:text-gray-200 transition-colors bg-transparent border-none"
onClick={() => setShowWorkflowLLMApiKey(!showWorkflowLLMApiKey)}
>
{showWorkflowLLMApiKey ? (
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
) : (
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
)}
</button>
</div>
</div>
{/* Workflow LLM Base URL */}
<div className="mb-4">
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-2">
Workflow LLM Server URL
</label>
<input
type="text"
value={workflowLLMBaseUrl}
onChange={(e) => setWorkflowLLMBaseUrl(e.target.value)}
placeholder="e.g. https://api.openai.com/v1 or http://localhost:1234/v1"
className="w-full px-4 py-3 border border-gray-200 dark:border-gray-600 rounded-lg text-xs
bg-gray-50 dark:bg-gray-700
text-gray-900 dark:text-white
placeholder-gray-500 dark:placeholder-gray-400
focus:border-blue-500 dark:focus:border-blue-400
focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20
focus:outline-none"
/>
<div className="mt-2 text-xs text-gray-500 dark:text-gray-400">
<div className="mb-1"><strong>Optional:</strong> Only set if your workflow tools use a different LLM.</div>
</div>
</div>
</div>
</CollapsibleCard>
{/* Action Buttons */}
<div className="flex justify-end gap-3">
<button