Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
"vue-i18n": "^11.1.11",
"vue-router": "4",
"vue-tsc": "^2.2.10",
"vue-use-monaco": "^0.0.6",
"vue-use-monaco": "^0.0.8",
"vue-virtual-scroller": "^2.0.0-beta.8",
"vuedraggable": "^4.1.0",
"yaml": "^2.8.0",
Expand Down
21 changes: 11 additions & 10 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,17 +156,18 @@ app.whenReady().then(async () => {
// 注册 'deepcdn' 协议,用于加载应用内置资源 (模拟 CDN)
protocol.handle('deepcdn', (request) => {
try {
// console.log('deepcdn', request.url)
const filePath = request.url.slice('deepcdn://'.length)
// 根据开发/生产环境确定资源路径
const resourcesPath = is.dev
? path.join(app.getAppPath(), 'resources')
: process.resourcesPath
// 检查资源是否被解包 (app.asar.unpacked),优先使用解包路径
const unpackedResourcesPath = path.join(resourcesPath, 'app.asar.unpacked', 'resources')

const baseResourcesDir = fs.existsSync(unpackedResourcesPath)
? unpackedResourcesPath
: path.join(resourcesPath, 'resources') // 否则使用默认资源路径
// 根据开发/生产环境确定资源路径(按候选目录探测,避免错误拼接导致重复 resources)
const candidates = is.dev
? [path.join(app.getAppPath(), 'resources')]
: [
path.join(process.resourcesPath, 'app.asar.unpacked', 'resources'),
path.join(process.resourcesPath, 'resources'),
process.resourcesPath
]
const baseResourcesDir =
candidates.find((p) => fs.existsSync(path.join(p, 'cdn'))) || candidates[0]

const fullPath = path.join(baseResourcesDir, 'cdn', filePath)

Expand Down
36 changes: 27 additions & 9 deletions src/renderer/src/components/artifacts/ArtifactDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ const appVersion = ref('')
const codeLanguage = ref(
artifactStore.currentArtifact?.language || artifactStore.currentArtifact?.type || ''
)
const { createEditor, updateCode } = useMonaco()
const { createEditor, updateCode } = useMonaco({ MAX_HEIGHT: '500px' })
const codeEditor = ref<any>(null)

// 创建节流版本的语言检测函数,1秒内最多执行一次
Expand All @@ -176,24 +176,29 @@ const throttledDetectLanguage = useThrottleFn(

watch(
() => artifactStore.currentArtifact,
() => {
// 如果当前 artifact 的语言已经被检测过了,就不再进行检测
codeLanguage.value =
artifactStore.currentArtifact?.language ||
getFileExtension(artifactStore.currentArtifact?.type || '')
(newArtifact) => {
if (!newArtifact) return

// Update language detection
codeLanguage.value = newArtifact.language || getFileExtension(newArtifact.type || '')

if (codeLanguage.value === 'mermaid') {
return
}
const newCode = artifactStore.currentArtifact?.content || ''

const newCode = newArtifact.content || ''

// Check if we need to detect language
if (!codeLanguage.value || codeLanguage.value === '') {
throttledDetectLanguage(newCode)
}
updateCode(artifactStore.currentArtifact?.content || '', codeLanguage.value)

// Always update Monaco editor content
updateCode(newCode, codeLanguage.value)
},
{
immediate: true
immediate: true,
deep: true // Add deep watching to catch property changes
Comment on lines +179 to +201
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid duplicate Monaco updates and remove unnecessary deep watch

This watcher sets language and also pushes content to Monaco; combined with the dedicated content watcher and the language watcher, this causes double/triple updateCode calls per change. Also, deep: true is unnecessary now that content has its own watcher and the store updates immutably.

Apply:

-    // Always update Monaco editor content
-    updateCode(newCode, codeLanguage.value)
+    // Defer editor updates to dedicated content/language watchers to avoid duplicate updates
   },
   {
-    immediate: true,
-    deep: true // Add deep watching to catch property changes
+    immediate: true
   }

Additionally, consider only updating the ref when the computed language actually changes:

const nextLang = newArtifact.language || getFileExtension(newArtifact.type || '')
if (nextLang !== codeLanguage.value) codeLanguage.value = nextLang
🤖 Prompt for AI Agents
In src/renderer/src/components/artifacts/ArtifactDialog.vue around lines 179 to
201, the artifact watcher currently performs language detection and also always
calls updateCode and uses deep: true, causing duplicate Monaco updates and
unnecessary deep watching; change it to only compute the next language, assign
it to codeLanguage.value only when it actually differs (use nextLang =
newArtifact.language || getFileExtension(newArtifact.type || '') and compare
before assigning), keep the mermaid early return, keep the
throttledDetectLanguage call when language is empty, remove the unconditional
updateCode call from this watcher (let the dedicated content watcher update
Monaco), and remove the deep: true option from the watcher configuration.

}
)

Expand All @@ -209,6 +214,19 @@ watch(
}
)

// Add a specific watcher for content changes to ensure real-time updates
watch(
() => artifactStore.currentArtifact?.content,
(newContent) => {
if (newContent !== undefined) {
updateCode(newContent, codeLanguage.value)
}
},
{
immediate: true
}
)
Comment on lines +217 to +228
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Content watcher updates Monaco for mermaid and before editor init; add guards and skip no-op updates

Currently this watcher will:

  • Push mermaid content to Monaco (defeats the earlier mermaid short-circuit).
  • Call updateCode before the editor is created when the editor is hidden (Preview mode).
  • Re-apply identical content repeatedly.

Apply:

-watch(
-  () => artifactStore.currentArtifact?.content,
-  (newContent) => {
-    if (newContent !== undefined) {
-      updateCode(newContent, codeLanguage.value)
-    }
-  },
-  {
-    immediate: true
-  }
-)
+watch(
+  () => artifactStore.currentArtifact?.content,
+  (newContent, oldContent) => {
+    if (newContent === undefined || newContent === oldContent) return
+    if (codeLanguage.value === 'mermaid') return
+    if (!codeEditor.value) return
+    updateCode(newContent, codeLanguage.value)
+  }
+)

This keeps streaming updates responsive while preventing invalid mermaid pushes and pre-init updates.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Add a specific watcher for content changes to ensure real-time updates
watch(
() => artifactStore.currentArtifact?.content,
(newContent) => {
if (newContent !== undefined) {
updateCode(newContent, codeLanguage.value)
}
},
{
immediate: true
}
)
// Add a specific watcher for content changes to ensure real-time updates
watch(
() => artifactStore.currentArtifact?.content,
(newContent, oldContent) => {
// Skip undefined or unchanged content
if (newContent === undefined || newContent === oldContent) return
// Don’t push mermaid to Monaco (handled elsewhere)
if (codeLanguage.value === 'mermaid') return
// Wait until the editor is initialized
if (!codeEditor.value) return
updateCode(newContent, codeLanguage.value)
}
)
🤖 Prompt for AI Agents
In src/renderer/src/components/artifacts/ArtifactDialog.vue around lines
217-228, the new watcher currently pushes mermaid content into Monaco, runs
before the editor exists (Preview mode), and re-applies identical content;
update the watcher to short-circuit early: 1) if artifact type/language
indicates mermaid (or other non-editor format) skip calling updateCode, 2) if
the editor instance is not initialized or the editor is hidden in Preview mode
skip calling updateCode, and 3) if newContent exactly equals the current editor
model value (no-op) skip calling updateCode; keep the watcher immediate but rely
on these guards so streaming updates stay responsive while avoiding invalid or
redundant editor updates.


watch(
() => codeEditor.value,
() => {
Expand Down
22 changes: 14 additions & 8 deletions src/renderer/src/components/message/MessageBlockContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,13 @@ watch(
if (part.type === 'artifact' && part.artifact) {
if (props.block.status === 'loading') {
if (artifactStore.currentArtifact?.id === part.artifact.identifier) {
artifactStore.currentArtifact.content = part.content
artifactStore.currentArtifact.title = part.artifact.title
artifactStore.currentArtifact.type = part.artifact.type
artifactStore.currentArtifact.status = part.loading ? 'loading' : 'loaded'
// Use updateArtifactContent to trigger reactivity
artifactStore.updateArtifactContent({
content: part.content,
title: part.artifact.title,
type: part.artifact.type,
status: part.loading ? 'loading' : 'loaded'
})
} else {
artifactStore.showArtifact(
{
Expand All @@ -91,10 +94,13 @@ watch(
}
} else {
if (artifactStore.currentArtifact?.id === part.artifact.identifier) {
artifactStore.currentArtifact.content = part.content
artifactStore.currentArtifact.title = part.artifact.title
artifactStore.currentArtifact.type = part.artifact.type
artifactStore.currentArtifact.status = 'loaded'
// Use updateArtifactContent to trigger reactivity
artifactStore.updateArtifactContent({
content: part.content,
title: part.artifact.title,
type: part.artifact.type,
status: 'loaded'
})
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion src/renderer/src/stores/artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,24 @@ export const useArtifactStore = defineStore('artifact', () => {
return currentMessageId.value === messageId && currentThreadId.value === threadId
}

const updateArtifactContent = (updates: Partial<ArtifactState>) => {
if (currentArtifact.value) {
// Create a new object to trigger reactivity
currentArtifact.value = {
...currentArtifact.value,
...updates
}
}
}

return {
currentArtifact,
currentMessageId,
currentThreadId,
isOpen,
showArtifact,
hideArtifact,
validateContext
validateContext,
updateArtifactContent
}
})