tripocli v2.0.0:Tripo AI 3D 生成与加工 CLI(面向 Agent 与开发者,纯 Python 标准库、零第三方依赖)

覆盖文生 3D、图生 3D、多视图、批量生成,以及重拓扑、绑定、动画、智能分割、
纹理、PBR、智能 UV、部件补全、格式导出与下载。

代码组织:一个功能一个模块(tripo/features/),入口薄壳 tripocli.py。
This commit is contained in:
Mrpzx committed 2026-10-13 19:22:54 +08:00
commit 977f97995a
60 files changed
+5622

No files matched your search

+178
View File
@@ -0,0 +1,178 @@
<!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>