merge: integrate upstream v0.1.95 with our customizations

Merge main (custom features) into release/custom-0.1.95 (upstream v0.1.95).
New upstream features: group subscription binding, multi-dimension quota (daily/weekly/total),
allow_messages_dispatch, default_mapped_model, recover state API.
Our customizations: simulate_claude_max_enabled, usage status detection, 403 validation handling.
This commit is contained in:
erio
2026-03-11 03:23:44 +08:00
68 changed files with 5269 additions and 165 deletions

View File

@@ -746,6 +746,58 @@
</div>
</div>
<!-- Claude Max Usage 模拟 anthropic 平台 -->
<div v-if="createForm.platform === 'anthropic'" class="border-t pt-4">
<div class="mb-1.5 flex items-center gap-1">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.groups.claudeMaxSimulation.title') }}
</label>
<div class="group relative inline-flex">
<Icon
name="questionCircle"
size="sm"
:stroke-width="2"
class="cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
/>
<div class="pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-80 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100">
<div class="rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800">
<p class="text-xs leading-relaxed text-gray-300">
{{ t('admin.groups.claudeMaxSimulation.tooltip') }}
</p>
<div class="absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800"></div>
</div>
</div>
</div>
</div>
<div class="flex items-center gap-3">
<button
type="button"
@click="createForm.simulate_claude_max_enabled = !createForm.simulate_claude_max_enabled"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
createForm.simulate_claude_max_enabled ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
createForm.simulate_claude_max_enabled ? 'translate-x-6' : 'translate-x-1'
]"
/>
</button>
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
createForm.simulate_claude_max_enabled
? t('admin.groups.claudeMaxSimulation.enabled')
: t('admin.groups.claudeMaxSimulation.disabled')
}}
</span>
</div>
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.groups.claudeMaxSimulation.hint') }}
</p>
</div>
<!-- 无效请求兜底 anthropic/antigravity 平台且非订阅分组 -->
<div
v-if="['anthropic', 'antigravity'].includes(createForm.platform) && createForm.subscription_type !== 'subscription'"
@@ -1481,6 +1533,58 @@
</div>
</div>
<!-- Claude Max Usage 模拟 anthropic 平台 -->
<div v-if="editForm.platform === 'anthropic'" class="border-t pt-4">
<div class="mb-1.5 flex items-center gap-1">
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.groups.claudeMaxSimulation.title') }}
</label>
<div class="group relative inline-flex">
<Icon
name="questionCircle"
size="sm"
:stroke-width="2"
class="cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
/>
<div class="pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-80 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100">
<div class="rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800">
<p class="text-xs leading-relaxed text-gray-300">
{{ t('admin.groups.claudeMaxSimulation.tooltip') }}
</p>
<div class="absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800"></div>
</div>
</div>
</div>
</div>
<div class="flex items-center gap-3">
<button
type="button"
@click="editForm.simulate_claude_max_enabled = !editForm.simulate_claude_max_enabled"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
editForm.simulate_claude_max_enabled ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
editForm.simulate_claude_max_enabled ? 'translate-x-6' : 'translate-x-1'
]"
/>
</button>
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
editForm.simulate_claude_max_enabled
? t('admin.groups.claudeMaxSimulation.enabled')
: t('admin.groups.claudeMaxSimulation.disabled')
}}
</span>
</div>
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.groups.claudeMaxSimulation.hint') }}
</p>
</div>
<!-- 无效请求兜底 anthropic/antigravity 平台且非订阅分组 -->
<div
v-if="['anthropic', 'antigravity'].includes(editForm.platform) && editForm.subscription_type !== 'subscription'"
@@ -1994,6 +2098,8 @@ const createForm = reactive({
sora_storage_quota_gb: null as number | null,
// Claude Code 客户端限制(仅 anthropic 平台使用)
claude_code_only: false,
// Claude Max usage 模拟开关(仅 anthropic 平台)
simulate_claude_max_enabled: false,
fallback_group_id: null as number | null,
fallback_group_id_on_invalid_request: null as number | null,
// OpenAI Messages 调度配置(仅 openai 平台使用)
@@ -2238,6 +2344,8 @@ const editForm = reactive({
sora_storage_quota_gb: null as number | null,
// Claude Code 客户端限制(仅 anthropic 平台使用)
claude_code_only: false,
// Claude Max usage 模拟开关(仅 anthropic 平台)
simulate_claude_max_enabled: false,
fallback_group_id: null as number | null,
fallback_group_id_on_invalid_request: null as number | null,
// OpenAI Messages 调度配置(仅 openai 平台使用)
@@ -2340,6 +2448,7 @@ const closeCreateModal = () => {
createForm.sora_video_price_per_request_hd = null
createForm.sora_storage_quota_gb = null
createForm.claude_code_only = false
createForm.simulate_claude_max_enabled = false
createForm.fallback_group_id = null
createForm.fallback_group_id_on_invalid_request = null
createForm.allow_messages_dispatch = false
@@ -2362,6 +2471,8 @@ const handleCreateGroup = async () => {
const requestData = {
...createRest,
sora_storage_quota_bytes: createQuotaGb ? Math.round(createQuotaGb * 1024 * 1024 * 1024) : 0,
simulate_claude_max_enabled:
createForm.platform === 'anthropic' ? createForm.simulate_claude_max_enabled : false,
model_routing: convertRoutingRulesToApiFormat(createModelRoutingRules.value)
}
await adminAPI.groups.create(requestData)
@@ -2402,6 +2513,7 @@ const handleEdit = async (group: AdminGroup) => {
editForm.sora_video_price_per_request_hd = group.sora_video_price_per_request_hd
editForm.sora_storage_quota_gb = group.sora_storage_quota_bytes ? Number((group.sora_storage_quota_bytes / (1024 * 1024 * 1024)).toFixed(2)) : null
editForm.claude_code_only = group.claude_code_only || false
editForm.simulate_claude_max_enabled = group.simulate_claude_max_enabled || false
editForm.fallback_group_id = group.fallback_group_id
editForm.fallback_group_id_on_invalid_request = group.fallback_group_id_on_invalid_request
editForm.allow_messages_dispatch = group.allow_messages_dispatch || false
@@ -2423,6 +2535,7 @@ const closeEditModal = () => {
showEditModal.value = false
editingGroup.value = null
editModelRoutingRules.value = []
editForm.simulate_claude_max_enabled = false
editForm.copy_accounts_from_group_ids = []
}
@@ -2440,6 +2553,8 @@ const handleUpdateGroup = async () => {
const payload = {
...editRest,
sora_storage_quota_bytes: editQuotaGb ? Math.round(editQuotaGb * 1024 * 1024 * 1024) : 0,
simulate_claude_max_enabled:
editForm.platform === 'anthropic' ? editForm.simulate_claude_max_enabled : false,
fallback_group_id: editForm.fallback_group_id === null ? 0 : editForm.fallback_group_id,
fallback_group_id_on_invalid_request:
editForm.fallback_group_id_on_invalid_request === null
@@ -2500,6 +2615,25 @@ watch(
createForm.allow_messages_dispatch = false
createForm.default_mapped_model = ''
}
if (newVal !== 'anthropic') {
createForm.simulate_claude_max_enabled = false
}
}
)
watch(
() => editForm.platform,
(newVal) => {
if (!['anthropic', 'antigravity'].includes(newVal)) {
editForm.fallback_group_id_on_invalid_request = null
}
if (newVal !== 'openai') {
editForm.allow_messages_dispatch = false
editForm.default_mapped_model = ''
}
if (newVal !== 'anthropic') {
editForm.simulate_claude_max_enabled = false
}
}
)