refactor: 统一 Mention 图标渲染逻辑
This commit is contained in:
parent
3b35493db3
commit
1842c8da85
@ -15,9 +15,7 @@
|
|||||||
- [x] 移除 lightrag
|
- [x] 移除 lightrag
|
||||||
- [x] 新增图谱构建,支持自定义 Schema、支持自定义并发数,知识库与向量检索解耦
|
- [x] 新增图谱构建,支持自定义 Schema、支持自定义并发数,知识库与向量检索解耦
|
||||||
- [x] 知识图谱抽取要求支持并发处理
|
- [x] 知识图谱抽取要求支持并发处理
|
||||||
|
- [ ] UV 的版本
|
||||||
|
|
||||||
## 权限部分
|
|
||||||
- [x] Agent 的 user_id 使用的有歧义,从数据库表到代码中都修改为统一使用 yuxi_id 来代替
|
- [x] Agent 的 user_id 使用的有歧义,从数据库表到代码中都修改为统一使用 yuxi_id 来代替
|
||||||
- [ ] 权限新增一个 guest 的预设字段,暂无任何权限
|
- [ ] 权限新增一个 guest 的预设字段,暂无任何权限
|
||||||
- [ ] rename database table name, such as skills -> agent_skills, subagents -> agent_subagents, mcp, tool_call, 等等
|
- [ ] rename database table name, such as skills -> agent_skills, subagents -> agent_subagents, mcp, tool_call, 等等
|
||||||
|
|||||||
@ -269,11 +269,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, nextTick, watch, onBeforeUnmount, useSlots } from 'vue'
|
import {
|
||||||
|
ref,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
nextTick,
|
||||||
|
watch,
|
||||||
|
onBeforeUnmount,
|
||||||
|
useSlots,
|
||||||
|
h,
|
||||||
|
render
|
||||||
|
} from 'vue'
|
||||||
import { SendOutlined, ArrowUpOutlined, PauseOutlined, FolderFilled } from '@ant-design/icons-vue'
|
import { SendOutlined, ArrowUpOutlined, PauseOutlined, FolderFilled } from '@ant-design/icons-vue'
|
||||||
import { Paperclip } from 'lucide-vue-next'
|
import { Paperclip } from 'lucide-vue-next'
|
||||||
import { searchMentionFiles } from '@/apis/mention_api'
|
import { searchMentionFiles } from '@/apis/mention_api'
|
||||||
import { getFileIcon, getFileIconColor } from '@/utils/file_utils'
|
import { getFileIcon, getFileIconColor } from '@/utils/file_utils'
|
||||||
|
import {
|
||||||
|
getMentionIconComponent,
|
||||||
|
getMentionIconStyle,
|
||||||
|
MENTION_ICON_SIZE,
|
||||||
|
MENTION_ICON_STROKE_WIDTH
|
||||||
|
} from '@/utils/mention_icon_utils'
|
||||||
import {
|
import {
|
||||||
buildMentionDisplayLabels,
|
buildMentionDisplayLabels,
|
||||||
expandMentionDeletionRange,
|
expandMentionDeletionRange,
|
||||||
@ -438,20 +454,10 @@ const serializeEditorNode = (node) => {
|
|||||||
const serializeEditorContent = () => serializeEditorNode(inputRef.value)
|
const serializeEditorContent = () => serializeEditorNode(inputRef.value)
|
||||||
const getEditorRawValue = () => (inputRef.value ? serializeEditorContent() : inputValue.value)
|
const getEditorRawValue = () => (inputRef.value ? serializeEditorContent() : inputValue.value)
|
||||||
|
|
||||||
const getEditorMentionIconSvg = (type) => {
|
const unmountEditorMentionIcons = () => {
|
||||||
if (type === 'knowledge') {
|
inputRef.value
|
||||||
return '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>'
|
?.querySelectorAll('.mention-ref-icon[data-vue-icon]')
|
||||||
}
|
.forEach((container) => render(null, container))
|
||||||
if (type === 'skill') {
|
|
||||||
return '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z"/></svg>'
|
|
||||||
}
|
|
||||||
if (type === 'subagent') {
|
|
||||||
return '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>'
|
|
||||||
}
|
|
||||||
if (type === 'mcp') {
|
|
||||||
return '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/><path d="M18 8v5a6 6 0 0 1-12 0V8Z"/></svg>'
|
|
||||||
}
|
|
||||||
return '<svg viewBox="0 0 24 24" width="15" height="15" stroke="currentColor" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEditorMentionElement = (segment) => {
|
const createEditorMentionElement = (segment) => {
|
||||||
@ -465,10 +471,15 @@ const createEditorMentionElement = (segment) => {
|
|||||||
|
|
||||||
const icon = document.createElement('span')
|
const icon = document.createElement('span')
|
||||||
icon.className = 'mention-ref-icon'
|
icon.className = 'mention-ref-icon'
|
||||||
icon.innerHTML = getEditorMentionIconSvg(segment.type)
|
icon.dataset.vueIcon = 'true'
|
||||||
if (segment.type === 'file') {
|
Object.assign(icon.style, getMentionIconStyle(segment.type, segment.value) || {})
|
||||||
icon.style.color = segment.value.endsWith('/') ? '#ffa940' : getFileIconColor(segment.value)
|
render(
|
||||||
}
|
h(getMentionIconComponent(segment.type, segment.value), {
|
||||||
|
size: MENTION_ICON_SIZE,
|
||||||
|
strokeWidth: MENTION_ICON_STROKE_WIDTH
|
||||||
|
}),
|
||||||
|
icon
|
||||||
|
)
|
||||||
token.appendChild(icon)
|
token.appendChild(icon)
|
||||||
|
|
||||||
const label = document.createElement('span')
|
const label = document.createElement('span')
|
||||||
@ -487,6 +498,7 @@ const renderEditorContent = (raw = '') => {
|
|||||||
const editor = inputRef.value
|
const editor = inputRef.value
|
||||||
if (!editor) return
|
if (!editor) return
|
||||||
|
|
||||||
|
unmountEditorMentionIcons()
|
||||||
editor.replaceChildren()
|
editor.replaceChildren()
|
||||||
parseMentionText(raw).forEach((segment) => {
|
parseMentionText(raw).forEach((segment) => {
|
||||||
editor.appendChild(
|
editor.appendChild(
|
||||||
@ -1165,6 +1177,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 组件卸载时清除定时器和事件监听器
|
// 组件卸载时清除定时器和事件监听器
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
unmountEditorMentionIcons()
|
||||||
if (debounceTimer.value) {
|
if (debounceTimer.value) {
|
||||||
clearTimeout(debounceTimer.value)
|
clearTimeout(debounceTimer.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,11 +13,11 @@
|
|||||||
:title="segment.raw"
|
:title="segment.raw"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="getTokenIcon(segment)"
|
:is="getMentionIconComponent(segment.type, segment.value)"
|
||||||
class="mention-ref-icon"
|
class="mention-ref-icon"
|
||||||
:style="getIconStyle(segment)"
|
:style="getMentionIconStyle(segment.type, segment.value)"
|
||||||
:stroke-width="2.2"
|
:stroke-width="MENTION_ICON_STROKE_WIDTH"
|
||||||
:size="15"
|
:size="MENTION_ICON_SIZE"
|
||||||
/>
|
/>
|
||||||
<span class="mention-ref-label">{{ getTokenLabel(segment) }}</span>
|
<span class="mention-ref-label">{{ getTokenLabel(segment) }}</span>
|
||||||
</span>
|
</span>
|
||||||
@ -27,9 +27,12 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { FolderFilled } from '@ant-design/icons-vue'
|
import {
|
||||||
import { BookMarked, BookOpen, Bot, Plug } from 'lucide-vue-next'
|
getMentionIconComponent,
|
||||||
import { getFileIcon, getFileIconColor } from '@/utils/file_utils'
|
getMentionIconStyle,
|
||||||
|
MENTION_ICON_SIZE,
|
||||||
|
MENTION_ICON_STROKE_WIDTH
|
||||||
|
} from '@/utils/mention_icon_utils'
|
||||||
import { getMentionDisplayLabel, parseMentionText } from '@/utils/mention_utils'
|
import { getMentionDisplayLabel, parseMentionText } from '@/utils/mention_utils'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -49,29 +52,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const segments = computed(() => parseMentionText(props.content))
|
const segments = computed(() => parseMentionText(props.content))
|
||||||
|
|
||||||
const typeIcons = {
|
|
||||||
knowledge: BookOpen,
|
|
||||||
skill: BookMarked,
|
|
||||||
mcp: Plug,
|
|
||||||
subagent: Bot
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTokenIcon = (segment) => {
|
|
||||||
if (segment.type === 'file') {
|
|
||||||
return segment.value.endsWith('/') ? FolderFilled : getFileIcon(segment.value)
|
|
||||||
}
|
|
||||||
return typeIcons[segment.type] || Plug
|
|
||||||
}
|
|
||||||
|
|
||||||
const getIconStyle = (segment) => {
|
|
||||||
if (segment.type === 'file') {
|
|
||||||
return {
|
|
||||||
color: segment.value.endsWith('/') ? '#ffa940' : getFileIconColor(segment.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTokenLabel = (segment) =>
|
const getTokenLabel = (segment) =>
|
||||||
getMentionDisplayLabel(segment.type, segment.value, props.displayLabels)
|
getMentionDisplayLabel(segment.type, segment.value, props.displayLabels)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
27
web/src/utils/mention_icon_utils.js
Normal file
27
web/src/utils/mention_icon_utils.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { FolderFilled } from '@ant-design/icons-vue'
|
||||||
|
import { BookMarked, BookOpen, Bot, Plug } from 'lucide-vue-next'
|
||||||
|
import { getFileIcon, getFileIconColor } from '@/utils/file_utils'
|
||||||
|
|
||||||
|
export const MENTION_ICON_SIZE = 15
|
||||||
|
export const MENTION_ICON_STROKE_WIDTH = 2.2
|
||||||
|
|
||||||
|
const MENTION_TYPE_ICON_COMPONENTS = {
|
||||||
|
knowledge: BookOpen,
|
||||||
|
skill: BookMarked,
|
||||||
|
mcp: Plug,
|
||||||
|
subagent: Bot
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMentionIconComponent = (type, value = '') => {
|
||||||
|
if (type === 'file') {
|
||||||
|
return String(value || '').endsWith('/') ? FolderFilled : getFileIcon(value)
|
||||||
|
}
|
||||||
|
return MENTION_TYPE_ICON_COMPONENTS[type] || Plug
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMentionIconStyle = (type, value = '') => {
|
||||||
|
if (type !== 'file') return null
|
||||||
|
return {
|
||||||
|
color: String(value || '').endsWith('/') ? '#ffa940' : getFileIconColor(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user