277 lines
9.1 KiB
TypeScript
277 lines
9.1 KiB
TypeScript
|
|
import {
|
||
|
|
ApartmentOutlined,
|
||
|
|
AppstoreOutlined,
|
||
|
|
BgColorsOutlined,
|
||
|
|
CodeOutlined,
|
||
|
|
DatabaseOutlined,
|
||
|
|
DownOutlined,
|
||
|
|
FileTextOutlined,
|
||
|
|
LoginOutlined,
|
||
|
|
PaperClipOutlined,
|
||
|
|
RobotOutlined,
|
||
|
|
RocketOutlined,
|
||
|
|
SendOutlined,
|
||
|
|
ThunderboltOutlined,
|
||
|
|
} from "@ant-design/icons";
|
||
|
|
import { useRef, useState } from "react";
|
||
|
|
import WorkspacePageShell from "../../components/WorkspacePageShell";
|
||
|
|
import type { WebGenerationPreviewTask } from "../../types";
|
||
|
|
|
||
|
|
interface AgentPageProps {
|
||
|
|
tasks: WebGenerationPreviewTask[];
|
||
|
|
isAuthenticated: boolean;
|
||
|
|
onCreateTask: (input: {
|
||
|
|
title: string;
|
||
|
|
type: WebGenerationPreviewTask["type"];
|
||
|
|
prompt: string;
|
||
|
|
}) => Promise<WebGenerationPreviewTask>;
|
||
|
|
onRequireLogin: (input: {
|
||
|
|
title: string;
|
||
|
|
type: WebGenerationPreviewTask["type"];
|
||
|
|
prompt: string;
|
||
|
|
}) => void;
|
||
|
|
onOpenLogin: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const agentModes = [
|
||
|
|
{
|
||
|
|
id: "task",
|
||
|
|
label: "任务拆解",
|
||
|
|
icon: <FileTextOutlined />,
|
||
|
|
placeholder: "拆解「新品发布会全流程」",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "workflow",
|
||
|
|
label: "流程编排",
|
||
|
|
icon: <ApartmentOutlined />,
|
||
|
|
placeholder: "规划「内容生产自动化链路」",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "data",
|
||
|
|
label: "数据分析",
|
||
|
|
icon: <DatabaseOutlined />,
|
||
|
|
placeholder: "分析「本周转化异常原因」",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "code",
|
||
|
|
label: "代码执行",
|
||
|
|
icon: <CodeOutlined />,
|
||
|
|
placeholder: "生成「落地页 A/B 测试脚本」",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "content",
|
||
|
|
label: "内容生成",
|
||
|
|
icon: <BgColorsOutlined />,
|
||
|
|
placeholder: "创作「品牌短片分镜脚本」",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: "agent",
|
||
|
|
label: "智能体编排",
|
||
|
|
icon: <RobotOutlined />,
|
||
|
|
placeholder: "启动「多 Agent 调研与交付」",
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
const quickStarts = ["「新品发布」全链路运营", "「销售日报」自动分析", "「竞品监控」每周报告"];
|
||
|
|
|
||
|
|
function getTaskSourceLabel(task: WebGenerationPreviewTask): string | null {
|
||
|
|
if (task.source === "server") return "正式";
|
||
|
|
if (task.source === "preview") return "预览";
|
||
|
|
if (task.source === "mock-fallback") return "示例";
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function AgentPage({
|
||
|
|
tasks,
|
||
|
|
isAuthenticated,
|
||
|
|
onCreateTask,
|
||
|
|
onRequireLogin,
|
||
|
|
onOpenLogin,
|
||
|
|
}: AgentPageProps) {
|
||
|
|
const composerRef = useRef<HTMLTextAreaElement>(null);
|
||
|
|
const [activeMode, setActiveMode] = useState(agentModes[1].id);
|
||
|
|
const [prompt, setPrompt] = useState("让 Omni Agent 帮我规划「新品发布会全流程」");
|
||
|
|
const [isRunning, setIsRunning] = useState(false);
|
||
|
|
const [notice, setNotice] = useState("选择一个 Agent 模式,输入目标后即可开始。");
|
||
|
|
|
||
|
|
const selectedMode = agentModes.find((item) => item.id === activeMode) ?? agentModes[0];
|
||
|
|
const recentTasks = tasks.slice(0, 3);
|
||
|
|
|
||
|
|
const focusComposer = () => {
|
||
|
|
composerRef.current?.focus();
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleQuickStart = (value: string) => {
|
||
|
|
setPrompt(`让 Omni Agent 帮我规划${value}`);
|
||
|
|
setNotice("已载入快速启动模板,可继续补充目标、资料或约束。");
|
||
|
|
window.requestAnimationFrame(focusComposer);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleRun = async () => {
|
||
|
|
const trimmedPrompt = prompt.trim();
|
||
|
|
if (!trimmedPrompt || isRunning) {
|
||
|
|
setNotice(trimmedPrompt ? "当前 Agent 正在创建任务。" : "先输入一个目标,Agent 会自动拆解任务。");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const taskInput = {
|
||
|
|
title: `${selectedMode.label} Agent`,
|
||
|
|
type: "agent" as const,
|
||
|
|
prompt: trimmedPrompt,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!isAuthenticated) {
|
||
|
|
onRequireLogin(taskInput);
|
||
|
|
setNotice("登录后即可运行 Agent,并保存执行记录。");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setIsRunning(true);
|
||
|
|
setNotice("正在拆解目标、选择工具并创建执行队列...");
|
||
|
|
try {
|
||
|
|
await onCreateTask(taskInput);
|
||
|
|
setNotice("Agent 任务已加入队列,执行记录会同步到最近运行。");
|
||
|
|
} catch (error) {
|
||
|
|
setNotice(error instanceof Error ? error.message : "Agent 任务创建失败,请稍后重试。");
|
||
|
|
} finally {
|
||
|
|
setIsRunning(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<WorkspacePageShell title="Agent" fullWidth className="agent-page page-motion">
|
||
|
|
<section className="agent-experience" aria-label="Omni Agent">
|
||
|
|
<header className="agent-nav">
|
||
|
|
<a className="agent-logo" href="#/workbench" aria-label="返回首页">
|
||
|
|
<span className="agent-logo__mark" aria-hidden="true" />
|
||
|
|
<span>Omni Agent</span>
|
||
|
|
</a>
|
||
|
|
<nav className="agent-nav__links" aria-label="Agent 页面导航">
|
||
|
|
<button type="button" onClick={focusComposer}>功能</button>
|
||
|
|
<button type="button" onClick={focusComposer}>定价</button>
|
||
|
|
</nav>
|
||
|
|
<div className="agent-nav__actions">
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
className="agent-nav__login"
|
||
|
|
onClick={onOpenLogin}
|
||
|
|
disabled={isAuthenticated}
|
||
|
|
>
|
||
|
|
<LoginOutlined />
|
||
|
|
{isAuthenticated ? "已登录" : "登录"}
|
||
|
|
</button>
|
||
|
|
<button type="button" className="agent-nav__start" onClick={focusComposer}>
|
||
|
|
立即开始
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<main className="agent-main">
|
||
|
|
<section className="agent-hero" aria-label="Agent 任务输入">
|
||
|
|
<h1>想法一闪 任务即成</h1>
|
||
|
|
|
||
|
|
<div className="agent-mode-selector" aria-label="Agent 能力模式">
|
||
|
|
{agentModes.map((mode) => (
|
||
|
|
<button
|
||
|
|
key={mode.id}
|
||
|
|
type="button"
|
||
|
|
className={`agent-mode${mode.id === activeMode ? " is-active" : ""}`}
|
||
|
|
aria-pressed={mode.id === activeMode}
|
||
|
|
onClick={() => {
|
||
|
|
setActiveMode(mode.id);
|
||
|
|
setNotice(`已切换到${mode.label}模式。`);
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<span className="agent-mode__glyph">{mode.icon}</span>
|
||
|
|
<span>{mode.label}</span>
|
||
|
|
</button>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<section className="agent-composer" aria-label="Agent 指令输入">
|
||
|
|
<textarea
|
||
|
|
ref={composerRef}
|
||
|
|
value={prompt}
|
||
|
|
placeholder={`让 Omni Agent 帮你${selectedMode.placeholder}`}
|
||
|
|
onChange={(event) => setPrompt(event.target.value)}
|
||
|
|
onKeyDown={(event) => {
|
||
|
|
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
||
|
|
event.preventDefault();
|
||
|
|
void handleRun();
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<div className="agent-composer__footer">
|
||
|
|
<div className="agent-composer__controls" aria-label="输入设置">
|
||
|
|
<button type="button" className="agent-tool-icon" aria-label="上传附件">
|
||
|
|
<PaperClipOutlined />
|
||
|
|
</button>
|
||
|
|
<button type="button" className="agent-tool-pill">
|
||
|
|
<ThunderboltOutlined />
|
||
|
|
自动模式
|
||
|
|
<DownOutlined />
|
||
|
|
</button>
|
||
|
|
<button type="button" className="agent-tool-icon" aria-label="工具集">
|
||
|
|
<AppstoreOutlined />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
className="agent-run-button"
|
||
|
|
disabled={isRunning}
|
||
|
|
onClick={() => void handleRun()}
|
||
|
|
>
|
||
|
|
{isRunning ? <RocketOutlined /> : <SendOutlined />}
|
||
|
|
{isRunning ? "运行中" : "运行"}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<p className="agent-status" aria-live="polite">{notice}</p>
|
||
|
|
|
||
|
|
<section className="agent-quick-start" aria-label="快速开始">
|
||
|
|
<span>快速开始</span>
|
||
|
|
<div>
|
||
|
|
{quickStarts.map((item) => (
|
||
|
|
<button key={item} type="button" onClick={() => handleQuickStart(item)}>
|
||
|
|
{item}
|
||
|
|
</button>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
{recentTasks.length > 0 ? (
|
||
|
|
<section className="agent-recent" aria-label="最近运行">
|
||
|
|
<div className="agent-recent__head">
|
||
|
|
<span>Recent Runs</span>
|
||
|
|
<strong>最近运行</strong>
|
||
|
|
</div>
|
||
|
|
<div className="agent-recent__grid">
|
||
|
|
{recentTasks.map((task) => {
|
||
|
|
const sourceLabel = getTaskSourceLabel(task);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<article key={task.id} className="agent-run-card">
|
||
|
|
<div>
|
||
|
|
<strong>{task.title}</strong>
|
||
|
|
{sourceLabel ? <span>{sourceLabel}</span> : null}
|
||
|
|
</div>
|
||
|
|
<p>{task.prompt}</p>
|
||
|
|
<small>{task.status} / {task.progress}%</small>
|
||
|
|
</article>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
) : null}
|
||
|
|
</main>
|
||
|
|
</section>
|
||
|
|
</WorkspacePageShell>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default AgentPage;
|