From 950c1fa7147c08c408c54559313b6375dfe51f16 Mon Sep 17 00:00:00 2001
From: ViperEkura <3081035982@qq.com>
Date: Thu, 26 Mar 2026 21:51:37 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6?=
=?UTF-8?q?=E7=AE=A1=E7=90=86=E9=83=A8=E5=88=86=E5=B9=B6=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?UI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
frontend/src/api/index.js | 36 ------
frontend/src/components/ChatView.vue | 2 +-
frontend/src/components/FileExplorer.vue | 133 ++++++++---------------
frontend/src/components/Sidebar.vue | 9 ++
frontend/src/components/StatsPanel.vue | 2 +-
5 files changed, 54 insertions(+), 128 deletions(-)
diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
index dae4a07..309a801 100644
--- a/frontend/src/api/index.js
+++ b/frontend/src/api/index.js
@@ -121,11 +121,6 @@ export const modelApi = {
return res
},
- // Clear cache (e.g., when models changed on server)
- clearCache() {
- modelsCache = null
- localStorage.removeItem('models_cache')
- }
}
export const statsApi = {
@@ -205,30 +200,6 @@ export const projectApi = {
return request(`/projects/${projectId}`, { method: 'DELETE' })
},
- uploadFolder(data) {
- const formData = new FormData()
- formData.append('name', data.name || '')
- formData.append('description', data.description || '')
- for (const file of data.files) {
- formData.append('files', file, file.webkitRelativePath)
- }
- return fetch(`${BASE}/projects/upload`, {
- method: 'POST',
- body: formData,
- }).then(async res => {
- let json
- try {
- json = await res.json()
- } catch (_) {
- throw new Error(`服务器错误 (${res.status}),请确认后端已重启`)
- }
- if (json.code !== 0) {
- throw new Error(json.message || 'Request failed')
- }
- return json
- })
- },
-
listFiles(projectId, path = '') {
return request(`/projects/${projectId}/files${buildQueryParams({ path })}`)
},
@@ -261,11 +232,4 @@ export const projectApi = {
body: { path: dirPath },
})
},
-
- search(projectId, query, options = {}) {
- return request(`/projects/${projectId}/search`, {
- method: 'POST',
- body: { query, ...options },
- })
- },
}
diff --git a/frontend/src/components/ChatView.vue b/frontend/src/components/ChatView.vue
index f3703af..c8f4818 100644
--- a/frontend/src/components/ChatView.vue
+++ b/frontend/src/components/ChatView.vue
@@ -93,7 +93,7 @@ const props = defineProps({
toolsEnabled: { type: Boolean, default: true },
})
-const emit = defineEmits(['sendMessage', 'deleteMessage', 'regenerateMessage', 'toggleSettings', 'toggleStats', 'loadMoreMessages', 'toggleTools'])
+const emit = defineEmits(['sendMessage', 'deleteMessage', 'regenerateMessage', 'loadMoreMessages', 'toggleTools'])
const scrollContainer = ref(null)
const inputRef = ref(null)
diff --git a/frontend/src/components/FileExplorer.vue b/frontend/src/components/FileExplorer.vue
index b1fe634..acb1e27 100644
--- a/frontend/src/components/FileExplorer.vue
+++ b/frontend/src/components/FileExplorer.vue
@@ -60,25 +60,13 @@
-
-
-
-
-
-
-
-
![]()
-
-
-
-
-
+
+
+
+
+
+
![]()
+
@@ -150,7 +131,6 @@ import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { projectApi } from '../api'
import FileTreeItem from './FileTreeItem.vue'
import { renderMarkdown } from '../utils/markdown'
-import { highlightCode } from '../utils/highlight'
import { normalizeFileTree } from '../utils/fileTree'
const IMAGE_EXTS = new Set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg', 'ico'])
@@ -169,10 +149,8 @@ const loadingTree = ref(false)
// -- Viewer state --
const activeFile = ref(null)
-const fileContent = ref('')
const fileError = ref('')
const loadingFile = ref(false)
-const editing = ref(false)
const editContent = ref('')
const saving = ref(false)
const editorRef = ref(null)
@@ -195,18 +173,11 @@ const fileType = computed(() => {
})
// -- Content rendering --
-const renderedContent = computed(() => {
- if (!fileContent.value) return ''
- if (isMarkdownFile.value) return renderMarkdown(fileContent.value)
- // Wrap code in fenced code block — rendered by same markdown pipeline, same CSS
- const lang = fileExt.value || ''
- return renderMarkdown('```' + lang + '\n' + fileContent.value + '\n```')
-})
-
const editorHighlighted = computed(() => {
if (!editContent.value) return ''
+ if (isMarkdownFile.value) return renderMarkdown(editContent.value)
const lang = fileExt.value || ''
- return highlightCode(editContent.value, lang)
+ return renderMarkdown('```' + lang + '\n' + editContent.value + '\n```')
})
function syncScroll() {
@@ -232,9 +203,8 @@ async function loadTree(path = '') {
async function openFile(filepath) {
activeFile.value = filepath
- fileContent.value = ''
fileError.value = ''
- editing.value = false
+ editContent.value = ''
imageUrl.value = ''
loadingFile.value = true
@@ -254,7 +224,7 @@ async function openFile(filepath) {
try {
const res = await projectApi.readFile(props.projectId, filepath)
- fileContent.value = res.data.content
+ editContent.value = res.data.content
} catch (e) {
fileError.value = e.message || '加载文件失败'
} finally {
@@ -262,23 +232,11 @@ async function openFile(filepath) {
}
}
-function startEdit() {
- editContent.value = fileContent.value
- editing.value = true
-}
-
-function cancelEdit() {
- editing.value = false
- editContent.value = ''
-}
-
async function saveFile() {
if (!activeFile.value || saving.value) return
saving.value = true
try {
await projectApi.writeFile(props.projectId, activeFile.value, editContent.value)
- fileContent.value = editContent.value
- editing.value = false
} catch (e) {
alert('保存失败: ' + e.message)
} finally {
@@ -293,7 +251,6 @@ function deleteFile() {
projectApi.deleteFile(props.projectId, activeFile.value).then(() => {
activeFile.value = null
- fileContent.value = ''
loadTree()
}).catch(e => {
alert('删除失败: ' + e.message)
@@ -308,7 +265,6 @@ async function createNewFile() {
await projectApi.writeFile(props.projectId, path, '')
await loadTree()
openFile(path)
- startEdit()
} catch (e) {
alert('创建失败: ' + e.message)
}
@@ -330,14 +286,11 @@ function onEditorKeydown(e) {
e.preventDefault()
saveFile()
}
- if (e.key === 'Escape') {
- cancelEdit()
- }
}
// Ctrl+S global shortcut
function onGlobalKeydown(e) {
- if (e.key === 's' && (e.ctrlKey || e.metaKey) && editing.value) {
+ if (e.key === 's' && (e.ctrlKey || e.metaKey) && activeFile.value) {
e.preventDefault()
saveFile()
}
@@ -345,9 +298,8 @@ function onGlobalKeydown(e) {
watch(() => props.projectId, () => {
activeFile.value = null
- fileContent.value = ''
+ editContent.value = ''
imageUrl.value = ''
- editing.value = false
loadTree()
})
@@ -536,10 +488,6 @@ onUnmounted(() => {
resize: none;
border: none;
outline: none;
- padding: 12px;
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
- font-size: 13px;
- line-height: 1.5;
color: var(--text-primary);
background: var(--bg-code);
tab-size: 4;
@@ -547,14 +495,12 @@ onUnmounted(() => {
overflow: auto;
}
-.file-editor::-webkit-scrollbar,
-.file-content-viewer::-webkit-scrollbar {
+.file-editor::-webkit-scrollbar {
width: 6px;
height: 6px;
}
-.file-editor::-webkit-scrollbar-thumb,
-.file-content-viewer::-webkit-scrollbar-thumb {
+.file-editor::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 3px;
}
@@ -575,48 +521,55 @@ onUnmounted(() => {
flex: 1;
display: flex;
position: relative;
- margin: 8px;
- border: 1px solid var(--border-light);
- border-radius: 8px;
- background: var(--bg-code);
overflow: hidden;
}
-.editor-container pre.editor-highlight {
+.editor-highlight {
position: absolute;
inset: 0;
margin: 0;
- padding: 12px;
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
- font-size: 13px;
- line-height: 1.5;
+ padding: 20px 24px;
overflow: auto;
pointer-events: none;
color: var(--text-primary);
- background: var(--bg-code);
+ background: transparent;
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ font-size: 14px;
+ line-height: 1.7;
white-space: pre;
tab-size: 4;
}
+/* Strip wrapper styles from markdown-rendered so text aligns with textarea */
+.editor-highlight :deep(pre),
+.editor-highlight :deep(code) {
+ margin: 0;
+ padding: 0;
+ background: transparent !important;
+ border: none;
+ border-radius: 0;
+ font-size: inherit;
+ font-family: inherit;
+ line-height: inherit;
+ white-space: inherit;
+ tab-size: inherit;
+ word-wrap: normal;
+}
+
.editor-container .file-editor {
position: relative;
z-index: 1;
flex: 1;
border: none;
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+ font-size: 14px;
+ line-height: 1.7;
+ padding: 20px 24px;
color: transparent;
caret-color: var(--text-primary);
background: transparent;
}
-.file-content-viewer {
- flex: 1;
- overflow: auto;
- padding: 20px 24px;
- font-size: 14px;
- line-height: 1.7;
- color: var(--text-primary);
-}
-
/* -- Image viewer -- */
.image-viewer {
flex: 1;
diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue
index 46c86e2..fe9d283 100644
--- a/frontend/src/components/Sidebar.vue
+++ b/frontend/src/components/Sidebar.vue
@@ -76,6 +76,9 @@
+
{{ groupedData.standalone.length }}
+