Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ python3 -m http.server 8000 --directory ui

1. 仪表盘 Dashboard — 点数余额、本月用量、共享收益、**本月点数变化(近 1 月按类型汇总收支:赠送/过期/收益/消费/充值/提现 + 净变化,取代静态"点数来源"分组)**
2. 模型市场 Marketplace — 模型浏览、搜索、厂商筛选、排序(按价格/上下文);可用性标注「多 key · 自动故障转移」(该模型配置多个上游 key,架构 v0.2 路由策略);每行「使用 / 消费」入口 → 聊天 Mock 模拟调用(按模型输出参考价扣小数点数,如 -0.38 点,产生消费交易;余额不足时阻断并提示)
3. 共享管理 Sharing — 默认只显示统计 + 我的共享列表(key 脱敏展示,可暂停/恢复/重新上架/彻底删除);点击"+ 添加 / 上架新 key"展开上架表单(须填 API Key,平台加密托管;分享者只填声明额度,单价由平台按模型定价自动计算并展示参考价),提交成功或取消后自动收起
3. 共享管理 Sharing — 默认只显示统计 + 我的共享列表(key 脱敏展示,可暂停/恢复/重新上架/彻底删除;列表展示「厂商 · Plan / 模型」,如「智谱 · GLM Coding Plan / glm-5.2」);点击"+ 添加 / 上架新 key"展开上架表单(**三级联动:厂商 → Plan → 模型**——内置国内已知 Plan 清单(阿里云百炼 / 智谱 / 火山方舟 / Kimi / MiniMax / DeepSeek,每家含「API(按量)」= 按量计价的 key),选 Plan 后显示 key 前缀 / 专属端点提示;须填 API Key,平台加密托管;分享者只填声明额度,单价由平台按模型定价自动计算并展示参考价),提交成功或取消后自动收起
4. 钱包 Wallet — 点数余额、**本月点数变化(近 1 月按类型汇总收支 + 净变化,与仪表盘一致)**、**充值入口(US-4:模拟流程——输入点数 → 余额增加 → topup 交易记录;文案注明"演示,真实支付后续接入";提现仍 disabled)**、**申请加额(US-20:企业成员余额低时申请更多点数 → 提交后等待管理员审批,与组织设置开关联动)**;收支明细已去重,统一到【交易记录】(页内提供跳转提示)
5. 交易记录 Transactions — 消费/收益/充值/提现/赠送(gift)唯一明细入口,Tab 筛选 + MRT 风格表格(列排序/列筛选/分页,与 Tab 叠加生效)
6. 设置 Settings — 账户、API Key 管理(完整增删改查:生成带名字、改名、删除需确认「删除后该 key 立即失效」、按名字搜索、一键复制完整 key——列表脱敏展示 atk_live_****xxxx,复制为完整值;file:// 下 clipboard API 受限时降级为选中+Ctrl/Cmd+C)、通知、偏好
Expand Down
10 changes: 7 additions & 3 deletions ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,14 @@ <h3>上架新 key <span class="en">List a key</span></h3>
<label>厂商
<select id="sf-provider" class="input"></select>
</label>
<label>模型
<select id="sf-model" class="input"></select>
<label>Plan
<select id="sf-plan" class="input"></select>
</label>
</div>
<span id="sf-plan-hint" class="form-hint"></span>
<label>模型
<select id="sf-model" class="input"></select>
</label>
<label>API Key
<input id="sf-key" type="password" class="input" placeholder="sk-xxx">
<span class="form-hint">key 由平台加密托管,仅用于代理调用,不会展示给其他用户</span>
Expand All @@ -158,7 +162,7 @@ <h3>我的共享列表 <span class="en">My listings</span></h3>
<p class="muted">收益已到账</p>
<div class="table-wrap compact">
<table class="table">
<thead><tr><th>模型</th><th>Key</th><th>已用/额度</th><th>单价</th><th>收益</th><th>状态</th><th></th></tr></thead>
<thead><tr><th>厂商 · Plan / 模型</th><th>Key</th><th>已用/额度</th><th>单价</th><th>收益</th><th>状态</th><th></th></tr></thead>
<tbody id="share-body"></tbody>
</table>
</div>
Expand Down
49 changes: 38 additions & 11 deletions ui/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@
}
}

// Plan 提示:按量/订阅 + key 前缀 + 专属端点说明(来自 PLANS)
function showPlanHint(planId) {
const el = $("#sf-plan-hint");
if (!el) return;
const pl = D.PLANS.find((x) => x.id === planId);
if (!pl) { el.textContent = ""; return; }
el.textContent = (pl.type === "api" ? "按量计价的 key" : "订阅 Plan") +
(pl.keyPrefix ? " · 建议 key 前缀 " + pl.keyPrefix : "") +
(pl.note ? " · " + pl.note : "");
}

/* ---------------- 导航 ---------------- */

const NAV = [
Expand Down Expand Up @@ -160,7 +171,7 @@
const on = D.SHARINGS.filter((s) => s.status === "on");
$("#dash-sharings").innerHTML = on.map((s) =>
'<div class="mini-item"><div><div class="t">' + esc(s.model) + "</div>" +
'<div class="d">已用 ' + D.fmt(s.used) + " / " + D.fmt(s.quota) + " 点 · 单价 " + D.fmt(s.price) + " 点/1M</div></div>" +
'<div class="d">' + esc(s.plan || "API") + " · 已用 " + D.fmt(s.used) + " / " + D.fmt(s.quota) + " 点 · 单价 " + D.fmt(s.price) + " 点/1M</div></div>" +
'<div class="r"><span class="pts">+' + D.fmt(s.earned) + "</span><div class='d'>累计收益</div></div></div>"
).join("") + (on.length ? "" : '<p class="muted">还没有上架的 key</p>');
renderMonthChanges();
Expand Down Expand Up @@ -217,25 +228,38 @@
stat("已用量 Used", D.fmt(totalUsed) + " 点", D.SHARINGS.reduce((a, s) => a + s.quota, 0) + " 点总额度"),
].join("");

// 表单下拉
// 表单下拉(厂商 → Plan → 模型 三级联动;Plan 中「API」= 按量计价的 key)
const selP = $("#sf-provider");
if (!selP.dataset.init) {
selP.innerHTML = '<option value="">选择厂商</option>' + D.PROVIDERS.map((p) => '<option value="' + p + '">' + p + "</option>").join("");
const planProviders = [...new Set(D.PLANS.map((pl) => pl.provider))];
selP.innerHTML = '<option value="">选择厂商</option>' + planProviders
.map((p) => '<option value="' + p + '">' + (D.PROVIDER_LABELS[p] || p) + "</option>").join("");
const selPlan = $("#sf-plan");
const selM = $("#sf-model");
const fillModels = () => {
const p = selP.value;
const plan = D.PLANS.find((pl) => pl.id === selPlan.value);
const p = plan ? plan.provider : selP.value;
selM.innerHTML = '<option value="">选择模型</option>' + D.MODELS.filter((m) => !p || m.provider === p)
.map((m) => '<option value="' + m.model + '">' + m.model + "</option>").join("");
showPriceHint(selM.value);
};
selP.addEventListener("change", fillModels);
const fillPlans = () => {
const p = selP.value;
selPlan.innerHTML = '<option value="">选择 Plan</option>' + D.PLANS.filter((pl) => pl.provider === p)
.map((pl) => '<option value="' + pl.id + '">' + esc(pl.name) + "</option>").join("");
showPlanHint("");
fillModels();
};
selP.addEventListener("change", fillPlans);
selPlan.addEventListener("change", () => { showPlanHint(selPlan.value); fillModels(); });
selM.addEventListener("change", () => showPriceHint(selM.value));
selP.dataset.init = "1";
fillModels();
fillPlans();
}

$("#share-body").innerHTML = D.SHARINGS.map((s, i) =>
"<tr><td><strong>" + esc(s.model) + "</strong></td>" +
"<tr><td><strong>" + esc(D.PROVIDER_LABELS[s.provider] || s.provider) + " · " + esc(s.plan || "API") +
"</strong><div class='muted' style='font-size:12px'>" + esc(s.model) + "</div></td>" +
"<td class='mono'>" + esc(maskKey(s.key)) + "</td>" +
"<td>" + D.fmt(s.used) + " / " + D.fmt(s.quota) + "</td>" +
"<td>" + D.fmt(s.price) + " 点/1M</td>" +
Expand Down Expand Up @@ -1048,19 +1072,22 @@
$("#share-add-btn").addEventListener("click", showShareForm);
$("#sf-cancel").addEventListener("click", hideShareForm);

// 共享上架表单(须填 API Key;单价由平台按模型定价自动计算)
// 共享上架表单(选 厂商 → Plan → 模型;单价由平台按模型定价自动计算)
$("#share-form").addEventListener("submit", (e) => {
e.preventDefault();
const model = $("#sf-model").value;
const planId = $("#sf-plan").value;
const plan = D.PLANS.find((pl) => pl.id === planId);
const quota = Number($("#sf-quota").value || 0);
const key = $("#sf-key").value.trim();
if (!key) { toast("请填写 API Key(上架 key 必须提供真实密钥)"); return; }
if (!model || quota <= 0) { toast("请选择模型并填写有效额度"); return; }
if (!plan || !model || quota <= 0) { toast("请选择厂商 / Plan / 模型并填写有效额度"); return; }
const price = autoPrice(model);
D.SHARINGS.unshift({ id: Date.now(), model, quota, used: 0, price, earned: 0, status: "on", key });
D.SHARINGS.unshift({ id: Date.now(), provider: plan.provider, plan: plan.name, model, quota, used: 0, price, earned: 0, status: "on", key });
renderSharing();
if (activeView === "dashboard") renderDashboard();
toast("已上架 " + model + "(key 已加密托管,单价 " + D.fmt(price) + " 点/1M 自动)");
const label = (D.PROVIDER_LABELS[plan.provider] || plan.provider) + " · " + plan.name;
toast("已上架「" + label + " · " + model + "」(key 已加密托管,单价 " + D.fmt(price) + " 点/1M 自动)");
e.target.reset();
const p = $("#sf-provider"); p.value = ""; p.dispatchEvent(new Event("change"));
$("#sf-quota").value = 5000;
Expand Down
38 changes: 34 additions & 4 deletions ui/js/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
{ provider: "moonshot", model: "kimi-k3", in: CNY(20.0), out: CNY(100.0), ctx: 1048576, max: null, tag: "长文" },
{ provider: "bytedance-ark", model: "doubao-seed-2.1-pro", in: CNY(6.0), out: CNY(30.0), ctx: 262144, max: null, tag: "通用" },
{ provider: "minimax", model: "minimax-m3", in: USD(0.3), out: USD(1.2), ctx: 1048576, max: null, tag: "轻量" },
{ provider: "aliyun-bailian", model: "qwen3-max", in: CNY(4.0), out: CNY(16.0), ctx: 262144, max: null, tag: "通用" },
];

// 可用性(市场展示用)
Expand All @@ -42,6 +43,34 @@

PROVIDERS: [...new Set(MODELS.map((m) => m.provider))],

// Plan 清单(内置国内已知 Plan,源自 docs/plan-api-matrix.md 与 config/config.example.toml)
// type: api=按量计价 key | coding=编码订阅 | token=统一计量订阅(Credits)
// 每个厂商至少含一个 type=api 的「API(按量)」plan;models 按 provider 关联 MODELS
PLANS: [
{ id: "aliyun-api", provider: "aliyun-bailian", name: "API(按量)", type: "api", keyPrefix: "sk-", note: "按量计价" },
{ id: "aliyun-token", provider: "aliyun-bailian", name: "Token Plan", type: "token", keyPrefix: "sk-sp-", note: "Credits 统一计量,专属 key sk-sp-,多工具通用" },
{ id: "zhipu-api", provider: "zhipu", name: "API(按量)", type: "api", keyPrefix: "sk-", note: "open.bigmodel.cn 按量" },
{ id: "zhipu-coding", provider: "zhipu", name: "GLM Coding Plan", type: "coding", keyPrefix: "sk-", note: "专属端点 /api/coding/paas/v4、/api/anthropic" },
{ id: "ark-api", provider: "bytedance-ark", name: "API(按量)", type: "api", keyPrefix: "sk-", note: "按量计价" },
{ id: "ark-coding", provider: "bytedance-ark", name: "Coding Plan", type: "coding", keyPrefix: "sk-", note: "/api/coding/v3、/api/coding" },
{ id: "ark-agent", provider: "bytedance-ark", name: "Agent Plan", type: "token", keyPrefix: "sk-", note: "/api/plan/v3、/api/plan" },
{ id: "kimi-api", provider: "moonshot", name: "API(按量)", type: "api", keyPrefix: "sk-", note: "api.kimi.com 按量" },
{ id: "kimi-code", provider: "moonshot", name: "Kimi Code 会员", type: "coding", keyPrefix: "sk-", note: "api.kimi.com/coding/v1、/coding" },
{ id: "minimax-api", provider: "minimax", name: "API(按量)", type: "api", keyPrefix: "sk-", note: "按量计价" },
{ id: "minimax-coding",provider: "minimax", name: "Coding Plan", type: "coding", keyPrefix: "sk-cp-", note: "专属 key sk-cp-,/anthropic、/v1" },
{ id: "deepseek-api", provider: "deepseek", name: "API(按量)", type: "api", keyPrefix: "sk-", note: "无订阅 plan,仅按量;OpenAI/Anthropic/Responses 端点" },
],

// 厂商显示名(上架表单 / 共享列表)
PROVIDER_LABELS: {
"aliyun-bailian": "阿里云百炼",
zhipu: "智谱",
"bytedance-ark": "火山方舟",
moonshot: "Kimi 月之暗面",
minimax: "MiniMax",
deepseek: "DeepSeek",
},

// 当前用户(公共版)
USER: { name: "阿零", email: "demo@aitokenpool.local", balance: 12471 },

Expand All @@ -68,11 +97,12 @@
],

// 我的共享(price = 按模型定价自动计算的输出单价 点数/1M;earned = 累计收益;key 仅脱敏展示)
// provider + plan:上架时选择「厂商 → Plan → 模型」,列表展示「厂商 · Plan」
SHARINGS: [
{ id: 1, model: "glm-5.2", quota: 100000, used: 32800, price: 28, earned: 1611, status: "on", key: "sk-zhipu-9f2c41ab7d22" },
{ id: 2, model: "deepseek-v4-flash", quota: 80000, used: 51200, price: 2, earned: 1116, status: "on", key: "sk-ds-3b88d077aa91" },
{ id: 3, model: "kimi-k3", quota: 50000, used: 800, price: 100, earned: 54, status: "paused", key: "sk-ms-c31f2e8b4405" },
{ id: 4, model: "minimax-m3", quota: 20000, used: 20000, price: 9, earned: 432, status: "off", key: "sk-mx-77d2a1c9e356" },
{ id: 1, provider: "zhipu", plan: "GLM Coding Plan", model: "glm-5.2", quota: 100000, used: 32800, price: 28, earned: 1611, status: "on", key: "sk-zhipu-9f2c41ab7d22" },
{ id: 2, provider: "deepseek", plan: "API(按量)", model: "deepseek-v4-flash", quota: 80000, used: 51200, price: 2, earned: 1116, status: "on", key: "sk-ds-3b88d077aa91" },
{ id: 3, provider: "moonshot", plan: "Kimi Code 会员", model: "kimi-k3", quota: 50000, used: 800, price: 100, earned: 54, status: "paused", key: "sk-ms-c31f2e8b4405" },
{ id: 4, provider: "minimax", plan: "Coding Plan", model: "minimax-m3", quota: 20000, used: 20000, price: 9, earned: 432, status: "off", key: "sk-mx-77d2a1c9e356" },
],

// 市场在售 key(公共版浏览;multi=true 表示该模型配置多个上游 key → 自动故障转移,架构 v0.2 路由策略 G5)
Expand Down
Loading