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
5 changes: 3 additions & 2 deletions docs/user-stories.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

> **本文档以中文撰写**(用户故事主体、验收标准、流程说明均为中文;关键术语保留中英对照)。
>
> **v1.10(2026-08-17)· 上架表单"可用时间段"结构化.** 上架 key 的可用时间段改为**正式字段**(星期多选 + 起止时间,可多选;留空 = 全天不限),后端可解析生效;**备注只作纯文本**,不承载任何逻辑。共享列表展示可用时间段(如「周一~周五 09:00-18:00」),未设置显示「全天」。更新 US-8 / US-9 AC。
> **v1.9(2026-08-17)· 上架流程改为「厂商 → Plan → 模型」.** 分享者上架时选择的是**厂商的 Plan**(而不是厂商/模型):大多数分享的是自己的 Plan 订阅(如阿里 Token Plan、Kimi Code),同一厂商不同 Plan 的 Base URL 可能不同。上架表单改为三级联动「选厂商 → 选 Plan → 选该 Plan 支持的模型」,Plan 下拉中「API(按量)」代表按量计价的 key,其余为订阅 Plan;内置国内已知 Plan 清单(阿里云百炼 / 智谱 / 火山方舟 / Kimi 月之暗面 / MiniMax / DeepSeek,每家至少一个「API(按量)」项)。共享列表展示「厂商 · Plan / 模型」。更新 US-8 / US-9 / 核心机制表「上架 Listing」行。
> **v1.8(2026-08-17)· 移除面向用户文案中的防薅表述,文档措辞中性化.** 「防薅羊毛」是产品内部设计动机,不应出现在面向用户的界面文案中(UI 侧已同步移除)。本文档中的设计备注措辞中性化:「防薅动机」改为「设计意图」(如每日赠送机制的设计意图是**鼓励每日活跃**,同时降低集中注册多号的收益)。面向用户可见的赠送规则保持「每日赠送 1 点(当日有效)· 连续 10 天」。更新核心机制表「赠送点数」行措辞。
> **v1.7(2026-08-15)· 平台收益抽成 10%.** 平台从分享者收益中抽取 **10%**:消费者支付 N 点 → 分享者获得 **N × 90%**(按小数点数规则,保留 2 位小数)、**N × 10% 归平台**。示例:A 消费 B 的 key 10 点 → A 扣 10 点、B 收益 9 点、平台分 1 点。核心机制表新增「平台抽成 Platform fee」;分享者收益相关 AC 更新(收益 = 消费点数 × 90%);新增边界场景 **E-16**(小数抽成精度,如消费 3 点 → 收益 2.7 点);平台运营者角色补充:平台分成是运营收入来源。
Expand Down Expand Up @@ -145,9 +146,9 @@ flowchart LR
#### 用户故事(User Stories)

- **US-8** 作为分享者,我希望选择「厂商 → Plan → 模型」就能上架一个闲置 key(大多数分享的是自己的 Plan 订阅),以便以最小成本开始赚点数。
- AC:上架表单为三级联动「选厂商 → 选 Plan → 选该 Plan 支持的模型」,Plan 下拉中「API(按量)」代表按量计价的 key,其余为订阅 Plan;内置国内已知 Plan 清单(阿里云百炼 / 智谱 / 火山方舟 / Kimi 月之暗面 / MiniMax / DeepSeek,每家至少一个「API(按量)」项);选择 Plan 后展示对应提示(key 前缀 / 专属端点);必填 API key(密码输入)、声明配额;参考价按模型价格表自动计算(模型无定价数据时按「默认价」兜底,不报错);平台加密存储 key;key 绝不对他人明文展示。
- AC:上架表单为三级联动「选厂商 → 选 Plan → 选该 Plan 支持的模型」,Plan 下拉中「API(按量)」代表按量计价的 key,其余为订阅 Plan;内置国内已知 Plan 清单(阿里云百炼 / 智谱 / 火山方舟 / Kimi 月之暗面 / MiniMax / DeepSeek,每家至少一个「API(按量)」项);选择 Plan 后展示对应提示(key 前缀 / 专属端点);**可用时间段为结构化可选字段(星期多选 + 起止时间,留空 = 全天不限),后端可解析生效;备注为纯文本,不解析**;必填 API key(密码输入)、声明配额;参考价按模型价格表自动计算(模型无定价数据时按「默认价」兜底,不报错);平台加密存储 key;key 绝不对他人明文展示。
- **US-9** 作为分享者,我希望看到我的上架列表(脱敏 key、配额用量、收益),以便监控表现。
- AC:分享页显示统计 + 我的上架列表(key 脱敏如 `sk-****1234`);每行显示「厂商 · Plan / 模型」(如「智谱 · GLM Coding Plan / glm-5.2」)、已用 / 配额、单价、累计收益、状态。
- AC:分享页显示统计 + 我的上架列表(key 脱敏如 `sk-****1234`);每行显示「厂商 · Plan / 模型」(如「智谱 · GLM Coding Plan / glm-5.2」)、**可用时间段(设置了显示如「周一~周五 09:00-18:00」,未设置显示「全天」)**、已用 / 配额、单价、累计收益、状态。
- **US-10** 作为分享者,我希望暂停 / 恢复 / 重新上架 / 删除上架,以便控制我的 key 何时被消费。
- AC:暂停 = 停止接单,可恢复;删除 = 从平台永久移除,不可逆,需确认;重新上架可激活已暂停 / 已下架的上架。
- **US-11** 作为分享者,我希望知道我的 key 何时被消费,以便看到收益累积。
Expand Down
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 脱敏展示,可暂停/恢复/重新上架/彻底删除;列表展示「厂商 · Plan / 模型」,如「智谱 · GLM Coding Plan / glm-5.2」);点击"+ 添加 / 上架新 key"展开上架表单(**三级联动:厂商 → Plan → 模型**——内置国内已知 Plan 清单(阿里云百炼 / 智谱 / 火山方舟 / Kimi / MiniMax / DeepSeek,每家含「API(按量)」= 按量计价的 key),选 Plan 后显示 key 前缀 / 专属端点提示;须填 API Key,平台加密托管;分享者只填声明额度,单价由平台按模型定价自动计算并展示参考价),提交成功或取消后自动收起
3. 共享管理 Sharing — 默认只显示统计 + 我的共享列表(key 脱敏展示,可暂停/恢复/重新上架/彻底删除;列表展示「厂商 · Plan / 模型 · 可用时间段」,如「智谱 · GLM Coding Plan / glm-5.2 · 周一~周五 09:00-18:00」,未设置显示「全天」);点击"+ 添加 / 上架新 key"展开上架表单(**三级联动:厂商 → Plan → 模型**——内置国内已知 Plan 清单(阿里云百炼 / 智谱 / 火山方舟 / Kimi / MiniMax / DeepSeek,每家含「API(按量)」= 按量计价的 key),选 Plan 后显示 key 前缀 / 专属端点提示;**可用时间段为结构化字段**:星期多选 chips + 起止时间(留空 = 全天不限),备注仅纯文本;须填 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: 10 additions & 0 deletions ui/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,16 @@ select.input option { background: var(--bg-card); }
.form label.check { flex-direction: row; align-items: center; gap: 8px; }
.form label.check input { accent-color: var(--accent); }

/* 可用时间段:星期多选 chips */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
display: inline-flex; align-items: center; gap: 4px;
padding: 3px 10px; border: 1px solid var(--border); border-radius: 999px;
background: var(--bg-soft); color: var(--text-dim); font-size: 12px; cursor: pointer;
}
.chip input { accent-color: var(--accent); }
.chip:has(input:checked) { border-color: var(--accent); color: var(--accent-text); background: var(--accent-soft); }

.mini-list { display: flex; flex-direction: column; gap: 8px; }
.mini-item {
display: flex; justify-content: space-between; align-items: center;
Expand Down
25 changes: 23 additions & 2 deletions ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,29 @@ <h3>上架新 key <span class="en">List a key</span></h3>
<span id="sf-price-view" class="price-view">选择模型后自动计算</span>
</label>
</div>
<label>备注(可选)
<input id="sf-note" class="input" placeholder="如:工作日 9:00-18:00 可用">
<label>可用时间段(可选,留空 = 全天不限)
<span class="chips" id="sf-days">
<label class="chip"><input type="checkbox" value="1">周一</label>
<label class="chip"><input type="checkbox" value="2">周二</label>
<label class="chip"><input type="checkbox" value="3">周三</label>
<label class="chip"><input type="checkbox" value="4">周四</label>
<label class="chip"><input type="checkbox" value="5">周五</label>
<label class="chip"><input type="checkbox" value="6">周六</label>
<label class="chip"><input type="checkbox" value="7">周日</label>
<label class="chip" id="sf-days-all"><input type="checkbox">每天</label>
</span>
<span class="form-hint">可多选星期并设置起止时间;不选任何星期 = 全天不限</span>
</label>
<div class="form-row">
<label>开始时间
<input id="sf-start" type="time" class="input">
</label>
<label>结束时间
<input id="sf-end" type="time" class="input">
</label>
</div>
<label>备注(可选,纯文本说明)
<input id="sf-note" class="input" placeholder="可填写任意说明,不影响生效规则">
</label>
<div class="form-row">
<button type="submit" class="btn btn-primary">上架 <span class="en">(List)</span></button>
Expand Down
46 changes: 44 additions & 2 deletions ui/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,33 @@
).join("") : '<tr><td colspan="7" class="muted">没有匹配的模型</td></tr>';
}

/* --- 可用时间段(rant 10:54:48:结构化字段,备注只作纯备注) --- */

const DAY_LABELS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];

// 星期数字 → 展示文本:连续区间压缩为「周一~周五」,间断用 / 连接
function fmtDays(nums) {
const sorted = [...nums].sort((a, b) => a - b);
const parts = [];
let i = 0;
while (i < sorted.length) {
let j = i;
while (j + 1 < sorted.length && sorted[j + 1] === sorted[j] + 1) j++;
parts.push(sorted[i] === sorted[j]
? DAY_LABELS[sorted[i] - 1]
: DAY_LABELS[sorted[i] - 1] + "~" + DAY_LABELS[sorted[j] - 1]);
i = j + 1;
}
return parts.join("/");
}

function fmtAvailable(s) {
const a = s && s.available;
if (!a || !a.days || !a.days.length) return "全天";
const t = a.start && a.end ? " " + a.start + "-" + a.end : "";
return fmtDays(a.days) + t;
}

/* --- 共享管理 --- */

const SHARE_STATUS = {
Expand Down Expand Up @@ -259,7 +286,7 @@

$("#share-body").innerHTML = D.SHARINGS.map((s, i) =>
"<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>" +
"</strong><div class='muted' style='font-size:12px'>" + esc(s.model) + " · " + esc(fmtAvailable(s)) + "</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 @@ -1072,6 +1099,14 @@
$("#share-add-btn").addEventListener("click", showShareForm);
$("#sf-cancel").addEventListener("click", hideShareForm);

// 「每天」快捷选项:勾选 = 全选周一~周日(并禁用单日),取消 = 全清
const allCb = document.querySelector("#sf-days-all input");
if (allCb) allCb.addEventListener("change", () => {
$$("#sf-days .chip input").forEach((cb) => {
if (cb !== allCb) { cb.checked = allCb.checked; cb.disabled = allCb.checked; }
});
});

// 共享上架表单(选 厂商 → Plan → 模型;单价由平台按模型定价自动计算)
$("#share-form").addEventListener("submit", (e) => {
e.preventDefault();
Expand All @@ -1080,10 +1115,17 @@
const plan = D.PLANS.find((pl) => pl.id === planId);
const quota = Number($("#sf-quota").value || 0);
const key = $("#sf-key").value.trim();
const note = $("#sf-note").value.trim();
if (!key) { toast("请填写 API Key(上架 key 必须提供真实密钥)"); return; }
if (!plan || !model || quota <= 0) { toast("请选择厂商 / Plan / 模型并填写有效额度"); return; }
// 可用时间段:星期多选 + 起止时间;不选任何星期 = null(全天不限)
const days = $$("#sf-days .chip input:not(#sf-days-all input)")
.filter((cb) => cb.checked).map((cb) => Number(cb.value)).sort((a, b) => a - b);
const start = $("#sf-start").value;
const end = $("#sf-end").value;
const available = days.length ? { days, start: start || "", end: end || "" } : null;
const price = autoPrice(model);
D.SHARINGS.unshift({ id: Date.now(), provider: plan.provider, plan: plan.name, 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, note, available });
renderSharing();
if (activeView === "dashboard") renderDashboard();
const label = (D.PROVIDER_LABELS[plan.provider] || plan.provider) + " · " + plan.name;
Expand Down
9 changes: 5 additions & 4 deletions ui/js/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,12 @@

// 我的共享(price = 按模型定价自动计算的输出单价 点数/1M;earned = 累计收益;key 仅脱敏展示)
// provider + plan:上架时选择「厂商 → Plan → 模型」,列表展示「厂商 · Plan」
// available:结构化可用时间段(days 星期 1-7 + start/end 起止;null = 全天不限)
SHARINGS: [
{ 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" },
{ 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", available: { days: [1, 2, 3, 4, 5], start: "09:00", end: "18:00" } },
{ id: 2, provider: "deepseek", plan: "API(按量)", model: "deepseek-v4-flash", quota: 80000, used: 51200, price: 2, earned: 1116, status: "on", key: "sk-ds-3b88d077aa91", available: null },
{ id: 3, provider: "moonshot", plan: "Kimi Code 会员", model: "kimi-k3", quota: 50000, used: 800, price: 100, earned: 54, status: "paused", key: "sk-ms-c31f2e8b4405", available: { days: [6, 7], start: "10:00", end: "22:00" } },
{ id: 4, provider: "minimax", plan: "Coding Plan", model: "minimax-m3", quota: 20000, used: 20000, price: 9, earned: 432, status: "off", key: "sk-mx-77d2a1c9e356", available: null },
],

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