Files
tripocli/tripo/web/accounts.html
T
Mrpzx 977f97995a tripocli v2.0.0:Tripo AI 3D 生成与加工 CLI(面向 Agent 与开发者,纯 Python 标准库、零第三方依赖)
覆盖文生 3D、图生 3D、多视图、批量生成,以及重拓扑、绑定、动画、智能分割、
纹理、PBR、智能 UV、部件补全、格式导出与下载。

代码组织:一个功能一个模块(tripo/features/),入口薄壳 tripocli.py。
2026-10-13 19:22:54 +08:00

179 lines
8.0 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[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>