feat(grok): 视频改走 Grok Console 并新增图片模型
- grok 视频/图片统一走 console.x.ai:sso 换 DPoP 短期 token,绕开 grok.com 的 statsig 反爬 - 新增 grok-image:不带参考图走 /v1/images/generations,带图自动走 quality 上游 + /v1/images/edits - grok 额度改本地写死 图5/视频2,成功各扣一次,两份归零直接判死,无恢复时间 - 视频渲染预算统一 20 分钟(adobe/runway/grok/custom) - 删除参考图人脸打码(facemask + onnxruntime 依赖 + 前台开关)
This commit is contained in:
@@ -259,6 +259,8 @@ function applyQuota(row, result) {
|
||||
if (result.unknown && result.remaining === null) { row.remaining = null; row._unknown = true; return }
|
||||
row._unknown = false
|
||||
row.remaining = result.remaining
|
||||
if (result.image_remaining !== undefined) row.image_remaining = result.image_remaining
|
||||
if (result.video_remaining !== undefined) row.video_remaining = result.video_remaining
|
||||
row.reset_after = result.reset_after
|
||||
// 会员身份随额度一起回写:改过会员/掉了会员的号,查一次额度徽章就跟着更新。
|
||||
if (result.plan !== undefined) row.plan = result.plan
|
||||
@@ -391,7 +393,7 @@ onMounted(() => { loadAccounts(); loadModelList() })
|
||||
<button @click="setFilter(() => planFilter = '')" class="fp" :class="planFilter === '' && 'fp-on'">全部账号</button>
|
||||
<button @click="setFilter(() => planFilter = 'free')" class="fp" :class="planFilter === 'free' && 'fp-slate'">普号</button>
|
||||
<button @click="setFilter(() => planFilter = 'sub')" class="fp" :class="planFilter === 'sub' && 'fp-sky'">子号</button>
|
||||
<button @click="setFilter(() => planFilter = 'master')" class="fp" :class="planFilter === 'master' && 'fp-purple'">母号</button>
|
||||
<button @click="setFilter(() => planFilter = 'master')" class="fp" :class="planFilter === 'master' && 'fp-purple'">会员号</button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex-1 min-w-[200px]">
|
||||
@@ -480,7 +482,7 @@ onMounted(() => { loadAccounts(); loadModelList() })
|
||||
<span v-if="a.sub_account" class="acct-tag acct-tag-sub"
|
||||
:title="'子号 · 会员 (剩余 ' + (a.remaining ?? '—') + ' 积分)'">子号</span>
|
||||
<span v-else-if="a.plan && a.plan !== 'free'" class="acct-tag acct-tag-master"
|
||||
:title="'母号 · 会员 (' + a.plan + ')'">母号</span>
|
||||
:title="'会员号 (' + a.plan + ')'">会员号</span>
|
||||
<span v-else-if="a.plan === 'free'" class="acct-tag acct-tag-free"
|
||||
title="普号 (无会员)">普号</span>
|
||||
<span v-if="a.image_limited && a.status !== 'quota' && a.plan !== 'free'" class="acct-tag acct-tag-limit"
|
||||
@@ -498,9 +500,16 @@ onMounted(() => { loadAccounts(); loadModelList() })
|
||||
<td class="px-3 py-3.5 align-middle text-right text-sm tabular-nums whitespace-nowrap">
|
||||
<!-- quota column: 数字 / — (never "未知"/"失败"/"检测中") -->
|
||||
<!-- remaining === -1 is the provider "unlimited" sentinel → show — not a scary red -1 -->
|
||||
<span v-if="(a.type === 'openai' || a.type === 'adobe' || a.type === 'runway' || a.type === 'leonardo' || a.type === 'krea' || a.type === 'imagine' || a.type === 'grok') && a.remaining != null && a.remaining !== -1"
|
||||
<!-- grok 额度是本地写死的两份计数:图 / 视频,用完即判死,没有恢复时间 -->
|
||||
<span v-if="a.type === 'grok' && a.image_remaining != null"
|
||||
class="font-mono font-semibold" title="图片剩余 / 视频剩余">
|
||||
<span :class="a.image_remaining > 0 ? 'text-emerald-300' : 'text-rose-300'">{{ a.image_remaining }}</span>
|
||||
<span class="text-white/20">/</span>
|
||||
<span :class="a.video_remaining > 0 ? 'text-emerald-300' : 'text-rose-300'">{{ a.video_remaining }}</span>
|
||||
</span>
|
||||
<span v-else-if="(a.type === 'openai' || a.type === 'adobe' || a.type === 'runway' || a.type === 'leonardo' || a.type === 'krea' || a.type === 'imagine') && a.remaining != null && a.remaining !== -1"
|
||||
class="font-mono font-semibold"
|
||||
:class="a.remaining > 0 ? 'text-emerald-300' : 'text-rose-300'">{{ a.remaining }}{{ a.type === 'grok' ? '%' : '' }}</span>
|
||||
:class="a.remaining > 0 ? 'text-emerald-300' : 'text-rose-300'">{{ a.remaining }}</span>
|
||||
<span v-else class="text-white/25" :title="a._quotaError || ''">—</span>
|
||||
</td>
|
||||
<!-- weight (edit via modal) -->
|
||||
|
||||
@@ -556,14 +556,23 @@ async function fireOne() {
|
||||
payload.reference_images = refs.filter(Boolean)
|
||||
}
|
||||
|
||||
// 同一个任务带固定的 Idempotency-Key:长请求期间连接被重置、浏览器/网关透明
|
||||
// 重发 POST 时,后端不会再生成一次、再扣一次积分。
|
||||
const opts = jsonBody('POST', payload)
|
||||
opts.headers['Idempotency-Key'] = task.id + '-' + task.ts
|
||||
|
||||
try {
|
||||
const r = await api('/generate', jsonBody('POST', payload))
|
||||
const r = await api('/generate', opts)
|
||||
if (r.ok && r.data?.url) {
|
||||
task.status = 'done'
|
||||
task.url = r.data.url
|
||||
task.elapsed_ms = r.data.elapsed_ms
|
||||
task.charged = r.data.charged ?? chargedPrice
|
||||
if (auth.user && r.data.credits != null) auth.user.credits = r.data.credits
|
||||
} else if (r.status === 409 && String(r.data?.detail || '').includes('重复提交')) {
|
||||
// 这次响应来自被透明重发的那个 POST —— 原任务还在生成,保持 running,
|
||||
// loadHistory() 在结果落库后认领它。
|
||||
task.status = 'running'
|
||||
} else if (GATEWAY_TIMEOUT.has(r.status)) {
|
||||
// CDN/代理回源超时(如 EdgeOne 524)—— 后端仍在生成。保持 running,
|
||||
// loadHistory() 在结果落库后认领它。
|
||||
|
||||
Reference in New Issue
Block a user