增加ai去特征

This commit is contained in:
2026-07-09 23:48:58 +08:00
parent 21cca6513b
commit 6d6bd2a638
9 changed files with 290 additions and 6 deletions
+43 -1
View File
@@ -120,6 +120,24 @@ const smtpBusy = ref(false); const smtpSaved = ref(false)
const credits = reactive({ checkin_enabled: true, checkin_reward: 3, invite_enabled: true, invite_reward: 3, cdk_redeem_enabled: true })
const credBusy = ref(false); const credSaved = ref(false)
// ---- deai (去AI特征 附加价格) ----
const deaiCfg = reactive({ price_1k: 1, price_2k: 2, price_4k: 3 })
const deaiBusy = ref(false); const deaiSaved = ref(false)
async function loadDeai() {
const r = await api('/settings/deai')
if (r.ok && r.data) Object.assign(deaiCfg, r.data)
}
async function saveDeai() {
deaiBusy.value = true; deaiSaved.value = false
const r = await api('/settings/deai', jsonBody('PUT', {
price_1k: Number(deaiCfg.price_1k) || 0,
price_2k: Number(deaiCfg.price_2k) || 0,
price_4k: Number(deaiCfg.price_4k) || 0,
}))
deaiBusy.value = false
if (r.ok) { deaiSaved.value = true; setTimeout(() => (deaiSaved.value = false), 2000) }
}
// ---- announcement (公告, markdown; re-pops for users who haven't seen edits) ----
const ann = reactive({ content: '' })
const annBusy = ref(false); const annSaved = ref(false)
@@ -269,7 +287,7 @@ async function saveCredits() {
if (r.ok) { credSaved.value = true; setTimeout(() => (credSaved.value = false), 2000) }
}
onMounted(() => { loadSite(); loadReg(); loadSmtp(); loadCredits(); loadAnnouncement(); loadPay(); loadProxy(); loadLogs(); loadMedia() })
onMounted(() => { loadSite(); loadReg(); loadSmtp(); loadCredits(); loadAnnouncement(); loadPay(); loadProxy(); loadLogs(); loadMedia(); loadDeai() })
</script>
<template>
@@ -462,6 +480,30 @@ onMounted(() => { loadSite(); loadReg(); loadSmtp(); loadCredits(); loadAnnounce
<div class="mt-4"><button @click="saveCredits" :disabled="credBusy" class="btn-primary">{{ credBusy ? '保存中…' : '保存设置' }}</button></div>
</div>
<!-- deai (去AI特征) -->
<div class="card p-5">
<div class="flex items-center justify-between mb-4">
<h2 class="text-sm font-semibold">去AI特征</h2>
<span v-if="deaiSaved" class="text-xs text-emerald-300">已保存 </span>
</div>
<p class="text-xs text-slate-400 mb-4">画图台开启去AI特征,按画质档位在模型价格之上额外扣除的积分仅对图片生成生效</p>
<div class="space-y-3">
<label class="row">
<span><span class="lbl">1K 附加价格</span><span class="hint">默认 1 积分</span></span>
<input type="number" min="0" v-model.number="deaiCfg.price_1k" class="num" />
</label>
<label class="row">
<span><span class="lbl">2K 附加价格</span><span class="hint">默认 2 积分</span></span>
<input type="number" min="0" v-model.number="deaiCfg.price_2k" class="num" />
</label>
<label class="row">
<span><span class="lbl">4K 附加价格</span><span class="hint">默认 3 积分</span></span>
<input type="number" min="0" v-model.number="deaiCfg.price_4k" class="num" />
</label>
</div>
<div class="mt-4"><button @click="saveDeai" :disabled="deaiBusy" class="btn-primary">{{ deaiBusy ? '保存中…' : '保存设置' }}</button></div>
</div>
<!-- announcement (公告) -->
<div class="card p-5">
<div class="flex items-center justify-between mb-1">
+29 -2
View File
@@ -28,6 +28,7 @@ const prompt = ref(draft.prompt || '')
const ratio = ref(draft.ratio || '')
const resolution = ref(draft.resolution || '')
const duration = ref(draft.duration || '')
const deai = ref(draft.deai || false)
watch(mode, (v) => { draft.mode = v })
watch(modelId, (v) => { draft.modelId = v })
@@ -35,6 +36,7 @@ watch(prompt, (v) => { draft.prompt = v })
watch(ratio, (v) => { draft.ratio = v })
watch(resolution, (v) => { draft.resolution = v })
watch(duration, (v) => { draft.duration = v })
watch(deai, (v) => { draft.deai = v })
const refImages = ref([]) // [{ name, dataUrl }]
const fileInput = ref(null)
@@ -136,6 +138,12 @@ function tierPrice(normalMap, agentMap, key) {
}
return Number(n)
}
// 去AI特征 per-tier surcharge (loaded from /deai-pricing; defaults 1/2/3 分).
const deaiPricing = ref({ price_1k: 1, price_2k: 2, price_4k: 3 })
const deaiSurcharge = computed(() => {
const key = { '1K': 'price_1k', '2K': 'price_2k', '4K': 'price_4k' }[resolution.value] || 'price_1k'
return Number(deaiPricing.value[key] ?? 0)
})
const price = computed(() => {
if (!model.value) return null
const m = model.value
@@ -145,7 +153,9 @@ const price = computed(() => {
if (rp == null || dp == null) return null
return rp + dp
}
return tierPrice(m.prices, m.prices_agent, resolution.value)
const base = tierPrice(m.prices, m.prices_agent, resolution.value)
if (base == null) return null
return base + (deai.value ? deaiSurcharge.value : 0)
})
const priceLabel = computed(() => price.value == null ? '—' : pointsLabel(price.value))
const canAfford = computed(() => price.value == null || credits.value >= price.value)
@@ -392,6 +402,7 @@ async function fireOne() {
ratio: ratio.value,
resolution: resolution.value,
duration: mode.value === 'video' ? duration.value : '',
deai: mode.value === 'image' ? deai.value : false,
status: 'pending',
url: '',
error: '',
@@ -414,6 +425,7 @@ async function fireOne() {
model: task.model, prompt: task.prompt, ratio: task.ratio, resolution: task.resolution,
}
if (task.kind === 'video') payload.duration = task.duration
if (task.kind === 'image' && task.deai) payload.deai = true
if (refsSnapshot.length) {
const refs = await Promise.all(refsSnapshot.map(refToBase64))
payload.reference_images = refs.filter(Boolean)
@@ -574,9 +586,10 @@ function onKey(e) { if (e.key === 'Escape') lightbox.value = null }
onMounted(async () => {
refreshMe() // pull the latest real balance
const [mm, pp] = await Promise.all([api('/managed-models'), api('/video-presets')])
const [mm, pp, dp] = await Promise.all([api('/managed-models'), api('/video-presets'), api('/deai-pricing')])
allModels.value = mm.data?.data || []
presets.value = pp.data?.data || []
if (dp.ok && dp.data) deaiPricing.value = dp.data
// Pre-fill from query string (?prompt=...&model=...) — used by the home
// page's example cards to seed the form in one click.
const qPrompt = String(route.query.prompt || '')
@@ -683,6 +696,20 @@ onUnmounted(() => {
</div>
</div>
<!-- 去AI特征 (image only): opt-in post-processing with a per-tier surcharge -->
<div v-if="mode === 'image'" class="flex items-center justify-between">
<label class="text-xs font-medium text-slate-500">
去AI特征
<span class="text-slate-400 font-normal">(+{{ deaiSurcharge }} 积分)</span>
</label>
<button type="button" role="switch" :aria-checked="deai" @click="deai = !deai"
class="relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors"
:class="deai ? 'bg-slate-900' : 'bg-slate-200'">
<span class="inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform"
:class="deai ? 'translate-x-[18px]' : 'translate-x-0.5'"></span>
</button>
</div>
<div v-if="mode === 'video' && durations.length > 0">
<label class="block text-xs font-medium text-slate-500 mb-1.5">时长</label>
<div class="flex flex-wrap gap-1.5">