mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 02:17:46 +08:00
fix(admin): persist provider settings and enforce quotas
This commit is contained in:
@@ -941,7 +941,11 @@
|
||||
<SelectTrigger class="h-auto w-auto gap-1.5 !border-0 bg-transparent !shadow-none p-0 font-medium rounded-none flex-row-reverse !ring-0 !ring-offset-0 !outline-none [&>svg]:h-4 [&>svg]:w-4 [&>svg]:opacity-70">
|
||||
<SelectValue placeholder="选择格式..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectContent
|
||||
:disable-portal="false"
|
||||
align="start"
|
||||
class="max-h-[min(24rem,var(--radix-select-content-available-height))] min-w-[max(12rem,var(--radix-select-trigger-width))]"
|
||||
>
|
||||
<SelectItem
|
||||
v-for="format in availableFormats"
|
||||
:key="format.value"
|
||||
|
||||
@@ -407,7 +407,7 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
saved: []
|
||||
saved: [key: EndpointAPIKey]
|
||||
}>()
|
||||
|
||||
const { success, error: showError } = useToast()
|
||||
@@ -1001,11 +1001,12 @@ async function handleSave() {
|
||||
updateData.auth_config = authConfig
|
||||
}
|
||||
|
||||
await updateProviderKey(props.editingKey.id, updateData)
|
||||
const updatedKey = await updateProviderKey(props.editingKey.id, updateData)
|
||||
success(legacyT('密钥已更新'), legacyT('成功'))
|
||||
emit('saved', updatedKey)
|
||||
} else {
|
||||
// 新增模式
|
||||
await addProviderKey(props.providerId, {
|
||||
const createdKey = await addProviderKey(props.providerId, {
|
||||
api_formats: form.value.api_formats,
|
||||
api_key: form.value.api_key,
|
||||
auth_type: form.value.auth_type,
|
||||
@@ -1027,12 +1028,11 @@ async function handleSave() {
|
||||
|
||||
success(legacyT('密钥已添加'), legacyT('成功'))
|
||||
// 添加模式:不关闭对话框,只清除名称和密钥以便继续添加
|
||||
emit('saved')
|
||||
emit('saved', createdKey)
|
||||
clearForNextAdd()
|
||||
return
|
||||
}
|
||||
|
||||
emit('saved')
|
||||
emit('close')
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = parseApiError(err, legacyT('保存密钥失败'))
|
||||
|
||||
@@ -217,7 +217,7 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
saved: []
|
||||
saved: [key: EndpointAPIKey]
|
||||
}>()
|
||||
|
||||
const { success, error: showError } = useToast()
|
||||
@@ -390,9 +390,9 @@ async function handleSave() {
|
||||
model_exclude_patterns: parsePatternText(form.value.model_exclude_patterns_text)
|
||||
}
|
||||
|
||||
await updateProviderKey(props.editingKey.id, updateData)
|
||||
const updatedKey = await updateProviderKey(props.editingKey.id, updateData)
|
||||
success('账号已更新', '成功')
|
||||
emit('saved')
|
||||
emit('saved', updatedKey)
|
||||
emit('close')
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = parseApiError(err, '保存失败')
|
||||
|
||||
@@ -2896,8 +2896,21 @@ async function openAntigravityQuotaDialog(key: EndpointAPIKey) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleKeyChanged() {
|
||||
function applyUpdatedKeySnapshot(updatedKey: EndpointAPIKey) {
|
||||
const index = providerKeys.value.findIndex(key => key.id === updatedKey.id)
|
||||
if (index >= 0) {
|
||||
providerKeys.value.splice(index, 1, updatedKey)
|
||||
}
|
||||
if (editingKey.value?.id === updatedKey.id) {
|
||||
editingKey.value = updatedKey
|
||||
}
|
||||
syncCurrentSelections(endpoints.value, providerKeys.value)
|
||||
}
|
||||
|
||||
async function handleKeyChanged(updatedKey?: EndpointAPIKey) {
|
||||
if (updatedKey) applyUpdatedKeySnapshot(updatedKey)
|
||||
await Promise.all([loadProvider(), loadEndpoints(), loadMappingPreview()])
|
||||
if (updatedKey) applyUpdatedKeySnapshot(updatedKey)
|
||||
emit('refresh')
|
||||
// 添加/修改 key 后自动获取已支持 provider 的配额(新 key 的 upstream_metadata 为空)
|
||||
void autoRefreshQuotaInBackground().then((changed) => {
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
function readSource(path: string): string {
|
||||
return readFileSync(resolve(process.cwd(), path), 'utf8')
|
||||
}
|
||||
|
||||
describe('endpoint form dialog layout', () => {
|
||||
it('portals the format selector outside the clipped dialog', () => {
|
||||
const source = readSource('src/features/providers/components/EndpointFormDialog.vue')
|
||||
const modelIndex = source.indexOf('v-model="newEndpoint.api_format"')
|
||||
const selectStart = source.lastIndexOf('<Select', modelIndex)
|
||||
const formatSelector = source.slice(
|
||||
selectStart,
|
||||
source.indexOf('</Select>', modelIndex),
|
||||
)
|
||||
|
||||
expect(modelIndex).toBeGreaterThan(-1)
|
||||
expect(selectStart).toBeGreaterThan(-1)
|
||||
expect(formatSelector).toContain(':disable-portal="false"')
|
||||
expect(formatSelector).toContain('var(--radix-select-content-available-height)')
|
||||
expect(formatSelector).toContain('var(--radix-select-trigger-width)')
|
||||
})
|
||||
})
|
||||
+5
@@ -372,6 +372,9 @@ describe('provider key concurrent_limit form behavior', () => {
|
||||
})
|
||||
|
||||
it('hydrates and serializes a positive concurrent_limit number from the normal key form', async () => {
|
||||
const saved = vi.fn()
|
||||
const updatedKey = createProviderKey({ rpm_limit: 42, concurrent_limit: 5 })
|
||||
endpointMocks.updateProviderKey.mockResolvedValue(updatedKey)
|
||||
const root = mountDialog(KeyFormDialog, {
|
||||
open: true,
|
||||
endpoint: null,
|
||||
@@ -379,6 +382,7 @@ describe('provider key concurrent_limit form behavior', () => {
|
||||
providerId: 'provider-1',
|
||||
providerType: 'openai',
|
||||
availableApiFormats: ['openai:chat'],
|
||||
onSaved: saved,
|
||||
})
|
||||
await settle()
|
||||
|
||||
@@ -394,6 +398,7 @@ describe('provider key concurrent_limit form behavior', () => {
|
||||
expect(typeof payload.concurrent_limit).toBe('number')
|
||||
expect(payload.concurrent_limit).not.toBe('')
|
||||
expect(payload.rpm_limit).toBe(42)
|
||||
expect(saved).toHaveBeenCalledWith(updatedKey)
|
||||
})
|
||||
|
||||
it('serializes cleared normal key concurrent_limit as null instead of an empty string', async () => {
|
||||
|
||||
Reference in New Issue
Block a user