mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
feat: 添加格式转换追踪、模型过滤规则和 Provider 超时配置
1. Usage 格式转换追踪 - 新增 endpoint_api_format 和 has_format_conversion 字段 - 在用量记录中显示格式转换信息(请求格式 → 端点格式) - 兼容历史数据的回填逻辑 2. Provider API Key 模型过滤规则 - 新增 model_include_patterns 和 model_exclude_patterns 字段 - 支持 * 和 ? 通配符,不区分大小写 - 自动获取模型时应用过滤规则 3. Provider 超时配置 - 新增 stream_first_byte_timeout 和 request_timeout 字段 - 支持每个 Provider 单独配置超时时间 - 优先使用 Provider 配置,否则回退到全局配置 Close #122, Close #123 Co-Authored-By: AAEE86 <33052466+AAEE86@users.noreply.github.com>
This commit is contained in:
@@ -754,14 +754,12 @@ watch(() => props.open, async (open) => {
|
||||
// 加载全局模型
|
||||
await loadGlobalModels()
|
||||
|
||||
// 自动获取模式下,获取上游模型并刷新(保留锁定的模型)
|
||||
// 自动获取模式下,获取上游模型用于显示(但选中状态使用已保存的 allowed_models)
|
||||
if (props.apiKey.auto_fetch_models) {
|
||||
await fetchUpstreamModels()
|
||||
// 锁定的模型 + 最新上游模型(去重)
|
||||
const newSelected = new Set(lockedModels.value)
|
||||
upstreamModelNames.value.forEach(m => newSelected.add(m))
|
||||
selectedModels.value = Array.from(newSelected)
|
||||
initialSelectedModels.value = [...selectedModels.value]
|
||||
// 注意:不再将所有上游模型自动标记为选中
|
||||
// 因为后端有过滤规则,实际保存的 allowed_models 是过滤后的结果
|
||||
// selectedModels 已在上面从 props.apiKey.allowed_models 初始化
|
||||
}
|
||||
|
||||
// 提取自定义模型(不在全局模型和上游模型中的)
|
||||
|
||||
@@ -211,20 +211,48 @@
|
||||
</div>
|
||||
|
||||
<!-- 自动获取模型 -->
|
||||
<div class="flex items-center justify-between py-2 px-3 rounded-md border border-border/60 bg-muted/30">
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-sm font-medium">自动获取上游可用模型</Label>
|
||||
<div class="space-y-3 py-2 px-3 rounded-md border border-border/60 bg-muted/30">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-sm font-medium">自动获取上游可用模型</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
定时更新上游模型, 配合模型映射使用
|
||||
</p>
|
||||
<p
|
||||
v-if="showAutoFetchWarning"
|
||||
class="text-xs text-amber-600 dark:text-amber-400"
|
||||
>
|
||||
已配置的模型权限将在下次获取时被覆盖
|
||||
</p>
|
||||
</div>
|
||||
<Switch v-model="form.auto_fetch_models" />
|
||||
</div>
|
||||
|
||||
<!-- 模型过滤规则(仅当开启自动获取时显示) -->
|
||||
<div
|
||||
v-if="form.auto_fetch_models"
|
||||
class="space-y-2 pt-2 border-t border-border/40"
|
||||
>
|
||||
<div>
|
||||
<Label class="text-xs">包含规则</Label>
|
||||
<Input
|
||||
v-model="form.model_include_patterns_text"
|
||||
placeholder="gpt-*, claude-*, 留空包含全部"
|
||||
class="h-8 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label class="text-xs">排除规则</Label>
|
||||
<Input
|
||||
v-model="form.model_exclude_patterns_text"
|
||||
placeholder="*-preview, *-beta"
|
||||
class="h-8 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
定时更新上游模型, 配合模型映射使用
|
||||
</p>
|
||||
<p
|
||||
v-if="showAutoFetchWarning"
|
||||
class="text-xs text-amber-600 dark:text-amber-400"
|
||||
>
|
||||
已配置的模型权限将在下次获取时被覆盖
|
||||
逗号分隔,支持 * ? 通配符,不区分大小写
|
||||
</p>
|
||||
</div>
|
||||
<Switch v-model="form.auto_fetch_models" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -335,7 +363,9 @@ const form = ref({
|
||||
note: '',
|
||||
is_active: true,
|
||||
capabilities: {} as Record<string, boolean>,
|
||||
auto_fetch_models: false
|
||||
auto_fetch_models: false,
|
||||
model_include_patterns_text: '', // 包含规则文本(逗号分隔)
|
||||
model_exclude_patterns_text: '' // 排除规则文本(逗号分隔)
|
||||
})
|
||||
|
||||
// 加载能力列表
|
||||
@@ -419,7 +449,9 @@ function resetForm() {
|
||||
note: '',
|
||||
is_active: true,
|
||||
capabilities: {},
|
||||
auto_fetch_models: false
|
||||
auto_fetch_models: false,
|
||||
model_include_patterns_text: '',
|
||||
model_exclude_patterns_text: ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,7 +481,9 @@ function loadKeyData() {
|
||||
note: props.editingKey.note || '',
|
||||
is_active: props.editingKey.is_active,
|
||||
capabilities: { ...(props.editingKey.capabilities || {}) },
|
||||
auto_fetch_models: props.editingKey.auto_fetch_models ?? false
|
||||
auto_fetch_models: props.editingKey.auto_fetch_models ?? false,
|
||||
model_include_patterns_text: (props.editingKey.model_include_patterns || []).join(', '),
|
||||
model_exclude_patterns_text: (props.editingKey.model_exclude_patterns || []).join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,6 +501,17 @@ function createFieldNonce(): string {
|
||||
return Math.random().toString(36).slice(2, 10)
|
||||
}
|
||||
|
||||
// 将逗号分隔的文本解析为数组(去空、去重)
|
||||
// 返回空数组而非 undefined,以便后端能正确清除已有规则
|
||||
function parsePatternText(text: string): string[] {
|
||||
if (!text.trim()) return []
|
||||
const patterns = text
|
||||
.split(',')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0)
|
||||
return [...new Set(patterns)]
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
// 必须有 providerId
|
||||
if (!props.providerId) {
|
||||
@@ -529,7 +574,9 @@ async function handleSave() {
|
||||
note: form.value.note,
|
||||
is_active: form.value.is_active,
|
||||
capabilities: capabilitiesData,
|
||||
auto_fetch_models: form.value.auto_fetch_models
|
||||
auto_fetch_models: form.value.auto_fetch_models,
|
||||
model_include_patterns: parsePatternText(form.value.model_include_patterns_text),
|
||||
model_exclude_patterns: parsePatternText(form.value.model_exclude_patterns_text)
|
||||
}
|
||||
|
||||
if (form.value.api_key.trim()) {
|
||||
@@ -551,7 +598,9 @@ async function handleSave() {
|
||||
max_probe_interval_minutes: form.value.max_probe_interval_minutes,
|
||||
note: form.value.note,
|
||||
capabilities: capabilitiesData || undefined,
|
||||
auto_fetch_models: form.value.auto_fetch_models
|
||||
auto_fetch_models: form.value.auto_fetch_models,
|
||||
model_include_patterns: parsePatternText(form.value.model_include_patterns_text),
|
||||
model_exclude_patterns: parsePatternText(form.value.model_exclude_patterns_text)
|
||||
})
|
||||
success('密钥已添加', '成功')
|
||||
// 添加模式:不关闭对话框,只清除名称和密钥以便继续添加
|
||||
|
||||
@@ -93,6 +93,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 超时配置 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
流式首字节超时
|
||||
<span class="text-xs text-muted-foreground">(秒)</span>
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.stream_first_byte_timeout ?? ''"
|
||||
type="number"
|
||||
min="1"
|
||||
max="300"
|
||||
step="1"
|
||||
placeholder="30"
|
||||
@update:model-value="(v) => form.stream_first_byte_timeout = parseNumberInput(v)"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
非流式请求超时
|
||||
<span class="text-xs text-muted-foreground">(秒)</span>
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.request_timeout ?? ''"
|
||||
type="number"
|
||||
min="1"
|
||||
max="600"
|
||||
step="1"
|
||||
placeholder="300"
|
||||
@update:model-value="(v) => form.request_timeout = parseNumberInput(v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 月卡配置 -->
|
||||
<div
|
||||
v-if="form.billing_type === 'monthly_quota'"
|
||||
@@ -267,6 +301,9 @@ const form = ref({
|
||||
concurrent_limit: undefined as number | undefined,
|
||||
// 请求配置
|
||||
max_retries: undefined as number | undefined,
|
||||
// 超时配置(秒)
|
||||
stream_first_byte_timeout: undefined as number | undefined,
|
||||
request_timeout: undefined as number | undefined,
|
||||
// 代理配置(扁平化便于表单绑定)
|
||||
proxy_enabled: false,
|
||||
proxy_url: '',
|
||||
@@ -291,6 +328,9 @@ function resetForm() {
|
||||
concurrent_limit: undefined,
|
||||
// 请求配置
|
||||
max_retries: undefined,
|
||||
// 超时配置
|
||||
stream_first_byte_timeout: undefined,
|
||||
request_timeout: undefined,
|
||||
// 代理配置
|
||||
proxy_enabled: false,
|
||||
proxy_url: '',
|
||||
@@ -321,6 +361,9 @@ function loadProviderData() {
|
||||
concurrent_limit: undefined,
|
||||
// 请求配置
|
||||
max_retries: props.provider.max_retries ?? undefined,
|
||||
// 超时配置
|
||||
stream_first_byte_timeout: props.provider.stream_first_byte_timeout ?? undefined,
|
||||
request_timeout: props.provider.request_timeout ?? undefined,
|
||||
// 代理配置
|
||||
proxy_enabled: proxy?.enabled ?? false,
|
||||
proxy_url: proxy?.url || '',
|
||||
@@ -376,6 +419,9 @@ const handleSubmit = async () => {
|
||||
is_active: form.value.is_active,
|
||||
// 请求配置
|
||||
max_retries: form.value.max_retries ?? undefined,
|
||||
// 超时配置(null 表示清除,使用全局配置)
|
||||
stream_first_byte_timeout: form.value.stream_first_byte_timeout ?? null,
|
||||
request_timeout: form.value.request_timeout ?? null,
|
||||
proxy,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user