diff --git a/frontend/README.md b/frontend/README.md index 79b89dd31..3e8eddde7 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -46,6 +46,11 @@ server that `veadk frontend` launches — no separate backend. Deployable source can be sent to Runtime manually; an incomplete verification report requires an explicit confirmation. No separate “start verification” action is required. +- **Conversational Agent workflow creation**: the creation chat can generate a + single Agent or an ordered workflow and refine it over multiple turns. Each + top-level Agent may own one level of direct LLM sub-agents for delegated work; + direct sub-agents are leaves, while ordered top-level steps remain represented + by an internal Sequential Agent wrapper. - **Reasoning & tool calls** shown inline (collapsible "thinking", tool blocks). - **Agent context rail** keeps the selected Agent's description, model, tools, skills, and optional live multi-Agent topology together in the conversation's diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6aa3b4f18..3775f6643 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -124,7 +124,8 @@ import { Blocks, ThinkingPlaceholder } from "./ui/Blocks"; import { Composer } from "./ui/Composer"; import { InvocationChips } from "./ui/InvocationChips"; import { MediaGroup } from "./ui/Media"; -import { StackCards } from "./ui/AddAgentMenu"; +import { CreateCanvas } from "./create/CreateCanvas"; +import { CreateWorkspace } from "./create/CreateWorkspace"; import { IntelligentCreate, type IntelligentPreparationStage, @@ -339,7 +340,7 @@ async function loadHydratedSessions( } type CreateView = "custom" | "package" | "migration" | null; -type AppView = CreateView | "intelligent"; +type AppView = CreateView | "intelligent" | "intelligent-workspace"; type CustomCreateMode = "custom" | "yaml_import"; type StudioPageId = | "new-chat" @@ -470,7 +471,7 @@ function mentionableDescendants(node: AgentNode): AgentTarget[] { function loadView(): AppView { const v = typeof localStorage !== "undefined" ? localStorage.getItem(LS.view) : null; if (v === "intelligent") return v; - if (["menu", "custom", "template", "workflow"].includes(v ?? "")) { + if (["menu", "custom", "workflow"].includes(v ?? "")) { return "custom"; } return v === "package" || v === "migration" ? v : null; @@ -547,66 +548,6 @@ function telemetrySandboxStatus( } } -/** Hand-drawn "from zero" mark: a blank Agent canvas ready to create. */ -function ScratchIcon({ className }: { className?: string }) { - return ( - - ); -} - -/** Hand-drawn code package mark: an archive with source inside. */ -function PackageIcon({ className }: { className?: string }) { - return ( - - ); -} - -/** Hand-drawn migration mark: an existing project moving into a new runtime. */ -function MigrationIcon({ className }: { className?: string }) { - return ( - - ); -} - /** Hand-drawn "tracing / observability" icon (stacked spans). */ function TraceIcon() { return ( @@ -1927,6 +1868,7 @@ export default function App() { } }; const [createView, setCreateView] = useState(loadView); + const [intelligentInitialGoal, setIntelligentInitialGoal] = useState(""); const [deploymentTasks, setDeploymentTasks] = useState< DeploymentTaskUpdate[] >([]); @@ -3041,6 +2983,8 @@ export default function App() { ? runtimeUpdateTarget?.name ? `更新 ${runtimeUpdateTarget.name}` : "创建智能体" + : createView === "intelligent-workspace" + ? "创建智能体" : createView === "package" ? "从代码包添加" : "迁移智能体", @@ -5931,6 +5875,7 @@ export default function App() { ) : showAddMenu ? ( - { - setAddMenu(false); - setImportedDraft(null); - setCustomCreateMode("custom"); - setRuntimeUpdateTarget(null); - setFocusedDeploymentTaskId(""); - setFocusedWorkspaceAgentId(""); - setEditingDraftId(`draft-${Date.now().toString(36)}`); - editingDraftBaselineRef.current = null; - setCreateView("custom"); - }, - }, - { - key: "intelligent", - icon: ScratchIcon, - title: "智能模式", - desc: intelligentCapabilitiesError - || intelligentCapabilities?.reason - || "描述目标,按你的意图构建、调试并验证 Agent。", - status: intelligentCapabilitiesLoading - ? "能力检查中" - : intelligentCapabilities?.enabled - ? undefined - : "暂不可用", - disabled: - intelligentCapabilitiesLoading || - intelligentCapabilities?.enabled !== true, - onClick: () => { - setAddMenu(false); - setImportedDraft(null); - setRuntimeUpdateTarget(null); - setFocusedDeploymentTaskId(""); - setFocusedWorkspaceAgentId(""); - setEditingDraftId(""); - editingDraftBaselineRef.current = null; - setCreateView("intelligent"); - }, - }, - { - key: "package", - icon: PackageIcon, - title: "从代码包添加和部署", - desc: "上传 Agent 项目压缩包,查看代码并直接部署到 AgentKit Runtime。", - onClick: () => { - setAddMenu(false); - setImportedDraft(null); - setCreateView("package"); - }, - }, - { - key: "migration", - icon: MigrationIcon, - title: "从存量迁移", - desc: "从您的 LangChain / Dify 等存量项目迁移至 AgentKit Runtime", - onClick: () => { - setAddMenu(false); - setImportedDraft(null); - setCreateView("migration"); - }, - }, - ]} + { + setIntelligentInitialGoal(prompt); + setAddMenu(false); + setCreateView("intelligent-workspace"); + }} + onBlank={() => { + setAddMenu(false); + setImportedDraft(null); + setCustomCreateMode("custom"); + setRuntimeUpdateTarget(null); + setFocusedDeploymentTaskId(""); + setFocusedWorkspaceAgentId(""); + setEditingDraftId(`draft-${Date.now().toString(36)}`); + editingDraftBaselineRef.current = null; + setCreateView("custom"); + }} + onUploadPackage={() => { + setAddMenu(false); + setImportedDraft(null); + setCreateView("package"); + }} + onMigration={() => { + setAddMenu(false); + setImportedDraft(null); + setCreateView("migration"); + }} /> ) : searchView ? ( - ) : visibleCreateView !== null && !["menu", "intelligent"].includes(visibleCreateView) && !hasCreds ? ( + ) : visibleCreateView !== null && !["menu", "intelligent", "intelligent-workspace"].includes(visibleCreateView) && !hasCreds ? (
+ ) : visibleCreateView === "intelligent-workspace" ? ( + { + setCreateView(null); + setAddMenu(true); + }} + /> ) : visibleCreateView === "intelligent" ? ( (res, "生成 Agent 配置失败"); } +export async function chatWithGeneratedAgent( + sessionId: string, + message: string, + currentDraft?: AgentDraft, + signal?: AbortSignal, +): Promise { + const res = await apiFetch( + "/web/generated-agent-conversations", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ sessionId, message, currentDraft }), + signal, + }, + {}, + GENERATED_AGENT_DRAFT_TIMEOUT_MS + 60_000, + ); + if (!res.ok) { + throw new Error(await httpErrorMessage(res, "创建助手回复失败")); + } + return parseJsonResponse( + res, + "创建助手回复失败", + ); +} + export async function createGeneratedAgentTestRun( draft: AgentDraft, runtime?: { runtimeId: string; region: string }, diff --git a/frontend/src/create/CreateCanvas.css b/frontend/src/create/CreateCanvas.css new file mode 100644 index 000000000..9abe4f5fd --- /dev/null +++ b/frontend/src/create/CreateCanvas.css @@ -0,0 +1,263 @@ +.create-canvas { + position: relative; + flex: 1; + min-width: 0; + min-height: 0; + overflow: hidden; + border-radius: 0; + background-color: hsl(var(--canvas)); + background-image: radial-gradient( + circle, + hsl(var(--foreground) / 0.09) 0 1px, + transparent 1.15px + ); + background-position: 8px 15px; + background-size: 18px 18px; +} + +.create-canvas, +.create-canvas *, +.create-canvas *::before, +.create-canvas *::after { + box-sizing: border-box; +} + +.create-canvas__send:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; +} + +.create-canvas__hero { + position: absolute; + z-index: 1; + top: 349px; + left: 50%; + width: min(800px, calc(100% - 32px)); + transform: translateX(-50%); +} + +.create-canvas__hero-title { + height: 45px; + display: flex; + align-items: center; + gap: 12px; +} + +.create-canvas__hero-title > svg { + width: 36px; + height: 36px; + flex: 0 0 36px; + color: #101013; +} + +.create-canvas__hero-title h2 { + margin: 0; + color: #101013; + font-size: 32px; + font-weight: 500; + line-height: 45px; + letter-spacing: -0.01em; +} + +.create-canvas__composer { + height: 54px; + display: flex; + align-items: center; + gap: 12px; + margin-top: 24px; + padding: 0 16px 0 20px; + border: 1px solid rgba(31, 35, 41, 0.08); + border-radius: 16px; + background: #fff; +} + +.create-canvas__composer:focus-within { + border-color: rgba(31, 35, 41, 0.18); +} + +.create-canvas__composer textarea { + min-width: 0; + height: 22px; + flex: 1; + overflow: hidden; + padding: 0; + border: 0; + outline: 0; + resize: none; + background: transparent; + color: #101013; + font: inherit; + font-size: 14px; + font-weight: 400; + line-height: 22px; + white-space: nowrap; +} + +.create-canvas__composer textarea::placeholder { + color: #7a7880; + opacity: 1; +} + +.create-canvas__send { + width: 32px; + height: 32px; + flex: 0 0 32px; + display: inline-grid; + place-items: center; + padding: 0; + border: 0; + border-radius: 50%; + background: #101013; + color: #fff; + cursor: pointer; +} + +.create-canvas__send:disabled { + background: rgba(0, 0, 0, 0.2); + cursor: default; +} + +.create-canvas__send svg { + width: 20px; + height: 20px; +} + +.create-canvas__shortcuts { + position: absolute; + left: 50%; + bottom: 49px; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + width: min(800px, calc(100% - 32px)); + transform: translateX(-50%); +} + +.create-canvas__shortcut { + min-width: 0; + height: 80px; + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + border: 0; + border-radius: 16px; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + box-shadow: 0 0 2px hsl(var(--foreground) / 0.06); + cursor: pointer; + font: inherit; + text-align: left; + transition: + background-color 160ms ease-out, + box-shadow 160ms ease-out; +} + +.create-canvas__shortcut:hover { + background: hsl(var(--background) / 0.94); + box-shadow: 0 4px 16px hsl(var(--foreground) / 0.08); +} + +.create-canvas__shortcut:active { + background: hsl(var(--background) / 0.88); +} + +.create-canvas__shortcut:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; +} + +.create-canvas__shortcut-icon { + width: 48px; + height: 48px; + flex: 0 0 48px; + display: inline-grid; + place-items: center; + border-radius: 14px; + background: hsl(var(--canvas)); +} + +.create-canvas__shortcut-icon svg { + width: 24px; + height: 24px; +} + +.create-canvas__shortcut-copy { + min-width: 0; + display: flex; + flex: 1; + flex-direction: column; + align-items: flex-start; + gap: 2px; +} + +.create-canvas__shortcut-title { + max-width: 100%; + overflow: hidden; + color: hsl(var(--sidebar-foreground)); + font-size: 14px; + font-weight: 500; + line-height: 22px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.create-canvas__shortcut-description { + max-width: 100%; + overflow: hidden; + color: hsl(var(--sidebar-section-title)); + font-size: 13px; + font-weight: 400; + line-height: 19.5px; + text-overflow: ellipsis; + white-space: nowrap; +} + +@media (max-width: 720px) { + .create-canvas__hero { + top: 112px; + } + + .create-canvas__hero-title { + height: auto; + align-items: flex-start; + } + + .create-canvas__hero-title > svg { + width: 30px; + height: 30px; + flex-basis: 30px; + } + + .create-canvas__hero-title h2 { + font-size: 24px; + line-height: 32px; + } + + .create-canvas__composer { + margin-top: 18px; + } + + .create-canvas__shortcuts { + bottom: 16px; + grid-template-columns: minmax(0, 1fr); + gap: 10px; + } + + .create-canvas__shortcut { + height: 72px; + padding: 12px; + } + + .create-canvas__shortcut-icon { + width: 44px; + height: 44px; + flex-basis: 44px; + } +} + +@media (prefers-reduced-motion: reduce) { + .create-canvas__shortcut { + transition: none; + } +} diff --git a/frontend/src/create/CreateCanvas.tsx b/frontend/src/create/CreateCanvas.tsx new file mode 100644 index 000000000..024899df6 --- /dev/null +++ b/frontend/src/create/CreateCanvas.tsx @@ -0,0 +1,216 @@ +import { useState, type ComponentType, type SVGProps } from "react"; +import { isImeCompositionEvent } from "../ui/composerKeyboard"; +import { CreateNavbar } from "./CreateNavbar"; +import "./CreateCanvas.css"; + +interface CreateCanvasProps { + onBack: () => void; + onPromptSubmit: (prompt: string) => void; + onBlank: () => void; + onUploadPackage: () => void; + onMigration: () => void; +} + +interface CreateShortcut { + key: string; + title: string; + description: string; + icon: ComponentType>; + onClick: () => void; +} + +function BlankCreateIcon(props: SVGProps) { + return ( + + ); +} + +function UploadCodeIcon(props: SVGProps) { + return ( + + ); +} + +function MigrationCubeIcon(props: SVGProps) { + return ( + + ); +} + +function CreateAgentIcon(props: SVGProps) { + return ( + + ); +} + +function SendIcon(props: SVGProps) { + return ( + + ); +} + +export function CreateCanvas({ + onBack, + onPromptSubmit, + onBlank, + onUploadPackage, + onMigration, +}: CreateCanvasProps) { + const [prompt, setPrompt] = useState(""); + const shortcuts: CreateShortcut[] = [ + { + key: "blank", + title: "从空白创建", + description: "手动配置智能体", + icon: BlankCreateIcon, + onClick: onBlank, + }, + { + key: "package", + title: "上传代码包", + description: "查看代码并一键部署", + icon: UploadCodeIcon, + onClick: onUploadPackage, + }, + { + key: "migration", + title: "存量迁移", + description: "从 LangChain/Dify 等迁移", + icon: MigrationCubeIcon, + onClick: onMigration, + }, + ]; + + function submitPrompt() { + const value = prompt.trim(); + if (value) onPromptSubmit(value); + } + + return ( +
+ + +
+
+ +

描述你想创建的智能体

+
+
+