优化: 对话交互修复与界面完善
- 修输入法组词回车直发/分离窗误关/出错后按钮死锁 - 新建任务失败弹窗内联提示,会话切换乐观高亮 - 状态多选联动刷新不再冲空可见行 - 测试连接保留已配开关与权重,防抖按提供商分键 - 代码块复制/长回复折叠带量/队列回填/排队时钟 - 子代理完成卡渲染,死链卡片与引用清理 - 截图渲染换 pro 版依赖,样式 token 化收尾
This commit is contained in:
@@ -481,6 +481,8 @@ async function fetchModels() {
|
||||
})
|
||||
// 手动优先:探测结果合并已存在的 UserSet 标注——用户手动改过的能力不被「测试连接」覆盖
|
||||
// (对齐后端 ai_fetch_models 合并逻辑:probe_source==UserSet 保留手动能力维度)。
|
||||
// enabled/weight 同理保留旧值:重拉只是刷新能力维度,用户已关掉的模型/调过的权重
|
||||
// 属用户配置意图,被探测默认值复位等于静默丢配置(走查 P1-2)。
|
||||
providerForm.models = models.map(m => {
|
||||
const old = providerForm.models.find(o => o.model_id === m.model_id)
|
||||
const isManual = old?.probe_source === 'user_set'
|
||||
@@ -489,6 +491,8 @@ async function fetchModels() {
|
||||
modalities: isManual ? [...old.modalities] : [...m.modalities],
|
||||
capabilities: isManual ? [...old.capabilities] : [...m.capabilities],
|
||||
probe_source: isManual ? 'user_set' : m.probe_source,
|
||||
enabled: old ? old.enabled : m.enabled,
|
||||
weight: old ? old.weight : m.weight,
|
||||
}
|
||||
})
|
||||
emit('toast', t('settings.toastFetchOk', { count: models.length }), 'info')
|
||||
@@ -600,7 +604,9 @@ async function setDefaultProvider(id: string) {
|
||||
// 重建 per_provider caps,下条消息即按新配置 acquire。
|
||||
// 不走 saveProvider(全量 INSERT OR REPLACE 会触发 R-PD-1 密钥迁移分支,与 c 轻量 UPDATE 路径冲突)。
|
||||
// ============================================================
|
||||
let _poolTimer: ReturnType<typeof setTimeout> | null = null
|
||||
// 计时器按 provider id 分键:单例计时器下 300ms 内先后改两家权重,B 的变更会清掉
|
||||
// A 尚未落库的保存,先改的一方静默丢失(走查 P2-8)。
|
||||
const _poolTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
|
||||
/**
|
||||
* toggle 即时生效(开关语义=立即入/出池,无 debounce 必要;但失败需回滚视图)。
|
||||
@@ -628,8 +634,11 @@ function onPoolWeightChange(p: AiProviderConfig, raw: number) {
|
||||
const clamped = Math.min(100, Math.max(0, Number.isFinite(raw) ? Math.trunc(raw) : 50))
|
||||
const prev = p.weight
|
||||
p.weight = clamped
|
||||
if (_poolTimer) clearTimeout(_poolTimer)
|
||||
_poolTimer = setTimeout(async () => {
|
||||
// 仅重置同一 provider 的待发计时;跨 provider 互不干扰
|
||||
const prevTimer = _poolTimers.get(p.id)
|
||||
if (prevTimer) clearTimeout(prevTimer)
|
||||
_poolTimers.set(p.id, setTimeout(async () => {
|
||||
_poolTimers.delete(p.id)
|
||||
try {
|
||||
await aiApi.updateProviderPool(p.id, p.enabled !== false, clamped)
|
||||
emit('toast', t('settings.toastPoolUpdateOk'), 'info')
|
||||
@@ -638,7 +647,7 @@ function onPoolWeightChange(p: AiProviderConfig, raw: number) {
|
||||
console.error(t('settings.toastPoolUpdateFail', { msg: errMsg(e) }), e)
|
||||
emit('toast', t('settings.toastPoolUpdateFail', { msg: errMsg(e) }), 'error')
|
||||
}
|
||||
}, 300)
|
||||
}, 300))
|
||||
}
|
||||
|
||||
function maskKey(key: string): string {
|
||||
@@ -654,7 +663,7 @@ defineExpose({
|
||||
loadProviders,
|
||||
isFormDirty,
|
||||
closeProviderForm,
|
||||
clearPoolTimer() { if (_poolTimer) clearTimeout(_poolTimer) },
|
||||
clearPoolTimer() { for (const t of _poolTimers.values()) clearTimeout(t); _poolTimers.clear() },
|
||||
})
|
||||
|
||||
// 阶段3 UX 重构:本面板改为 ai-model 类懒挂载,不再由 Settings.vue onMounted 调用,
|
||||
|
||||
Reference in New Issue
Block a user