feat: publish CLMM video hot update v1.5.24
This commit is contained in:
+50
-42
@@ -2053,10 +2053,9 @@ function cvNodeBody(kind) {
|
||||
</div>
|
||||
<select class="cv-field cv-field-group" title="中转分组"></select>
|
||||
<div class="cv-model-row">
|
||||
<input class="cv-field cv-field-model" placeholder="模型(可留空,默认 video-v1)" />
|
||||
<select class="cv-field cv-field-model" title="CLMM 模型"><option value="">先拉取模型</option></select>
|
||||
<button class="cv-fetch-models" title="拉取该分组的模型">拉取</button>
|
||||
</div>
|
||||
<datalist class="cv-node-datalist"></datalist>
|
||||
<div class="cv-row2">
|
||||
<select class="cv-field cv-field-ratio" title="比例">
|
||||
<option value="16:9">16:9</option>
|
||||
@@ -2076,10 +2075,10 @@ function cvNodeBody(kind) {
|
||||
<option value="10">10s</option>
|
||||
<option value="15">15s</option>
|
||||
</select>
|
||||
<span class="cv-node-hint">接口时长仅支持 5 / 10 / 15 秒</span>
|
||||
</div>
|
||||
<div class="cv-node-hint">CLMM 文档接口:模型名、时长和参考图按上游能力校验</div>
|
||||
<details class="cv-adv"><summary>⚙️ 高级</summary>
|
||||
<input class="cv-field cv-field-endpoint" placeholder="/v1/video/generations" />
|
||||
<input class="cv-field cv-field-endpoint" placeholder="/v1/videos" />
|
||||
<select class="cv-field cv-field-refsource" title="多参考图字段">
|
||||
<option value="images">多参考图字段 images</option>
|
||||
<option value="ref_assets">全能参考字段 ref_assets</option>
|
||||
@@ -2115,10 +2114,9 @@ function cvNodeBody(kind) {
|
||||
</div>
|
||||
<select class="cv-field cv-field-group" title="中转分组"></select>
|
||||
<div class="cv-model-row">
|
||||
<input class="cv-field cv-field-model" value="grok-video-1.5" placeholder="grok-video-1.5" />
|
||||
<select class="cv-field cv-field-model" title="CLMM 模型"><option value="">先拉取模型</option></select>
|
||||
<button class="cv-fetch-models" title="拉取该分组的模型">拉取</button>
|
||||
</div>
|
||||
<datalist class="cv-node-datalist"></datalist>
|
||||
<div class="cv-row2">
|
||||
<select class="cv-field cv-field-ratio" title="视频格式">
|
||||
<option value="16:9">16:9 横屏</option>
|
||||
@@ -2144,9 +2142,9 @@ function cvNodeBody(kind) {
|
||||
<option value="15">15s</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="cv-node-hint cv-grok-hint">首帧图生视频使用 grok-video-1.5;多参考图使用 grok-imagine-video + 720p</div>
|
||||
<div class="cv-node-hint cv-grok-hint">通过 CLMM 聚合接口提交,模型请使用拉取到的完整名称</div>
|
||||
<details class="cv-adv"><summary>⚙️ 高级</summary>
|
||||
<input class="cv-field cv-field-endpoint" placeholder="/v1/videos/generations" />
|
||||
<input class="cv-field cv-field-endpoint" placeholder="/v1/videos" />
|
||||
</details>
|
||||
<button class="cv-run">运行</button>
|
||||
<div class="cv-node-err hidden"></div>
|
||||
@@ -2467,13 +2465,44 @@ function cvFillGroupSelect(sel) {
|
||||
if (want) sel.value = want;
|
||||
}
|
||||
|
||||
// 节点创建后统一初始化:唯一化 datalist + 让模型框指向它 + 填充分组下拉
|
||||
function cvSetNodeModelOptions(nodeEl, models) {
|
||||
if (!nodeEl) return;
|
||||
const model = nodeEl.querySelector(".cv-field-model");
|
||||
const list = Array.isArray(models) ? models.filter(Boolean) : [];
|
||||
if (model && model.tagName === "SELECT") {
|
||||
const current = model.value.trim();
|
||||
const options = ['<option value="">请选择模型</option>']
|
||||
.concat(list.map((item) => `<option value="${esc(item)}">${esc(item)}</option>`));
|
||||
if (current && !list.includes(current)) {
|
||||
options.splice(1, 0, `<option value="${esc(current)}">${esc(current)}(当前)</option>`);
|
||||
}
|
||||
model.innerHTML = options.join("");
|
||||
if (current) model.value = current;
|
||||
return;
|
||||
}
|
||||
const dl = nodeEl.querySelector(".cv-node-datalist");
|
||||
if (dl) dl.innerHTML = list.map((item) => `<option value="${esc(item)}"></option>`).join("");
|
||||
}
|
||||
|
||||
// 节点创建后统一初始化:视频模型迁移为真正的下拉框,其它节点继续使用 datalist。
|
||||
function cvSetupNode(id) {
|
||||
const nodeEl = document.getElementById(`node-${id}`);
|
||||
if (!nodeEl) return;
|
||||
let model = nodeEl.querySelector(".cv-field-model");
|
||||
const isVideo = !!nodeEl.querySelector(".cv-node-video, .cv-node-grokVideo");
|
||||
if (isVideo && model && model.tagName !== "SELECT") {
|
||||
const current = model.value.trim();
|
||||
const replacement = document.createElement("select");
|
||||
replacement.className = model.className;
|
||||
replacement.title = "CLMM 模型";
|
||||
replacement.innerHTML = '<option value="">请选择模型</option>' + (current
|
||||
? `<option value="${esc(current)}">${esc(current)}(当前)</option>`
|
||||
: "");
|
||||
model.replaceWith(replacement);
|
||||
model = replacement;
|
||||
}
|
||||
const dl = nodeEl.querySelector(".cv-node-datalist");
|
||||
const model = nodeEl.querySelector(".cv-field-model");
|
||||
if (dl && model) {
|
||||
if (dl && model && model.tagName !== "SELECT") {
|
||||
const listId = `cv-models-node-${id}`;
|
||||
dl.id = listId;
|
||||
model.setAttribute("list", listId);
|
||||
@@ -2483,18 +2512,15 @@ function cvSetupNode(id) {
|
||||
if (nodeEl.querySelector(".cv-field-vmode")) cvSyncVideoMode(nodeEl);
|
||||
}
|
||||
|
||||
// 分组下拉变化:清空旧模型候选;若模型框为空,用该分组默认 model 预填
|
||||
// 分组下拉变化:清空旧模型候选,不自动锁定第一个模型,保留手动选择。
|
||||
function cvOnGroupChange(nodeEl, groupId) {
|
||||
const dl = nodeEl.querySelector(".cv-node-datalist");
|
||||
if (dl) dl.innerHTML = "";
|
||||
const model = nodeEl.querySelector(".cv-field-model");
|
||||
if (model && !model.value.trim()) {
|
||||
const g = (CANVAS.groups || []).find((x) => String(x.id) === String(groupId));
|
||||
if (g && g.model) model.value = g.model;
|
||||
}
|
||||
if (model && model.tagName === "SELECT") model.innerHTML = '<option value="">请点击“拉取”选择模型</option>';
|
||||
}
|
||||
|
||||
// 拉取某节点所选分组的模型 → 填充该节点自己的 datalist
|
||||
// 拉取某节点所选分组的模型 → 填充完整模型下拉/候选列表
|
||||
async function cvFetchNodeModels(nodeId, btn) {
|
||||
const nodeEl = document.getElementById(`node-${nodeId}`);
|
||||
if (!nodeEl) return;
|
||||
@@ -2504,15 +2530,14 @@ async function cvFetchNodeModels(nodeId, btn) {
|
||||
toast("请先选择中转分组(或在『路由中转』添加分组)", "");
|
||||
return;
|
||||
}
|
||||
const dl = nodeEl.querySelector(".cv-node-datalist");
|
||||
busy(btn, true, "…");
|
||||
try {
|
||||
if (!apic.canvasModels) throw new Error("拉取模型能力尚未就绪(后端未接入)");
|
||||
const r = await apic.canvasModels(groupId);
|
||||
if (!r || !r.ok) throw new Error((r && r.error) || "拉取失败");
|
||||
const models = Array.isArray(r.models) ? r.models : [];
|
||||
if (dl) dl.innerHTML = models.map((m) => `<option value="${esc(m)}"></option>`).join("");
|
||||
toast(`已拉取 ${models.length} 个模型,点模型框可选`, "ok");
|
||||
cvSetNodeModelOptions(nodeEl, models);
|
||||
toast(`已拉取 ${models.length} 个模型,请从下拉框手动选择`, "ok");
|
||||
} catch (e) {
|
||||
toast("拉取模型失败:" + (e.message || e), "err");
|
||||
} finally {
|
||||
@@ -2646,12 +2671,6 @@ function cvSyncVideoMode(nodeEl) {
|
||||
const box = nodeEl.querySelector(".cv-vmat-" + slot);
|
||||
if (box) box.classList.toggle("hidden", !show[slot]);
|
||||
});
|
||||
if (mode === "reference" && nodeEl.querySelector(".cv-node-grokVideo")) {
|
||||
const model = nodeEl.querySelector(".cv-field-model");
|
||||
const resolution = nodeEl.querySelector(".cv-field-vsize");
|
||||
if (model) model.value = "grok-imagine-video";
|
||||
if (resolution) resolution.value = "720p";
|
||||
}
|
||||
}
|
||||
|
||||
// 读取单个节点的「输出产物」:文本节点实时读 textarea,其它读运行后缓存的 dataUrl
|
||||
@@ -2956,7 +2975,7 @@ async function cvRunNode(nodeId, btn) {
|
||||
if (endpointPath) params.endpointPath = endpointPath;
|
||||
} else if (kind === "grokVideo") {
|
||||
const mode = val(".cv-field-vmode") || "text";
|
||||
const model = val(".cv-field-model") || "grok-video-1.5";
|
||||
const model = val(".cv-field-model");
|
||||
const endpointPath = val(".cv-field-endpoint");
|
||||
const duration = val(".cv-field-duration");
|
||||
const mats = CANVAS.videoMaterials[nodeId] || {};
|
||||
@@ -2977,14 +2996,6 @@ async function cvRunNode(nodeId, btn) {
|
||||
aspectRatio: val(".cv-field-ratio") || "16:9",
|
||||
};
|
||||
if (!params.prompt) return cvSetErr(nodeEl, "请填写 Grok 视频提示词,或连接一个文本节点作为输入");
|
||||
if (
|
||||
mode === "text" &&
|
||||
/grok-(?:imagine-)?video-1\.5/i.test(model) &&
|
||||
params.resolution === "1080p" &&
|
||||
String(duration) === "15"
|
||||
) {
|
||||
return cvSetErr(nodeEl, "1.5 的 1080p/15s 需要首帧图;请切到「首帧图生视频」并提供图片,或降低文生视频规格");
|
||||
}
|
||||
if (mode === "first") {
|
||||
const first = matUrl("first") || mats.first || up.image || "";
|
||||
if (!first) return cvSetErr(nodeEl, "首帧图生视频需要首帧:连上游『文生图』/ 上传 / 手填公网URL");
|
||||
@@ -2994,8 +3005,6 @@ async function cvRunNode(nodeId, btn) {
|
||||
const refs = [up.image].concat(mats.refs || [], matUrls("refs")).filter(Boolean);
|
||||
if (!refs.length) return cvSetErr(nodeEl, "多参考图模式请连接上游图片、上传参考图,或填写公网图片URL");
|
||||
params.referenceImages = refs;
|
||||
params.model = "grok-imagine-video";
|
||||
params.resolution = "720p";
|
||||
params.aspectRatio = params.aspectRatio || "16:9";
|
||||
}
|
||||
if (duration) params.seconds = duration;
|
||||
@@ -3099,7 +3108,7 @@ $$("[data-cv-add]").forEach((b) =>
|
||||
const cvGoto = document.getElementById("cv-goto-router");
|
||||
if (cvGoto) cvGoto.addEventListener("click", () => activatePanel("router"));
|
||||
|
||||
// 工具条「🔄 拉取模型」:用当前激活分组拉取,一次性灌进所有节点的 datalist
|
||||
// 工具条「🔄 拉取模型」:用当前激活分组拉取,一次性灌进所有节点的模型选择器
|
||||
const cvModelsBtn = document.getElementById("cv-models-btn");
|
||||
if (cvModelsBtn)
|
||||
cvModelsBtn.addEventListener("click", async (ev) => {
|
||||
@@ -3110,10 +3119,9 @@ if (cvModelsBtn)
|
||||
const r = await apic.canvasModels(CANVAS.activeGroupId || undefined);
|
||||
if (!r || !r.ok) throw new Error((r && r.error) || "拉取失败");
|
||||
const models = Array.isArray(r.models) ? r.models : [];
|
||||
const opts = models.map((m) => `<option value="${esc(m)}"></option>`).join("");
|
||||
const dls = document.querySelectorAll("#drawflow .cv-node-datalist");
|
||||
dls.forEach((dl) => { dl.innerHTML = opts; });
|
||||
toast(`已按当前分组拉取 ${models.length} 个模型,填充 ${dls.length} 个节点`, "ok");
|
||||
const nodes = document.querySelectorAll("#drawflow .drawflow-node");
|
||||
nodes.forEach((node) => cvSetNodeModelOptions(node, models));
|
||||
toast(`已按当前分组拉取 ${models.length} 个模型,可在各节点手动切换`, "ok");
|
||||
} catch (e) {
|
||||
toast("拉取模型失败:" + (e.message || e), "err");
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user