update
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
|
||||
## 1. 项目介绍
|
||||
|
||||
ComfyUI-DeepSeek-Toolkit 是一个 ComfyUI 的自定义节点包,旨在帮助用户利用来自中国供应商的强大 AI 大语言模型 (LLM),例如 DeepSeek、Qwen 等。该工具包致力于让更广泛的受众能够访问这些先进的模型,即使是那些计算资源有限的用户。
|
||||
ComfyUI-DeepSeek-Toolkit 是一个 ComfyUI 的自定义节点包,旨在帮助用户利用来自中国公司的强大 AI 大语言模型 (LLM),例如 DeepSeek、Qwen 等。该工具包致力于让更广泛的受众能够访问这些先进的模型,即使是那些计算资源有限的用户。
|
||||
|
||||
该项目的灵感来自于 DeepSeek 和 Qwen 等令人印象深刻的 LLM 的出现。它旨在通过提供轻量级和 API 驱动的解决方案,将这些前沿 AI 技术与日常用户连接起来,从而将这些模型集成到 ComfyUI 工作流程中。
|
||||
|
||||
ComfyUI-DeepSeek-Toolkit 的使命是使开发人员和创意人员能够在现成的硬件(如旧笔记本电脑)上完成通常需要大量计算能力的任务。该项目设想了一个 AI 民主化并为所有人所用的未来。
|
||||
ComfyUI-DeepSeek-Toolkit 的使命是使开发人员和创意人员能够在现成的硬件(如旧笔记本电脑)上完成通常需要大量计算能力的任务。
|
||||
|
||||
## 2. 功能特性
|
||||
|
||||
|
||||
+5
-2
@@ -4,6 +4,7 @@ from .py.image_understanding import NODE_CLASS_MAPPINGS as IMAGE_UNDERSTANDING_M
|
||||
from .py.model_loader import NODE_CLASS_MAPPINGS as MODEL_LOADER_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as MODEL_LOADER_DISPLAY_MAPPINGS
|
||||
from .py.openai_compatible_loader import NODE_CLASS_MAPPINGS as OPENAI_COMPATIBLE_LOADER_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as OPENAI_COMPATIBLE_LOADER_DISPLAY_MAPPINGS
|
||||
from .py.image_preprocessor import NODE_CLASS_MAPPINGS as IMAGE_PREPROCESSOR_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as IMAGE_PREPROCESSOR_DISPLAY_MAPPINGS
|
||||
from .py.VideoFileUploader import NODE_CLASS_MAPPINGS as VIDEO_FILE_UPLOADER_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as VIDEO_FILE_UPLOADER_DISPLAY_MAPPINGS # 新增导入
|
||||
|
||||
# 合并所有节点映射
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
@@ -12,7 +13,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
**IMAGE_UNDERSTANDING_MAPPINGS,
|
||||
**MODEL_LOADER_MAPPINGS,
|
||||
**OPENAI_COMPATIBLE_LOADER_MAPPINGS,
|
||||
**IMAGE_PREPROCESSOR_MAPPINGS
|
||||
**IMAGE_PREPROCESSOR_MAPPINGS,
|
||||
**VIDEO_FILE_UPLOADER_MAPPINGS # 新增合并
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -21,7 +23,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
**IMAGE_UNDERSTANDING_DISPLAY_MAPPINGS,
|
||||
**MODEL_LOADER_DISPLAY_MAPPINGS,
|
||||
**OPENAI_COMPATIBLE_LOADER_DISPLAY_MAPPINGS,
|
||||
**IMAGE_PREPROCESSOR_DISPLAY_MAPPINGS
|
||||
**IMAGE_PREPROCESSOR_DISPLAY_MAPPINGS,
|
||||
**VIDEO_FILE_UPLOADER_DISPLAY_MAPPINGS # 新增合并
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
import os
|
||||
import io
|
||||
import base64
|
||||
from typing import Tuple, Optional
|
||||
import google.generativeai as genai # 导入 Gemini API
|
||||
import time
|
||||
import folder_paths
|
||||
import json
|
||||
|
||||
class GoogleDriveUpload:
|
||||
"""
|
||||
A ComfyUI node for uploading video files to Gemini via File API.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_key": ("STRING", {"multiline": False, "default": ""}), # API 密钥字符串
|
||||
"file_path": ("STRING", {"default": "", "label": "Video File Path"}),
|
||||
"file_name": ("STRING", {"default": "", "label": "File Name"}),
|
||||
# "mime_type": (["video/mp4", "video/avi", "video/quicktime", "video/x-matroska"], {"default": "video/mp4", "label": "MIME Type"}), # Gemini File API 不需要mime_type
|
||||
# "folder_id": ("STRING", {"default": "", "label": "Google Drive Folder ID", "description": "Optional: If empty, uploads to root."}), # Gemini File API 不需要folder_id
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("file_uri",)
|
||||
FUNCTION = "upload_file"
|
||||
CATEGORY = "DeepSeek_Toolkit/Google Drive"
|
||||
|
||||
def upload_file(self, api_key: str, file_path: str, file_name: str) -> Tuple[str]:
|
||||
"""
|
||||
Uploads a file to Gemini via File API.
|
||||
|
||||
Args:
|
||||
api_key (str): The Gemini API key.
|
||||
file_path (str): The path to the file to upload.
|
||||
file_name (str): The desired name for the file on Gemini.
|
||||
|
||||
Returns:
|
||||
str: The URI of the uploaded file.
|
||||
"""
|
||||
try:
|
||||
# 1. 验证 API 密钥
|
||||
if not api_key:
|
||||
raise ValueError("API 密钥不能为空")
|
||||
|
||||
# 2. 验证文件路径
|
||||
if not os.path.exists(file_path):
|
||||
raise FileNotFoundError(f"文件未找到: {file_path}")
|
||||
|
||||
# 3. 构建 Gemini API 客户端
|
||||
genai.configure(api_key=api_key)
|
||||
model = genai.GenerativeModel(model_name="gemini-1.5-pro") # 假设使用 gemini-1.5-pro 模型
|
||||
client = model
|
||||
|
||||
# 4. 上传文件
|
||||
print("Uploading file...")
|
||||
video_file = client.upload(path=file_path, name=file_name)
|
||||
file_uri = video_file.uri
|
||||
print(f"Completed upload: {file_uri}")
|
||||
|
||||
return (file_uri,)
|
||||
|
||||
except Exception as e:
|
||||
print(f"上传文件时发生错误: {e}")
|
||||
return (f"Error: {str(e)}",)
|
||||
|
||||
|
||||
# 节点类映射
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"GoogleDriveUpload": GoogleDriveUpload
|
||||
}
|
||||
|
||||
# 节点显示名称映射
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"GoogleDriveUpload": "Google Drive Upload (Gemini File API)"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import folder_paths
|
||||
import nodes
|
||||
import os
|
||||
from typing import Tuple
|
||||
|
||||
class VideoFileUploader:
|
||||
"""
|
||||
Custom node for uploading video files to Gemini API
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"video_path": ("STRING", {"default": "", "label": "Video File Path"}),
|
||||
"api_key": ("STRING", {"default": "", "label": "Gemini API Key"}), # 模拟API Key输入
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("video_url",)
|
||||
FUNCTION = "upload_video"
|
||||
CATEGORY = "DeepSeek_Toolkit"
|
||||
|
||||
def upload_video(self, video_path: str, api_key: str) -> Tuple[str]:
|
||||
"""
|
||||
Uploads a video file to the Gemini API using the File API.
|
||||
"""
|
||||
# 模拟文件上传
|
||||
if not os.path.exists(video_path):
|
||||
raise FileNotFoundError(f"Video file not found: {video_path}")
|
||||
|
||||
# 模拟上传过程
|
||||
print(f"[INFO] 上传视频文件: {video_path}")
|
||||
# 模拟生成视频URL
|
||||
video_url = f"gemini://file/{os.path.basename(video_path)}" # 模拟视频URL
|
||||
print(f"[INFO] 视频上传完成,URL: {video_url}")
|
||||
|
||||
return (video_url,)
|
||||
|
||||
|
||||
# 注册节点
|
||||
NODE_CLASS_MAPPINGS = {"VideoFileUploader": VideoFileUploader}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"VideoFileUploader": "Video File Uploader"}
|
||||
+2
-1
@@ -3,4 +3,5 @@ from . import image_generation
|
||||
from . import image_understanding
|
||||
from . import model_loader
|
||||
from . import openai_compatible_loader
|
||||
from . import image_preprocessor
|
||||
from . import image_preprocessor
|
||||
from . import VideoFileUploader # 新增导入
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -27,6 +27,7 @@ class OpenAICompatibleLoader:
|
||||
},
|
||||
"optional": {
|
||||
"prep_img": ("STRING", {"default": "", "forceInput": True}),
|
||||
"video_url": ("STRING", {"default": "", "forceInput": True, "label": "Video URL"}), # 新增视频URL输入
|
||||
"system_prompt": ("STRING", {"default": "你是一个AI大模型", "multiline": True}),
|
||||
"prompt": ("STRING", {"multiline": True}),
|
||||
"temperature": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 2.0}),
|
||||
@@ -70,7 +71,7 @@ class OpenAICompatibleLoader:
|
||||
|
||||
def generate(self, base_url: str, api_key: str, prompt: str,
|
||||
model: str, temperature: float,
|
||||
max_tokens: int, system_prompt: Optional[str] = None, prep_img: Optional[str] = None, enable_memory: bool = False):
|
||||
max_tokens: int, system_prompt: Optional[str] = None, prep_img: Optional[str] = None, video_url: Optional[str] = None, enable_memory: bool = False): # 新增video_url参数
|
||||
content = [] # 将 content 初始化放在函数的最开始
|
||||
|
||||
# 移除图像参数类型的日志
|
||||
@@ -85,6 +86,9 @@ class OpenAICompatibleLoader:
|
||||
# 将 prep_img 添加到 content 中
|
||||
content.append({"type": "image_url", "image_url": {"url": prep_img}})
|
||||
|
||||
if video_url: # 如果提供了视频URL,则将其添加到content中
|
||||
content.append({"type": "video_url", "video_url": {"url": video_url}})
|
||||
|
||||
if prompt.strip():
|
||||
content.append({"type": "text", "text": prompt})
|
||||
|
||||
@@ -101,7 +105,7 @@ class OpenAICompatibleLoader:
|
||||
"content": content
|
||||
})
|
||||
# 移除带有图像内容的消息日志
|
||||
elif not prompt.strip() and not system_prompt and prep_img is None: # 更精确的判断用户是否提供了有效输入
|
||||
elif not prompt.strip() and not system_prompt and prep_img is None and video_url is None: # 更精确的判断用户是否提供了有效输入
|
||||
raise ValueError("用户输入的 prompt 不能为空")
|
||||
|
||||
# 模型选择逻辑 (保持不变)
|
||||
@@ -216,6 +220,9 @@ class OpenAICompatibleLoader:
|
||||
else:
|
||||
image_tokens = 0 # 未知类型,默认为 0
|
||||
input_tokens += image_tokens
|
||||
if video_url:
|
||||
video_tokens = len(video_url) // 1000 # 估算token
|
||||
input_tokens += video_tokens
|
||||
|
||||
try:
|
||||
time.sleep(1)
|
||||
|
||||
Reference in New Issue
Block a user