Files
deerflow-code/frontend-web/src/open-canvas/components/artifact/CanvasEmptyState.tsx
T
2026-05-15 14:08:01 +08:00

169 lines
4.6 KiB
TypeScript

import { FileText, Code2, Globe, Atom, Terminal } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { useCanvasGraphContext } from "../../contexts/CanvasGraphContext";
import { createDraftArtifact } from "../../utils/artifact";
import type { ProgrammingLanguageOptions } from "../../types/artifact";
const HTML_TEMPLATE = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页面标题</title>
<style>
body { font-family: sans-serif; margin: 2rem; color: #333; }
</style>
</head>
<body>
<h1>Hello World</h1>
<p>在这里开始编写你的页面</p>
</body>
</html>`;
const REACT_TEMPLATE = `import React from "react";
interface Props {
// 在这里定义 props
}
export default function MyComponent({}: Props) {
return (
<div>
<h1>My Component</h1>
<p>在这里开始编写你的组件</p>
</div>
);
}`;
const PYTHON_TEMPLATE = `def main():
"""主函数"""
print("Hello, World!")
if __name__ == "__main__":
main()`;
interface QuickStartOption {
icon: LucideIcon;
title: string;
description: string;
type: "text" | "code";
templateTitle: string;
content: string;
language?: ProgrammingLanguageOptions;
}
const QUICK_START_OPTIONS: QuickStartOption[] = [
{
icon: FileText,
title: "空白文档",
description: "从空白 Markdown 文档开始",
type: "text",
templateTitle: "未命名文档",
content: "",
},
{
icon: Code2,
title: "空白代码",
description: "从空白代码文件开始",
type: "code",
templateTitle: "未命名代码",
content: "",
language: "python",
},
{
icon: Globe,
title: "HTML 页面",
description: "基础 HTML 页面模板",
type: "code",
templateTitle: "HTML 页面",
content: HTML_TEMPLATE,
language: "html",
},
{
icon: Atom,
title: "React 组件",
description: "TypeScript React 组件模板",
type: "code",
templateTitle: "React 组件",
content: REACT_TEMPLATE,
language: "typescript",
},
{
icon: Terminal,
title: "Python 脚本",
description: "Python 入口脚本模板",
type: "code",
templateTitle: "Python 脚本",
content: PYTHON_TEMPLATE,
language: "python",
},
];
interface CanvasEmptyStateProps {
initialType?: "text" | "code" | null;
}
export function CanvasEmptyState({ initialType }: CanvasEmptyStateProps) {
const { setArtifact } = useCanvasGraphContext();
function handleSelect(option: QuickStartOption) {
setArtifact(
createDraftArtifact(option.type, {
title: option.templateTitle,
content: option.content,
language: option.language,
}),
);
}
const filtered =
initialType
? QUICK_START_OPTIONS.filter(
(o) => !initialType || o.type === initialType || o.type === "text",
)
: QUICK_START_OPTIONS;
return (
<div className="h-full flex flex-col items-center justify-center px-6">
<div className="w-full max-w-sm space-y-6">
<div className="text-center space-y-1">
<p className="text-sm font-medium">选择模板快速开始</p>
<p className="text-xs text-muted-foreground">
或直接在左侧聊天区发送消息
</p>
</div>
<div className="space-y-2">
{QUICK_START_OPTIONS.map((option) => {
const Icon = option.icon;
const isHighlighted = initialType && option.type === initialType && option.content === "";
return (
<button
key={option.title}
onClick={() => handleSelect(option)}
className={`w-full flex items-center gap-3 rounded-lg border px-4 py-3 text-left transition-all hover:border-primary/50 hover:bg-accent/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
isHighlighted
? "border-primary/40 bg-primary/5"
: "bg-card"
}`}
>
<div className="w-8 h-8 rounded-md bg-muted flex items-center justify-center shrink-0">
<Icon className="w-4 h-4 text-muted-foreground" />
</div>
<div className="min-w-0">
<p className="text-sm font-medium truncate">{option.title}</p>
<p className="text-xs text-muted-foreground truncate">
{option.description}
</p>
</div>
</button>
);
})}
</div>
</div>
</div>
);
}