Add JavaScript execution support to code blocks

- Register new IPC channel and handler for JavaScript code execution
- Extend code block execution to support JavaScript, TypeScript, and JS languages
- Add JavaScript service with sandboxed execution using quickJs
- Update UI to show JavaScript execution option alongside Python
This commit is contained in:
suyao 2025-10-08 07:07:13 +08:00
parent f68f6e9896
commit 651e9a529e
No known key found for this signature in database
5 changed files with 57 additions and 15 deletions

View File

@ -91,6 +91,9 @@ export enum IpcChannel {
// Python
Python_Execute = 'python:execute',
// JavaScript
Js_Execute = 'js:execute',
//copilot
Copilot_GetAuthMessage = 'copilot:get-auth-message',
Copilot_GetCopilotToken = 'copilot:get-copilot-token',

View File

@ -27,6 +27,7 @@ import DxtService from './services/DxtService'
import { ExportService } from './services/ExportService'
import { fileStorage as fileManager } from './services/FileStorage'
import FileService from './services/FileSystemService'
import { jsService } from './services/JsService'
import KnowledgeService from './services/KnowledgeService'
import mcpService from './services/MCPService'
import MemoryService from './services/memory/MemoryService'
@ -709,6 +710,11 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
}
)
// Register JavaScript execution handler
ipcMain.handle(IpcChannel.Js_Execute, async (_, code: string, timeout?: number) => {
return await jsService.executeScript(code, { timeout })
})
ipcMain.handle(IpcChannel.App_IsBinaryExist, (_, name: string) => isBinaryExists(name))
ipcMain.handle(IpcChannel.App_GetBinaryPath, (_, name: string) => getBinaryPath(name))
ipcMain.handle(IpcChannel.App_InstallUvBinary, () => runInstallScript('install-uv.js'))

View File

@ -343,6 +343,9 @@ const api = {
execute: (script: string, context?: Record<string, any>, timeout?: number) =>
ipcRenderer.invoke(IpcChannel.Python_Execute, script, context, timeout)
},
js: {
execute: (code: string, timeout?: number) => ipcRenderer.invoke(IpcChannel.Js_Execute, code, timeout)
},
shell: {
openExternal: (url: string, options?: Electron.OpenExternalOptions) => shell.openExternal(url, options)
},

View File

@ -87,7 +87,8 @@ export const CodeBlockView: React.FC<Props> = memo(({ children, language, onSave
const [tools, setTools] = useState<ActionTool[]>([])
const isExecutable = useMemo(() => {
return codeExecution.enabled && language === 'python'
const executableLanguages = ['python', 'javascript', 'js', 'typescript', 'ts']
return codeExecution.enabled && executableLanguages.includes(language.toLowerCase())
}, [codeExecution.enabled, language])
const sourceViewRef = useRef<CodeEditorHandles>(null)
@ -152,21 +153,49 @@ export const CodeBlockView: React.FC<Props> = memo(({ children, language, onSave
setIsRunning(true)
setExecutionResult(null)
pyodideService
.runScript(children, {}, codeExecution.timeoutMinutes * 60000)
.then((result) => {
setExecutionResult(result)
})
.catch((error) => {
logger.error('Unexpected error:', error)
setExecutionResult({
text: `Unexpected error: ${error.message || 'Unknown error'}`
const isPython = language === 'python'
const isJavaScript = ['javascript', 'js', 'typescript', 'ts'].includes(language.toLowerCase())
if (isPython) {
pyodideService
.runScript(children, {}, codeExecution.timeoutMinutes * 60000)
.then((result) => {
setExecutionResult(result)
})
})
.finally(() => {
setIsRunning(false)
})
}, [children, codeExecution.timeoutMinutes])
.catch((error) => {
logger.error('Unexpected error:', error)
setExecutionResult({
text: `Unexpected error: ${error.message || 'Unknown error'}`
})
})
.finally(() => {
setIsRunning(false)
})
} else if (isJavaScript) {
window.api.js
.execute(children, codeExecution.timeoutMinutes * 60000)
.then((result) => {
if (result.error) {
setExecutionResult({
text: `Error: ${result.error}\n${result.stderr || ''}`
})
} else {
setExecutionResult({
text: result.stdout || (result.stderr ? `stderr: ${result.stderr}` : 'Execution completed')
})
}
})
.catch((error) => {
logger.error('Unexpected error:', error)
setExecutionResult({
text: `Unexpected error: ${error.message || 'Unknown error'}`
})
})
.finally(() => {
setIsRunning(false)
})
}
}, [children, codeExecution.timeoutMinutes, language])
const showPreviewTools = useMemo(() => {
return viewMode !== 'source' && hasSpecialView

View File

@ -3346,6 +3346,7 @@
"dify_knowledge": "Dify 的 MCP 伺服器實現,提供了一個簡單的 API 來與 Dify 進行互動。需要配置 Dify Key",
"fetch": "用於獲取 URL 網頁內容的 MCP 伺服器",
"filesystem": "實現文件系統操作的模型上下文協議MCP的 Node.js 伺服器。需要配置允許訪問的目錄",
"js": "在安全的沙盒環境中執行 JavaScript 程式碼。使用 quickJs 執行 JavaScript支援大多數標準函式庫和流行的第三方函式庫",
"mcp_auto_install": "自動安裝 MCP 服務(測試版)",
"memory": "基於本地知識圖譜的持久性記憶基礎實現。這使得模型能夠在不同對話間記住使用者的相關資訊。需要配置 MEMORY_FILE_PATH 環境變數。",
"no": "無描述",