fix: bug
This commit is contained in:
60
app/main.py
60
app/main.py
@@ -4,6 +4,7 @@ import logging
|
||||
import math
|
||||
import re
|
||||
import secrets
|
||||
import socket
|
||||
import time
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
@@ -175,18 +176,31 @@ def _platform_model_cfg() -> dict:
|
||||
|
||||
def _select_model_cfg(user_id: int, prefer_vip: bool = True) -> tuple[dict | None, str]:
|
||||
vip = users.get_vip_status(user_id)
|
||||
now = int(time.time())
|
||||
active_user_model = users.get_active_ai_model(user_id)
|
||||
has_user_model = bool(
|
||||
active_user_model
|
||||
and (active_user_model.get("api_key") or "").strip()
|
||||
and (active_user_model.get("model") or "").strip()
|
||||
)
|
||||
purchased_cycle_active = bool(int(vip.get("cycle_started_at") or 0) > 0 and int(vip.get("cycle_expires_at") or 0) > now)
|
||||
if prefer_vip and vip.get("vip_enabled"):
|
||||
if int(vip.get("total_available_credits") or 0) <= 0:
|
||||
total_available = int(vip.get("total_available_credits") or 0)
|
||||
if total_available < _min_platform_credits_required():
|
||||
return None, "vip_empty"
|
||||
cfg = _platform_model_cfg()
|
||||
if cfg.get("api_key"):
|
||||
return cfg, "vip"
|
||||
cfg = users.get_active_ai_model(user_id)
|
||||
# 已购买订阅:优先平台模型
|
||||
# 未购买订阅:若未配置自有模型,则默认走平台模型(可使用赠送额度)
|
||||
if purchased_cycle_active or not has_user_model:
|
||||
cfg = _platform_model_cfg()
|
||||
if cfg.get("api_key"):
|
||||
return cfg, "vip"
|
||||
cfg = active_user_model
|
||||
return cfg, "user"
|
||||
|
||||
|
||||
def _quota_detail() -> str:
|
||||
return "Credits 额度已用完(席位额度+共享加油包),请充值或等待下个计费周期"
|
||||
min_required = _min_platform_credits_required()
|
||||
return f"Credits 额度不足,平台模型至少需要 {min_required} Credits(1张图片或10000Token门槛),请订阅或充值"
|
||||
|
||||
|
||||
def _credits_from_cny(amount_cny: float) -> int:
|
||||
@@ -222,6 +236,13 @@ def _estimate_image_cost(image_count: int) -> int:
|
||||
return _credits_from_cny((cnt / pkg_images) * pkg_cny)
|
||||
|
||||
|
||||
def _min_platform_credits_required() -> int:
|
||||
one_image_credits = _estimate_image_cost(1)
|
||||
token_10k_cny = (10_000.0 / 1_000_000.0) * max(0.0, float(settings.credits_token_price_per_million_cny))
|
||||
token_10k_credits = _credits_from_cny(token_10k_cny)
|
||||
return max(1, one_image_credits, token_10k_credits)
|
||||
|
||||
|
||||
def _new_order_no() -> str:
|
||||
return f"RC{time.strftime('%Y%m%d%H%M%S')}{uuid.uuid4().hex[:10].upper()}"
|
||||
|
||||
@@ -743,6 +764,31 @@ async def pay_address_suggest(request: Request):
|
||||
return {"ok": True, "detail": "IP地址解析失败,已填入IP信息,可手动修改", "ip": ip, "address": f"IP:{ip}"}
|
||||
|
||||
|
||||
@app.get("/api/tools/server-ip")
|
||||
async def tools_server_ip(request: Request):
|
||||
user = _require_user(request)
|
||||
if not user:
|
||||
return {"ok": False, "detail": "请先登录"}
|
||||
public_ip = ""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5) as client:
|
||||
resp = await client.get("https://api64.ipify.org?format=json")
|
||||
body = resp.json() if resp.content else {}
|
||||
if resp.status_code < 400 and isinstance(body, dict):
|
||||
public_ip = str(body.get("ip") or "").strip()
|
||||
except Exception:
|
||||
public_ip = ""
|
||||
private_ip = ""
|
||||
try:
|
||||
private_ip = socket.gethostbyname(socket.gethostname())
|
||||
except Exception:
|
||||
private_ip = ""
|
||||
ip = public_ip or private_ip
|
||||
if not ip:
|
||||
return {"ok": False, "detail": "无法识别服务器IP,请稍后重试"}
|
||||
return {"ok": True, "ip": ip, "public_ip": public_ip, "private_ip": private_ip}
|
||||
|
||||
|
||||
@app.post("/api/billing/recharge/notify")
|
||||
async def billing_recharge_notify(req: BillingRechargeNotifyRequest, request: Request):
|
||||
token = (request.headers.get("X-Shop-Token") or "").strip()
|
||||
@@ -967,7 +1013,7 @@ async def rewrite(req: RewriteRequest, request: Request):
|
||||
billed_basis = "usage_tokens" if total_tokens > 0 else "char_estimate"
|
||||
token_cost = _estimate_rewrite_cost(req, result)
|
||||
vip_status = users.get_vip_status(user["id"])
|
||||
should_consume = bool(vip_status.get("vip_enabled"))
|
||||
should_consume = model_source == "vip"
|
||||
if should_consume:
|
||||
ok_cost, balance = users.consume_tokens(
|
||||
user["id"],
|
||||
|
||||
@@ -709,10 +709,10 @@ class UserStore:
|
||||
cycle_started_at = int(row["cycle_started_at"] or 0) if row else 0
|
||||
cycle_expires_at = int(row["cycle_expires_at"] or 0) if row else 0
|
||||
now = int(time.time())
|
||||
cycle_active = cycle_expires_at > now if cycle_expires_at > 0 else True
|
||||
# 仅当已开启有效计费周期时,席位额度才可用;新用户未购买时不应显示席位 1500
|
||||
cycle_active = cycle_started_at > 0 and cycle_expires_at > now
|
||||
if not cycle_active:
|
||||
seat_remaining = 0
|
||||
shared_credits = 0
|
||||
return {
|
||||
"vip_enabled": bool(int(row["vip_enabled"] or 0)) if row else False,
|
||||
"token_balance": shared_credits,
|
||||
|
||||
36
app/static/guide.js
Normal file
36
app/static/guide.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const guideGetServerIpBtn = document.getElementById("getServerIpBtn");
|
||||
|
||||
async function showGuideAlert(message, title = "提示") {
|
||||
if (typeof window.uiAlert === "function") {
|
||||
await window.uiAlert(message, title);
|
||||
return;
|
||||
}
|
||||
window.alert(message);
|
||||
}
|
||||
|
||||
if (guideGetServerIpBtn) {
|
||||
guideGetServerIpBtn.addEventListener("click", async () => {
|
||||
const idleText = "获取服务器IP";
|
||||
guideGetServerIpBtn.disabled = true;
|
||||
guideGetServerIpBtn.textContent = "获取中...";
|
||||
try {
|
||||
const res = await fetch("/api/tools/server-ip");
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data.ok) {
|
||||
await showGuideAlert((data && data.detail) || "获取服务器IP失败,请稍后重试", "获取失败");
|
||||
return;
|
||||
}
|
||||
const ip = String(data.ip || "").trim();
|
||||
if (!ip) {
|
||||
await showGuideAlert("服务器IP为空,请稍后重试", "获取失败");
|
||||
return;
|
||||
}
|
||||
await showGuideAlert(`当前服务器IP:${ip}`, "API IP白名单");
|
||||
} catch {
|
||||
await showGuideAlert("获取服务器IP失败,请稍后重试", "获取失败");
|
||||
} finally {
|
||||
guideGetServerIpBtn.disabled = false;
|
||||
guideGetServerIpBtn.textContent = idleText;
|
||||
}
|
||||
});
|
||||
}
|
||||
27
app/static/mode-hint.js
Normal file
27
app/static/mode-hint.js
Normal file
@@ -0,0 +1,27 @@
|
||||
(() => {
|
||||
const badges = Array.from(document.querySelectorAll(".global-mode-hint"));
|
||||
if (!badges.length) return;
|
||||
|
||||
function setText(text) {
|
||||
badges.forEach((el) => {
|
||||
el.textContent = text;
|
||||
});
|
||||
}
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const res = await fetch("/api/auth/me");
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data || !data.logged_in) return;
|
||||
const vip = data.vip || {};
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const enabled = Boolean(vip.vip_enabled);
|
||||
const hasActiveSubscription = Number(vip.cycle_started_at || 0) > 0 && Number(vip.cycle_expires_at || 0) > now;
|
||||
setText(enabled && hasActiveSubscription ? "当前模型模式:平台模型" : "当前模型模式:自由模型");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
})();
|
||||
@@ -207,7 +207,7 @@ if (saveModelBtn) {
|
||||
api_key: ($("apiKey") && $("apiKey").value.trim()) || "",
|
||||
base_url: ($("baseUrl") && $("baseUrl").value.trim()) || "",
|
||||
model: ($("modelValue") && $("modelValue").value.trim()) || "",
|
||||
image_model: "",
|
||||
image_model: ($("imageModelValue") && $("imageModelValue").value.trim()) || "",
|
||||
timeout_sec: Number((($("timeoutSec") && $("timeoutSec").value) || "120").trim()),
|
||||
max_output_tokens: Number((($("maxOutputTokens") && $("maxOutputTokens").value) || "8192").trim()),
|
||||
max_retries: Number((($("maxRetries") && $("maxRetries").value) || "0").trim()),
|
||||
@@ -219,6 +219,7 @@ if (saveModelBtn) {
|
||||
setStatus("模型配置已保存并设为当前。");
|
||||
if ($("apiKey")) $("apiKey").value = "";
|
||||
if ($("modelName")) $("modelName").value = "";
|
||||
if ($("imageModelValue")) $("imageModelValue").value = "";
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setStatus(e.message || "模型保存失败", true);
|
||||
|
||||
@@ -215,6 +215,41 @@ a {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.upgrade-topbar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.topbar-spacer {
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.topbar-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.upgrade-topbar .topbar-actions {
|
||||
justify-self: end;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.mode-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: var(--control-h);
|
||||
padding: 0 12px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--accent);
|
||||
background: linear-gradient(180deg, #ff922e, #ff6b16);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 850;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 10px 20px rgba(255, 107, 22, 0.2);
|
||||
}
|
||||
|
||||
.wechat-account-switch {
|
||||
min-width: 250px;
|
||||
display: grid;
|
||||
@@ -333,12 +368,14 @@ a {
|
||||
|
||||
.panel-scroll {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.input-panel .panel-scroll {
|
||||
overflow: visible;
|
||||
overflow-y: visible;
|
||||
overflow-x: hidden;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@@ -783,27 +820,29 @@ button.secondary:hover,
|
||||
}
|
||||
|
||||
.target-chars-quick {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
align-items: stretch;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.target-char-chip,
|
||||
button.target-char-chip {
|
||||
width: auto;
|
||||
min-width: 54px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 30px;
|
||||
margin-top: 0;
|
||||
padding: 5px 8px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 999px;
|
||||
padding: 5px 6px;
|
||||
border-radius: 8px;
|
||||
border-color: var(--line-strong);
|
||||
color: #344054;
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
line-height: 1.1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -1587,7 +1626,7 @@ button.target-char-chip {
|
||||
.upgrade-grid {
|
||||
margin-top: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.4fr) minmax(320px, 0.8fr);
|
||||
grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@@ -2009,6 +2048,12 @@ button.target-char-chip {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.upgrade-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.upgrade-hero {
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -85,6 +85,8 @@ function renderVip(vip) {
|
||||
const enabled = Boolean(vip.vip_enabled);
|
||||
const cycleStart = Number(vip.cycle_started_at || 0);
|
||||
const cycleEnd = Number(vip.cycle_expires_at || 0);
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const hasActiveSubscription = cycleStart > 0 && cycleEnd > now;
|
||||
if ($("upgradeTokenBalance")) $("upgradeTokenBalance").textContent = String(totalAvailable);
|
||||
if ($("upgradeTokenBalanceHero")) $("upgradeTokenBalanceHero").textContent = String(totalAvailable);
|
||||
if ($("vipTokenBalance")) $("vipTokenBalance").textContent = String(shared);
|
||||
@@ -103,6 +105,15 @@ function renderVip(vip) {
|
||||
$("vipCycleHint").textContent = "当前未开始月周期,首次支付成功后开始计时。";
|
||||
}
|
||||
}
|
||||
if ($("vipModeHint")) {
|
||||
if (enabled && hasActiveSubscription) {
|
||||
$("vipModeHint").textContent = "当前模型模式:平台模型(订阅有效)";
|
||||
} else if (enabled && !hasActiveSubscription) {
|
||||
$("vipModeHint").textContent = "当前模型模式:自由模型(未开通订阅)";
|
||||
} else {
|
||||
$("vipModeHint").textContent = "当前模型模式:自由模型";
|
||||
}
|
||||
}
|
||||
if (totalAvailable <= 0) {
|
||||
setStatus("Credits 额度已用完,请充值共享加油包或等待下个计费周期。", true);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<div class="workspace">
|
||||
<header class="topbar topbar-compact">
|
||||
<div class="topbar-actions">
|
||||
<span class="mode-badge global-mode-hint">当前模型模式:自由模型</span>
|
||||
<a class="icon-btn" href="/" aria-label="返回工作台" title="返回工作台">⌂</a>
|
||||
<a class="icon-btn" href="/settings" aria-label="账号与模型设置" title="账号与模型设置">⚙</a>
|
||||
<a class="icon-btn" href="/upgrade" aria-label="升级" title="升级">★</a>
|
||||
@@ -95,6 +96,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/ui-dialog.js?v=20260428a"></script>
|
||||
<script src="/static/mode-hint.js?v=20260428a"></script>
|
||||
<script src="/static/billing.js?v=20260428w"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<div class="workspace">
|
||||
<header class="topbar topbar-compact">
|
||||
<div class="topbar-actions">
|
||||
<span class="mode-badge global-mode-hint">当前模型模式:自由模型</span>
|
||||
<a class="icon-btn" href="/" aria-label="返回工作台" title="返回工作台">⌂</a>
|
||||
<a class="icon-btn" href="/upgrade" aria-label="升级" title="升级">★</a>
|
||||
<a class="icon-btn" href="/profile" aria-label="个人中心" title="个人中心">☺</a>
|
||||
@@ -58,39 +59,50 @@
|
||||
<div class="guide-step">01</div>
|
||||
<h3>准备发布账号</h3>
|
||||
<p>进入账号与模型设置,绑定公众号 AppID 和 Secret。草稿发布、封面上传、段落海报素材都会使用当前选中的发表主体。</p>
|
||||
<p class="muted small">
|
||||
不知道哪里查看 ID?可先阅读微信文档:
|
||||
<a href="https://developers.weixin.qq.com/doc/oplatform/developers/product/subscription_service/appid.html" target="_blank" rel="noopener noreferrer">查看 AppID / AppSecret 获取说明</a>
|
||||
</p>
|
||||
<a href="/settings" class="guide-link">打开账号设置</a>
|
||||
</article>
|
||||
|
||||
<article class="guide-card">
|
||||
<div class="guide-step">02</div>
|
||||
<h3>配置 API IP 白名单</h3>
|
||||
<p>若第三方模型平台开启了 API IP 白名单,请先把当前服务器 IP 加入白名单,避免接口请求被拒绝。</p>
|
||||
<button id="getServerIpBtn" class="guide-link" type="button">获取服务器IP</button>
|
||||
</article>
|
||||
|
||||
<article class="guide-card">
|
||||
<div class="guide-step">03</div>
|
||||
<h3>配置 AI 模型</h3>
|
||||
<p>保存模型名称、API Key、Base URL、超时秒数和输出 token 上限。未配置模型时将无法进行 AI 改写,请先完成模型配置。</p>
|
||||
<a href="/settings#model-settings" class="guide-link">打开模型配置</a>
|
||||
</article>
|
||||
|
||||
<article class="guide-card">
|
||||
<div class="guide-step">03</div>
|
||||
<div class="guide-step">04</div>
|
||||
<h3>输入原文与策略</h3>
|
||||
<p>在内容生产页粘贴原文,补充标题提示、目标读者、语气风格、必须保留观点和避免词汇。目标字数建议先从 500 或 800 开始。</p>
|
||||
<a href="/" class="guide-link">进入写作输入</a>
|
||||
</article>
|
||||
|
||||
<article class="guide-card">
|
||||
<div class="guide-step">04</div>
|
||||
<div class="guide-step">05</div>
|
||||
<h3>生成并人工复核</h3>
|
||||
<p>点击“改写并排版”后,检查标题、摘要、正文结构和排版预览。涉及事实、数据、引用和品牌表达时,发布前务必人工确认。</p>
|
||||
<a href="/" class="guide-link">查看发布内容</a>
|
||||
</article>
|
||||
|
||||
<article class="guide-card">
|
||||
<div class="guide-step">05</div>
|
||||
<div class="guide-step">06</div>
|
||||
<h3>补齐封面和海报</h3>
|
||||
<p>可按输出标题自动生成 900×383 公众号封面并绑定 thumb_media_id,也可以生成段落海报。勾选自动插入后,发布草稿时会把正文和海报一起编排。</p>
|
||||
<a href="/" class="guide-link">处理内容素材</a>
|
||||
</article>
|
||||
|
||||
<article class="guide-card">
|
||||
<div class="guide-step">06</div>
|
||||
<div class="guide-step">07</div>
|
||||
<h3>发布到草稿箱</h3>
|
||||
<p>确认发表主体无误后,点击“发布到公众号草稿箱”。需要团队同步时,再点击“发送到 IM”。草稿发布后仍建议在公众号后台最终预览。</p>
|
||||
<a href="/" class="guide-link">回到发布动作</a>
|
||||
@@ -135,5 +147,8 @@
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/ui-dialog.js?v=20260428a"></script>
|
||||
<script src="/static/mode-hint.js?v=20260428a"></script>
|
||||
<script src="/static/guide.js?v=20260428a"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<div class="workspace">
|
||||
<header class="topbar topbar-compact">
|
||||
<div class="topbar-actions">
|
||||
<span class="mode-badge global-mode-hint">当前模型模式:自由模型</span>
|
||||
<div class="wechat-account-switch" title="发布将使用当前账号">
|
||||
<label for="wechatAccountSelect" class="wechat-account-label">发表主体</label>
|
||||
<select id="wechatAccountSelect" class="topbar-select" aria-label="切换公众号"></select>
|
||||
@@ -248,6 +249,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/mode-hint.js?v=20260428a"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="/static/app.js?v=20260428s"></script>
|
||||
</body>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<div class="workspace">
|
||||
<header class="topbar topbar-compact">
|
||||
<div class="topbar-actions">
|
||||
<span class="mode-badge global-mode-hint">当前模型模式:自由模型</span>
|
||||
<a class="icon-btn" href="/" aria-label="返回工作台" title="返回工作台">⌂</a>
|
||||
<a class="icon-btn" href="/upgrade" aria-label="升级" title="升级">★</a>
|
||||
<a class="icon-btn" href="/settings" aria-label="账号与模型设置" title="账号与模型设置">⚙</a>
|
||||
@@ -70,6 +71,7 @@
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/mode-hint.js?v=20260428a"></script>
|
||||
<script src="/static/profile.js?v=20260428a"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<div class="workspace">
|
||||
<header class="topbar topbar-compact">
|
||||
<div class="topbar-actions">
|
||||
<span class="mode-badge global-mode-hint">当前模型模式:自由模型</span>
|
||||
<a class="icon-btn" href="/" aria-label="返回工作台" title="返回工作台">⌂</a>
|
||||
<a class="icon-btn" href="/upgrade" aria-label="升级" title="升级">★</a>
|
||||
<a class="icon-btn" href="/profile" aria-label="个人中心" title="个人中心">☺</a>
|
||||
@@ -91,6 +92,10 @@
|
||||
<input id="modelValue" type="text" placeholder="如:gpt-4.1-mini / qwen-max" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>生图模型名</label>
|
||||
<input id="imageModelValue" type="text" placeholder="如:wanx2.0-t2i-turbo / gpt-image-1" />
|
||||
</div>
|
||||
<div class="grid2">
|
||||
<div>
|
||||
<label>Base URL(可选)</label>
|
||||
@@ -154,6 +159,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/ui-dialog.js?v=20260428a"></script>
|
||||
<script src="/static/settings.js?v=20260428q"></script>
|
||||
<script src="/static/mode-hint.js?v=20260428a"></script>
|
||||
<script src="/static/settings.js?v=20260428r"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{ app_name }} - 升级</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=20260428h" />
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260428za" />
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260428zd" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="product-shell">
|
||||
@@ -29,7 +29,11 @@
|
||||
</aside>
|
||||
|
||||
<div class="workspace">
|
||||
<header class="topbar topbar-compact">
|
||||
<header class="topbar topbar-compact upgrade-topbar">
|
||||
<div class="topbar-spacer" aria-hidden="true"></div>
|
||||
<div class="topbar-center">
|
||||
<span id="vipModeHint" class="mode-badge global-mode-hint">当前模型模式:自由模型</span>
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<a class="icon-btn" href="/" aria-label="返回工作台" title="返回工作台">⌂</a>
|
||||
<a class="icon-btn" href="/settings" aria-label="账号与模型设置" title="账号与模型设置">⚙</a>
|
||||
@@ -172,6 +176,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/ui-dialog.js?v=20260428a"></script>
|
||||
<script src="/static/upgrade.js?v=20260428ae"></script>
|
||||
<script src="/static/mode-hint.js?v=20260428a"></script>
|
||||
<script src="/static/upgrade.js?v=20260428ag"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user