优化: 会话入口与对话交互打磨
- 完成卡点开子代理轨迹浮层,任务抽屉关联会话跳转 - 提及片段编辑差分三向判定,不再错位 - 长回复折叠完成瞬间锚定消息顶,视口不跳 - 空编辑/技能无匹配/回填覆盖草稿均有提示确认 - 队列编辑态切会话重置,刷新快捷键不再误触 - git 变更面板去重缓存,项目卡骨架与溢出重测
This commit is contained in:
@@ -25,7 +25,8 @@
|
||||
: (pendingSkill ? (pendingSkill.argument_hint || $t('aiChat.placeholderWithSkill')) : $t('aiChat.placeholderDefault'))"
|
||||
rows="1"
|
||||
@keydown="handleKeydown"
|
||||
@input="autoResize"
|
||||
@beforeinput="onBeforeInputText"
|
||||
@input="onInputText"
|
||||
@paste="onPaste"
|
||||
@dragover="onDragOver"
|
||||
@drop="onDrop"
|
||||
@@ -401,6 +402,38 @@ const mentionStart = ref(-1)
|
||||
// 后端据 mentionSpans resolve 投影成 Augmentation 注入;MessageList 据此精确切区间渲染 chip。
|
||||
const pendingMentionSpans = ref<MentionSpan[]>([])
|
||||
|
||||
// ── UX-P3: mention span 编辑平移 ──
|
||||
// span.start 是 selectMention 时刻的绝对偏移,用户在 chip 文本之前增删字符后全部 span 错位
|
||||
// (MessageList 渲染守卫对不上文本即降级纯文本,chip 全灭)。借 beforeinput(仅用户编辑触发,
|
||||
// 程序赋值不派发事件)快照旧文本,input 时做公共前后缀差分求出编辑区间,按区间相对各 span
|
||||
// 的位置处理:编辑点在 span 前→整体平移;在 span 后→不动;落在 span 文本内→丢弃该 span
|
||||
// (文本已被改写区间失真,宁丢一枚 chip 也不让后端 resolve 到错位区间)。
|
||||
let mentionTextBeforeEdit = ''
|
||||
function onBeforeInputText(): void {
|
||||
mentionTextBeforeEdit = inputEl.value?.value ?? inputText.value
|
||||
}
|
||||
function onInputText(): void {
|
||||
autoResize()
|
||||
shiftMentionSpans(mentionTextBeforeEdit, inputEl.value?.value ?? inputText.value)
|
||||
}
|
||||
function shiftMentionSpans(oldText: string, newText: string): void {
|
||||
if (pendingMentionSpans.value.length === 0 || oldText === newText) return
|
||||
// 公共前后缀夹出旧文本中被编辑的区间(粘贴/剪切的多字符增删同为一段区间,该差分通用)
|
||||
const minLen = Math.min(oldText.length, newText.length)
|
||||
let p = 0
|
||||
while (p < minLen && oldText[p] === newText[p]) p++
|
||||
let s = 0
|
||||
while (s < minLen - p && oldText[oldText.length - 1 - s] === newText[newText.length - 1 - s]) s++
|
||||
const editStart = p
|
||||
const editEnd = oldText.length - s
|
||||
const delta = newText.length - oldText.length
|
||||
pendingMentionSpans.value = pendingMentionSpans.value.flatMap((sp) => {
|
||||
if (editEnd <= sp.start) return [{ ...sp, start: sp.start + delta }]
|
||||
if (editStart >= sp.start + sp.length) return [sp]
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
// ── ⑥.4 Phase 4: @项目展开摘要(输入区 enrichment 预览) ──
|
||||
// 当用户 @[项目:xxx] 后,从 projectsStore 反查项目+关联任务/灵感,
|
||||
// 在输入区下方显式 badge + 可展开的上下文参考面板。
|
||||
@@ -685,6 +718,14 @@ function handleKeydown(e: KeyboardEvent) {
|
||||
// P1-1 输入法组词守卫:拼音/双拼组词未上屏时 Enter=选词、Esc=取消组词,均非用户发送/取消意图,
|
||||
// 直接放行给 IME。isComposing 覆盖标准浏览器;keyCode===229 兜部分环境 keydown 阶段不上报 isComposing。
|
||||
if (e.isComposing || e.keyCode === 229) return
|
||||
// UX-P3: AI 输入框聚焦时 Ctrl/Cmd+R 会触发 WebView 刷新(浏览器直开/开发态整页刷新丢会话态),
|
||||
// 此处 preventDefault 掐掉刷新默认行为。只拦输入框聚焦这一场景(本 handler 仅 textarea 触发,
|
||||
// 全局其他区域不拦);不 stopPropagation,冒泡到 AiChat onGlobalKeydown 的 Ctrl+R 分支,
|
||||
// 重新生成语义照常生效。
|
||||
if ((e.ctrlKey || e.metaKey) && (e.key === 'r' || e.key === 'R')) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
// 各 Esc 消费分支 stopPropagation:P1-2 双层防,分离窗口 AiChat 的 window 级 Esc 会关整个窗口,
|
||||
// 内层已消费的 Esc 不再外抛(主防线是 AiChat 侧的 e.target 输入控件守卫,此处为辅)
|
||||
// 编辑态 Escape 取消编辑(最高优先级)
|
||||
@@ -732,6 +773,14 @@ function handleKeydown(e: KeyboardEvent) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// UX-P3: 技能联想浮层开着但无匹配时,Enter 原会落到下方发送分支把 "/xxx" 原文当普通
|
||||
// 消息发出。改为不发送 + toast 提示无此技能;确要发送斜杠原文可先 Esc 关浮层再 Enter。
|
||||
if (skillOpen.value && e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
emit('error', { msg: t('aiChat.skillNoMatchQuery', { q: skillQuery.value.trim() }), type: 'warning' })
|
||||
return
|
||||
}
|
||||
// @ 实体引用浮层导航(仅有匹配项时拦截方向/回车;与技能联想互斥)
|
||||
if (mentionOpen.value && mentionItems.value.length) {
|
||||
if (e.key === 'ArrowDown') {
|
||||
@@ -775,7 +824,12 @@ async function handleSend() {
|
||||
const text = inputText.value.trim()
|
||||
// UX-09 编辑态:走 editMessage(不 push 新 user,编辑末条 user 后重生成)
|
||||
if (props.editingMsgId) {
|
||||
if (!text) return
|
||||
// UX-P3 空编辑静默:清空文本后 Enter 原来静默 return,用户无反馈卡在编辑态。
|
||||
// 仍阻止空提交(editMessage 空文本语义不明),但补轻提示告知原因。
|
||||
if (!text) {
|
||||
emit('error', { msg: t('aiChat.editEmptyBlocked'), type: 'warning' })
|
||||
return
|
||||
}
|
||||
inputText.value = ''
|
||||
if (inputEl.value) inputEl.value.style.height = 'auto'
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user