优化: 对话交互修复与界面完善
- 修输入法组词回车直发/分离窗误关/出错后按钮死锁 - 新建任务失败弹窗内联提示,会话切换乐观高亮 - 状态多选联动刷新不再冲空可见行 - 测试连接保留已配开关与权重,防抖按提供商分键 - 代码块复制/长回复折叠带量/队列回填/排队时钟 - 子代理完成卡渲染,死链卡片与引用清理 - 截图渲染换 pro 版依赖,样式 token 化收尾
This commit is contained in:
@@ -61,8 +61,9 @@
|
||||
<span class="setting-desc">{{ $t('settings.descApprovalTimeout') }}</span>
|
||||
</div>
|
||||
<!-- 文件上传服务端点:配置后聊天图片上传到用户自己的文件服务转公开 URL;留空则图片本地 base64 内嵌。
|
||||
与 fileUpload.ts 的读取约定同 key(df-file-upload-base),blur/enter 提交即落库生效 -->
|
||||
<SettingRow ref="rowUploadBase" :label="$t('settings.labelFileUploadBase')" :desc="$t('settings.descFileUploadBase')">
|
||||
与 fileUpload.ts 的读取约定同 key(df-file-upload-base),blur/enter 提交即落库生效。
|
||||
upload-row 整行堆叠(对齐 prompt-row 先例):原横排 280px 最小宽在窄列强挤 label 致逐字竖排 -->
|
||||
<SettingRow ref="rowUploadBase" class="upload-row" :label="$t('settings.labelFileUploadBase')" :desc="$t('settings.descFileUploadBase')">
|
||||
<input
|
||||
v-model="fileUploadBase"
|
||||
class="setting-input upload-base-input"
|
||||
@@ -264,9 +265,17 @@ function onApprovalTimeoutChange() {
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
/* 文件上传端点行:整行堆叠——label/desc 在上,输入独立一行拉满(对齐 prompt-row 先例)。
|
||||
窄列不再 280px 强制最小宽挤压 label 逐字竖排 */
|
||||
.upload-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
/* 端点输入走等宽字体(URL 逐字符核对更易读),对齐 AllowedDirsPanel 路径输入风格 */
|
||||
.upload-base-input {
|
||||
min-width: 280px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: var(--df-font-mono, monospace);
|
||||
}
|
||||
/* 自定义提示词行:整行堆叠,textarea 独立一行从左到右拉满。
|
||||
|
||||
@@ -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 调用,
|
||||
|
||||
@@ -20,18 +20,21 @@
|
||||
|
||||
/* ===== 设置行容器(表单型 Panel 共用) ===== */
|
||||
.general-settings { display: flex; flex-direction: column; }
|
||||
/* 窄列(设置抽屉/面板窄态)三防线:gap 隔开 info 与 control;info min-width:0 允许收缩、
|
||||
label/desc 文本按常规断行(多词换行,不逐字竖排);control 不被压缩挤扁 */
|
||||
.setting-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 0.5px solid var(--df-border);
|
||||
}
|
||||
.setting-row:last-child { border-bottom: none; }
|
||||
.setting-info { display: flex; flex-direction: column; gap: 2px; }
|
||||
.setting-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
||||
.setting-label { font-size: 14px; font-weight: 500; color: var(--df-text); }
|
||||
.setting-desc { font-size: 12px; color: var(--df-text-dim); }
|
||||
.setting-control { display: flex; align-items: center; }
|
||||
.setting-control { display: flex; align-items: center; flex-shrink: 0; }
|
||||
|
||||
/* ===== 控件(5/5 Panel 重复) ===== */
|
||||
/* 控件统一基准(padding/border/radius/font 对齐),各 type 仅差异化必要属性 */
|
||||
|
||||
Reference in New Issue
Block a user