优化: 对话交互修复与界面完善

- 修输入法组词回车直发/分离窗误关/出错后按钮死锁
- 新建任务失败弹窗内联提示,会话切换乐观高亮
- 状态多选联动刷新不再冲空可见行
- 测试连接保留已配开关与权重,防抖按提供商分键
- 代码块复制/长回复折叠带量/队列回填/排队时钟
- 子代理完成卡渲染,死链卡片与引用清理
- 截图渲染换 pro 版依赖,样式 token 化收尾
This commit is contained in:
lxy
2026-10-03 15:35:18 +08:00
parent b4fafa0e9a
commit 2d43f90e50
20 changed files with 609 additions and 255 deletions
+14 -5
View File
@@ -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 调用,