Repository navigation
Expand file tree
/
Copy pathuseScriptRunner.ts
More file actions
93 lines (81 loc) · 3.13 KB
/
Copy pathuseScriptRunner.ts
File metadata and controls
93 lines (81 loc) · 3.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
* [INPUT]: 依赖 react, react-i18next, @tauri-apps/api/event, ../../lib/tauri
* [OUTPUT]: 对外提供 useScriptRunner hook (isRunning, run, stop)
* [POS]: editor/ 的脚本执行生命周期管理,监听 script-output/error/completed 事件
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
*/
import { useState, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { listen } from '@tauri-apps/api/event'
import { TauriAPI } from '../../lib/tauri'
/* ================================================================
* Options
* ================================================================ */
interface UseScriptRunnerOptions {
filePath: string | null
projectPath: string
onConsoleOutput?: (output: string) => void
onConsoleError?: (error: string) => void
onScriptStart?: () => void
onScriptStop?: () => void
}
/* ================================================================
* Hook
* ================================================================ */
export function useScriptRunner({
filePath, projectPath,
onConsoleOutput, onConsoleError,
onScriptStart, onScriptStop,
}: UseScriptRunnerOptions) {
const { t } = useTranslation()
const [isRunning, setIsRunning] = useState(false)
/* ---- 事件监听: script-output / script-error / script-completed ---- */
useEffect(() => {
const unOutput = listen('script-output', (e) => onConsoleOutput?.(e.payload as string))
const unError = listen('script-error', (e) => onConsoleError?.(e.payload as string))
const unDone = listen('script-completed', () => {
setIsRunning(false)
onScriptStop?.()
})
return () => {
unOutput.then(f => f())
unError.then(f => f())
unDone.then(f => f())
}
}, [onConsoleOutput, onConsoleError, onScriptStop])
/* ---- run: 优先 UV → 回退 streaming ---- */
const run = useCallback(async () => {
if (!filePath || !projectPath || isRunning) return
if (!filePath.endsWith('.py')) {
onConsoleError?.(`${t('messages.pythonFileOnly')}\n`)
return
}
setIsRunning(true)
onConsoleOutput?.(`${t('messages.runningScript', { path: filePath })}\n`)
onScriptStart?.()
try {
const hasPyproject = await TauriAPI.fileExists(`${projectPath}/pyproject.toml`)
if (hasPyproject) {
onConsoleOutput?.(`${t('messages.usingUvStreaming')}\n`)
try {
await TauriAPI.runScriptWithUvStreaming(projectPath, filePath)
return
} catch {
onConsoleOutput?.(`${t('messages.uvFallback')}\n`)
}
}
await TauriAPI.runScriptWithStreaming(projectPath, filePath)
} catch (error) {
onConsoleError?.(`${t('messages.scriptError', { error: String(error) })}\n`)
setIsRunning(false)
onScriptStop?.()
}
}, [filePath, projectPath, isRunning, onConsoleOutput, onConsoleError, onScriptStart, onScriptStop, t])
/* ---- stop ---- */
const stop = useCallback(() => {
if (!isRunning) return
TauriAPI.stopRunningScript()
.finally(() => { setIsRunning(false); onScriptStop?.() })
}, [isRunning, onScriptStop])
return { isRunning, run, stop }
}