覆盖文生 3D、图生 3D、多视图、批量生成,以及重拓扑、绑定、动画、智能分割、 纹理、PBR、智能 UV、部件补全、格式导出与下载。 代码组织:一个功能一个模块(tripo/features/),入口薄壳 tripocli.py。
179 lines
8.0 KiB
HTML
179 lines
8.0 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>tripocli · 账号管理</title>
|
||
<style>
|
||
:root { color-scheme: dark; --bg:#0f1115; --panel:#171a21; --line:#262b35; --fg:#e6e8ee;
|
||
--muted:#8b93a7; --accent:#4f8cff; --ok:#3ddc84; --warn:#ffb020; --err:#ff5c5c; }
|
||
* { box-sizing: border-box; }
|
||
body { margin:0; background:var(--bg); color:var(--fg); font:14px/1.55 -apple-system,
|
||
"PingFang SC", "Microsoft YaHei", system-ui, sans-serif; }
|
||
header { padding:22px 26px 14px; border-bottom:1px solid var(--line); }
|
||
h1 { margin:0 0 4px; font-size:19px; }
|
||
header p { margin:0; color:var(--muted); font-size:13px; }
|
||
main { padding:22px 26px 60px; max-width:1080px; }
|
||
.card { background:var(--panel); border:1px solid var(--line); border-radius:10px; padding:18px 20px; margin-bottom:20px; }
|
||
.card h2 { margin:0 0 14px; font-size:15px; font-weight:600; }
|
||
table { width:100%; border-collapse:collapse; }
|
||
th, td { text-align:left; padding:10px 8px; border-bottom:1px solid var(--line); vertical-align:middle; }
|
||
th { color:var(--muted); font-weight:500; font-size:12.5px; }
|
||
tr:last-child td { border-bottom:none; }
|
||
.name { font-weight:600; }
|
||
.badge { display:inline-block; padding:1px 8px; border-radius:20px; font-size:11.5px;
|
||
border:1px solid var(--line); color:var(--muted); }
|
||
.badge.active { color:#0b1220; background:var(--ok); border-color:var(--ok); font-weight:600; }
|
||
.ok { color:var(--ok); } .warn { color:var(--warn); } .err { color:var(--err); } .muted { color:var(--muted); }
|
||
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:12.5px; }
|
||
button { font:inherit; padding:6px 12px; border-radius:7px; border:1px solid var(--line);
|
||
background:#1e2330; color:var(--fg); cursor:pointer; }
|
||
button:hover { border-color:var(--accent); }
|
||
button.primary { background:var(--accent); border-color:var(--accent); color:#fff; font-weight:600; }
|
||
button.danger:hover { border-color:var(--err); color:var(--err); }
|
||
input, textarea { width:100%; background:#12151c; color:var(--fg); border:1px solid var(--line);
|
||
border-radius:8px; padding:9px 11px; font:inherit; }
|
||
textarea { min-height:88px; resize:vertical; font-family: ui-monospace, Menlo, monospace; font-size:12.5px; }
|
||
.row { display:flex; gap:12px; align-items:flex-end; }
|
||
.row > div { flex:1; }
|
||
label { display:block; margin:0 0 6px; color:var(--muted); font-size:12.5px; }
|
||
.msg { margin-top:12px; font-size:13px; min-height:18px; }
|
||
.hint { color:var(--muted); font-size:12.5px; margin:10px 0 0; }
|
||
.actions { display:flex; gap:8px; flex-wrap:wrap; }
|
||
code { background:#12151c; padding:1px 5px; border-radius:5px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<h1>tripocli · 账号管理</h1>
|
||
<p>粘贴 cookie 添加账号;CLI 会用 cookie 自动续期 JWT,无需浏览器常驻。</p>
|
||
</header>
|
||
<main>
|
||
<div class="card">
|
||
<h2>添加 / 更新账号</h2>
|
||
<div class="row">
|
||
<div style="max-width:220px">
|
||
<label for="name">账号名(留空自动取邮箱)</label>
|
||
<input id="name" placeholder="work">
|
||
</div>
|
||
<div>
|
||
<label for="cookie">Cookie(整条,或 ory_kratos_session 的值)</label>
|
||
<textarea id="cookie" placeholder="ory_kratos_session=...; csrf_token_...=..."></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="actions" style="margin-top:12px">
|
||
<button class="primary" onclick="addAccount()">保存并校验</button>
|
||
<button onclick="load()">刷新列表</button>
|
||
</div>
|
||
<p class="hint">获取 cookie:DevTools → Network → 任意 <code>api.tripo3d.ai</code> 请求 → Request Headers → 复制整条 <code>Cookie</code>。</p>
|
||
<div id="addMsg" class="msg"></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>账号列表</h2>
|
||
<table>
|
||
<thead>
|
||
<tr><th>账号</th><th>身份</th><th>会话有效期</th><th>积分</th><th>会员</th><th>操作</th></tr>
|
||
</thead>
|
||
<tbody id="rows"><tr><td colspan="6" class="muted">加载中…</td></tr></tbody>
|
||
</table>
|
||
<div id="listMsg" class="msg"></div>
|
||
</div>
|
||
</main>
|
||
|
||
<script>
|
||
const $ = (id) => document.getElementById(id);
|
||
const esc = (s) => (s == null ? "" : String(s).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c])));
|
||
|
||
async function api(path, options) {
|
||
const res = await fetch(path, options);
|
||
let body = null;
|
||
try { body = await res.json(); } catch (e) { body = { error: res.statusText }; }
|
||
if (!res.ok) throw new Error(body.error || res.statusText);
|
||
return body;
|
||
}
|
||
|
||
function expiryCell(iso) {
|
||
if (!iso) return '<span class="muted">未知</span>';
|
||
const t = Date.parse(iso);
|
||
if (isNaN(t)) return `<span class="mono">${esc(iso)}</span>`;
|
||
const days = (t - Date.now()) / 86400000;
|
||
const cls = days < 0 ? "err" : days < 5 ? "warn" : "ok";
|
||
const d = new Date(t).toLocaleString();
|
||
const left = days < 0 ? "已过期" : `剩 ${Math.floor(days)} 天`;
|
||
return `<span class="${cls}">${esc(d)}</span><br><span class="muted">${left}</span>`;
|
||
}
|
||
|
||
async function load() {
|
||
const rows = $("rows");
|
||
try {
|
||
const data = await api("/api/accounts");
|
||
if (!data.accounts.length) {
|
||
rows.innerHTML = '<tr><td colspan="6" class="muted">暂无账号,粘贴 cookie 添加。</td></tr>';
|
||
return;
|
||
}
|
||
rows.innerHTML = data.accounts.map(a => `
|
||
<tr>
|
||
<td><span class="name">${esc(a.name)}</span> ${a.active ? '<span class="badge active">当前</span>' : ''}
|
||
${a.error ? `<div class="err">${esc(a.error)}</div>` : ''}</td>
|
||
<td>${esc(a.email || a.user_id || "-")}</td>
|
||
<td>${expiryCell(a.session_expires_at)}</td>
|
||
<td>${a.total_credit == null ? '<span class="muted">-</span>' : a.total_credit}
|
||
${a.expiring_credit ? `<div class="muted">将过期 ${a.expiring_credit} (${esc(a.expiring_date||"-")})</div>` : ''}</td>
|
||
<td>${esc(a.member_type || "-")}${a.member_valid_until ? `<div class="muted">至 ${esc(a.member_valid_until)}</div>` : ''}</td>
|
||
<td><div class="actions">
|
||
${a.active ? '' : `<button onclick="activate('${esc(a.name)}')">设为当前</button>`}
|
||
<button onclick="refresh('${esc(a.name)}')">重新校验</button>
|
||
<button class="danger" onclick="remove('${esc(a.name)}')">删除</button>
|
||
</div></td>
|
||
</tr>`).join("");
|
||
} catch (e) {
|
||
rows.innerHTML = `<tr><td colspan="6" class="err">${esc(e.message)}</td></tr>`;
|
||
}
|
||
}
|
||
|
||
async function addAccount() {
|
||
const msg = $("addMsg");
|
||
msg.textContent = "校验中…"; msg.className = "msg muted";
|
||
try {
|
||
const data = await api("/api/accounts", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ name: $("name").value.trim(), cookie: $("cookie").value.trim() })
|
||
});
|
||
msg.className = "msg ok";
|
||
msg.textContent = `✅ 已保存 ${data.name}${data.email ? " (" + data.email + ")" : ""}`;
|
||
$("cookie").value = "";
|
||
load();
|
||
} catch (e) {
|
||
msg.className = "msg err";
|
||
msg.textContent = "❌ " + e.message;
|
||
}
|
||
}
|
||
|
||
async function activate(name) {
|
||
try { await api(`/api/accounts/${encodeURIComponent(name)}/activate`, { method: "POST" }); load(); }
|
||
catch (e) { $("listMsg").className = "msg err"; $("listMsg").textContent = "❌ " + e.message; }
|
||
}
|
||
|
||
async function refresh(name) {
|
||
$("listMsg").className = "msg muted"; $("listMsg").textContent = `正在重新校验 ${name}…`;
|
||
try {
|
||
const d = await api(`/api/accounts/${encodeURIComponent(name)}/refresh`, { method: "POST" });
|
||
$("listMsg").className = "msg ok";
|
||
$("listMsg").textContent = `✅ ${name} 校验通过,JWT 到期 ${d.token_expires_at || "未知"}`;
|
||
load();
|
||
} catch (e) { $("listMsg").className = "msg err"; $("listMsg").textContent = "❌ " + e.message; }
|
||
}
|
||
|
||
async function remove(name) {
|
||
if (!confirm(`确定删除账号 ${name}?`)) return;
|
||
try { await api(`/api/accounts/${encodeURIComponent(name)}`, { method: "DELETE" }); load(); }
|
||
catch (e) { $("listMsg").className = "msg err"; $("listMsg").textContent = "❌ " + e.message; }
|
||
}
|
||
|
||
load();
|
||
</script>
|
||
</body>
|
||
</html>
|