-
记忆片段
+
+
搜索结果 · {{ searchMemoryResults.length }}条
-
-
+
+
-
{{ item.text }}
+
{{ result.content }}
-
+
+
{{ showSearchResults ? '搜索结果' : '记忆片段' }}
+
+
+
+
暂无记忆数据
+
进行对话后,AI 会自动提取并存储记忆
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
- {{ tag }}
+ {{ tag }}
-
+
{{ int }}
-
{{ userPortrait.interactionCount.toLocaleString() }} 次互动
+
{{ (userPortrait?.interactionCount || 0).toLocaleString() }} 条记忆
-
记忆健康 {{ userPortrait.memoryHealth }}%
+
记忆健康 {{ userPortrait?.memoryHealth || 0 }}%
+
+
+
+
+
+
+
+
+ 分类:
+
+
+
+
+
+
+
@@ -274,6 +583,26 @@ function handleSearch() {
overflow: hidden;
}
+.memory-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ flex: 1;
+ color: var(--text-muted);
+ font-size: 14px;
+}
+
+.spinning {
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
.memory-header {
display: flex;
align-items: center;
@@ -322,7 +651,8 @@ function handleSearch() {
transition: all 300ms ease-in-out;
}
-.search-bar:focus-within {
+.search-bar:focus-within,
+.search-bar.search-expanded {
border-color: #8b5cf6;
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.15);
}
@@ -333,7 +663,7 @@ function handleSearch() {
}
.search-bar input {
- width: 180px;
+ width: 140px;
font-size: 13px;
background: transparent;
color: var(--text);
@@ -343,18 +673,32 @@ function handleSearch() {
color: var(--text-muted);
}
-.search-refresh {
+.search-clear-btn,
+.search-trigger-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 6px;
color: var(--text-muted);
cursor: pointer;
- transition: transform 300ms ease-in-out;
+ transition: all 200ms;
}
-.search-refresh.spinning {
- animation: spin 1s linear infinite;
+.search-clear-btn:hover,
+.search-trigger-btn:hover {
+ background: var(--surface-hover);
+ color: var(--text);
}
-@keyframes spin {
- to { transform: rotate(360deg); }
+.search-trigger-btn:disabled {
+ opacity: 0.4;
+ cursor: default;
+}
+
+.search-refresh {
+ color: var(--text-muted);
}
.h-btn {
@@ -375,6 +719,16 @@ function handleSearch() {
color: var(--text);
}
+.h-btn.primary {
+ color: var(--text);
+ background: rgba(139, 92, 246, 0.1);
+ border: 1px solid rgba(139, 92, 246, 0.2);
+}
+
+.h-btn.primary:hover {
+ background: rgba(139, 92, 246, 0.18);
+}
+
.memory-body {
display: flex;
flex: 1;
@@ -410,8 +764,15 @@ function handleSearch() {
}
@keyframes card-enter {
- from { opacity: 0; transform: translateX(-16px); }
- to { opacity: 1; transform: translateX(0); }
+ from {
+ opacity: 0;
+ transform: translateX(-16px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
}
.layer-card:hover {
@@ -616,6 +977,30 @@ function handleSearch() {
color: var(--text);
}
+.empty-layer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 40px 20px;
+ color: var(--text-muted);
+}
+
+.empty-layer svg {
+ margin-bottom: 12px;
+ opacity: 0.5;
+}
+
+.empty-layer p {
+ font-size: 14px;
+ margin-bottom: 4px;
+}
+
+.empty-hint {
+ font-size: 12px !important;
+ opacity: 0.7;
+}
+
.memo-items {
display: flex;
flex-direction: column;
@@ -633,16 +1018,27 @@ function handleSearch() {
opacity: 0;
animation: memo-in 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
animation-delay: var(--item-delay);
+ position: relative;
}
@keyframes memo-in {
- from { opacity: 0; transform: translateY(8px); }
- to { opacity: 1; transform: translateY(0); }
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
.memo-item:hover {
border-color: var(--border);
- transform: translateX(4px);
+}
+
+.memo-item:hover .memo-actions {
+ opacity: 1;
}
.memo-dot {
@@ -668,7 +1064,7 @@ function handleSearch() {
.memo-footer {
display: flex;
align-items: center;
- gap: 10px;
+ gap: 6px;
}
.memo-tag {
@@ -680,11 +1076,104 @@ function handleSearch() {
font-weight: 500;
}
+.memo-tag.category-tag {
+ background: rgba(245, 158, 11, 0.1);
+ color: #b45309;
+}
+
.memo-time {
font-size: 11px;
color: var(--text-muted);
}
+.memo-actions {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ opacity: 0;
+ transition: opacity 200ms;
+ flex-shrink: 0;
+}
+
+.memo-action-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 26px;
+ height: 26px;
+ border-radius: 6px;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 200ms;
+}
+
+.memo-action-btn:hover {
+ background: var(--surface-hover);
+ color: var(--lumi-primary);
+}
+
+.memo-action-btn.danger:hover {
+ background: rgba(244, 63, 94, 0.1);
+ color: #f43f5e;
+}
+
+.edit-textarea {
+ width: 100%;
+ padding: 8px;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ background: var(--bg);
+ color: var(--text);
+ font-size: 13px;
+ resize: vertical;
+ font-family: inherit;
+ outline: none;
+}
+
+.edit-textarea:focus {
+ border-color: #8b5cf6;
+}
+
+.edit-actions {
+ display: flex;
+ gap: 8px;
+ margin-top: 8px;
+}
+
+.edit-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 10px;
+ border-radius: 6px;
+ font-size: 12px;
+ cursor: pointer;
+ transition: all 200ms;
+}
+
+.edit-btn.save {
+ background: rgba(139, 92, 246, 0.1);
+ color: #8b5cf6;
+}
+
+.edit-btn.save:hover {
+ background: rgba(139, 92, 246, 0.2);
+}
+
+.edit-btn.save:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+.edit-btn.cancel {
+ background: var(--surface-hover);
+ color: var(--text-muted);
+}
+
+.edit-btn.cancel:hover {
+ color: var(--text);
+}
+
.portrait-card {
padding: 18px;
border-radius: 14px;
@@ -730,7 +1219,7 @@ function handleSearch() {
margin-bottom: 14px;
}
-.portrait-interests > svg {
+.portrait-interests>svg {
color: var(--text-muted);
flex-shrink: 0;
}
@@ -762,9 +1251,192 @@ function handleSearch() {
color: var(--lumi-primary);
}
+.dialog-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.4);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 100;
+}
+
+.dialog-card {
+ width: 460px;
+ max-width: 90vw;
+ background: var(--bg);
+ border-radius: 16px;
+ box-shadow: var(--shadow-lg);
+ overflow: hidden;
+}
+
+.dialog-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--border);
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--text);
+}
+
+.dialog-close-btn {
+ margin-left: auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border-radius: 6px;
+ color: var(--text-muted);
+ cursor: pointer;
+}
+
+.dialog-close-btn:hover {
+ background: var(--surface-hover);
+}
+
+.dialog-body {
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.dialog-textarea {
+ width: 100%;
+ padding: 12px;
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ background: var(--surface);
+ color: var(--text);
+ font-size: 13px;
+ resize: none;
+ font-family: inherit;
+ outline: none;
+}
+
+.dialog-textarea:focus {
+ border-color: #8b5cf6;
+}
+
+.dialog-textarea::placeholder {
+ color: var(--text-muted);
+}
+
+.dialog-category {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.category-label {
+ font-size: 13px;
+ color: var(--text-muted);
+ flex-shrink: 0;
+}
+
+.category-select {
+ flex: 1;
+ padding: 8px 12px;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ background: var(--surface);
+ color: var(--text);
+ font-size: 13px;
+ outline: none;
+}
+
+.dialog-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 8px;
+ padding: 14px 20px;
+ border-top: 1px solid var(--border);
+}
+
+.dialog-btn {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 16px;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 200ms;
+}
+
+.dialog-btn.cancel {
+ background: var(--surface);
+ color: var(--text-muted);
+}
+
+.dialog-btn.cancel:hover {
+ background: var(--surface-hover);
+ color: var(--text);
+}
+
+.dialog-btn.confirm {
+ background: rgba(139, 92, 246, 0.1);
+ color: #8b5cf6;
+ border: 1px solid rgba(139, 92, 246, 0.2);
+}
+
+.dialog-btn.confirm:hover {
+ background: rgba(139, 92, 246, 0.2);
+}
+
+.dialog-btn.confirm:disabled {
+ opacity: 0.5;
+ cursor: default;
+}
+
+.dialog-fade-enter-active {
+ animation: fade-in 0.25s ease-out;
+}
+
+.dialog-fade-enter-active .dialog-card {
+ animation: scale-in 0.3s cubic-bezier(0.22, 1, 0.36, 1);
+}
+
+.dialog-fade-leave-active {
+ animation: fade-in 0.2s ease-out reverse;
+}
+
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes scale-in {
+ from {
+ opacity: 0;
+ transform: scale(0.92);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
@keyframes fade-up {
- 0% { opacity: 0; transform: translateY(16px); }
- 100% { opacity: 1; transform: translateY(0); }
+ 0% {
+ opacity: 0;
+ transform: translateY(16px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
.animate-fade-up {
@@ -772,8 +1444,15 @@ function handleSearch() {
}
@keyframes slide-left {
- 0% { opacity: 0; transform: translateX(24px); }
- 100% { opacity: 1; transform: translateX(0); }
+ 0% {
+ opacity: 0;
+ transform: translateX(24px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateX(0);
+ }
}
.animate-slide-left {
@@ -784,12 +1463,14 @@ function handleSearch() {
.memo-list-leave-active {
transition: all 300ms ease-in-out;
}
+
.memo-list-enter-from {
opacity: 0;
transform: translateY(8px);
}
+
.memo-list-leave-to {
opacity: 0;
transform: translateX(-12px);
}
-
+
\ No newline at end of file
diff --git a/frontend/src/renderer/src/views/WorkspaceView.vue b/frontend/src/renderer/src/views/WorkspaceView.vue
index c1613c4..c3bd2b8 100644
--- a/frontend/src/renderer/src/views/WorkspaceView.vue
+++ b/frontend/src/renderer/src/views/WorkspaceView.vue
@@ -25,12 +25,22 @@ import {
PanelRightOpen,
PanelRightClose,
Square,
+ UploadCloud,
+ FileText,
+ Image,
+ File,
+ Brain,
+ Download,
} from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { useChatStore } from '../stores/chat'
import { useAgentStore } from '../stores/agent'
import { useModelStore } from '../stores/model'
import { useSkillStore } from '../stores/skill'
+import { useMemoryStore } from '../stores/memory'
+import FileUpload from '../components/FileUpload.vue'
+import FilePreview from '../components/FilePreview.vue'
+import { useFileUpload } from '../composables/useFileUpload'
import { getProviderLogo } from '../config/provider-logos'
import { marked } from 'marked'
@@ -44,6 +54,12 @@ const chatStore = useChatStore()
const agentStore = useAgentStore()
const modelStore = useModelStore()
const skillStore = useSkillStore()
+const memoryStore = useMemoryStore()
+
+const showMemoryInject = ref(false)
+
+const { uploadingFile, isUploading, parsedContent, fileType, fileName, uploadAndForward, clearUploadState } = useFileUpload()
+const fileUploadRef = ref
| null>(null)
const inputText = ref('')
const messagesContainer = ref(null)
@@ -55,12 +71,36 @@ const showSearchPanel = ref(false)
const searchQuery = ref('')
const searchResults = ref([])
const copiedId = ref(null)
+const showReasoning = ref>({})
+const reasoningRefs = ref>({})
+const reasoningScrollRefs = ref(null)
const isNearBottom = ref(true)
const SCROLL_BOTTOM_THRESHOLD = 120
const showScrollToBottomBtn = ref(false)
const isLoadingCurrentConv = computed(() => chatStore.isLoadingCurrentConversation)
let resizeObserver: ResizeObserver | null = null
+const showGlobalDropOverlay = ref(false)
+let globalDragCounter = 0
+let dragLeaveTimer: ReturnType | null = null
+
+const showFilePreview = ref(false)
+const previewFile = ref<{ name: string; type?: string; content?: string } | null>(null)
+
+const toastMessage = ref('')
+const showToast = ref(false)
+let toastTimer: ReturnType | null = null
+
+const displayToast = (msg: string) => {
+ if (toastTimer) clearTimeout(toastTimer)
+ toastMessage.value = msg
+ showToast.value = true
+ toastTimer = setTimeout(() => {
+ showToast.value = false
+ toastTimer = null
+ }, 3000)
+}
+
const messages = computed(() => chatStore.messages)
const isStreaming = computed(() => chatStore.isStreaming)
const isBackendReady = computed(() => chatStore.isBackendReady)
@@ -126,13 +166,28 @@ const selectModel = (providerId: string, modelId: string) => {
showModelDropdown.value = false
}
+const canSend = computed(() => {
+ if (!isBackendReady.value) return false
+ if (isUploading.value) return false
+ return inputText.value.trim().length > 0 || !!parsedContent.value
+})
+
const sendMessage = async () => {
- if (!inputText.value.trim()) return
- if (!isBackendReady.value) return
+ if (!canSend.value) return
+
+ let content = inputText.value.trim()
+ const fileContent = parsedContent.value
+ const currentFileName = fileName.value
+ const currentFileType = fileType.value
+
+ if (!content && fileContent) {
+ content = '请帮我分析上传的文件'
+ }
- const content = inputText.value
inputText.value = ''
resetTextareaHeight()
+ clearUploadState()
+ fileUploadRef.value?.clearUploadState()
const agent = agentStore.activeAgent
const resolved = modelStore.resolveModel
@@ -147,6 +202,12 @@ const sendMessage = async () => {
if (agent?.systemPrompt) options.systemPrompt = agent.systemPrompt
if (agent?.id) options.agentId = agent.id
+ if (fileContent) {
+ options.fileContent = fileContent
+ options.fileType = currentFileType
+ options.fileName = currentFileName
+ }
+
isNearBottom.value = true
await chatStore.sendMessage(content, options)
await nextTick()
@@ -204,6 +265,22 @@ const renderMarkdown = (text: string): string => {
return marked.parse(text) as string
}
+const getFileIcon = (fileType?: string) => {
+ if (!fileType) return File
+ if (fileType === 'image') return Image
+ return FileText
+}
+
+const openFilePreview = (file: { name: string; type?: string; content?: string }) => {
+ previewFile.value = { name: file.name, type: file.type, content: file.content }
+ showFilePreview.value = true
+}
+
+const closeFilePreview = () => {
+ showFilePreview.value = false
+ previewFile.value = null
+}
+
const contextUsage = computed(() => {
// 修复:倒序渲染问题 - 改用正序查找最后一条完成的助手消息
const lastAssistantMsg = messages.value.findLast(m => m.role === 'assistant' && m.done)
@@ -215,6 +292,46 @@ const contextPercent = computed(() => {
return Math.min(100, Math.round((contextUsage.value.totalTokens / modelStore.modelConfig.defaultMaxTokens) * 100))
})
+const toggleReasoning = (msgId: string) => {
+ showReasoning.value = {
+ ...showReasoning.value,
+ [msgId]: !showReasoning.value[msgId]
+ }
+}
+
+const lastAssistantMsg = computed(() => {
+ const msgs = messages.value
+ if (msgs.length === 0) return null
+ const last = msgs[msgs.length - 1]
+ return last && last.role === 'assistant' ? last : null
+})
+
+const reasoningIsRunning = computed(() => {
+ const msg = lastAssistantMsg.value
+ if (!msg) return false
+ return !msg.done && (!msg.content || msg.content.length === 0) && (msg.reasoningContent !== undefined)
+})
+
+watch(() => messages.value, async (msgs) => {
+ for (const msg of msgs) {
+ if (msg.role !== 'assistant') continue
+ if (msg.content && msg.content.length > 0 && showReasoning.value[msg.id] === undefined) {
+ showReasoning.value = { ...showReasoning.value, [msg.id]: false }
+ }
+ }
+ await nextTick()
+ // 对所有正在推理的消息自动滚动到底部
+ const scrollEls = reasoningScrollRefs.value
+ if (scrollEls) {
+ const els = Array.isArray(scrollEls) ? scrollEls : [scrollEls]
+ for (const el of els) {
+ if (el && el.scrollHeight > el.clientHeight) {
+ el.scrollTop = el.scrollHeight
+ }
+ }
+ }
+}, { deep: false, immediate: true })
+
const copyMessage = async (msgId: string, content: string) => {
try {
await navigator.clipboard.writeText(content)
@@ -223,19 +340,121 @@ const copyMessage = async (msgId: string, content: string) => {
} catch {}
}
+const handleGlobalDragEnter = (e: DragEvent) => {
+ if (e.dataTransfer?.types.includes('Files')) {
+ e.preventDefault()
+ if (dragLeaveTimer) {
+ clearTimeout(dragLeaveTimer)
+ dragLeaveTimer = null
+ }
+ globalDragCounter++
+ showGlobalDropOverlay.value = true
+ }
+}
+
+const handleGlobalDragOver = (e: DragEvent) => {
+ if (e.dataTransfer?.types.includes('Files')) {
+ e.preventDefault()
+ if (dragLeaveTimer) {
+ clearTimeout(dragLeaveTimer)
+ dragLeaveTimer = null
+ }
+ showGlobalDropOverlay.value = true
+ }
+}
+
+const handleGlobalDragLeave = (e: DragEvent) => {
+ if (e.dataTransfer?.types.includes('Files')) {
+ e.preventDefault()
+ globalDragCounter--
+ if (globalDragCounter <= 0) {
+ dragLeaveTimer = setTimeout(() => {
+ showGlobalDropOverlay.value = false
+ globalDragCounter = 0
+ }, 100)
+ }
+ }
+}
+
+const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.pdf', '.docx', '.doc', '.txt', '.md', '.csv', '.json', '.xml', '.html', '.css', '.js', '.py', '.java', '.cpp', '.c', '.h', '.go', '.rs', '.ts', '.sql', '.yaml', '.yml']
+
+const isFileAllowed = (fileName: string): boolean => {
+ const ext = fileName.toLowerCase().substring(fileName.lastIndexOf('.'))
+ return allowedExtensions.includes(ext)
+}
+
+const handleGlobalDrop = async (e: DragEvent) => {
+ e.preventDefault()
+ showGlobalDropOverlay.value = false
+ globalDragCounter = 0
+ if (dragLeaveTimer) {
+ clearTimeout(dragLeaveTimer)
+ dragLeaveTimer = null
+ }
+ const files = e.dataTransfer?.files
+ if (files && files.length > 0 && !isUploading.value) {
+ const file = files[0]
+ if (isFileAllowed(file.name)) {
+ await uploadAndForward(file)
+ } else {
+ displayToast(`不支持的文件类型: ${file.name}`)
+ }
+ }
+}
+
+const handlePaste = async (e: ClipboardEvent) => {
+ const items = e.clipboardData?.items
+ if (!items) return
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].kind === 'file') {
+ const file = items[i].getAsFile()
+ if (file && !isUploading.value) {
+ if (isFileAllowed(file.name)) {
+ e.preventDefault()
+ await uploadAndForward(file)
+ return
+ } else {
+ displayToast(`不支持的文件类型: ${file.name}`)
+ }
+ }
+ }
+ }
+}
+
const formatTime = (dateStr: string) => {
- // 修复:历史记录实时更新 - 处理Invalid Date问题
if (!dateStr || dateStr === 'undefined' || dateStr === 'null') {
return '刚刚'
}
try {
- const d = new Date(dateStr)
+ let d: Date
+ const numDate = Number(dateStr)
+ if (!isNaN(numDate)) {
+ d = new Date(numDate)
+ } else {
+ d = new Date(dateStr)
+ }
+
if (isNaN(d.getTime())) {
return '刚刚'
}
const now = new Date()
+ const diffMs = now.getTime() - d.getTime()
+ const diffMins = Math.floor(diffMs / 60000)
+ const diffHours = Math.floor(diffMs / 3600000)
+ const diffDays = Math.floor(diffMs / 86400000)
+
+ if (diffMins < 1) {
+ return '刚刚'
+ } else if (diffMins < 60) {
+ return `${diffMins}分钟前`
+ } else if (diffHours < 24) {
+ return `${diffHours}小时前`
+ } else if (diffDays < 7) {
+ return `${diffDays}天前`
+ }
+
const isToday = d.toDateString() === now.toDateString()
if (isToday) {
@@ -304,6 +523,37 @@ const handleClickOutsideModel = (e: MouseEvent) => {
}
}
+async function injectMemoryToInput() {
+ showMemoryInject.value = true
+ try {
+ const result = await memoryStore.fetchInjectionContent(agentStore.activeAgent?.id)
+ if (result.has_memory && result.content) {
+ inputText.value = `\n\n---\n系统已注入以下用户记忆,请参考:\n${result.content}\n---\n\n${inputText.value}`
+ }
+ } finally {
+ showMemoryInject.value = false
+ }
+}
+
+function handleChatTrigger(event: CustomEvent) {
+ if (event.detail?.message) {
+ inputText.value = event.detail.message
+ }
+}
+
+function handleMemoryChatTrigger(event: CustomEvent) {
+ const text = event.detail?.text
+ if (text) {
+ inputText.value = `关于我之前提到的「${text.slice(0, 80)}」,请帮我进一步分析。`
+ }
+}
+
+function handleMemoryChatTriggerDirect(text: string) {
+ inputText.value = `关于我之前提到的「${text.slice(0, 80)}」,请帮我进一步分析。`
+}
+
+(window as any).__memoryChatTrigger = handleMemoryChatTriggerDirect
+
onMounted(async () => {
await chatStore.checkBackend()
if (chatStore.isBackendReady) {
@@ -317,12 +567,26 @@ onMounted(async () => {
])
}
document.addEventListener('click', handleClickOutsideModel)
+ document.addEventListener('dragenter', handleGlobalDragEnter)
+ document.addEventListener('dragover', handleGlobalDragOver)
+ document.addEventListener('dragleave', handleGlobalDragLeave)
+ document.addEventListener('drop', handleGlobalDrop)
+ document.addEventListener('paste', handlePaste)
+ window.addEventListener('luominest:chat-trigger', handleChatTrigger as EventListener)
+ window.addEventListener('luominest:memory-chat-trigger', handleMemoryChatTrigger as EventListener)
nextTick(() => setupResizeObserver())
})
onBeforeUnmount(() => {
resizeObserver?.disconnect()
document.removeEventListener('click', handleClickOutsideModel)
+ document.removeEventListener('dragenter', handleGlobalDragEnter)
+ document.removeEventListener('dragover', handleGlobalDragOver)
+ document.removeEventListener('dragleave', handleGlobalDragLeave)
+ document.removeEventListener('drop', handleGlobalDrop)
+ document.removeEventListener('paste', handlePaste)
+ window.removeEventListener('luominest:chat-trigger', handleChatTrigger as EventListener)
+ window.removeEventListener('luominest:memory-chat-trigger', handleMemoryChatTrigger as EventListener)
})
@@ -403,11 +667,53 @@ onBeforeUnmount(() => {
{{ agentStore.activeAgent?.name || 'LuomiNest' }}
-
-
{{ msg.content }}
-
-
-
正在分析问题...
+
+
+
+ {{ msg.reasoningContent || '...' }}
+
+
+
+
+
+ {{ msg.content }}
+
+
+
+ {{ file.name }}
+
+
+
@@ -455,6 +761,7 @@ onBeforeUnmount(() => {