feat(portable): 虾盘云插上即用 — 自动开户送试用额度 + Config 首选卡片
解决「插上 U 盘选虾盘云模型却 Invalid token」:之前客户端只生成 sk-uc-指纹 key 写进配置,但后台从没注册该 token。 - xiapan-client.mjs 加 provisionApiKey():把设备指纹送到虾盘云后台 (POST api.u-claw.org/internal/token/provision) 自动开一个带试用额度 的 token,返回真正可用的 key。 - bootstrap-xiapan.mjs 改:首启(配置无 cloud key 时)调 provision 拿真 key 写配置;已有 key 则幂等跳过;provision 失败回退指纹 key 不阻塞启动。 后端按 deviceId 幂等,刷不出额度。 - Config.html:模型网格加「虾盘云」首选卡片(一个 key 用全部模型,无需 申请,选中直接启动跳过填 Key);更新过时型号(MiniMax-M2/kimi-k2/ qwen-plus/doubao-seed-1.6/gpt-5.4/claude-opus-4-6/deepseek-v4); banner 文案改为「已含试用额度」。 注:开户/送额度的商业逻辑在闭源服务端(token-key-service),开源仓只放 调用机制。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -89,3 +89,30 @@ export function getRechargeUrl(apiKey) {
|
||||
// Page already has #recharge anchor; preserve it
|
||||
return `${url.toString()}#recharge`;
|
||||
}
|
||||
|
||||
// 首启静默开户:把设备指纹送到虾盘云后台,自动创建一个 token 并附带试用额度。
|
||||
// 后端按 deviceId 幂等(同一台机器永远拿同一个 token,刷不出额度),不重启容器。
|
||||
// 返回 { ok, apiKey, reused } —— apiKey 是后端真正注册过的 key(可直接聊天)。
|
||||
// 失败时返回 { ok:false, reason },调用方应回退到指纹派生 key(至少配置不空)。
|
||||
export async function provisionApiKey(fingerprint, { source = 'uclaw-portable' } = {}) {
|
||||
if (!fingerprint || !/^[0-9a-f]{64}$/i.test(fingerprint)) {
|
||||
return { ok: false, reason: 'bad-fingerprint' };
|
||||
}
|
||||
const base = getApiBase(); // https://api.u-claw.org/v1
|
||||
const root = base.replace(/\/v1$/, ''); // https://api.u-claw.org
|
||||
const url = `${root}/internal/token/provision`;
|
||||
try {
|
||||
const res = await fetchWithTimeout(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ deviceId: fingerprint.toLowerCase(), source }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || !data.success || !data.data || !data.data.apiKey) {
|
||||
return { ok: false, reason: data.message || `http-${res.status}` };
|
||||
}
|
||||
return { ok: true, apiKey: data.data.apiKey, reused: !!data.reused };
|
||||
} catch (err) {
|
||||
return { ok: false, reason: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user