From 189be3406cc48e5c3c995c03cead4c6501f94c08 Mon Sep 17 00:00:00 2001 From: zhangyahui Date: Mon, 23 Feb 2026 14:53:29 +0800 Subject: [PATCH] =?UTF-8?q?bugfix:=20report=E6=A0=87=E7=AD=BE=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + src/assets/images/close-icon.png | Bin 0 -> 212 bytes src/assets/images/light-icon.png | Bin 0 -> 602 bytes src/views/home/agent/components/Agent.vue | 8 +- src/views/home/components/Input.vue | 239 +++++++++++++++------- 5 files changed, 178 insertions(+), 71 deletions(-) create mode 100644 src/assets/images/close-icon.png create mode 100644 src/assets/images/light-icon.png diff --git a/.gitignore b/.gitignore index 6609edf..e4b31a1 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ coverage *.njsproj *.sln *.sw? + +.assistant-rules.md \ No newline at end of file diff --git a/src/assets/images/close-icon.png b/src/assets/images/close-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5708e37f925a2f92de06cfd6a888c491706f0149 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7Bet#3xhBt!>l*8o|0J>k`I(+Bjv*C{y#WV#8w@yf?k@U!Be10CuFIb( zh5|{m7bLw=^_|uHm~X+gUG3b(=YDdqxrEH9yjq_VGjV%oM$xP-VhcyG*;nLzc+w9{m{fVCW1ZXLPr>mdKI;Vst E0Q{y)Pyhe` literal 0 HcmV?d00001 diff --git a/src/assets/images/light-icon.png b/src/assets/images/light-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..13e34cf679042c1cca36f0d66906be9e7a51bacc GIT binary patch literal 602 zcmV-g0;TS^t#L(vRIkfk63OA0Pg9T0{TDJopRGid z`&}A^nI};&y?imC&}`k63o^ivFeMYuC|GwLQIez84-RZNQjseqB$~DA1`$oF!{a0J z`<)@(n2%*!K|Ti8L>3KL>IQ&0@oI!De)41 z_Fc_tMUXe^bzq`Rl)dRmjf*&su{3xCjF}$~$P8Eq&V%3m`8gHQr>!KF0hzI&d}LLy zT3EYeSZj~n+aNo3+CI*hKG*83$=e`+pmr{rK60O)&tlZ0=eAlbIbPi$maPZwr@XM+ ot~%-^6#NfjUEB)~Wv}VF1+1dZe4<(6%>V!Z07*qoM6N<$f)*bEvH$=8 literal 0 HcmV?d00001 diff --git a/src/views/home/agent/components/Agent.vue b/src/views/home/agent/components/Agent.vue index 0667d73..f79ea91 100644 --- a/src/views/home/agent/components/Agent.vue +++ b/src/views/home/agent/components/Agent.vue @@ -100,7 +100,10 @@ // 非流式错误响应,使用 text() 读取错误信息 const errorText = await response.text() console.error('请求错误:', errorText) - throw new Error(`发起对话错误--- ${response.status}: ${errorText}`) + aiMessage.text = '发送失败,请重试' + aiMessage.streaming = false + aiMessage.loading = false + return } // 不是流式响应,使用 text()读取错误信息 @@ -112,6 +115,7 @@ } catch (e) { console.error('非流式响应文本:', text) } + aiMessage.text = '发送失败,请重试' aiMessage.streaming = false aiMessage.loading = false return @@ -195,6 +199,7 @@ } } catch (error) { console.error('流式传输错误:', error) + aiMessage.text = '发送失败,请重试' aiMessage.streaming = false aiMessage.loading = false } finally { @@ -202,6 +207,7 @@ } } catch (error) { console.error('fetch请求失败:', error) + aiMessage.text = '发送失败,请重试' aiMessage.streaming = false aiMessage.loading = false } diff --git a/src/views/home/components/Input.vue b/src/views/home/components/Input.vue index 652e783..6a64335 100644 --- a/src/views/home/components/Input.vue +++ b/src/views/home/components/Input.vue @@ -23,24 +23,7 @@ @input="handleEditorInput" @paste="handleEditorPaste" @keypress="handleKeyPress" - > - -
- - {{ $t('Input.trendingReport') }} - -
- + >
@@ -178,6 +161,8 @@ import { computed, ref, watch, nextTick, onMounted } from 'vue' import { areaList } from '@/utils/area' import { useI18n } from 'vue-i18n' + import lightIcon from '@/assets/images/light-icon.png' + import closeIcon from '@/assets/images/close-icon.png' // import Tag from './Tag.vue' const props = withDefaults( @@ -202,8 +187,6 @@ fileInputRef.value?.click() } - // BUG 标签被删除后无法重新出现 - // 处理文件选择 const handleFileChange = (event: Event) => { const input = event.target as HTMLInputElement @@ -243,21 +226,101 @@ 'NordicNoir' ] - // 标签相关(固定标签,v-show 控制显示) - const showReportTag = ref(false) const editorRef = ref(null) const inputValue = ref('') - const toogltReportTag = () => { - console.log(showReportTag.value) + const reportTags = ref([]) + const addReportTag = () => { + // create container matching static structure:
... + const tag = document.createElement('div') + tag.className = 'editor-tag report-btn flex-center' + tag.contentEditable = 'false' - showReportTag.value = !showReportTag.value + const imgLeft = document.createElement('img') + imgLeft.className = 'light-icon' + imgLeft.src = lightIcon as unknown as string + + const textSpan = document.createElement('span') + textSpan.innerText = t('Input.trendingReport') + + const imgClose = document.createElement('img') + imgClose.className = 'close-icon' + imgClose.src = closeIcon as unknown as string + imgClose.addEventListener('click', (ev) => { + ev.stopPropagation() + // remove tag when close clicked + tag.remove() + const idx = reportTags.value.indexOf(tag) + if (idx > -1) reportTags.value.splice(idx, 1) + }) + + // assemble + tag.appendChild(imgLeft) + tag.appendChild(textSpan) + tag.appendChild(imgClose) + + // Insert tag at the current cursor position + const selection = window.getSelection() + if (selection && selection.rangeCount > 0) { + const range = selection.getRangeAt(0) + range.insertNode(tag) + + // Insert a zero-width space text node after the tag so the caret can be placed there + const zwsp = document.createTextNode('\u200B') + if (tag.parentNode) tag.parentNode.insertBefore(zwsp, tag.nextSibling) + + // Create a new collapsed range positioned inside the zwsp (after the tag) + const newRange = document.createRange() + newRange.setStart(zwsp, 1) + newRange.collapse(true) + + selection.removeAllRanges() + selection.addRange(newRange) + + // ensure editor has focus + editorRef.value && (editorRef.value as HTMLElement).focus() + } else if (editorRef.value) { + // If no selection, append directly to editor and place caret after + editorRef.value.appendChild(tag) + const zwsp = document.createTextNode('\u200B') + editorRef.value.appendChild(zwsp) + const sel = window.getSelection() + if (sel) { + const r = document.createRange() + r.setStart(zwsp, 1) + r.collapse(true) + sel.removeAllRanges() + sel.addRange(r) + } + editorRef.value && (editorRef.value as HTMLElement).focus() + } + + reportTags.value.push(tag) + } + + const toogltReportTag = () => { + // 清理掉已被删除的标签引用(从 DOM 中移除的元素) + reportTags.value = reportTags.value.filter((tag) => tag.parentNode !== null) + + if (reportTags.value.length > 0) { + // 移除所有标签及其关联的零宽空格 + reportTags.value.forEach((tag) => { + if (tag.nextSibling && tag.nextSibling.nodeType === Node.TEXT_NODE && tag.nextSibling.textContent === '\u200B') { + tag.nextSibling.remove() + } + tag.remove() + }) + reportTags.value = [] + } else { + // 添加标签 + addReportTag() + } } const handleEditorInput = () => { if (!editorRef.value) return - // 提取纯文本(排除标签) + // 提取纯文本(排除插入的report标签) let text = '' const walker = document.createTreeWalker(editorRef.value, NodeFilter.SHOW_TEXT, null) @@ -266,7 +329,7 @@ text += node.textContent } - // 移除末尾的空格(如果有的话) + // 移除末尾的空格 text = text.replace(/\s+$/, '') inputValue.value = text @@ -288,19 +351,58 @@ // editor.style.overflowY = 'auto' return } else { - editor.style.height = 'auto' - const maxHeight = - 20 * parseFloat(getComputedStyle(document.documentElement).fontSize || '16') - editor.style.height = Math.min(editor.scrollHeight, maxHeight) + 'px' + // editor.style.height = 'auto' + // const maxHeight = + // 20 * parseFloat(getComputedStyle(document.documentElement).fontSize || '16') + // editor.style.height = Math.min(editor.scrollHeight, maxHeight) + 'px' } } } const handleKeyPress = (e) => { // 检测回车 - if (e.key === 'Enter' && !e.shiftKey) { + if (e.key === 'Enter') { e.preventDefault() handleSendAgent() + return + } + if (e.key === 'Backspace') { + const selection = window.getSelection() + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0) + if (range.collapsed) { + let nodeToDelete = null + const startContainer = range.startContainer + const startOffset = range.startOffset + + if (startContainer.nodeType === Node.TEXT_NODE) { + // Cursor at the end of a text node, check next sibling + if (startOffset === startContainer.length) { + nodeToDelete = startContainer.nextSibling + } + } else if (startContainer.nodeType === Node.ELEMENT_NODE) { + // Cursor positioned between child nodes + nodeToDelete = startContainer.childNodes[startOffset] + } + + if ( + nodeToDelete && + nodeToDelete.nodeType === Node.ELEMENT_NODE && + (nodeToDelete as Element).classList && + ((nodeToDelete as Element).classList.contains('editor-tag') || + (nodeToDelete as Element).classList.contains('report-tag')) + ) { + e.preventDefault() + ;(nodeToDelete as Element).remove() + // Optional: remove from reportTags if tracking + const index = reportTags.value.indexOf(nodeToDelete) + if (index > -1) { + reportTags.value.splice(index, 1) + } + return + } + } + } } } @@ -410,14 +512,14 @@ } .scroll-content { - overflow-y: visible; + flex: 1; + overflow-y: auto; padding: 3.4rem 1.7rem 1.7rem; } .editor { width: 100%; - min-height: 5rem; - max-height: 20rem; + flex: 1; border: none; outline: none; padding: 0 1.4rem 1.4rem; @@ -437,41 +539,6 @@ color: #999; pointer-events: none; } - - // 标签样式 - .editor-tag { - width: 21.8rem; - height: 4.4rem; - display: inline-flex; - position: initial; - bottom: initial; - border: 0.11rem solid #0000001a; - font-family: 'GeneralMedium'; - font-weight: 500; - font-size: 1.8rem; - column-gap: 0; - span { - margin: 0 0.7rem 0 1.2rem; - } - .c-svg.close-icon { - width: 2.4rem; - height: 2.4rem; - cursor: pointer; - } - } - } - - // 标签容器(已废弃,保留兼容性) - .tags-container { - display: inline-flex; - flex-wrap: wrap; - gap: 0.5rem; - padding: 0 1.4rem 0.5rem; - - .tag-item { - display: inline-flex; - align-items: center; - } } // 图片预览区域样式 @@ -804,4 +871,36 @@ display: none; } } + + /* 动态添加的编辑器标签样式 */ + .assist-input-wrapper .editor .editor-tag { + width: 21.8rem; + height: 4.4rem; + display: inline-flex; + border: 0.11rem solid #0000001a; + font-family: 'GeneralMedium'; + font-weight: 500; + font-size: 1.8rem; + column-gap: 0; + margin: 0 0.5rem; + vertical-align: middle; + border-radius: 2.2rem; + + span { + margin: 0 0.7rem 0 1.2rem; + } + + .light-icon { + width: 1.5rem; + height: 1.9rem; + flex-shrink: 0; + } + + .close-icon { + width: 1rem; + height: 1rem; + cursor: pointer; + flex-shrink: 0; + } + }