feat(portal): add logical group packaging config
This commit is contained in:
@@ -545,6 +545,36 @@
|
||||
<textarea id="group-next-step-hint" placeholder="例如:先创建测试 Key,再按推荐模型发起第一次请求。"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-grid two" style="margin-top:12px;">
|
||||
<label>
|
||||
visibility_scope
|
||||
<select id="group-visibility-scope">
|
||||
<option value="public">public</option>
|
||||
<option value="login_required">login_required</option>
|
||||
<option value="entitled_only">entitled_only</option>
|
||||
<option value="hidden">hidden</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
package_tier
|
||||
<select id="group-package-tier">
|
||||
<option value="free">free</option>
|
||||
<option value="standard">standard</option>
|
||||
<option value="pro">pro</option>
|
||||
<option value="enterprise">enterprise</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-grid two" style="margin-top:12px;">
|
||||
<label>
|
||||
purchase_cta_label
|
||||
<input id="group-purchase-cta-label" type="text" placeholder="例如:升级到 Pro">
|
||||
</label>
|
||||
<label>
|
||||
purchase_cta_url
|
||||
<input id="group-purchase-cta-url" type="text" placeholder="例如:https://sub.tksea.top/portal/upgrade/pro">
|
||||
</label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="primary" id="create-group-btn" type="button">创建分组</button>
|
||||
<button class="secondary" id="update-group-btn" type="button">更新分组</button>
|
||||
@@ -708,6 +738,10 @@
|
||||
const groupUsageScenarioInput = document.getElementById("group-usage-scenario");
|
||||
const groupRecommendationInput = document.getElementById("group-recommendation");
|
||||
const groupNextStepHintInput = document.getElementById("group-next-step-hint");
|
||||
const groupVisibilityScopeInput = document.getElementById("group-visibility-scope");
|
||||
const groupPackageTierInput = document.getElementById("group-package-tier");
|
||||
const groupPurchaseCTALabelInput = document.getElementById("group-purchase-cta-label");
|
||||
const groupPurchaseCTAURLInput = document.getElementById("group-purchase-cta-url");
|
||||
const groupRoutePolicyInput = document.getElementById("group-route-policy");
|
||||
const groupStickyModeInput = document.getElementById("group-sticky-mode");
|
||||
const groupConversationTTLInput = document.getElementById("group-conversation-ttl");
|
||||
@@ -888,6 +922,10 @@
|
||||
usage_scenario: groupUsageScenarioInput.value.trim(),
|
||||
recommendation: groupRecommendationInput.value.trim(),
|
||||
next_step_hint: groupNextStepHintInput.value.trim(),
|
||||
visibility_scope: groupVisibilityScopeInput.value,
|
||||
package_tier: groupPackageTierInput.value,
|
||||
purchase_cta_label: groupPurchaseCTALabelInput.value.trim(),
|
||||
purchase_cta_url: groupPurchaseCTAURLInput.value.trim(),
|
||||
route_policy: groupRoutePolicyInput.value,
|
||||
sticky_mode: groupStickyModeInput.value,
|
||||
conversation_ttl_seconds: Number(groupConversationTTLInput.value || "0"),
|
||||
@@ -919,6 +957,10 @@
|
||||
groupUsageScenarioInput.value = group?.usage_scenario || "";
|
||||
groupRecommendationInput.value = group?.recommendation || "";
|
||||
groupNextStepHintInput.value = group?.next_step_hint || "";
|
||||
groupVisibilityScopeInput.value = group?.visibility_scope || "public";
|
||||
groupPackageTierInput.value = group?.package_tier || "standard";
|
||||
groupPurchaseCTALabelInput.value = group?.purchase_cta_label || "";
|
||||
groupPurchaseCTAURLInput.value = group?.purchase_cta_url || "";
|
||||
groupRoutePolicyInput.value = group?.route_policy || "priority";
|
||||
groupStickyModeInput.value = group?.sticky_mode || "conversation_preferred";
|
||||
groupConversationTTLInput.value = String(group?.conversation_ttl_seconds || 7200);
|
||||
|
||||
@@ -528,6 +528,23 @@
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.cta-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(15, 118, 110, 0.24);
|
||||
background: rgba(15, 118, 110, 0.08);
|
||||
color: var(--teal);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
.cta-link:hover {
|
||||
background: rgba(15, 118, 110, 0.14);
|
||||
}
|
||||
.toast {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
@@ -1106,6 +1123,38 @@
|
||||
: [];
|
||||
}
|
||||
|
||||
function logicalGroupVisibilityScope(group) {
|
||||
return String(group && group.visibility_scope ? group.visibility_scope : "public").trim() || "public";
|
||||
}
|
||||
|
||||
function logicalGroupPackageTier(group) {
|
||||
return String(group && group.package_tier ? group.package_tier : "standard").trim() || "standard";
|
||||
}
|
||||
|
||||
function logicalGroupPurchaseCTA(group) {
|
||||
return {
|
||||
label: String(group && group.purchase_cta_label ? group.purchase_cta_label : "").trim(),
|
||||
url: String(group && group.purchase_cta_url ? group.purchase_cta_url : "").trim()
|
||||
};
|
||||
}
|
||||
|
||||
function logicalGroupVisibleForViewer(row) {
|
||||
const scope = logicalGroupVisibilityScope(row.logicalGroup);
|
||||
if (scope === "hidden") {
|
||||
return false;
|
||||
}
|
||||
if (scope === "public") {
|
||||
return true;
|
||||
}
|
||||
if (scope === "login_required") {
|
||||
return !!state.user;
|
||||
}
|
||||
if (scope === "entitled_only") {
|
||||
return !!state.user && (row.stateKey === "active" || row.stateKey === "granted" || row.stateKey === "ambiguous");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function legacyCompatibilityCandidates(group) {
|
||||
const portalModels = new Set(portalLogicalGroupModels(group));
|
||||
if (!portalModels.size) {
|
||||
@@ -1125,7 +1174,8 @@
|
||||
enabledCandidates
|
||||
};
|
||||
});
|
||||
rows.sort((left, right) => {
|
||||
const filtered = rows.filter((row) => logicalGroupVisibleForViewer(row));
|
||||
filtered.sort((left, right) => {
|
||||
const leftActive = Number(left.logicalGroup.active_route_count || 0);
|
||||
const rightActive = Number(right.logicalGroup.active_route_count || 0);
|
||||
if (leftActive !== rightActive) {
|
||||
@@ -1136,7 +1186,7 @@
|
||||
"zh-CN"
|
||||
);
|
||||
});
|
||||
return rows;
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function activeSubscriptionItems() {
|
||||
@@ -1221,11 +1271,15 @@
|
||||
: "pending";
|
||||
const compatibilityNames = (row.candidates || []).map((candidate) => candidate.title).join(" / ") || "无兼容线路";
|
||||
const models = portalLogicalGroupModels(group).join(", ") || "--";
|
||||
const packageTier = logicalGroupPackageTier(group);
|
||||
const visibilityScope = logicalGroupVisibilityScope(group);
|
||||
return (
|
||||
'<article class="group-card ' + (row.stateKey === "active" ? "active" : (row.stateKey === "catalog_only" ? "neutral" : "pending")) + '">' +
|
||||
'<h4>' + escapeHTML(group.display_name || group.logicalGroupID || "未命名逻辑分组") + '</h4>' +
|
||||
'<div class="group-meta">' +
|
||||
'<span class="badge strong ' + stateBadgeClass + '">' + escapeHTML(row.stateText) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(packageTier) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(visibilityScope) + '</span>' +
|
||||
'<span class="badge">兼容线路 ' + String(row.candidates.length) + '</span>' +
|
||||
'<span class="badge">活跃订阅 ' + String(row.matchingActiveSubscriptions.length) + '</span>' +
|
||||
'<span class="badge">已有 Key ' + String(row.matchingKeys.length) + '</span>' +
|
||||
@@ -1277,10 +1331,13 @@
|
||||
models,
|
||||
stateText: row.stateText,
|
||||
stateKey: row.stateKey,
|
||||
packageTier: logicalGroupPackageTier(group),
|
||||
visibilityScope: logicalGroupVisibilityScope(group),
|
||||
scenario: configuredScenario || guidance.scenario,
|
||||
recommendation: configuredRecommendation || guidance.recommendation,
|
||||
nextStep: configuredNextStep || defaultNextStep,
|
||||
compatibility,
|
||||
cta: logicalGroupPurchaseCTA(group),
|
||||
stickyMode: group.sticky_mode || "conversation_preferred",
|
||||
routePolicy: group.route_policy || "priority"
|
||||
};
|
||||
@@ -1304,6 +1361,8 @@
|
||||
'<article class="guide-card">' +
|
||||
'<div class="group-meta">' +
|
||||
'<span class="badge strong ' + badgeClass + '">' + escapeHTML(guide.stateText) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(guide.packageTier) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(guide.visibilityScope) + '</span>' +
|
||||
'<span class="badge mono">' + escapeHTML(guide.logicalGroupID) + '</span>' +
|
||||
'</div>' +
|
||||
'<h4>' + escapeHTML(guide.title) + '</h4>' +
|
||||
@@ -1315,6 +1374,9 @@
|
||||
'<div class="guide-line"><strong>兼容线路:</strong>' + escapeHTML(guide.compatibility) + '</div>' +
|
||||
'<div class="guide-line"><strong>路由策略:</strong><span class="mono">' + escapeHTML(guide.routePolicy) + '</span> / <span class="mono">' + escapeHTML(guide.stickyMode) + '</span></div>' +
|
||||
'</div>' +
|
||||
((guide.cta.label && guide.cta.url && guide.stateKey !== "active")
|
||||
? ('<a class="cta-link" href="' + escapeHTML(guide.cta.url) + '" target="_blank" rel="noreferrer">' + escapeHTML(guide.cta.label) + '</a>')
|
||||
: '') +
|
||||
'</article>'
|
||||
);
|
||||
}).join("");
|
||||
@@ -1392,6 +1454,8 @@
|
||||
const group = row.logicalGroup;
|
||||
const presentation = getPresentationStatus(row);
|
||||
const models = portalLogicalGroupModels(group);
|
||||
const packageTier = logicalGroupPackageTier(group);
|
||||
const visibilityScope = logicalGroupVisibilityScope(group);
|
||||
const modelsHTML = models.length
|
||||
? "<ul class=\"group-models\">" + models.map((model) => "<li><span class=\"mono\">" + escapeHTML(model) + "</span></li>").join("") + "</ul>"
|
||||
: "<div class=\"group-note\">当前尚未登记公开模型。</div>";
|
||||
@@ -1409,6 +1473,8 @@
|
||||
'<div class="group-meta">' +
|
||||
'<span class="badge">logical group</span>' +
|
||||
'<span class="badge mono">' + escapeHTML(group.logical_group_id || "--") + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(packageTier) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(visibilityScope) + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(group.route_policy || "priority") + '</span>' +
|
||||
'<span class="badge">' + escapeHTML(group.sticky_mode || "conversation_preferred") + '</span>' +
|
||||
'<span class="badge strong ' + presentation.cls + '">' + escapeHTML(presentation.text) + '</span>' +
|
||||
@@ -1456,11 +1522,14 @@
|
||||
? "你的账号暂未开通兼容宿主线路,当前只能浏览目录,不能直接申请测试 Key。"
|
||||
: "当前逻辑分组尚未建立自动申请测试 Key 的兼容线路。";
|
||||
$("create-key-btn").disabled = !canCreate;
|
||||
const cta = logicalGroupPurchaseCTA(row.logicalGroup);
|
||||
$("selection-summary").innerHTML = [
|
||||
'<div><strong>' + escapeHTML(row.logicalGroup.display_name || row.logicalGroup.logical_group_id || "未命名逻辑分组") + '</strong> / <span class="mono">' + escapeHTML(logicalGroupID) + '</span></div>',
|
||||
'<div class="mono">公开模型: ' + escapeHTML(models.join(", ") || "--") + '</div>',
|
||||
'<div class="mono">package_tier = ' + escapeHTML(logicalGroupPackageTier(row.logicalGroup)) + ' / visibility_scope = ' + escapeHTML(logicalGroupVisibilityScope(row.logicalGroup)) + '</div>',
|
||||
'<div class="mono">route_policy = ' + escapeHTML(row.logicalGroup.route_policy || "priority") + ' / sticky_mode = ' + escapeHTML(row.logicalGroup.sticky_mode || "conversation_preferred") + '</div>',
|
||||
'<div>' + escapeHTML(compatibility) + '</div>'
|
||||
'<div>' + escapeHTML(compatibility) + '</div>',
|
||||
(cta.label && cta.url ? ('<div><a class="cta-link" href="' + escapeHTML(cta.url) + '" target="_blank" rel="noreferrer">' + escapeHTML(cta.label) + '</a></div>') : '')
|
||||
].join("");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user