feat: Beta策略支持按模型区分处理(模型白名单)

This commit is contained in:
shaw
2026-04-07 20:28:14 +08:00
parent b2e379cf7a
commit 7c60ee3c85
8 changed files with 255 additions and 24 deletions

View File

@@ -630,6 +630,108 @@
{{ t('admin.settings.betaPolicy.errorMessageHint') }}
</p>
</div>
<!-- Quick Presets (only for tokens with presets) -->
<div v-if="betaPresets[rule.beta_token]?.length" class="mt-3">
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
{{ t('admin.settings.betaPolicy.quickPresets') }}
</label>
<div class="flex flex-wrap gap-2">
<button
v-for="preset in betaPresets[rule.beta_token]"
:key="preset.label"
type="button"
class="inline-flex items-center gap-1 rounded-md border border-primary-200 bg-primary-50 px-2.5 py-1 text-xs font-medium text-primary-700 transition-colors hover:bg-primary-100 dark:border-primary-800 dark:bg-primary-900/30 dark:text-primary-300 dark:hover:bg-primary-900/50"
@click="applyBetaPreset(rule, preset)"
:title="preset.description"
>
{{ preset.label }}
</button>
</div>
</div>
<!-- Model Whitelist -->
<div class="mt-3">
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
{{ t('admin.settings.betaPolicy.modelWhitelist') }}
</label>
<p class="mb-2 text-xs text-gray-400 dark:text-gray-500">
{{ t('admin.settings.betaPolicy.modelWhitelistHint') }}
</p>
<!-- Existing patterns -->
<div
v-for="(_, index) in (rule.model_whitelist || [])"
:key="index"
class="mb-1.5 flex items-center gap-2"
>
<input
v-model="rule.model_whitelist![index]"
type="text"
class="input input-sm flex-1"
:placeholder="t('admin.settings.betaPolicy.modelPatternPlaceholder')"
/>
<button
type="button"
@click="rule.model_whitelist!.splice(index, 1)"
class="shrink-0 rounded p-1 text-red-400 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Add pattern button -->
<button
type="button"
@click="if (!rule.model_whitelist) rule.model_whitelist = []; rule.model_whitelist.push('')"
class="mb-2 inline-flex items-center gap-1 text-xs text-primary-600 transition-colors hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300"
>
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
</svg>
{{ t('admin.settings.betaPolicy.addModelPattern') }}
</button>
<!-- Common pattern chips -->
<div class="flex flex-wrap items-center gap-1.5">
<span class="text-xs text-gray-400 dark:text-gray-500">{{ t('admin.settings.betaPolicy.commonPatterns') }}:</span>
<button
v-for="pattern in commonModelPatterns"
:key="pattern"
type="button"
class="rounded border border-gray-200 px-2 py-0.5 text-xs text-gray-600 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:border-dark-600 dark:text-gray-400 dark:hover:border-primary-700 dark:hover:bg-primary-900/30 dark:hover:text-primary-300"
@click="addQuickPattern(rule, pattern)"
>
{{ pattern }}
</button>
</div>
</div>
<!-- Fallback Action (only when model_whitelist is non-empty) -->
<div v-if="rule.model_whitelist && rule.model_whitelist.length > 0" class="mt-3">
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
{{ t('admin.settings.betaPolicy.fallbackAction') }}
</label>
<Select
:modelValue="rule.fallback_action || 'pass'"
@update:modelValue="rule.fallback_action = $event as any"
:options="betaPolicyActionOptions"
/>
<p class="mt-1 text-xs text-gray-400 dark:text-gray-500">
{{ t('admin.settings.betaPolicy.fallbackActionHint') }}
</p>
<!-- Fallback Error Message (only when fallback_action=block) -->
<div v-if="rule.fallback_action === 'block'" class="mt-2">
<input
v-model="rule.fallback_error_message"
type="text"
class="input"
:placeholder="t('admin.settings.betaPolicy.fallbackErrorMessagePlaceholder')"
/>
<p class="mt-1 text-xs text-gray-400 dark:text-gray-500">
{{ t('admin.settings.betaPolicy.errorMessageHint') }}
</p>
</div>
</div>
</div>
<!-- Save Button -->
@@ -2058,6 +2160,9 @@ const betaPolicyForm = reactive({
action: 'pass' | 'filter' | 'block'
scope: 'all' | 'oauth' | 'apikey' | 'bedrock'
error_message?: string
model_whitelist?: string[]
fallback_action?: 'pass' | 'filter' | 'block'
fallback_error_message?: string
}>
})
@@ -2716,10 +2821,48 @@ const betaDisplayNames: Record<string, string> = {
'context-1m-2025-08-07': 'Context 1M'
}
// 快捷预设:按 beta_token 定义预设方案
const betaPresets: Record<string, Array<{
label: string
description: string
action: 'pass' | 'filter' | 'block'
model_whitelist: string[]
fallback_action: 'pass' | 'filter' | 'block'
}>> = {
'context-1m-2025-08-07': [
{
label: t('admin.settings.betaPolicy.presetOpusOnly'),
description: t('admin.settings.betaPolicy.presetOpusOnlyDesc'),
action: 'pass',
model_whitelist: ['claude-opus-4-6'],
fallback_action: 'filter',
},
],
}
// 常用模型模式(具体 ID + 通配符示例)
const commonModelPatterns = ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-opus-*', 'claude-sonnet-*']
function getBetaDisplayName(token: string): string {
return betaDisplayNames[token] || token
}
function applyBetaPreset(
rule: (typeof betaPolicyForm.rules)[number],
preset: { action: 'pass' | 'filter' | 'block'; model_whitelist: string[]; fallback_action: 'pass' | 'filter' | 'block' }
) {
rule.action = preset.action
rule.model_whitelist = [...preset.model_whitelist]
rule.fallback_action = preset.fallback_action
}
function addQuickPattern(rule: (typeof betaPolicyForm.rules)[number], pattern: string) {
if (!rule.model_whitelist) rule.model_whitelist = []
if (!rule.model_whitelist.includes(pattern)) {
rule.model_whitelist.push(pattern)
}
}
async function loadBetaPolicySettings() {
betaPolicyLoading.value = true
try {
@@ -2735,8 +2878,22 @@ async function loadBetaPolicySettings() {
async function saveBetaPolicySettings() {
betaPolicySaving.value = true
try {
// Clean up empty patterns before saving
const cleanedRules = betaPolicyForm.rules.map(rule => {
const whitelist = rule.model_whitelist?.filter(p => p.trim() !== '')
const hasWhitelist = whitelist && whitelist.length > 0
return {
beta_token: rule.beta_token,
action: rule.action,
scope: rule.scope,
error_message: rule.error_message,
model_whitelist: hasWhitelist ? whitelist : undefined,
fallback_action: hasWhitelist ? (rule.fallback_action || 'pass') : undefined,
fallback_error_message: hasWhitelist && rule.fallback_action === 'block' ? rule.fallback_error_message : undefined,
}
})
const updated = await adminAPI.settings.updateBetaPolicySettings({
rules: betaPolicyForm.rules
rules: cleanedRules
})
betaPolicyForm.rules = updated.rules
appStore.showSuccess(t('admin.settings.betaPolicy.saved'))