update skill option add description
This commit is contained in:
@@ -26,7 +26,8 @@
|
||||
"display_name": "OpenAI Text Skill Options",
|
||||
"description": "Load a local SKILL.md package for progressive use by OpenAI Text API.",
|
||||
"inputs": {
|
||||
"skill_name": {"name": "skill_name", "tooltip": "Select a configured local Skill"}
|
||||
"skill_name": {"name": "skill_name", "tooltip": "Select a configured local Skill"},
|
||||
"description": {"name": "Skill description", "tooltip": "Read-only description of the selected Skill"}
|
||||
},
|
||||
"outputs": {"0": {"name": "skill_options"}}
|
||||
},
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
"display_name": "OpenAI 文本 Skill 选项",
|
||||
"description": "加载本地 SKILL.md,并供 OpenAI 文本 API 渐进式读取引用资料。",
|
||||
"inputs": {
|
||||
"skill_name": {"name": "Skill 名称", "tooltip": "选择配置中发现的本地 Skill"}
|
||||
"skill_name": {"name": "Skill 名称", "tooltip": "选择配置中发现的本地 Skill"},
|
||||
"description": {"name": "Skill 描述", "tooltip": "当前所选 Skill 的只读描述"}
|
||||
},
|
||||
"outputs": {"0": {"name": "Skill 选项"}}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
from aiohttp import web
|
||||
from server import PromptServer
|
||||
from comfy_api.latest import io
|
||||
|
||||
from ..utils.skill_utils import create_skill_options, discover_skills
|
||||
from ..utils.skill_utils import create_skill_options, get_skill_summaries
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/ycyy/openai/skills/all")
|
||||
async def get_all_openai_skills(request):
|
||||
try:
|
||||
return web.json_response(get_skill_summaries())
|
||||
except Exception as exc:
|
||||
return web.json_response({"error": str(exc)}, status=500)
|
||||
|
||||
|
||||
class OpenAITextSkillOptions(io.ComfyNode):
|
||||
@@ -8,8 +18,9 @@ class OpenAITextSkillOptions(io.ComfyNode):
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
skills, _ = discover_skills(strict=True)
|
||||
skills = get_skill_summaries()
|
||||
names = [item["name"] for item in skills] or [""]
|
||||
description = skills[0]["description"] if skills else ""
|
||||
return io.Schema(
|
||||
node_id="YCYY_OpenAI_Text_Skill_Options",
|
||||
display_name="OpenAI Text Skill Options",
|
||||
@@ -20,7 +31,14 @@ class OpenAITextSkillOptions(io.ComfyNode):
|
||||
options=names,
|
||||
default=names[0],
|
||||
tooltip="Select a configured local SKILL.md package.",
|
||||
)
|
||||
),
|
||||
io.String.Input(
|
||||
id="description",
|
||||
default=description,
|
||||
multiline=True,
|
||||
socketless=True,
|
||||
tooltip="Description of the selected Skill (read-only).",
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
io.AnyType.Output(
|
||||
@@ -33,7 +51,8 @@ class OpenAITextSkillOptions(io.ComfyNode):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, skill_name) -> io.NodeOutput:
|
||||
def execute(cls, skill_name, description="") -> io.NodeOutput:
|
||||
if not skill_name:
|
||||
raise ValueError("No valid local Skills were found")
|
||||
# Description is display-only. Always rebuild options from the selected Skill.
|
||||
return io.NodeOutput(create_skill_options(skill_name))
|
||||
|
||||
@@ -279,6 +279,15 @@ def discover_skills(strict=True):
|
||||
return result, errors
|
||||
|
||||
|
||||
def get_skill_summaries():
|
||||
"""Return only the metadata needed to render the Skill selector."""
|
||||
skills, _ = discover_skills(strict=True)
|
||||
return [
|
||||
{"name": skill["name"], "description": skill["description"]}
|
||||
for skill in skills
|
||||
]
|
||||
|
||||
|
||||
|
||||
def get_skill_snapshot(skill_name):
|
||||
skills, _ = discover_skills(strict=True)
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
const NODE_CLASS = "YCYY_OpenAI_Text_Skill_Options";
|
||||
let skillMap = new Map();
|
||||
|
||||
function refreshSkillNodes() {
|
||||
for (const node of app.graph?._nodes ?? []) {
|
||||
if (node.comfyClass === NODE_CLASS || node.type === NODE_CLASS) syncNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSkills(retries = 1) {
|
||||
try {
|
||||
const response = await api.fetchApi("/ycyy/openai/skills/all");
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const data = await response.json();
|
||||
skillMap = new Map(
|
||||
(Array.isArray(data) ? data : []).map(skill => [skill.name, skill])
|
||||
);
|
||||
refreshSkillNodes();
|
||||
} catch (error) {
|
||||
console.error("[YCYY] Failed to load Skill descriptions:", error);
|
||||
if (retries > 0) setTimeout(() => loadSkills(retries - 1), 2000);
|
||||
}
|
||||
}
|
||||
|
||||
function makeReadOnly(widget) {
|
||||
if (!widget) return;
|
||||
widget.options = { ...(widget.options ?? {}), readOnly: true };
|
||||
if (widget.inputEl) {
|
||||
widget.inputEl.readOnly = true;
|
||||
widget.inputEl.setAttribute("aria-readonly", "true");
|
||||
widget.inputEl.spellcheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
function applyDescription(node, skillName) {
|
||||
const descriptionWidget = node.widgets?.find(widget => widget.name === "description");
|
||||
if (!descriptionWidget) return;
|
||||
const skill = skillMap.get(skillName);
|
||||
descriptionWidget.value = typeof skill?.description === "string"
|
||||
? skill.description
|
||||
: "";
|
||||
makeReadOnly(descriptionWidget);
|
||||
app.canvas?.draw(true, true);
|
||||
}
|
||||
|
||||
function syncNode(node) {
|
||||
const skillWidget = node.widgets?.find(widget => widget.name === "skill_name");
|
||||
const descriptionWidget = node.widgets?.find(widget => widget.name === "description");
|
||||
if (!skillWidget || !descriptionWidget) return;
|
||||
|
||||
const values = skillWidget.options?.values ?? [];
|
||||
if (values.length && !values.includes(skillWidget.value)) {
|
||||
const fallback = values[0] ?? "";
|
||||
console.warn(
|
||||
`[YCYY] Skill "${skillWidget.value}" no longer exists; using "${fallback}"`
|
||||
);
|
||||
skillWidget.value = fallback;
|
||||
}
|
||||
makeReadOnly(descriptionWidget);
|
||||
applyDescription(node, skillWidget.value);
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "YCYY.OpenAI.Text.SkillOptions",
|
||||
async setup() {
|
||||
await loadSkills();
|
||||
},
|
||||
async beforeRegisterNodeDef(nodeType) {
|
||||
if (nodeType.comfyClass !== NODE_CLASS) return;
|
||||
|
||||
const originalCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const result = originalCreated?.apply(this, arguments);
|
||||
const skillWidget = this.widgets?.find(widget => widget.name === "skill_name");
|
||||
const descriptionWidget = this.widgets?.find(widget => widget.name === "description");
|
||||
if (skillWidget && descriptionWidget) {
|
||||
makeReadOnly(descriptionWidget);
|
||||
const originalCallback = skillWidget.callback;
|
||||
skillWidget.callback = value => {
|
||||
applyDescription(this, value);
|
||||
originalCallback?.call(this, value);
|
||||
};
|
||||
setTimeout(() => syncNode(this), 0);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const originalConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function () {
|
||||
const result = originalConfigure?.apply(this, arguments);
|
||||
setTimeout(() => syncNode(this), 0);
|
||||
return result;
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user