Initial commit: OmniAI Web Frontend
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user