feat: complete image console and add verified REST and MCP workflows
This commit is contained in:
+212
@@ -0,0 +1,212 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>模型 · ai-image</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<script src="api.js"></script>
|
||||
<script src="components.js"></script>
|
||||
</head>
|
||||
<body data-page="models">
|
||||
|
||||
<aside class="app-nav">
|
||||
<div class="app-brand">
|
||||
<span class="brand-mark" aria-hidden="true"></span>
|
||||
<span class="brand-text"><b>ai-image</b><small>绘图聚合控制台</small></span>
|
||||
</div>
|
||||
<nav class="nav-list">
|
||||
<p class="nav-group">工作台</p>
|
||||
<a class="nav-item" data-nav="create" href="create.html"><span class="nav-ico"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 3-1.9 5.8L4 10.7l6.1 1.9L12 18.4l1.9-5.8 6.1-1.9-6.1-1.9Z"/><path d="M5 3v4"/><path d="M19 17v4"/><path d="M3 5h4"/><path d="M17 19h4"/></svg></span>出图</a>
|
||||
<a class="nav-item" data-nav="gallery" href="index.html"><span class="nav-ico"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg></span>画廊</a>
|
||||
<a class="nav-item" data-nav="records" href="records.html"><span class="nav-ico"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/></svg></span>生成记录</a>
|
||||
<p class="nav-group">配置</p>
|
||||
<a class="nav-item" data-nav="providers" href="providers.html"><span class="nav-ico"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/><path d="M18 8v3a6 6 0 0 1-12 0V8Z"/></svg></span>供应商</a>
|
||||
<a class="nav-item" data-nav="models" href="models.html"><span class="nav-ico"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"/><path d="m7 16.5-4.74-2.85"/><path d="m7 16.5 5-3"/><path d="M7 16.5v5.17"/><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"/><path d="m17 16.5-5-3"/><path d="m17 16.5 4.74-2.85"/><path d="M17 16.5v5.17"/><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"/><path d="M12 8 7.26 5.15"/><path d="m12 8 4.74-2.85"/><path d="M12 13.5V8"/></svg></span>模型</a>
|
||||
<p class="nav-group">Agent</p>
|
||||
<a class="nav-item" data-nav="skills" href="skills.html"><span class="nav-ico"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg></span>SKILL.md</a>
|
||||
</nav>
|
||||
<div class="nav-foot">
|
||||
<span class="dot dot-ok"></span>服务在线 <span class="mono">:8765</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="app-content">
|
||||
<header class="top-bar">
|
||||
<div class="crumb">
|
||||
<span class="crumb-sec">配置</span>
|
||||
<span class="crumb-sep">/</span>
|
||||
<h1>模型</h1>
|
||||
</div>
|
||||
<div class="top-actions"><span class="tag tag-info">仅启用模型对 Agent 可见</span><a class="btn btn-secondary btn-sm" href="providers.html">供应商</a></div>
|
||||
</header>
|
||||
<section class="page-body">
|
||||
<div class="toolbar">
|
||||
<div class="chip-group" id="providerChips">
|
||||
<button class="chip active" data-provider="all">全部供应商</button>
|
||||
</div>
|
||||
<div class="input-group" style="max-width:280px;flex:1">
|
||||
<input type="search" id="searchInput" placeholder="搜索模型 ID / 展示名" />
|
||||
</div>
|
||||
<span class="spacer"></span>
|
||||
<span class="dim mono" id="modelCount">—</span>
|
||||
<button class="btn btn-secondary btn-sm" id="enableAllBtn">批量启用</button>
|
||||
<button class="btn btn-secondary btn-sm" id="disableAllBtn">批量停用</button>
|
||||
<button class="btn btn-secondary btn-sm" id="refreshBtn"><span class="ico" data-icon="refresh"></span>刷新</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-4" style="margin-bottom:16px">
|
||||
<div class="card stat">
|
||||
<div class="k">模型总数</div>
|
||||
<div class="v" id="statTotal">—</div>
|
||||
<div class="sub">所有供应商</div>
|
||||
</div>
|
||||
<div class="card stat">
|
||||
<div class="k">已启用</div>
|
||||
<div class="v" id="statEnabled">—</div>
|
||||
<div class="sub">对 Agent 可见</div>
|
||||
</div>
|
||||
<div class="card stat">
|
||||
<div class="k">支持图生图</div>
|
||||
<div class="v" id="statImageInput">—</div>
|
||||
<div class="sub">可接收参考图</div>
|
||||
</div>
|
||||
<div class="card stat">
|
||||
<div class="k">最近发现</div>
|
||||
<div class="v" id="statLatest" style="font-size:18px">—</div>
|
||||
<div class="sub">上游模型同步时间</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<div>
|
||||
<h3>模型清单</h3>
|
||||
<div class="sub">仅启用的模型会出现在 REST / MCP 的可用列表中</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body tight">
|
||||
<div class="table-wrap">
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>模型 ID</th>
|
||||
<th>展示名</th>
|
||||
<th>供应商</th>
|
||||
<th>尺寸能力</th>
|
||||
<th>图生图</th>
|
||||
<th>发现时间</th>
|
||||
<th style="width:120px">启用</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="modelRows"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="empty hidden" id="modelEmpty">
|
||||
<span class="ico" data-icon="boxes"></span>
|
||||
<h4>没有匹配的模型</h4>
|
||||
<p>先到供应商页拉取上游模型,或放宽筛选条件。</p>
|
||||
<a class="btn btn-secondary btn-sm" href="providers.html">去拉取模型</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const $ = (s) => document.querySelector(s);
|
||||
let models = [];
|
||||
let providers = [];
|
||||
let visible = [];
|
||||
|
||||
|
||||
function providerName(id) {
|
||||
const p = providers.find((x) => x.id === id);
|
||||
return p ? p.name : id;
|
||||
}
|
||||
|
||||
function applyFilter() {
|
||||
const pid = $("#providerChips .chip.active").dataset.provider;
|
||||
const q = $("#searchInput").value.trim().toLowerCase();
|
||||
visible = models.filter((m) => {
|
||||
if (pid !== "all" && m.provider_id !== pid) return false;
|
||||
if (q && !((m.model_id + m.display_name + providerName(m.provider_id)).toLowerCase().includes(q))) return false;
|
||||
return true;
|
||||
});
|
||||
render();
|
||||
}
|
||||
|
||||
function render() {
|
||||
const tb = $("#modelRows");
|
||||
tb.innerHTML = "";
|
||||
$("#modelEmpty").classList.toggle("hidden", visible.length !== 0);
|
||||
visible.forEach((m) => {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML =
|
||||
'<td class="mono">' + esc(m.model_id) + (m.note ? '<div class="dim" style="font-size:11px">' + esc(m.note) + "</div>" : "") + "</td>" +
|
||||
"<td>" + esc(m.display_name) + "</td>" +
|
||||
'<td><span class="tag">' + esc(providerName(m.provider_id)) + "</span></td>" +
|
||||
'<td class="mono dim">' + esc(m.size_hint) + "</td>" +
|
||||
"<td>" + (m.supports_image_input ? '<span class="tag tag-success">支持</span>' : '<span class="tag">不支持</span>') + "</td>" +
|
||||
'<td class="dim mono">' + esc(m.discovered_at) + "</td>" +
|
||||
'<td><label class="switch"><input type="checkbox" ' + (m.enabled ? "checked" : "") + ' data-toggle="' + esc(m.id) + '" /><span class="track"></span></label></td>';
|
||||
tb.appendChild(tr);
|
||||
});
|
||||
$("#modelCount").textContent = visible.length + " / " + models.length + " 个模型";
|
||||
$("#statTotal").textContent = models.length;
|
||||
$("#statEnabled").textContent = models.filter((m) => m.enabled).length;
|
||||
$("#statImageInput").textContent = models.filter((m) => m.supports_image_input).length;
|
||||
const dates = models.map((m) => m.discovered_at).filter((d) => d && d !== "—").sort();
|
||||
$("#statLatest").textContent = dates.length ? dates[dates.length - 1] : "—";
|
||||
}
|
||||
|
||||
$("#providerChips").addEventListener("click", (e) => {
|
||||
const c = e.target.closest(".chip");
|
||||
if (!c) return;
|
||||
$("#providerChips .chip.active").classList.remove("active");
|
||||
c.classList.add("active");
|
||||
applyFilter();
|
||||
});
|
||||
$("#searchInput").addEventListener("input", applyFilter);
|
||||
|
||||
$("#modelRows").addEventListener("change", (e) => runTask(async () => {
|
||||
const t = e.target.closest("input[data-toggle]");
|
||||
if (!t) return;
|
||||
try {
|
||||
models = await window.API.toggleModel(t.dataset.toggle, t.checked);
|
||||
toast(t.checked ? "模型已启用" : "模型已停用", "info");
|
||||
} finally {
|
||||
applyFilter();
|
||||
}
|
||||
}));
|
||||
|
||||
async function bulk(enabled) {
|
||||
const ids = visible.filter((m) => m.enabled !== enabled).map((m) => m.id);
|
||||
if (!ids.length) { toast("没有需要变更的模型", "info"); return; }
|
||||
models = await window.API.setModelsEnabled(ids, enabled);
|
||||
toast(enabled ? "已启用 " + ids.length + " 个模型" : "已停用 " + ids.length + " 个模型");
|
||||
applyFilter();
|
||||
}
|
||||
$("#enableAllBtn").addEventListener("click", (event) => runTask(() => bulk(true), event.currentTarget));
|
||||
$("#disableAllBtn").addEventListener("click", (event) => runTask(() => bulk(false), event.currentTarget));
|
||||
$("#refreshBtn").addEventListener("click", (event) => runTask(async () => {
|
||||
await load();
|
||||
toast("已刷新");
|
||||
}, event.currentTarget));
|
||||
|
||||
async function load() {
|
||||
providers = await window.API.listProviders();
|
||||
models = await window.API.listModels();
|
||||
$("#providerChips").innerHTML = '<button class="chip active" data-provider="all">全部供应商</button>' +
|
||||
providers.map((p) => '<button class="chip" data-provider="' + esc(p.id) + '">' + esc(p.name) + "</button>").join("");
|
||||
applyFilter();
|
||||
}
|
||||
|
||||
paintIcons();
|
||||
runTask(load);
|
||||
})();
|
||||
</script>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user