优化页面交互并增强 Provider 配置能力
Build / build (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s

- 表单下拉框不再用 label 包裹,改为 div + aria-label,避免点击后
  下拉面板瞬间关闭
- Provider 卡片新增「从上游获取模型」和「修改」入口,支持编辑
  base URL、API Key 与 api_mode,凭据写入对应的环境变量
- 服务端抽出 provider-model-discovery 统一不同 api_mode 的模型发现,
  旧的 custom_providers 与新的 providers 字典结构都能正确更新
- Agent Bridge 改为后台启动,HTTP 接口在 Python 运行时就绪前即可用;
  worker 连接被拒时按类型重试,不重放可能已产生副作用的请求
- 断线重连后恢复 profiles、models 与会话列表,健康检查改为自调度
  的 setTimeout,离线时 2 秒后重试
- 批量删除确认文案改用 Popconfirm content,避免变成侧栏触发器文本
This commit is contained in:
2026-10-22 14:29:48 +08:00
committed by trae-solo-agent
parent 90c759c760
commit 3ca960f388
35 changed files with 876 additions and 78 deletions
@@ -6,6 +6,8 @@ import type { AvailableModelGroup } from '@/api/hermes/system'
import { useModelsStore } from '@/stores/hermes/models'
import { useAppStore } from '@/stores/hermes/app'
import { useChatStore } from '@/stores/hermes/chat'
import ProviderEditModal from './ProviderEditModal.vue'
import { fetchProviderModels } from '@/api/hermes/system'
import { checkCopilotToken, disableCopilot } from '@/api/hermes/copilot-auth'
import { useI18n } from 'vue-i18n'
@@ -24,6 +26,32 @@ const isConfigBackedProvider = computed(() => isCustomProviderKey.value || (!pro
const isCopilot = computed(() => props.provider.provider === 'copilot')
const displayName = computed(() => props.provider.label)
const deleting = ref(false)
const showEditModal = ref(false)
const fetchingUpstream = ref(false)
const canEditProvider = computed(() => isConfigBackedProvider.value || props.provider.credentials_editable || !!props.provider.base_url_env)
async function handleFetchUpstream() {
if (fetchingUpstream.value) return
fetchingUpstream.value = true
try {
const result = await fetchProviderModels({
provider: props.provider.provider,
label: props.provider.label,
base_url: props.provider.base_url,
api_key: props.provider.api_key,
update_cache: true,
api_mode: props.provider.api_mode,
freeOnly: props.provider.provider === 'openrouter',
})
await modelsStore.fetchProviders()
await appStore.reloadModels()
message.success(t('models.upstreamModelsFetched', { count: result.models.length }))
} catch (e: any) {
message.error(e?.message || t('models.upstreamModelsFailed'))
} finally {
fetchingUpstream.value = false
}
}
const destructiveActionLabel = computed(() => {
if (isConfigBackedProvider.value) return t('common.delete')
if (isCopilot.value) return t('models.disableProvider')
@@ -302,11 +330,15 @@ async function handleDelete() {
</div>
<div class="cy-panel-b card-actions">
<a-button size="mini" type="text" :loading="fetchingUpstream" :disabled="!provider.base_url || showEditModal" @click="handleFetchUpstream">{{ t('models.fetchUpstreamModels') }}</a-button>
<a-button v-if="canEditProvider" size="mini" type="text" :disabled="fetchingUpstream || deleting" @click="showEditModal = true">{{ t('common.edit') }}</a-button>
<a-button size="mini" type="text" @click="showAliasListModal = true">{{ t('models.aliasManage') }}</a-button>
<a-button size="mini" type="text" @click="openVisibilityModal">{{ t('models.manageVisibleModels') }}</a-button>
<a-button size="mini" type="text" status="danger" :loading="deleting" @click="handleDelete">{{ destructiveActionLabel }}</a-button>
</div>
<ProviderEditModal v-if="showEditModal" :provider="provider" @close="showEditModal = false" />
<a-modal
v-model:visible="showAliasListModal"
:footer="false"