mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-10 11:42:27 +08:00
feat(ui): support batch endpoint creation with multiple API formats (#76)
Replace single API format selector with multi-select checkbox interface in endpoint creation dialog. Users can now select multiple API formats to create multiple endpoints simultaneously with shared configuration (URL, path, timeout, etc.). - Change API format selection from dropdown to checkbox grid layout - Add selectedFormats array to track multiple format selections - Implement batch creation logic with individual error handling - Update submit button to show endpoint count being created - Adjust form layout to improve visual hierarchy - Display appropriate success/failure messages for batch operations - Reset selectedFormats on form reset
This commit is contained in:
@@ -20,44 +20,45 @@
|
|||||||
API 配置
|
API 配置
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<!-- API 格式 -->
|
||||||
<!-- API 格式 -->
|
<div class="space-y-2">
|
||||||
<div class="space-y-2">
|
<Label for="api_format">API 格式 *</Label>
|
||||||
<Label for="api_format">API 格式 *</Label>
|
<template v-if="isEditMode">
|
||||||
<template v-if="isEditMode">
|
<Input
|
||||||
<Input
|
id="api_format"
|
||||||
id="api_format"
|
v-model="form.api_format"
|
||||||
v-model="form.api_format"
|
disabled
|
||||||
disabled
|
class="bg-muted"
|
||||||
class="bg-muted"
|
/>
|
||||||
/>
|
<p class="text-xs text-muted-foreground">
|
||||||
<p class="text-xs text-muted-foreground">
|
API 格式创建后不可修改
|
||||||
API 格式创建后不可修改
|
</p>
|
||||||
</p>
|
</template>
|
||||||
</template>
|
<template v-else>
|
||||||
<template v-else>
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<Select
|
<label
|
||||||
v-model="form.api_format"
|
v-for="format in apiFormats"
|
||||||
v-model:open="selectOpen"
|
:key="format.value"
|
||||||
required
|
class="flex items-center gap-3 rounded-lg border p-4 cursor-pointer transition-colors hover:bg-accent"
|
||||||
|
:class="selectedFormats.includes(format.value) ? 'border-primary bg-accent' : 'border-border'"
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<input
|
||||||
<SelectValue placeholder="请选择 API 格式" />
|
type="checkbox"
|
||||||
</SelectTrigger>
|
:value="format.value"
|
||||||
<SelectContent>
|
v-model="selectedFormats"
|
||||||
<SelectItem
|
class="h-4 w-4 text-primary focus:ring-2 focus:ring-primary rounded"
|
||||||
v-for="format in apiFormats"
|
/>
|
||||||
:key="format.value"
|
<span class="text-sm font-medium">{{ format.label }}</span>
|
||||||
:value="format.value"
|
</label>
|
||||||
>
|
</div>
|
||||||
{{ format.label }}
|
<p class="text-xs text-muted-foreground">
|
||||||
</SelectItem>
|
选择一个或多个 API 格式,将为每个格式创建独立的端点
|
||||||
</SelectContent>
|
</p>
|
||||||
</Select>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- API URL -->
|
<!-- API URL 和自定义路径 -->
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="base_url">API URL *</Label>
|
<Label for="base_url">API URL *</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -67,16 +68,18 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 自定义路径 -->
|
<div class="space-y-2">
|
||||||
<div class="space-y-2">
|
<Label for="custom_path">自定义请求路径(可选)</Label>
|
||||||
<Label for="custom_path">自定义请求路径(可选)</Label>
|
<Input
|
||||||
<Input
|
id="custom_path"
|
||||||
id="custom_path"
|
v-model="form.custom_path"
|
||||||
v-model="form.custom_path"
|
:placeholder="isEditMode ? defaultPathPlaceholder : '留空使用各格式的默认路径'"
|
||||||
:placeholder="defaultPathPlaceholder"
|
/>
|
||||||
/>
|
<p v-if="!isEditMode && selectedFormats.length > 0" class="text-xs text-muted-foreground">
|
||||||
|
将为所有选中的格式使用相同的 URL 和路径配置
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -217,10 +220,10 @@
|
|||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
:disabled="loading || !form.base_url || (!isEditMode && !form.api_format)"
|
:disabled="loading || !form.base_url || (!isEditMode && selectedFormats.length === 0)"
|
||||||
@click="handleSubmit()"
|
@click="handleSubmit()"
|
||||||
>
|
>
|
||||||
{{ loading ? (isEditMode ? '保存中...' : '创建中...') : (isEditMode ? '保存修改' : '创建') }}
|
{{ loading ? (isEditMode ? '保存中...' : '创建中...') : (isEditMode ? '保存修改' : `创建 ${selectedFormats.length} 个端点`) }}
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -306,6 +309,9 @@ const form = ref({
|
|||||||
proxy_password: '',
|
proxy_password: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 选中的 API 格式(多选)
|
||||||
|
const selectedFormats = ref<string[]>([])
|
||||||
|
|
||||||
// API 格式列表
|
// API 格式列表
|
||||||
const apiFormats = ref<Array<{ value: string; label: string; default_path: string; aliases: string[] }>>([])
|
const apiFormats = ref<Array<{ value: string; label: string; default_path: string; aliases: string[] }>>([])
|
||||||
|
|
||||||
@@ -400,6 +406,7 @@ function resetForm() {
|
|||||||
proxy_username: '',
|
proxy_username: '',
|
||||||
proxy_password: '',
|
proxy_password: '',
|
||||||
}
|
}
|
||||||
|
selectedFormats.value = []
|
||||||
proxyEnabled.value = false
|
proxyEnabled.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,6 +486,8 @@ const handleSubmit = async (skipCredentialCheck = false) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
let successCount = 0
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const proxyConfig = buildProxyConfig()
|
const proxyConfig = buildProxyConfig()
|
||||||
|
|
||||||
@@ -497,27 +506,52 @@ const handleSubmit = async (skipCredentialCheck = false) => {
|
|||||||
|
|
||||||
success('端点已更新', '保存成功')
|
success('端点已更新', '保存成功')
|
||||||
emit('endpointUpdated')
|
emit('endpointUpdated')
|
||||||
|
emit('update:modelValue', false)
|
||||||
} else if (props.provider) {
|
} else if (props.provider) {
|
||||||
// 创建端点
|
// 批量创建端点
|
||||||
await createEndpoint(props.provider.id, {
|
let failCount = 0
|
||||||
provider_id: props.provider.id,
|
const errors: string[] = []
|
||||||
api_format: form.value.api_format,
|
|
||||||
base_url: form.value.base_url,
|
|
||||||
custom_path: form.value.custom_path || undefined,
|
|
||||||
timeout: form.value.timeout,
|
|
||||||
max_retries: form.value.max_retries,
|
|
||||||
max_concurrent: form.value.max_concurrent,
|
|
||||||
rate_limit: form.value.rate_limit,
|
|
||||||
is_active: form.value.is_active,
|
|
||||||
proxy: proxyConfig,
|
|
||||||
})
|
|
||||||
|
|
||||||
success('端点创建成功', '成功')
|
for (const apiFormat of selectedFormats.value) {
|
||||||
emit('endpointCreated')
|
try {
|
||||||
resetForm()
|
await createEndpoint(props.provider.id, {
|
||||||
|
provider_id: props.provider.id,
|
||||||
|
api_format: apiFormat,
|
||||||
|
base_url: form.value.base_url,
|
||||||
|
custom_path: form.value.custom_path || undefined,
|
||||||
|
timeout: form.value.timeout,
|
||||||
|
max_retries: form.value.max_retries,
|
||||||
|
max_concurrent: form.value.max_concurrent,
|
||||||
|
rate_limit: form.value.rate_limit,
|
||||||
|
is_active: form.value.is_active,
|
||||||
|
proxy: proxyConfig,
|
||||||
|
})
|
||||||
|
successCount++
|
||||||
|
} catch (error: any) {
|
||||||
|
failCount++
|
||||||
|
const formatLabel = apiFormats.value.find((f: any) => f.value === apiFormat)?.label || apiFormat
|
||||||
|
errors.push(`${formatLabel}: ${error.response?.data?.detail || '创建失败'}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示结果
|
||||||
|
if (successCount > 0 && failCount === 0) {
|
||||||
|
success(`成功创建 ${successCount} 个端点`, '创建成功')
|
||||||
|
} else if (successCount > 0 && failCount > 0) {
|
||||||
|
success(`成功创建 ${successCount} 个端点,${failCount} 个失败`, '部分成功')
|
||||||
|
if (errors.length > 0) {
|
||||||
|
log.error('创建端点失败:', errors)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showError(errors.join('\n') || '创建端点失败', '创建失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successCount > 0) {
|
||||||
|
emit('endpointCreated')
|
||||||
|
resetForm()
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('update:modelValue', false)
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const action = isEditMode.value ? '更新' : '创建'
|
const action = isEditMode.value ? '更新' : '创建'
|
||||||
showError(error.response?.data?.detail || `${action}端点失败`, '错误')
|
showError(error.response?.data?.detail || `${action}端点失败`, '错误')
|
||||||
|
|||||||
Reference in New Issue
Block a user