初始化模版工程
This commit is contained in:
162
components/nova-sdk/task-panel/Preview/ScriptPreview.tsx
Normal file
162
components/nova-sdk/task-panel/Preview/ScriptPreview.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import type { Element } from 'hast'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { cn } from '@/utils/cn'
|
||||
import { useHighlighter } from './useHighlighter'
|
||||
|
||||
export interface ScriptPreviewProps {
|
||||
/** 脚本代码 */
|
||||
code: string
|
||||
/** 执行结果 */
|
||||
output?: string
|
||||
/** 语言类型 */
|
||||
language?: string
|
||||
/** 脚本名称/标题 */
|
||||
title?: string
|
||||
/** 自定义类名 */
|
||||
className?: string
|
||||
/** 主题 */
|
||||
theme?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据工具名称或内容检测语言
|
||||
*/
|
||||
function detectScriptLanguage(code: string, hint?: string): string {
|
||||
// 优先使用提示
|
||||
if (hint) {
|
||||
const lowerHint = hint.toLowerCase()
|
||||
if (lowerHint.includes('python') || lowerHint.includes('py')) return 'python'
|
||||
if (lowerHint.includes('javascript') || lowerHint.includes('js')) return 'javascript'
|
||||
if (lowerHint.includes('typescript') || lowerHint.includes('ts')) return 'typescript'
|
||||
if (lowerHint.includes('shell') || lowerHint.includes('bash') || lowerHint.includes('sh')) return 'bash'
|
||||
if (lowerHint.includes('sql')) return 'sql'
|
||||
}
|
||||
|
||||
const trimmed = code.trim()
|
||||
|
||||
// Python
|
||||
if (
|
||||
/^(def|class|import|from|if __name__|async def|@\w+)\s/.test(trimmed) ||
|
||||
/\bprint\s*\(/.test(trimmed)
|
||||
) {
|
||||
return 'python'
|
||||
}
|
||||
|
||||
// Bash/Shell
|
||||
if (
|
||||
/^(#!\/bin\/(bash|sh)|curl|wget|npm|yarn|cd|ls|echo|sudo)\s/.test(trimmed) ||
|
||||
/^\$\s/.test(trimmed)
|
||||
) {
|
||||
return 'bash'
|
||||
}
|
||||
|
||||
// JavaScript/Node
|
||||
if (
|
||||
/^(const|let|var|function|async|import|export)\s/.test(trimmed) ||
|
||||
/console\.(log|error|warn)/.test(trimmed)
|
||||
) {
|
||||
return 'javascript'
|
||||
}
|
||||
|
||||
// SQL
|
||||
if (/^(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)\s/i.test(trimmed)) {
|
||||
return 'sql'
|
||||
}
|
||||
|
||||
return 'plaintext'
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码块组件
|
||||
*/
|
||||
function CodeBlock({
|
||||
code,
|
||||
lang,
|
||||
theme = 'one-light',
|
||||
}: {
|
||||
code: string
|
||||
lang: string
|
||||
theme?: string
|
||||
title?: string
|
||||
}) {
|
||||
const highlighter = useHighlighter()
|
||||
const [highlightedHtml, setHighlightedHtml] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
const generateHighlightedHtml = async () => {
|
||||
if (!highlighter || !code) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return highlighter.codeToHtml(code, {
|
||||
lang,
|
||||
theme,
|
||||
transformers: [
|
||||
{
|
||||
code(node: Element) {
|
||||
const className = node.properties.className
|
||||
if (Array.isArray(className)) {
|
||||
className.push('whitespace-pre-wrap', 'break-all')
|
||||
} else {
|
||||
node.properties.className = ['whitespace-pre-wrap', 'break-all']
|
||||
}
|
||||
},
|
||||
pre(node: Element) {
|
||||
node.tagName = 'div'
|
||||
const className = node.properties.className
|
||||
if (Array.isArray(className)) {
|
||||
className.push('overflow-auto')
|
||||
} else {
|
||||
node.properties.className = ['overflow-auto']
|
||||
}
|
||||
// 移除背景色
|
||||
delete node.properties.style
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
generateHighlightedHtml().then(html => {
|
||||
setHighlightedHtml(html)
|
||||
})
|
||||
}, [code, lang, theme, highlighter])
|
||||
|
||||
if (!highlightedHtml) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8 text-muted-foreground bg-transparent rounded-lg">
|
||||
<div className="w-6 h-6 rounded-full animate-spin" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-lg overflow-hidden bg-transparent">
|
||||
<div dangerouslySetInnerHTML={{ __html: highlightedHtml }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 脚本预览组件 - 专门用于显示脚本代码
|
||||
*/
|
||||
export function ScriptPreview({
|
||||
code,
|
||||
language,
|
||||
title = '脚本代码',
|
||||
className,
|
||||
theme = 'one-light',
|
||||
}: Omit<ScriptPreviewProps, 'output'>) {
|
||||
const detectedLang = language || detectScriptLanguage(code, title)
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col h-full', className)}>
|
||||
<ScrollArea className="flex-1">
|
||||
<CodeBlock code={code} lang={detectedLang} theme={theme} />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScriptPreview
|
||||
Reference in New Issue
Block a user