bugfix: report标签添加

This commit is contained in:
2026-02-23 14:53:29 +08:00
parent bb225327b7
commit 189be3406c
5 changed files with 178 additions and 71 deletions

2
.gitignore vendored
View File

@@ -27,3 +27,5 @@ coverage
*.njsproj
*.sln
*.sw?
.assistant-rules.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

View File

@@ -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
}

View File

@@ -23,24 +23,7 @@
@input="handleEditorInput"
@paste="handleEditorPaste"
@keypress="handleKeyPress"
>
<!-- <Tag v-if="showReportTag" /> -->
<div
class="editor-tag report-btn flex-center"
v-if="showReportTag"
contenteditable="false"
>
<SvgIcon class="light-icon" name="light" size="16" />
<span>{{ $t('Input.trendingReport') }}</span>
<SvgIcon
class="close-icon"
name="closeTransparent"
size="24"
color="#e6e6e6"
@click="showReportTag = false"
/>
</div>
</div>
></div>
</div>
<div class="operate flex align-center space-between">
<div class="left flex align-center">
@@ -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<HTMLDivElement | null>(null)
const inputValue = ref<string>('')
const toogltReportTag = () => {
console.log(showReportTag.value)
const reportTags = ref([])
const addReportTag = () => {
// create container matching static structure: <div class="editor-tag report-btn flex-center" contenteditable="false">...
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;
}
}
</style>