diff --git a/modeldata/deepseek.json b/modeldata/deepseek.json new file mode 100644 index 0000000..21ef1a4 --- /dev/null +++ b/modeldata/deepseek.json @@ -0,0 +1,5 @@ +{ + "endpoint": "https://api.deepseek.com/v1/chat/completions", + "api_key": "", + "model_name": "deepseek-chat" +} \ No newline at end of file diff --git a/modeldata/default.json b/modeldata/default.json new file mode 100644 index 0000000..a31fc3c --- /dev/null +++ b/modeldata/default.json @@ -0,0 +1,5 @@ +{ + "endpoint": "http://localhost:11434/v1/chat/completions", + "api_key": "ollama", + "model_name": "huihui_ai/qwen3-vl-abliterated:4b-instruct" +} \ No newline at end of file diff --git a/modeldata/gemini.json b/modeldata/gemini.json new file mode 100644 index 0000000..dad8234 --- /dev/null +++ b/modeldata/gemini.json @@ -0,0 +1,5 @@ +{ + "endpoint": "https://generativelanguage.googleapis.com/v1beta/chat/completions", + "api_key": "", + "model_name": "gemini-2.0-flash-exp" +} \ No newline at end of file diff --git a/modeldata/openai.json b/modeldata/openai.json new file mode 100644 index 0000000..e6d4221 --- /dev/null +++ b/modeldata/openai.json @@ -0,0 +1,5 @@ +{ + "endpoint": "https://api.openai.com/v1/chat/completions", + "api_key": "", + "model_name": "gpt-4o" +} \ No newline at end of file diff --git a/openai_helper.py b/openai_helper.py index a32dc1c..27d080e 100644 --- a/openai_helper.py +++ b/openai_helper.py @@ -16,21 +16,58 @@ try: except ImportError: print("⚠️ torchaudio 未安裝,音訊功能將無法使用") +# Import JSON prompt extractor for automatic prompt list extraction +from .json_prompt_extractor import extract_prompts_from_json + class OpenAIHelper: """ OpenAI Helper節點,用於呼叫OpenAI相容的API - 支援圖片、音訊輸入,配置管理,模型列表獲取 + 支援圖片、音訊輸入,配置管理,模型列表獲取,多配置範本 """ @classmethod - def _load_config(cls): - """載入配置從openaimodel.json文件""" - config_file = os.path.join(os.path.dirname(__file__), "openaimodel.json") + def _get_config_files(cls): + """獲取 modeldata 資料夾中的所有配置檔案""" + modeldata_dir = os.path.join(os.path.dirname(__file__), "modeldata") + + # 確保資料夾存在 + if not os.path.exists(modeldata_dir): + os.makedirs(modeldata_dir) + # 創建預設配置 + default_config = { + "endpoint": "", + "api_key": "", + "model_name": "" + } + default_path = os.path.join(modeldata_dir, "default.json") + with open(default_path, 'w', encoding='utf-8') as f: + json.dump(default_config, f, ensure_ascii=False, indent=2) + + # 獲取所有 .json 檔案 + config_files = [] try: - with open(config_file, 'r', encoding='utf-8') as f: + for file in os.listdir(modeldata_dir): + if file.lower().endswith('.json'): + config_files.append(file) + except Exception as e: + print(f"⚠️ 讀取 modeldata 資料夾失敗: {e}") + + if not config_files: + return ["default.json"] + + return sorted(config_files) + + @classmethod + def _load_config(cls, config_file="default.json"): + """載入指定的配置檔案""" + modeldata_dir = os.path.join(os.path.dirname(__file__), "modeldata") + config_path = os.path.join(modeldata_dir, config_file) + + try: + with open(config_path, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: - print(f"⚠️ 載入openaimodel.json失敗: {e}") + print(f"⚠️ 載入配置檔案 {config_file} 失敗: {e}") return { "endpoint": "", "api_key": "", @@ -38,19 +75,21 @@ class OpenAIHelper: } @classmethod - def _save_config(cls, endpoint, api_key, model_name): - """保存配置到openaimodel.json文件""" - config_file = os.path.join(os.path.dirname(__file__), "openaimodel.json") + def _save_config(cls, config_file, endpoint, api_key, model_name): + """保存配置到指定的配置檔案""" + modeldata_dir = os.path.join(os.path.dirname(__file__), "modeldata") + config_path = os.path.join(modeldata_dir, config_file) + try: config = { "endpoint": endpoint, "api_key": api_key, "model_name": model_name } - with open(config_file, 'w', encoding='utf-8') as f: + with open(config_path, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) except Exception as e: - print(f"❌ 保存openaimodel.json失敗: {e}") + print(f"❌ 保存配置檔案 {config_file} 失敗: {e}") @classmethod def _get_prompt_templates(cls): @@ -90,8 +129,12 @@ class OpenAIHelper: @classmethod def INPUT_TYPES(cls): - # 載入保存的配置 - config = cls._load_config() + # 獲取配置檔案列表 + config_files = cls._get_config_files() + + # 載入預設配置(第一個配置檔案) + default_config_file = config_files[0] if config_files else "default.json" + config = cls._load_config(default_config_file) # 獲取範本列表 templates = cls._get_prompt_templates() @@ -99,6 +142,10 @@ class OpenAIHelper: return { "required": { + "config_template": (config_files, { + "default": default_config_file, + "tooltip": "選擇 API 配置範本(從 modeldata 資料夾)" + }), "endpoint": ("STRING", { "multiline": False, "default": config.get("endpoint", ""), @@ -141,8 +188,9 @@ class OpenAIHelper: } } - RETURN_TYPES = ("STRING", "STRING") - RETURN_NAMES = ("text", "model_name_list") + RETURN_TYPES = ("STRING", "STRING", "STRING") + RETURN_NAMES = ("text", "model_name_list", "prompts") + OUTPUT_IS_LIST = (False, False, True) # prompts 是列表 FUNCTION = "process_openai" CATEGORY = "ListHelper/LLM" @@ -331,23 +379,23 @@ class OpenAIHelper: print(f"❌ API呼叫異常: {e}") return {"error": {"message": str(e)}} - def process_openai(self, endpoint, api_key, model_name, user_prompt, prompt_template, max_tokens, + def process_openai(self, config_template, endpoint, api_key, model_name, user_prompt, prompt_template, max_tokens, system_prompt=None, image1=None, image2=None, image3=None, audio=None, file_path=None): """處理OpenAI請求""" # 驗證必填參數 if not endpoint or not endpoint.strip(): - return ("❌ 錯誤: 請提供API端點", "") + return ("❌ 錯誤: 請提供API端點", "", []) if not api_key or not api_key.strip(): - return ("❌ 錯誤: 請提供API金鑰", "") + return ("❌ 錯誤: 請提供API金鑰", "", []) if not model_name or not model_name.strip(): - return ("❌ 錯誤: 請提供模型名稱", "") + return ("❌ 錯誤: 請提供模型名稱", "", []) - # 保存配置 - self._save_config(endpoint.strip(), api_key.strip(), model_name.strip()) + # 保存配置到選擇的配置範本 + self._save_config(config_template, endpoint.strip(), api_key.strip(), model_name.strip()) # 獲取模型列表 model_list = self._get_model_list(endpoint.strip(), api_key.strip()) @@ -401,7 +449,7 @@ class OpenAIHelper: audio_b64 = None if audio is not None: if not TORCHAUDIO_AVAILABLE: - return ("❌ 錯誤: torchaudio未安裝,無法處理音訊", model_list) + return ("❌ 錯誤: torchaudio未安裝,無法處理音訊", model_list, []) print(f"處理音訊輸入") try: @@ -417,10 +465,10 @@ class OpenAIHelper: }) print(f"✓ 音訊處理成功") else: - return ("❌ 錯誤: 音訊處理失敗", model_list) + return ("❌ 錯誤: 音訊處理失敗", model_list, []) except Exception as e: print(f"❌ 處理音訊時出錯: {str(e)}") - return (f"❌ 錯誤: 處理音訊時出錯: {str(e)}", model_list) + return (f"❌ 錯誤: 處理音訊時出錯: {str(e)}", model_list, []) # 添加用戶消息 if len(user_content) > 1: @@ -454,7 +502,7 @@ class OpenAIHelper: # 處理響應 if not response: - return ("❌ API呼叫失敗", model_list) + return ("❌ API呼叫失敗", model_list, []) # 檢查錯誤 if 'error' in response: @@ -463,13 +511,22 @@ class OpenAIHelper: error_msg = f"❌ API錯誤: {error_info.get('message', str(error_info))}" else: error_msg = f"❌ API錯誤: {str(error_info)}" - return (error_msg, model_list) + return (error_msg, model_list, []) if 'choices' not in response or not response['choices']: - return ("❌ API回應格式異常", model_list) + return ("❌ API回應格式異常", model_list, []) # 獲取回應訊息 message = response['choices'][0]['message'] response_content = message.get('content', '') - return (response_content, model_list) + # 自動提取提示詞列表(如果輸出包含 JSON 格式的雜誌數據) + try: + prompts = extract_prompts_from_json(response_content) + if prompts: + print(f"✅ 自動提取到 {len(prompts)} 個圖片提示詞") + except Exception as e: + print(f"ℹ️ 提示詞提取失敗(這是正常的,如果輸出不是雜誌 JSON): {e}") + prompts = [] + + return (response_content, model_list, prompts)