mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat(provider): 增加 Claude Code 适配器、高级配置能力与 OAuth 账号类型统一解析
- 新增 Claude Code provider adapter (context, envelope, plugin, constants) - 扩展 provider admin 路由,支持 Claude Code 高级配置 (CRUD) - 统一 OAuth 账号类型解析逻辑,前后端对齐 - 重构 BatchAssignModelsDialog / ModelMappingDialog,简化组件逻辑 - handler 基类增强: request_builder 支持 Claude Code 信封格式 - CLI stream/sync mixin 适配 Claude Code 流式与同步模式 - 扩展 candidate builder / failover / scheduler 对 Claude Code 的支持 - 前端增加请求时间线可视化 (HorizontalRequestTimeline) - 补充 Claude Code envelope / runtime controls / distributed sessions 等测试 Closes #183 Closes #185 Co-Authored-By: AAEE86 <ppk0227@hotmail.com>
This commit is contained in:
@@ -252,6 +252,62 @@
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentAttempt.extra_data?.pool_selection"
|
||||
class="info-item"
|
||||
>
|
||||
<span class="info-label">号池调度</span>
|
||||
<span class="info-value info-value-stacked">
|
||||
<span class="pool-reason">
|
||||
<span
|
||||
class="pool-reason-tag"
|
||||
:class="'pool-' + currentAttempt.extra_data.pool_selection.reason"
|
||||
>
|
||||
{{ poolSelectionLabel(currentAttempt.extra_data.pool_selection.reason) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="currentAttempt.extra_data.pool_selection.cost_soft_threshold"
|
||||
class="pool-cost-warn"
|
||||
>接近限额</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="currentAttempt.extra_data.pool_selection.cost_window_usage"
|
||||
class="text-xs text-muted-foreground"
|
||||
>
|
||||
{{ formatNumber(currentAttempt.extra_data.pool_selection.cost_window_usage) }}
|
||||
<template v-if="currentAttempt.extra_data.pool_selection.cost_limit">
|
||||
/ {{ formatNumber(currentAttempt.extra_data.pool_selection.cost_limit) }}
|
||||
</template>
|
||||
tokens
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentAttempt.extra_data?.pool_skip"
|
||||
class="info-item"
|
||||
>
|
||||
<span class="info-label">号池跳过</span>
|
||||
<span class="info-value info-value-stacked">
|
||||
<span class="pool-skip-type">
|
||||
{{ poolSkipLabel(currentAttempt.extra_data.pool_skip.type) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="currentAttempt.extra_data.pool_skip.cooldown_reason"
|
||||
class="text-xs text-muted-foreground"
|
||||
>
|
||||
{{ currentAttempt.extra_data.pool_skip.cooldown_reason }}
|
||||
<template v-if="currentAttempt.extra_data.pool_skip.cooldown_ttl != null">
|
||||
({{ currentAttempt.extra_data.pool_skip.cooldown_ttl }}s)
|
||||
</template>
|
||||
</span>
|
||||
<span
|
||||
v-if="currentAttempt.extra_data.pool_skip.cost_window_usage"
|
||||
class="text-xs text-muted-foreground"
|
||||
>
|
||||
{{ formatNumber(currentAttempt.extra_data.pool_skip.cost_window_usage) }} tokens
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="mergedCapabilities.length > 0"
|
||||
class="info-item"
|
||||
@@ -764,6 +820,25 @@ const formatCapabilityLabel = (cap: string): string => {
|
||||
return labels[cap] || cap
|
||||
}
|
||||
|
||||
const poolSelectionLabel = (reason: string): string => {
|
||||
const labels: Record<string, string> = {
|
||||
sticky: '粘性会话',
|
||||
lru: 'LRU',
|
||||
random: '随机',
|
||||
tiebreak: '随机 (平分)',
|
||||
}
|
||||
return labels[reason] || reason
|
||||
}
|
||||
|
||||
const poolSkipLabel = (type: string): string => {
|
||||
const labels: Record<string, string> = {
|
||||
cooldown: '冷却中',
|
||||
cost_exhausted: '额度耗尽',
|
||||
upstream: '上游跳过',
|
||||
}
|
||||
return labels[type] || type
|
||||
}
|
||||
|
||||
// 检查组是否被悬浮
|
||||
const isGroupHovered = (groupIndex: number) => {
|
||||
return hoveredGroupIndex.value === groupIndex
|
||||
@@ -1485,6 +1560,52 @@ const getStatusColorClass = (status: string) => {
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
/* 号池调度 */
|
||||
.pool-reason {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.pool-reason-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.15rem 0.5rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.pool-reason-tag.pool-sticky {
|
||||
color: hsl(var(--chart-4));
|
||||
border-color: hsl(var(--chart-4) / 0.3);
|
||||
background: hsl(var(--chart-4) / 0.08);
|
||||
}
|
||||
|
||||
.pool-reason-tag.pool-lru {
|
||||
color: hsl(var(--chart-2));
|
||||
border-color: hsl(var(--chart-2) / 0.3);
|
||||
background: hsl(var(--chart-2) / 0.08);
|
||||
}
|
||||
|
||||
.pool-reason-tag.pool-random,
|
||||
.pool-reason-tag.pool-tiebreak {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.pool-cost-warn {
|
||||
font-size: 0.65rem;
|
||||
color: hsl(var(--chart-5));
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pool-skip-type {
|
||||
font-weight: 500;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* 能力标签 */
|
||||
.capability-tags {
|
||||
display: flex;
|
||||
|
||||
@@ -370,6 +370,51 @@
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- 号池调度摘要 -->
|
||||
<Card v-if="poolSummary">
|
||||
<div class="p-3 sm:p-4">
|
||||
<div class="text-xs text-muted-foreground mb-2 font-medium">
|
||||
号池调度
|
||||
</div>
|
||||
<div class="flex items-center gap-3 flex-wrap text-sm">
|
||||
<span class="font-mono">
|
||||
{{ poolSummary.total_keys }} 候选
|
||||
</span>
|
||||
<span class="text-muted-foreground">|</span>
|
||||
<span class="font-mono">
|
||||
{{ poolSummary.attempted }} 尝试
|
||||
</span>
|
||||
<template v-if="poolSummary.skipped_cooldown > 0">
|
||||
<span class="text-muted-foreground">|</span>
|
||||
<span class="font-mono text-amber-600 dark:text-amber-400">
|
||||
{{ poolSummary.skipped_cooldown }} 冷却跳过
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="poolSummary.skipped_cost > 0">
|
||||
<span class="text-muted-foreground">|</span>
|
||||
<span class="font-mono text-orange-600 dark:text-orange-400">
|
||||
{{ poolSummary.skipped_cost }} 成本跳过
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="poolSummary.sticky_session">
|
||||
<span class="text-muted-foreground">|</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="text-[10px] px-1.5 py-0 h-4"
|
||||
>
|
||||
粘性会话
|
||||
</Badge>
|
||||
</template>
|
||||
<template v-if="poolSummary.success_reason">
|
||||
<span class="text-muted-foreground">|</span>
|
||||
<span class="text-xs text-muted-foreground">
|
||||
{{ poolSummary.success_reason }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- 请求链路追踪卡片 -->
|
||||
<div v-if="detail.request_id || detail.id">
|
||||
<HorizontalRequestTimeline
|
||||
@@ -733,6 +778,13 @@ const isDark = computed(() => {
|
||||
return document.documentElement.classList.contains('dark')
|
||||
})
|
||||
|
||||
// 号池调度摘要
|
||||
const poolSummary = computed(() => {
|
||||
const ps = detail.value?.metadata?.pool_summary as Record<string, unknown> | undefined
|
||||
if (!ps || !ps.enabled) return null
|
||||
return ps
|
||||
})
|
||||
|
||||
// 检测是否有提供商请求头
|
||||
const hasProviderHeaders = computed(() => {
|
||||
return !!(detail.value?.provider_request_headers &&
|
||||
|
||||
Reference in New Issue
Block a user