新增: F-01模型拉取IPC+Settings拉取UI(FR-S1密钥内存解析闭环)
This commit is contained in:
@@ -92,6 +92,38 @@
|
||||
<button class="btn btn-primary" @click="saveProvider" :disabled="providerForm.saving">
|
||||
{{ providerForm.saving ? $t('settings.saving') : $t('common.save') }}
|
||||
</button>
|
||||
<button class="btn btn-ghost" @click="fetchModels" :disabled="providerForm.fetching || !providerForm.editId">
|
||||
{{ providerForm.fetching ? $t('settings.fetchingModels') : $t('settings.testConnection') }}
|
||||
</button>
|
||||
<span v-if="!providerForm.editId" class="form-hint">{{ $t('settings.fetchHintSaveFirst') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 拉取到的模型列表(F-01 阶段6)═══ -->
|
||||
<div class="form-field form-field--full" v-if="providerForm.models.length > 0">
|
||||
<label class="form-label">{{ $t('settings.modelListTitle', { count: providerForm.models.length }) }}</label>
|
||||
<div class="model-list">
|
||||
<div class="model-row" v-for="m in providerForm.models" :key="m.model_id">
|
||||
<div class="model-row-main">
|
||||
<label class="toggle toggle--sm">
|
||||
<input type="checkbox" v-model="m.enabled" />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<span class="model-id">{{ m.model_id }}</span>
|
||||
<div class="model-tags">
|
||||
<span v-for="md in m.modalities" :key="'md-' + md" class="tag tag-modality">{{ $t('settings.tagModality.' + md) }}</span>
|
||||
<span v-for="cap in m.capabilities" :key="'cap-' + cap" class="tag tag-capability">{{ $t('settings.tagCapability.' + cap) }}</span>
|
||||
<span class="tag tag-cost" :class="'tag-cost--' + m.cost_tier">{{ $t('settings.tagCost.' + m.cost_tier) }}</span>
|
||||
<span class="tag tag-intel" :class="'tag-intel--' + m.intelligence">{{ $t('settings.tagIntel.' + m.intelligence) }}</span>
|
||||
<span v-if="m.probe_source" class="tag tag-source">{{ $t('settings.tagSource.' + m.probe_source) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-row-side">
|
||||
<label class="model-weight-label">{{ $t('settings.labelWeight') }}</label>
|
||||
<input type="number" class="setting-input setting-input--sm" min="0" max="100" v-model.number="m.weight" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-list-hint">{{ $t('settings.modelListHint') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -397,7 +429,7 @@ import { aiApi, knowledgeApi } from '@/api'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useAiStore } from '@/stores/ai'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import type { AiProviderConfig } from '@/api/types'
|
||||
import type { AiProviderConfig, ModelConfig } from '@/api/types'
|
||||
import i18n from '@/i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -437,6 +469,9 @@ const providerForm = reactive({
|
||||
apiKey: '',
|
||||
defaultModel: '',
|
||||
saving: false,
|
||||
// F-01 阶段6:模型拉取 + 列表
|
||||
fetching: false,
|
||||
models: [] as ModelConfig[],
|
||||
})
|
||||
|
||||
async function loadProviders() {
|
||||
@@ -458,6 +493,8 @@ function openProviderForm(p?: AiProviderConfig) {
|
||||
providerForm.apiKey = '' // 编辑不回填 api_key(list 返回 mask,留空=不改,FR-S1)
|
||||
providerForm.defaultModel = p.default_model
|
||||
providerForm.providerType = p.provider_type || 'openai_compat'
|
||||
// F-01 阶段6:回填已保存的 model_configs(深拷贝避免双向绑定污染列表源)
|
||||
providerForm.models = (p.model_configs ?? []).map(m => ({ ...m, modalities: [...m.modalities], capabilities: [...m.capabilities] }))
|
||||
} else {
|
||||
providerForm.editId = ''
|
||||
providerForm.name = ''
|
||||
@@ -465,10 +502,43 @@ function openProviderForm(p?: AiProviderConfig) {
|
||||
providerForm.baseUrl = ''
|
||||
providerForm.apiKey = ''
|
||||
providerForm.defaultModel = ''
|
||||
providerForm.models = []
|
||||
}
|
||||
providerForm.fetching = false
|
||||
providerForm.visible = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试连接并拉取模型列表(F-01 阶段6)。
|
||||
* 后端 ai_fetch_models:DB 取 provider + 内存解析 api_key(FR-S1)+ 网络拉取 + 探测 4 维度 + 写回。
|
||||
* 新建态(editId 空)无 provider_id → 按钮已 disabled,提示先保存。
|
||||
*
|
||||
* enabled toggle / weight input 为本地会话内编辑(仅本批展示交互,持久化另见 issues);
|
||||
* 下次「测试连接」会以最新探测结果覆盖 model_configs 落库。
|
||||
*/
|
||||
async function fetchModels() {
|
||||
if (!providerForm.editId) {
|
||||
showToast(t('settings.fetchHintSaveFirst'), 'warning')
|
||||
return
|
||||
}
|
||||
providerForm.fetching = true
|
||||
try {
|
||||
const models = await aiApi.fetchModels(providerForm.editId)
|
||||
providerForm.models = models.map(m => ({ ...m, modalities: [...m.modalities], capabilities: [...m.capabilities] }))
|
||||
showToast(t('settings.toastFetchOk', { count: models.length }), 'info')
|
||||
} catch (e) {
|
||||
const msg = errMsg(e)
|
||||
// 按 model_fetch 错误映射友好提示:超时 / 鉴权失败 / 端点不存在 / 厂商服务异常 / 不支持
|
||||
let key = 'settings.toastFetchFail'
|
||||
if (msg.includes('超时')) key = 'settings.fetchFailedTimeout'
|
||||
else if (msg.includes('鉴权失败')) key = 'settings.fetchFailedAuth'
|
||||
else if (msg.includes('端点不存在') || msg.includes('404')) key = 'settings.fetchFailedNotFound'
|
||||
showToast(t(key, { msg }), 'error')
|
||||
} finally {
|
||||
providerForm.fetching = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProvider() {
|
||||
// apiKey:新建必填;编辑可留空(不改,FR-S1 后端保留原 DB 值)
|
||||
const needKey = !providerForm.editId
|
||||
|
||||
Reference in New Issue
Block a user