mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-09 06:15:41 -05:00
feat: refine copilot tools styles
This commit is contained in:
@@ -41,8 +41,9 @@ export const ChatContainer = ({
|
||||
isLoading={isLoadingSession}
|
||||
/>
|
||||
<motion.div
|
||||
layoutId={inputLayoutId}
|
||||
transition={{ type: "spring", bounce: 0.2, duration: 0.65 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="relative px-3 pb-2 pt-2"
|
||||
>
|
||||
<div className="pointer-events-none absolute left-0 right-0 top-[-18px] z-10 h-6 bg-gradient-to-b from-transparent to-[#f8f8f9]" />
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@/components/ai-elements/message";
|
||||
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
|
||||
import { UIDataTypes, UIMessage, UITools, ToolUIPart } from "ai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FindBlocksTool } from "../../tools/FindBlocks/FindBlocks";
|
||||
import { FindAgentsTool } from "../../tools/FindAgents/FindAgents";
|
||||
import { SearchDocsTool } from "../../tools/SearchDocs/SearchDocs";
|
||||
@@ -19,6 +20,23 @@ import { ViewAgentOutputTool } from "../../tools/ViewAgentOutput/ViewAgentOutput
|
||||
import { CreateAgentTool } from "../../tools/CreateAgent/CreateAgent";
|
||||
import { EditAgentTool } from "../../tools/EditAgent/EditAgent";
|
||||
|
||||
const THINKING_PHRASES = [
|
||||
"Thinking...",
|
||||
"Considering this...",
|
||||
"Working through this...",
|
||||
"Analyzing your request...",
|
||||
"Reasoning...",
|
||||
"Looking into it...",
|
||||
"Processing your request...",
|
||||
"Mulling this over...",
|
||||
"Piecing it together...",
|
||||
"On it...",
|
||||
];
|
||||
|
||||
function getRandomPhrase() {
|
||||
return THINKING_PHRASES[Math.floor(Math.random() * THINKING_PHRASES.length)];
|
||||
}
|
||||
|
||||
interface ChatMessagesContainerProps {
|
||||
messages: UIMessage<unknown, UIDataTypes, UITools>[];
|
||||
status: string;
|
||||
@@ -32,6 +50,27 @@ export const ChatMessagesContainer = ({
|
||||
error,
|
||||
isLoading,
|
||||
}: ChatMessagesContainerProps) => {
|
||||
const [thinkingPhrase, setThinkingPhrase] = useState(getRandomPhrase);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "submitted") {
|
||||
setThinkingPhrase(getRandomPhrase());
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
const lastAssistantHasVisibleContent =
|
||||
lastMessage?.role === "assistant" &&
|
||||
lastMessage.parts.some(
|
||||
(p) =>
|
||||
(p.type === "text" && p.text.trim().length > 0) ||
|
||||
p.type.startsWith("tool-"),
|
||||
);
|
||||
|
||||
const showThinking =
|
||||
status === "submitted" ||
|
||||
(status === "streaming" && !lastAssistantHasVisibleContent);
|
||||
|
||||
return (
|
||||
<Conversation className="min-h-0 flex-1">
|
||||
<ConversationContent className="gap-6 px-3 py-6">
|
||||
@@ -40,94 +79,112 @@ export const ChatMessagesContainer = ({
|
||||
<LoadingSpinner size="large" className="text-neutral-400" />
|
||||
</div>
|
||||
)}
|
||||
{messages.map((message) => (
|
||||
<Message from={message.role} key={message.id}>
|
||||
<MessageContent
|
||||
className={
|
||||
"text-[1rem] leading-relaxed " +
|
||||
"group-[.is-user]:rounded-xl group-[.is-user]:bg-purple-100 group-[.is-user]:px-3 group-[.is-user]:py-2.5 group-[.is-user]:text-slate-900 group-[.is-user]:[border-bottom-right-radius:0] " +
|
||||
"group-[.is-assistant]:bg-transparent group-[.is-assistant]:text-slate-900"
|
||||
}
|
||||
>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return (
|
||||
<MessageResponse key={`${message.id}-${i}`}>
|
||||
{part.text}
|
||||
</MessageResponse>
|
||||
);
|
||||
case "tool-find_block":
|
||||
return (
|
||||
<FindBlocksTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-find_agent":
|
||||
case "tool-find_library_agent":
|
||||
return (
|
||||
<FindAgentsTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-search_docs":
|
||||
case "tool-get_doc_page":
|
||||
return (
|
||||
<SearchDocsTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-run_block":
|
||||
return (
|
||||
<RunBlockTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-run_agent":
|
||||
case "tool-schedule_agent":
|
||||
return (
|
||||
<RunAgentTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-create_agent":
|
||||
return (
|
||||
<CreateAgentTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-edit_agent":
|
||||
return (
|
||||
<EditAgentTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-view_agent_output":
|
||||
return (
|
||||
<ViewAgentOutputTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
{messages.map((message, messageIndex) => {
|
||||
const isLastAssistant =
|
||||
messageIndex === messages.length - 1 &&
|
||||
message.role === "assistant";
|
||||
const messageHasVisibleContent = message.parts.some(
|
||||
(p) =>
|
||||
(p.type === "text" && p.text.trim().length > 0) ||
|
||||
p.type.startsWith("tool-"),
|
||||
);
|
||||
|
||||
return (
|
||||
<Message from={message.role} key={message.id}>
|
||||
<MessageContent
|
||||
className={
|
||||
"text-[1rem] leading-relaxed " +
|
||||
"group-[.is-user]:rounded-xl group-[.is-user]:bg-purple-100 group-[.is-user]:px-3 group-[.is-user]:py-2.5 group-[.is-user]:text-slate-900 group-[.is-user]:[border-bottom-right-radius:0] " +
|
||||
"group-[.is-assistant]:bg-transparent group-[.is-assistant]:text-slate-900"
|
||||
}
|
||||
})}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
))}
|
||||
{status === "submitted" && (
|
||||
>
|
||||
{message.parts.map((part, i) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return (
|
||||
<MessageResponse key={`${message.id}-${i}`}>
|
||||
{part.text}
|
||||
</MessageResponse>
|
||||
);
|
||||
case "tool-find_block":
|
||||
return (
|
||||
<FindBlocksTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-find_agent":
|
||||
case "tool-find_library_agent":
|
||||
return (
|
||||
<FindAgentsTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-search_docs":
|
||||
case "tool-get_doc_page":
|
||||
return (
|
||||
<SearchDocsTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-run_block":
|
||||
return (
|
||||
<RunBlockTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-run_agent":
|
||||
case "tool-schedule_agent":
|
||||
return (
|
||||
<RunAgentTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-create_agent":
|
||||
return (
|
||||
<CreateAgentTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-edit_agent":
|
||||
return (
|
||||
<EditAgentTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
case "tool-view_agent_output":
|
||||
return (
|
||||
<ViewAgentOutputTool
|
||||
key={`${message.id}-${i}`}
|
||||
part={part as ToolUIPart}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
{isLastAssistant &&
|
||||
!messageHasVisibleContent &&
|
||||
showThinking && (
|
||||
<span className="inline-block animate-shimmer bg-gradient-to-r from-neutral-400 via-neutral-600 to-neutral-400 bg-[length:200%_100%] bg-clip-text text-transparent">
|
||||
{thinkingPhrase}
|
||||
</span>
|
||||
)}
|
||||
</MessageContent>
|
||||
</Message>
|
||||
);
|
||||
})}
|
||||
{showThinking && lastMessage?.role !== "assistant" && (
|
||||
<Message from="assistant">
|
||||
<MessageContent className="text-[1rem] leading-relaxed">
|
||||
<span className="inline-block animate-shimmer bg-gradient-to-r from-neutral-400 via-neutral-600 to-neutral-400 bg-[length:200%_100%] bg-clip-text text-transparent">
|
||||
Thinking...
|
||||
{thinkingPhrase}
|
||||
</span>
|
||||
</MessageContent>
|
||||
</Message>
|
||||
|
||||
@@ -52,9 +52,9 @@ export function EmptySession({
|
||||
<div className="flex h-full flex-1 items-center justify-center overflow-y-auto bg-[#f8f8f9] px-0 py-5 md:px-6 md:py-10">
|
||||
<motion.div
|
||||
className="w-full max-w-3xl text-center"
|
||||
initial={{ opacity: 0, y: 14, filter: "blur(6px)" }}
|
||||
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
||||
transition={{ type: "spring", bounce: 0.2, duration: 0.7 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<Text variant="h3" className="mb-1 !text-[1.375rem] text-zinc-700">
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
import { Link } from "@/components/atoms/Link/Link";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Layout */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function ContentGrid({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return <div className={cn("grid gap-2", className)}>{children}</div>;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Card */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function ContentCard({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-lg bg-gradient-to-r from-purple-500/30 to-blue-500/30 p-[1px]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="rounded-lg bg-neutral-100 p-3">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Flex row with a left content area (`children`) and an optional right‑side `action`. */
|
||||
export function ContentCardHeader({
|
||||
children,
|
||||
action,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
action?: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("flex items-start justify-between gap-2", className)}>
|
||||
<div className="min-w-0">{children}</div>
|
||||
{action}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ContentCardTitle({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Text variant="body-medium" className={cn("truncate text-zinc-800", className)}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
export function ContentCardSubtitle({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Text variant="small" className={cn("mt-0.5 truncate text-zinc-800", className)}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
export function ContentCardDescription({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Text variant="small" className={cn("mt-2 text-zinc-800", className)}>{children}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Text */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function ContentMessage({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return <Text variant="body" className={cn("text-zinc-800", className)}>{children}</Text>;
|
||||
}
|
||||
|
||||
export function ContentHint({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Text variant="small" className={cn("italic text-neutral-800", className)}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Code / data */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function ContentCodeBlock({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<pre
|
||||
className={cn(
|
||||
"whitespace-pre-wrap rounded-lg border bg-black p-3 text-xs text-neutral-200",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Inline elements */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function ContentBadge({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Text
|
||||
variant="small"
|
||||
as="span"
|
||||
className={cn(
|
||||
"shrink-0 rounded-full border bg-muted px-2 py-0.5 text-[11px] text-zinc-800",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
export function ContentLink({
|
||||
href,
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: Omit<React.ComponentProps<typeof Link>, "className"> & {
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
variant="primary"
|
||||
isExternal
|
||||
href={href}
|
||||
className={cn("shrink-0 text-xs text-purple-500", className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Lists */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function ContentSuggestionsList({
|
||||
items,
|
||||
max = 5,
|
||||
className,
|
||||
}: {
|
||||
items: string[];
|
||||
max?: number;
|
||||
className?: string;
|
||||
}) {
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<ul
|
||||
className={cn(
|
||||
"mt-2 list-disc space-y-1 pl-5 font-sans text-[0.75rem] leading-[1.125rem] text-zinc-800",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{items.slice(0, max).map((s) => (
|
||||
<li key={s}>{s}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
@@ -7,8 +7,9 @@ import { useId } from "react";
|
||||
import { useToolAccordion } from "./useToolAccordion";
|
||||
|
||||
interface Props {
|
||||
badgeText: string;
|
||||
icon: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
titleClassName?: string;
|
||||
description?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
@@ -18,8 +19,9 @@ interface Props {
|
||||
}
|
||||
|
||||
export function ToolAccordion({
|
||||
badgeText,
|
||||
icon,
|
||||
title,
|
||||
titleClassName,
|
||||
description,
|
||||
children,
|
||||
className,
|
||||
@@ -36,7 +38,12 @@ export function ToolAccordion({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={cn("mt-2 w-full rounded-lg border px-3 py-2", className)}>
|
||||
<div
|
||||
className={cn(
|
||||
"mt-2 w-full rounded-lg border border-slate-200 bg-slate-100 px-3 py-2",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={isExpanded}
|
||||
@@ -44,24 +51,22 @@ export function ToolAccordion({
|
||||
onClick={toggle}
|
||||
className="flex w-full items-center justify-between gap-3 py-1 text-left"
|
||||
>
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
|
||||
{badgeText}
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span className="flex shrink-0 items-center text-gray-800">
|
||||
{icon}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
<p className={cn("truncate text-sm font-medium text-gray-800", titleClassName)}>
|
||||
{title}
|
||||
</p>
|
||||
{description && (
|
||||
<p className="truncate text-xs text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
<p className="truncate text-xs text-slate-800">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CaretDownIcon
|
||||
className={cn(
|
||||
"h-4 w-4 shrink-0 text-muted-foreground transition-transform",
|
||||
"h-4 w-4 shrink-0 text-slate-500 transition-transform",
|
||||
isExpanded && "rotate-180",
|
||||
)}
|
||||
weight="bold"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import type { ToolUIPart } from "ai";
|
||||
import Link from "next/link";
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import {
|
||||
ContentCardDescription,
|
||||
ContentCardSubtitle,
|
||||
ContentCodeBlock,
|
||||
ContentGrid,
|
||||
ContentHint,
|
||||
ContentLink,
|
||||
ContentMessage,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import { useCopilotChatActions } from "../../components/CopilotChatActionsProvider/useCopilotChatActions";
|
||||
import {
|
||||
ClarificationQuestionsWidget,
|
||||
@@ -20,6 +28,7 @@ import {
|
||||
isOperationInProgressOutput,
|
||||
isOperationPendingOutput,
|
||||
isOperationStartedOutput,
|
||||
AccordionIcon,
|
||||
ToolIcon,
|
||||
truncateText,
|
||||
type CreateAgentToolOutput,
|
||||
@@ -38,16 +47,18 @@ interface Props {
|
||||
}
|
||||
|
||||
function getAccordionMeta(output: CreateAgentToolOutput): {
|
||||
badgeText: string;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description?: string;
|
||||
} {
|
||||
const icon = <AccordionIcon />;
|
||||
|
||||
if (isAgentSavedOutput(output)) {
|
||||
return { badgeText: "Create agent", title: output.agent_name };
|
||||
return { icon, title: output.agent_name };
|
||||
}
|
||||
if (isAgentPreviewOutput(output)) {
|
||||
return {
|
||||
badgeText: "Create agent",
|
||||
icon,
|
||||
title: output.agent_name,
|
||||
description: `${output.node_count} block${output.node_count === 1 ? "" : "s"}`,
|
||||
};
|
||||
@@ -55,7 +66,7 @@ function getAccordionMeta(output: CreateAgentToolOutput): {
|
||||
if (isClarificationNeededOutput(output)) {
|
||||
const questions = output.questions ?? [];
|
||||
return {
|
||||
badgeText: "Create agent",
|
||||
icon,
|
||||
title: "Needs clarification",
|
||||
description: `${questions.length} question${questions.length === 1 ? "" : "s"}`,
|
||||
};
|
||||
@@ -65,9 +76,9 @@ function getAccordionMeta(output: CreateAgentToolOutput): {
|
||||
isOperationPendingOutput(output) ||
|
||||
isOperationInProgressOutput(output)
|
||||
) {
|
||||
return { badgeText: "Create agent", title: "Creating agent" };
|
||||
return { icon, title: "Creating agent" };
|
||||
}
|
||||
return { badgeText: "Create agent", title: "Error" };
|
||||
return { icon, title: "Error" };
|
||||
}
|
||||
|
||||
export function CreateAgentTool({ part }: Props) {
|
||||
@@ -117,64 +128,58 @@ export function CreateAgentTool({ part }: Props) {
|
||||
>
|
||||
{(isOperationStartedOutput(output) ||
|
||||
isOperationPendingOutput(output)) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
<ContentCardSubtitle>
|
||||
Operation: {output.operation_id}
|
||||
</p>
|
||||
<p className="text-xs italic text-muted-foreground">
|
||||
</ContentCardSubtitle>
|
||||
<ContentHint>
|
||||
Check your library in a few minutes.
|
||||
</p>
|
||||
</div>
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isOperationInProgressOutput(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<p className="text-xs italic text-muted-foreground">
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
<ContentHint>
|
||||
Please wait for the current operation to finish.
|
||||
</p>
|
||||
</div>
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isAgentSavedOutput(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Link
|
||||
href={output.library_agent_link}
|
||||
className="text-xs font-medium text-purple-600 hover:text-purple-700"
|
||||
>
|
||||
<ContentLink href={output.library_agent_link}>
|
||||
Open in library
|
||||
</Link>
|
||||
<Link
|
||||
href={output.agent_page_link}
|
||||
className="text-xs font-medium text-purple-600 hover:text-purple-700"
|
||||
>
|
||||
</ContentLink>
|
||||
<ContentLink href={output.agent_page_link}>
|
||||
Open in builder
|
||||
</Link>
|
||||
</ContentLink>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{truncateText(
|
||||
formatMaybeJson({ agent_id: output.agent_id }),
|
||||
800,
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
</ContentCodeBlock>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isAgentPreviewOutput(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.description?.trim() && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<ContentCardDescription>
|
||||
{output.description}
|
||||
</p>
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{truncateText(formatMaybeJson(output.agent_json), 1600)}
|
||||
</pre>
|
||||
</div>
|
||||
</ContentCodeBlock>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isClarificationNeededOutput(output) && (
|
||||
@@ -197,19 +202,19 @@ export function CreateAgentTool({ part }: Props) {
|
||||
)}
|
||||
|
||||
{isErrorOutput(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.error && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{formatMaybeJson(output.error)}
|
||||
</pre>
|
||||
</ContentCodeBlock>
|
||||
)}
|
||||
{output.details && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{formatMaybeJson(output.details)}
|
||||
</pre>
|
||||
</ContentCodeBlock>
|
||||
)}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
)}
|
||||
</ToolAccordion>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { PlusIcon } from "@phosphor-icons/react";
|
||||
import type { AgentPreviewResponse } from "@/app/api/__generated__/models/agentPreviewResponse";
|
||||
import type { AgentSavedResponse } from "@/app/api/__generated__/models/agentSavedResponse";
|
||||
import type { ClarificationNeededResponse } from "@/app/api/__generated__/models/clarificationNeededResponse";
|
||||
@@ -8,6 +6,8 @@ import type { OperationInProgressResponse } from "@/app/api/__generated__/models
|
||||
import type { OperationPendingResponse } from "@/app/api/__generated__/models/operationPendingResponse";
|
||||
import type { OperationStartedResponse } from "@/app/api/__generated__/models/operationStartedResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import { PlusCircleIcon, PlusIcon } from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
|
||||
export type CreateAgentToolOutput =
|
||||
| OperationStartedResponse
|
||||
@@ -165,6 +165,10 @@ export function ToolIcon({
|
||||
);
|
||||
}
|
||||
|
||||
export function AccordionIcon() {
|
||||
return <PlusCircleIcon size={32} weight="light" />;
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
if (typeof value === "string") return value;
|
||||
try {
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import type { ToolUIPart } from "ai";
|
||||
import Link from "next/link";
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import {
|
||||
ContentCardDescription,
|
||||
ContentCardSubtitle,
|
||||
ContentCodeBlock,
|
||||
ContentGrid,
|
||||
ContentHint,
|
||||
ContentLink,
|
||||
ContentMessage,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import { useCopilotChatActions } from "../../components/CopilotChatActionsProvider/useCopilotChatActions";
|
||||
import {
|
||||
ClarificationQuestionsWidget,
|
||||
@@ -20,6 +28,7 @@ import {
|
||||
isOperationInProgressOutput,
|
||||
isOperationPendingOutput,
|
||||
isOperationStartedOutput,
|
||||
AccordionIcon,
|
||||
ToolIcon,
|
||||
truncateText,
|
||||
type EditAgentToolOutput,
|
||||
@@ -38,16 +47,18 @@ interface Props {
|
||||
}
|
||||
|
||||
function getAccordionMeta(output: EditAgentToolOutput): {
|
||||
badgeText: string;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description?: string;
|
||||
} {
|
||||
const icon = <AccordionIcon />;
|
||||
|
||||
if (isAgentSavedOutput(output)) {
|
||||
return { badgeText: "Edit agent", title: output.agent_name };
|
||||
return { icon, title: output.agent_name };
|
||||
}
|
||||
if (isAgentPreviewOutput(output)) {
|
||||
return {
|
||||
badgeText: "Edit agent",
|
||||
icon,
|
||||
title: output.agent_name,
|
||||
description: `${output.node_count} block${output.node_count === 1 ? "" : "s"}`,
|
||||
};
|
||||
@@ -55,7 +66,7 @@ function getAccordionMeta(output: EditAgentToolOutput): {
|
||||
if (isClarificationNeededOutput(output)) {
|
||||
const questions = output.questions ?? [];
|
||||
return {
|
||||
badgeText: "Edit agent",
|
||||
icon,
|
||||
title: "Needs clarification",
|
||||
description: `${questions.length} question${questions.length === 1 ? "" : "s"}`,
|
||||
};
|
||||
@@ -65,9 +76,9 @@ function getAccordionMeta(output: EditAgentToolOutput): {
|
||||
isOperationPendingOutput(output) ||
|
||||
isOperationInProgressOutput(output)
|
||||
) {
|
||||
return { badgeText: "Edit agent", title: "Editing agent" };
|
||||
return { icon, title: "Editing agent" };
|
||||
}
|
||||
return { badgeText: "Edit agent", title: "Error" };
|
||||
return { icon, title: "Error" };
|
||||
}
|
||||
|
||||
export function EditAgentTool({ part }: Props) {
|
||||
@@ -117,64 +128,58 @@ export function EditAgentTool({ part }: Props) {
|
||||
>
|
||||
{(isOperationStartedOutput(output) ||
|
||||
isOperationPendingOutput(output)) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
<ContentCardSubtitle>
|
||||
Operation: {output.operation_id}
|
||||
</p>
|
||||
<p className="text-xs italic text-muted-foreground">
|
||||
</ContentCardSubtitle>
|
||||
<ContentHint>
|
||||
Check your library in a few minutes.
|
||||
</p>
|
||||
</div>
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isOperationInProgressOutput(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<p className="text-xs italic text-muted-foreground">
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
<ContentHint>
|
||||
Please wait for the current operation to finish.
|
||||
</p>
|
||||
</div>
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isAgentSavedOutput(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Link
|
||||
href={output.library_agent_link}
|
||||
className="text-xs font-medium text-purple-600 hover:text-purple-700"
|
||||
>
|
||||
<ContentLink href={output.library_agent_link}>
|
||||
Open in library
|
||||
</Link>
|
||||
<Link
|
||||
href={output.agent_page_link}
|
||||
className="text-xs font-medium text-purple-600 hover:text-purple-700"
|
||||
>
|
||||
</ContentLink>
|
||||
<ContentLink href={output.agent_page_link}>
|
||||
Open in builder
|
||||
</Link>
|
||||
</ContentLink>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{truncateText(
|
||||
formatMaybeJson({ agent_id: output.agent_id }),
|
||||
800,
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
</ContentCodeBlock>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isAgentPreviewOutput(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.description?.trim() && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<ContentCardDescription>
|
||||
{output.description}
|
||||
</p>
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{truncateText(formatMaybeJson(output.agent_json), 1600)}
|
||||
</pre>
|
||||
</div>
|
||||
</ContentCodeBlock>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isClarificationNeededOutput(output) && (
|
||||
@@ -197,19 +202,19 @@ export function EditAgentTool({ part }: Props) {
|
||||
)}
|
||||
|
||||
{isErrorOutput(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.error && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{formatMaybeJson(output.error)}
|
||||
</pre>
|
||||
</ContentCodeBlock>
|
||||
)}
|
||||
{output.details && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{formatMaybeJson(output.details)}
|
||||
</pre>
|
||||
</ContentCodeBlock>
|
||||
)}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
)}
|
||||
</ToolAccordion>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { PencilLineIcon } from "@phosphor-icons/react";
|
||||
import type { AgentPreviewResponse } from "@/app/api/__generated__/models/agentPreviewResponse";
|
||||
import type { AgentSavedResponse } from "@/app/api/__generated__/models/agentSavedResponse";
|
||||
import type { ClarificationNeededResponse } from "@/app/api/__generated__/models/clarificationNeededResponse";
|
||||
@@ -8,6 +6,8 @@ import type { OperationInProgressResponse } from "@/app/api/__generated__/models
|
||||
import type { OperationPendingResponse } from "@/app/api/__generated__/models/operationPendingResponse";
|
||||
import type { OperationStartedResponse } from "@/app/api/__generated__/models/operationStartedResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import { NotePencilIcon, PencilLineIcon } from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
|
||||
export type EditAgentToolOutput =
|
||||
| OperationStartedResponse
|
||||
@@ -165,6 +165,10 @@ export function ToolIcon({
|
||||
);
|
||||
}
|
||||
|
||||
export function AccordionIcon() {
|
||||
return <NotePencilIcon size={32} weight="light" />;
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
if (typeof value === "string") return value;
|
||||
try {
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { ToolUIPart } from "ai";
|
||||
import Link from "next/link";
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import {
|
||||
ContentBadge,
|
||||
ContentCard,
|
||||
ContentCardDescription,
|
||||
ContentCardHeader,
|
||||
ContentCardTitle,
|
||||
ContentGrid,
|
||||
ContentLink,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import {
|
||||
AccordionIcon,
|
||||
getAgentHref,
|
||||
getAnimationText,
|
||||
getFindAgentsOutput,
|
||||
@@ -12,7 +22,6 @@ import {
|
||||
isErrorOutput,
|
||||
ToolIcon,
|
||||
} from "./helpers";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
|
||||
export interface FindAgentsToolPart {
|
||||
type: string;
|
||||
@@ -77,11 +86,11 @@ export function FindAgentsTool({ part }: Props) {
|
||||
|
||||
{hasAgents && agentsFoundOutput && (
|
||||
<ToolAccordion
|
||||
badgeText={sourceLabel}
|
||||
icon={<AccordionIcon toolType={part.type} />}
|
||||
title="Agent results"
|
||||
description={accordionDescription}
|
||||
>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
<ContentGrid className="sm:grid-cols-2">
|
||||
{agentsFoundOutput.agents.map((agent) => {
|
||||
const href = getAgentHref(agent);
|
||||
const agentSource =
|
||||
@@ -91,39 +100,26 @@ export function FindAgentsTool({ part }: Props) {
|
||||
? "Marketplace"
|
||||
: null;
|
||||
return (
|
||||
<div
|
||||
key={agent.id}
|
||||
className="rounded-2xl border bg-background p-3"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
{agent.name}
|
||||
</p>
|
||||
{agentSource && (
|
||||
<span className="shrink-0 rounded-full border bg-muted px-2 py-0.5 text-[11px] text-muted-foreground">
|
||||
{agentSource}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
|
||||
{agent.description}
|
||||
</p>
|
||||
<ContentCard key={agent.id}>
|
||||
<ContentCardHeader
|
||||
action={
|
||||
href ? <ContentLink href={href}>Open</ContentLink> : null
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<ContentCardTitle>{agent.name}</ContentCardTitle>
|
||||
{agentSource && (
|
||||
<ContentBadge>{agentSource}</ContentBadge>
|
||||
)}
|
||||
</div>
|
||||
{href && (
|
||||
<Link
|
||||
href={href}
|
||||
className="shrink-0 text-xs font-medium text-purple-600 hover:text-purple-700"
|
||||
>
|
||||
Open
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ContentCardDescription className="mt-1 line-clamp-2">
|
||||
{agent.description}
|
||||
</ContentCardDescription>
|
||||
</ContentCardHeader>
|
||||
</ContentCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
</ToolAccordion>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { ToolUIPart } from "ai";
|
||||
import { MagnifyingGlassIcon, SquaresFourIcon } from "@phosphor-icons/react";
|
||||
import type { AgentInfo } from "@/app/api/__generated__/models/agentInfo";
|
||||
import type { AgentsFoundResponse } from "@/app/api/__generated__/models/agentsFoundResponse";
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import type { NoResultsResponse } from "@/app/api/__generated__/models/noResultsResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import {
|
||||
FolderOpenIcon,
|
||||
MagnifyingGlassIcon,
|
||||
SquaresFourIcon,
|
||||
StorefrontIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import { ToolUIPart } from "ai";
|
||||
|
||||
export interface FindAgentInput {
|
||||
query: string;
|
||||
@@ -174,3 +179,9 @@ export function ToolIcon({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccordionIcon({ toolType }: { toolType?: FindAgentsToolType }) {
|
||||
const { source } = getSourceLabelFromToolType(toolType);
|
||||
const IconComponent = source === "library" ? FolderOpenIcon : StorefrontIcon;
|
||||
return <IconComponent size={32} weight="light" />;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import {
|
||||
ContentCard,
|
||||
ContentCardDescription,
|
||||
ContentCardTitle,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import type { BlockListResponse } from "@/app/api/__generated__/models/blockListResponse";
|
||||
import type { BlockInfoSummary } from "@/app/api/__generated__/models/blockInfoSummary";
|
||||
import { ToolUIPart } from "ai";
|
||||
import { HorizontalScroll } from "@/app/(platform)/build/components/NewControlPanel/NewBlockMenu/HorizontalScroll";
|
||||
import { getAnimationText, parseOutput, ToolIcon } from "./helpers";
|
||||
import { AccordionIcon, getAnimationText, parseOutput, ToolIcon } from "./helpers";
|
||||
|
||||
export interface FindBlockInput {
|
||||
query: string;
|
||||
@@ -30,14 +35,12 @@ interface Props {
|
||||
|
||||
function BlockCard({ block }: { block: BlockInfoSummary }) {
|
||||
return (
|
||||
<div className="w-48 shrink-0 rounded-2xl border bg-background p-3">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
{block.name}
|
||||
</p>
|
||||
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
|
||||
<ContentCard className="w-48 shrink-0">
|
||||
<ContentCardTitle>{block.name}</ContentCardTitle>
|
||||
<ContentCardDescription className="mt-1 line-clamp-2">
|
||||
{block.description}
|
||||
</p>
|
||||
</div>
|
||||
</ContentCardDescription>
|
||||
</ContentCard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,7 +71,7 @@ export function FindBlocksTool({ part }: Props) {
|
||||
|
||||
{hasBlocks && parsed && (
|
||||
<ToolAccordion
|
||||
badgeText="Blocks"
|
||||
icon={<AccordionIcon />}
|
||||
title="Block results"
|
||||
description={accordionDescription}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ToolUIPart } from "ai";
|
||||
import type { BlockListResponse } from "@/app/api/__generated__/models/blockListResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import { CubeIcon, PackageIcon } from "@phosphor-icons/react";
|
||||
import { FindBlockInput, FindBlockToolPart } from "./FindBlocks";
|
||||
import { PackageIcon } from "@phosphor-icons/react";
|
||||
|
||||
export function parseOutput(output: unknown): BlockListResponse | null {
|
||||
if (!output) return null;
|
||||
@@ -70,3 +69,7 @@ export function ToolIcon({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccordionIcon() {
|
||||
return <CubeIcon size={32} weight="light" />;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import { ContentMessage } from "../../components/ToolAccordion/AccordionContent";
|
||||
import {
|
||||
getAccordionMeta,
|
||||
getAnimationText,
|
||||
@@ -80,7 +81,7 @@ export function RunAgentTool({ part }: Props) {
|
||||
)}
|
||||
|
||||
{isRunAgentNeedLoginOutput(output) && (
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
)}
|
||||
|
||||
{isRunAgentErrorOutput(output) && <ErrorCard output={output} />}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { FormRenderer } from "@/components/renderers/InputRenderer/FormRenderer";
|
||||
import type { AgentDetailsResponse } from "@/app/api/__generated__/models/agentDetailsResponse";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { FormRenderer } from "@/components/renderers/InputRenderer/FormRenderer";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions";
|
||||
import { ContentMessage } from "../../../../components/ToolAccordion/AccordionContent";
|
||||
import { buildInputSchema } from "./helpers";
|
||||
|
||||
interface Props {
|
||||
@@ -38,21 +40,16 @@ export function AgentDetailsCard({ output }: Props) {
|
||||
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">
|
||||
<ContentMessage>
|
||||
Run this agent with example values or your own inputs.
|
||||
</p>
|
||||
</ContentMessage>
|
||||
|
||||
<div className="flex gap-2 pt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="small"
|
||||
className="w-fit"
|
||||
onClick={handleRunWithExamples}
|
||||
>
|
||||
<Button size="small" className="w-fit" onClick={handleRunWithExamples}>
|
||||
Run with example values
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
variant="outline"
|
||||
size="small"
|
||||
className="w-fit"
|
||||
onClick={() => setShowInputForm((prev) => !prev)}
|
||||
@@ -76,9 +73,9 @@ export function AgentDetailsCard({ output }: Props) {
|
||||
style={{ willChange: "height, opacity, filter" }}
|
||||
>
|
||||
<div className="mt-4 rounded-2xl border bg-background p-3 pt-4">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
<Text variant="body-medium">
|
||||
Enter your inputs
|
||||
</p>
|
||||
</Text>
|
||||
<FormRenderer
|
||||
jsonSchema={buildInputSchema(output.agent.inputs)!}
|
||||
handleChange={(v) => setInputValues(v.formData ?? {})}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import {
|
||||
ContentCodeBlock,
|
||||
ContentGrid,
|
||||
ContentMessage,
|
||||
} from "../../../../components/ToolAccordion/AccordionContent";
|
||||
import { formatMaybeJson } from "../../helpers";
|
||||
|
||||
interface Props {
|
||||
@@ -9,18 +14,14 @@ interface Props {
|
||||
|
||||
export function ErrorCard({ output }: Props) {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.error && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
{formatMaybeJson(output.error)}
|
||||
</pre>
|
||||
<ContentCodeBlock>{formatMaybeJson(output.error)}</ContentCodeBlock>
|
||||
)}
|
||||
{output.details && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
{formatMaybeJson(output.details)}
|
||||
</pre>
|
||||
<ContentCodeBlock>{formatMaybeJson(output.details)}</ContentCodeBlock>
|
||||
)}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import type { ExecutionStartedResponse } from "@/app/api/__generated__/models/executionStartedResponse";
|
||||
import {
|
||||
ContentCard,
|
||||
ContentCardDescription,
|
||||
ContentCardSubtitle,
|
||||
ContentCardTitle,
|
||||
ContentGrid,
|
||||
} from "../../../../components/ToolAccordion/AccordionContent";
|
||||
|
||||
interface Props {
|
||||
output: ExecutionStartedResponse;
|
||||
@@ -12,17 +19,11 @@ export function ExecutionStartedCard({ output }: Props) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<div className="rounded-2xl border bg-background p-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Execution started
|
||||
</p>
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
||||
{output.execution_id}
|
||||
</p>
|
||||
<p className="mt-2 text-xs text-muted-foreground">{output.message}</p>
|
||||
</div>
|
||||
<ContentGrid>
|
||||
<ContentCard>
|
||||
<ContentCardTitle>Execution started</ContentCardTitle>
|
||||
<ContentCardSubtitle>{output.execution_id}</ContentCardSubtitle>
|
||||
<ContentCardDescription>{output.message}</ContentCardDescription>
|
||||
{output.library_agent_link && (
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -33,7 +34,7 @@ export function ExecutionStartedCard({ output }: Props) {
|
||||
View Execution
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ContentCard>
|
||||
</ContentGrid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@ import { Button } from "@/components/atoms/Button/Button";
|
||||
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
|
||||
import type { SetupRequirementsResponse } from "@/app/api/__generated__/models/setupRequirementsResponse";
|
||||
import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions";
|
||||
import {
|
||||
ContentBadge,
|
||||
ContentCardDescription,
|
||||
ContentCardTitle,
|
||||
ContentMessage,
|
||||
} from "../../../../components/ToolAccordion/AccordionContent";
|
||||
import { coerceCredentialFields, coerceExpectedInputs } from "./helpers";
|
||||
|
||||
interface Props {
|
||||
@@ -45,7 +51,7 @@ export function SetupRequirementsCard({ output }: Props) {
|
||||
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
|
||||
{credentialFields.length > 0 && (
|
||||
<div className="rounded-2xl border bg-background p-3">
|
||||
@@ -71,22 +77,22 @@ export function SetupRequirementsCard({ output }: Props) {
|
||||
|
||||
{expectedInputs.length > 0 && (
|
||||
<div className="rounded-2xl border bg-background p-3">
|
||||
<p className="text-xs font-medium text-foreground">Expected inputs</p>
|
||||
<ContentCardTitle className="text-xs">Expected inputs</ContentCardTitle>
|
||||
<div className="mt-2 grid gap-2">
|
||||
{expectedInputs.map((input) => (
|
||||
<div key={input.name} className="rounded-xl border p-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="truncate text-xs font-medium text-foreground">
|
||||
<ContentCardTitle className="text-xs">
|
||||
{input.title}
|
||||
</p>
|
||||
<span className="shrink-0 rounded-full border bg-muted px-2 py-0.5 text-[11px] text-muted-foreground">
|
||||
</ContentCardTitle>
|
||||
<ContentBadge>
|
||||
{input.required ? "Required" : "Optional"}
|
||||
</span>
|
||||
</ContentBadge>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<ContentCardDescription className="mt-1">
|
||||
{input.name} • {input.type}
|
||||
{input.description ? ` \u2022 ${input.description}` : ""}
|
||||
</p>
|
||||
</ContentCardDescription>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { PlayIcon } from "@phosphor-icons/react";
|
||||
import type { AgentDetailsResponse } from "@/app/api/__generated__/models/agentDetailsResponse";
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import type { ExecutionStartedResponse } from "@/app/api/__generated__/models/executionStartedResponse";
|
||||
import type { NeedLoginResponse } from "@/app/api/__generated__/models/needLoginResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import type { SetupRequirementsResponse } from "@/app/api/__generated__/models/setupRequirementsResponse";
|
||||
import {
|
||||
PlayIcon,
|
||||
RocketLaunchIcon,
|
||||
WarningDiamondIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
|
||||
export interface RunAgentInput {
|
||||
username_agent_slug?: string;
|
||||
@@ -111,12 +115,6 @@ function getAgentIdentifierText(
|
||||
return null;
|
||||
}
|
||||
|
||||
function getExecutionModeText(input: RunAgentInput | undefined): string | null {
|
||||
if (!input) return null;
|
||||
const isSchedule = Boolean(input.schedule_name?.trim() || input.cron?.trim());
|
||||
return isSchedule ? "Scheduled run" : "Run";
|
||||
}
|
||||
|
||||
export function getAnimationText(part: {
|
||||
state: ToolUIPart["state"];
|
||||
input?: unknown;
|
||||
@@ -166,21 +164,22 @@ export function ToolIcon({
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
if (isError) {
|
||||
return <WarningDiamondIcon size={14} weight="regular" className="text-red-500" />;
|
||||
}
|
||||
return (
|
||||
<PlayIcon
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
className={isStreaming ? "text-neutral-500" : "text-neutral-400"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccordionIcon() {
|
||||
return <RocketLaunchIcon size={28} weight="light" />;
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
if (typeof value === "string") return value;
|
||||
try {
|
||||
@@ -190,19 +189,21 @@ export function formatMaybeJson(value: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function getAccordionMeta(output: RunAgentToolOutput): {
|
||||
badgeText: string;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
titleClassName?: string;
|
||||
description?: string;
|
||||
} {
|
||||
const icon = <AccordionIcon />;
|
||||
|
||||
if (isRunAgentExecutionStartedOutput(output)) {
|
||||
const statusText =
|
||||
typeof output.status === "string" && output.status.trim()
|
||||
? output.status.trim()
|
||||
: "started";
|
||||
return {
|
||||
badgeText: "Run agent",
|
||||
icon,
|
||||
title: output.graph_name,
|
||||
description: `Status: ${statusText}`,
|
||||
};
|
||||
@@ -210,7 +211,7 @@ export function getAccordionMeta(output: RunAgentToolOutput): {
|
||||
|
||||
if (isRunAgentAgentDetailsOutput(output)) {
|
||||
return {
|
||||
badgeText: "Run agent",
|
||||
icon,
|
||||
title: output.agent.name,
|
||||
description: "Inputs required",
|
||||
};
|
||||
@@ -224,7 +225,7 @@ export function getAccordionMeta(output: RunAgentToolOutput): {
|
||||
>,
|
||||
).length;
|
||||
return {
|
||||
badgeText: "Run agent",
|
||||
icon,
|
||||
title: output.setup_info.agent_name,
|
||||
description:
|
||||
missingCredsCount > 0
|
||||
@@ -234,8 +235,12 @@ export function getAccordionMeta(output: RunAgentToolOutput): {
|
||||
}
|
||||
|
||||
if (isRunAgentNeedLoginOutput(output)) {
|
||||
return { badgeText: "Run agent", title: "Sign in required" };
|
||||
return { icon, title: "Sign in required" };
|
||||
}
|
||||
|
||||
return { badgeText: "Run agent", title: "Error" };
|
||||
}
|
||||
return {
|
||||
icon: <WarningDiamondIcon size={28} weight="light" className="text-red-500" />,
|
||||
title: "Error",
|
||||
titleClassName: "text-red-500",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import { BlockOutputCard } from "./components/BlockOutputCard/BlockOutputCard";
|
||||
import { ErrorCard } from "./components/ErrorCard/ErrorCard";
|
||||
import { SetupRequirementsCard } from "./components/SetupRequirementsCard/SetupRequirementsCard";
|
||||
import {
|
||||
getAccordionMeta,
|
||||
getAnimationText,
|
||||
@@ -11,11 +14,7 @@ import {
|
||||
isRunBlockErrorOutput,
|
||||
isRunBlockSetupRequirementsOutput,
|
||||
ToolIcon,
|
||||
type RunBlockToolOutput,
|
||||
} from "./helpers";
|
||||
import { BlockOutputCard } from "./components/BlockOutputCard/BlockOutputCard";
|
||||
import { SetupRequirementsCard } from "./components/SetupRequirementsCard/SetupRequirementsCard";
|
||||
import { ErrorCard } from "./components/ErrorCard/ErrorCard";
|
||||
|
||||
export interface RunBlockToolPart {
|
||||
type: string;
|
||||
|
||||
@@ -4,6 +4,13 @@ import React, { useState } from "react";
|
||||
import { getGetWorkspaceDownloadFileByIdUrl } from "@/app/api/__generated__/endpoints/workspace/workspace";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import type { BlockOutputResponse } from "@/app/api/__generated__/models/blockOutputResponse";
|
||||
import {
|
||||
ContentBadge,
|
||||
ContentCard,
|
||||
ContentCardTitle,
|
||||
ContentGrid,
|
||||
ContentMessage,
|
||||
} from "../../../../components/ToolAccordion/AccordionContent";
|
||||
import { formatMaybeJson } from "../../helpers";
|
||||
|
||||
interface Props {
|
||||
@@ -103,14 +110,12 @@ function OutputKeySection({
|
||||
const visibleItems = expanded ? items : items.slice(0, COLLAPSED_LIMIT);
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border bg-background p-3">
|
||||
<ContentCard>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="truncate text-xs font-medium text-foreground">
|
||||
{outputKey}
|
||||
</p>
|
||||
<span className="shrink-0 rounded-full border bg-muted px-2 py-0.5 text-[11px] text-muted-foreground">
|
||||
<ContentCardTitle className="text-xs">{outputKey}</ContentCardTitle>
|
||||
<ContentBadge>
|
||||
{items.length} item{items.length === 1 ? "" : "s"}
|
||||
</span>
|
||||
</ContentBadge>
|
||||
</div>
|
||||
{mediaContent || (
|
||||
<pre className="mt-2 whitespace-pre-wrap text-xs text-muted-foreground">
|
||||
@@ -127,18 +132,18 @@ function OutputKeySection({
|
||||
{expanded ? "Show less" : `Show all ${items.length} items`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</ContentCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function BlockOutputCard({ output }: Props) {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
|
||||
{Object.entries(output.outputs ?? {}).map(([key, items]) => (
|
||||
<OutputKeySection key={key} outputKey={key} items={items} />
|
||||
))}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import {
|
||||
ContentCodeBlock,
|
||||
ContentGrid,
|
||||
ContentMessage,
|
||||
} from "../../../../components/ToolAccordion/AccordionContent";
|
||||
import { formatMaybeJson } from "../../helpers";
|
||||
|
||||
interface Props {
|
||||
@@ -9,18 +14,14 @@ interface Props {
|
||||
|
||||
export function ErrorCard({ output }: Props) {
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.error && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
{formatMaybeJson(output.error)}
|
||||
</pre>
|
||||
<ContentCodeBlock>{formatMaybeJson(output.error)}</ContentCodeBlock>
|
||||
)}
|
||||
{output.details && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
{formatMaybeJson(output.details)}
|
||||
</pre>
|
||||
<ContentCodeBlock>{formatMaybeJson(output.details)}</ContentCodeBlock>
|
||||
)}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView";
|
||||
import type { SetupRequirementsResponse } from "@/app/api/__generated__/models/setupRequirementsResponse";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView";
|
||||
import { FormRenderer } from "@/components/renderers/InputRenderer/FormRenderer";
|
||||
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
|
||||
import type { SetupRequirementsResponse } from "@/app/api/__generated__/models/setupRequirementsResponse";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions";
|
||||
import {
|
||||
ContentBadge,
|
||||
ContentCardDescription,
|
||||
ContentCardTitle,
|
||||
ContentMessage,
|
||||
} from "../../../../components/ToolAccordion/AccordionContent";
|
||||
import {
|
||||
buildExpectedInputsSchema,
|
||||
coerceCredentialFields,
|
||||
coerceExpectedInputs,
|
||||
buildExpectedInputsSchema,
|
||||
} from "./helpers";
|
||||
|
||||
interface Props {
|
||||
@@ -69,7 +76,7 @@ export function SetupRequirementsCard({ output }: Props) {
|
||||
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
|
||||
{credentialFields.length > 0 && (
|
||||
<div className="rounded-2xl border bg-background p-3">
|
||||
@@ -96,7 +103,7 @@ export function SetupRequirementsCard({ output }: Props) {
|
||||
{inputSchema && (
|
||||
<div className="flex gap-2 pt-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
variant="outline"
|
||||
size="small"
|
||||
className="w-fit"
|
||||
onClick={() => setShowInputForm((prev) => !prev)}
|
||||
@@ -121,9 +128,7 @@ export function SetupRequirementsCard({ output }: Props) {
|
||||
style={{ willChange: "height, opacity, filter" }}
|
||||
>
|
||||
<div className="rounded-2xl border bg-background p-3 pt-4">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Block inputs
|
||||
</p>
|
||||
<Text variant="body-medium">Block inputs</Text>
|
||||
<FormRenderer
|
||||
jsonSchema={inputSchema}
|
||||
handleChange={(v) => setInputValues(v.formData ?? {})}
|
||||
@@ -164,22 +169,24 @@ export function SetupRequirementsCard({ output }: Props) {
|
||||
|
||||
{expectedInputs.length > 0 && !inputSchema && (
|
||||
<div className="rounded-2xl border bg-background p-3">
|
||||
<p className="text-xs font-medium text-foreground">Expected inputs</p>
|
||||
<ContentCardTitle className="text-xs">
|
||||
Expected inputs
|
||||
</ContentCardTitle>
|
||||
<div className="mt-2 grid gap-2">
|
||||
{expectedInputs.map((input) => (
|
||||
<div key={input.name} className="rounded-xl border p-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="truncate text-xs font-medium text-foreground">
|
||||
<ContentCardTitle className="text-xs">
|
||||
{input.title}
|
||||
</p>
|
||||
<span className="shrink-0 rounded-full border bg-muted px-2 py-0.5 text-[11px] text-muted-foreground">
|
||||
</ContentCardTitle>
|
||||
<ContentBadge>
|
||||
{input.required ? "Required" : "Optional"}
|
||||
</span>
|
||||
</ContentBadge>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<ContentCardDescription className="mt-1">
|
||||
{input.name} • {input.type}
|
||||
{input.description ? ` \u2022 ${input.description}` : ""}
|
||||
</p>
|
||||
</ContentCardDescription>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { PlayIcon } from "@phosphor-icons/react";
|
||||
import type { BlockOutputResponse } from "@/app/api/__generated__/models/blockOutputResponse";
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import type { SetupRequirementsResponse } from "@/app/api/__generated__/models/setupRequirementsResponse";
|
||||
import {
|
||||
PlayCircleIcon,
|
||||
PlayIcon,
|
||||
WarningDiamondIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
|
||||
export interface RunBlockInput {
|
||||
block_id?: string;
|
||||
@@ -109,21 +113,22 @@ export function ToolIcon({
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
if (isError) {
|
||||
return <WarningDiamondIcon size={14} weight="regular" className="text-red-500" />;
|
||||
}
|
||||
return (
|
||||
<PlayIcon
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
className={isStreaming ? "text-neutral-500" : "text-neutral-400"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccordionIcon() {
|
||||
return <PlayCircleIcon size={32} weight="light" />;
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
if (typeof value === "string") return value;
|
||||
try {
|
||||
@@ -133,17 +138,18 @@ export function formatMaybeJson(value: unknown): string {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getAccordionMeta(output: RunBlockToolOutput): {
|
||||
badgeText: string;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
titleClassName?: string;
|
||||
description?: string;
|
||||
} {
|
||||
const icon = <AccordionIcon />;
|
||||
|
||||
if (isRunBlockBlockOutput(output)) {
|
||||
const keys = Object.keys(output.outputs ?? {});
|
||||
return {
|
||||
badgeText: "Run block",
|
||||
icon,
|
||||
title: output.block_name,
|
||||
description:
|
||||
keys.length > 0
|
||||
@@ -160,7 +166,7 @@ export function getAccordionMeta(output: RunBlockToolOutput): {
|
||||
>,
|
||||
).length;
|
||||
return {
|
||||
badgeText: "Run block",
|
||||
icon,
|
||||
title: output.setup_info.agent_name,
|
||||
description:
|
||||
missingCredsCount > 0
|
||||
@@ -169,5 +175,9 @@ export function getAccordionMeta(output: RunBlockToolOutput): {
|
||||
};
|
||||
}
|
||||
|
||||
return { badgeText: "Run block", title: "Error" };
|
||||
return {
|
||||
icon: <WarningDiamondIcon size={32} weight="light" className="text-red-500" />,
|
||||
title: "Error",
|
||||
titleClassName: "text-red-500",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import type { ToolUIPart } from "ai";
|
||||
import Link from "next/link";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import {
|
||||
ContentCard,
|
||||
ContentCardDescription,
|
||||
ContentCardHeader,
|
||||
ContentCardSubtitle,
|
||||
ContentCardTitle,
|
||||
ContentGrid,
|
||||
ContentLink,
|
||||
ContentMessage,
|
||||
ContentSuggestionsList,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import {
|
||||
AccordionIcon,
|
||||
getAnimationText,
|
||||
getDocsToolOutput,
|
||||
getDocsToolTitle,
|
||||
getToolLabel,
|
||||
getAnimationText,
|
||||
isDocPageOutput,
|
||||
isDocSearchResultsOutput,
|
||||
isErrorOutput,
|
||||
isNoResultsOutput,
|
||||
ToolIcon,
|
||||
toDocsUrl,
|
||||
ToolIcon,
|
||||
type DocsToolType,
|
||||
} from "./helpers";
|
||||
|
||||
@@ -97,96 +109,73 @@ export function SearchDocsTool({ part }: Props) {
|
||||
|
||||
{hasExpandableContent && normalized && (
|
||||
<ToolAccordion
|
||||
badgeText={normalized.label}
|
||||
icon={<AccordionIcon toolType={part.type} />}
|
||||
title={normalized.title}
|
||||
description={accordionDescription}
|
||||
>
|
||||
{docSearchOutput && (
|
||||
<div className="grid gap-2">
|
||||
<ContentGrid>
|
||||
{docSearchOutput.results.map((r) => {
|
||||
const href = r.doc_url ?? toDocsUrl(r.path);
|
||||
return (
|
||||
<div
|
||||
key={r.path}
|
||||
className="rounded-2xl border bg-background p-3"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
{r.title}
|
||||
</p>
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
||||
{r.path}
|
||||
{r.section ? ` • ${r.section}` : ""}
|
||||
</p>
|
||||
<p className="mt-2 text-xs text-muted-foreground">
|
||||
{truncate(r.snippet, 240)}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="shrink-0 text-xs font-medium text-purple-600 hover:text-purple-700"
|
||||
>
|
||||
Open
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<ContentCard key={r.path}>
|
||||
<ContentCardHeader
|
||||
action={<ContentLink href={href}>Open</ContentLink>}
|
||||
>
|
||||
<ContentCardTitle>{r.title}</ContentCardTitle>
|
||||
<ContentCardSubtitle>
|
||||
{r.path}
|
||||
{r.section ? ` • ${r.section}` : ""}
|
||||
</ContentCardSubtitle>
|
||||
<ContentCardDescription>
|
||||
{truncate(r.snippet, 240)}
|
||||
</ContentCardDescription>
|
||||
</ContentCardHeader>
|
||||
</ContentCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{docPageOutput && (
|
||||
<div>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
{docPageOutput.title}
|
||||
</p>
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
||||
{docPageOutput.path}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={docPageOutput.doc_url ?? toDocsUrl(docPageOutput.path)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="shrink-0 text-xs font-medium text-purple-600 hover:text-purple-700"
|
||||
>
|
||||
Open
|
||||
</Link>
|
||||
</div>
|
||||
<p className="mt-2 whitespace-pre-wrap text-xs text-muted-foreground">
|
||||
<ContentCardHeader
|
||||
action={
|
||||
<ContentLink
|
||||
href={
|
||||
docPageOutput.doc_url ?? toDocsUrl(docPageOutput.path)
|
||||
}
|
||||
>
|
||||
Open
|
||||
</ContentLink>
|
||||
}
|
||||
>
|
||||
<ContentCardTitle>{docPageOutput.title}</ContentCardTitle>
|
||||
<ContentCardSubtitle>{docPageOutput.path}</ContentCardSubtitle>
|
||||
</ContentCardHeader>
|
||||
<ContentCardDescription className="whitespace-pre-wrap">
|
||||
{truncate(docPageOutput.content, 800)}
|
||||
</p>
|
||||
</ContentCardDescription>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{noResultsOutput && (
|
||||
<div>
|
||||
<p className="text-sm text-foreground">
|
||||
{noResultsOutput.message}
|
||||
</p>
|
||||
<ContentMessage>{noResultsOutput.message}</ContentMessage>
|
||||
{noResultsOutput.suggestions &&
|
||||
noResultsOutput.suggestions.length > 0 && (
|
||||
<ul className="mt-2 list-disc space-y-1 pl-5 text-xs text-muted-foreground">
|
||||
{noResultsOutput.suggestions.slice(0, 5).map((s) => (
|
||||
<li key={s}>{s}</li>
|
||||
))}
|
||||
</ul>
|
||||
<ContentSuggestionsList items={noResultsOutput.suggestions} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errorOutput && (
|
||||
<div>
|
||||
<p className="text-sm text-foreground">{errorOutput.message}</p>
|
||||
<ContentMessage>{errorOutput.message}</ContentMessage>
|
||||
{errorOutput.error && (
|
||||
<p className="mt-2 text-xs text-muted-foreground">
|
||||
<ContentCardDescription>
|
||||
{errorOutput.error}
|
||||
</p>
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { ToolUIPart } from "ai";
|
||||
import { FileMagnifyingGlassIcon, FileTextIcon } from "@phosphor-icons/react";
|
||||
import type { DocPageResponse } from "@/app/api/__generated__/models/docPageResponse";
|
||||
import type { DocSearchResultsResponse } from "@/app/api/__generated__/models/docSearchResultsResponse";
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import type { NoResultsResponse } from "@/app/api/__generated__/models/noResultsResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import {
|
||||
ArticleIcon,
|
||||
FileMagnifyingGlassIcon,
|
||||
FileTextIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import { ToolUIPart } from "ai";
|
||||
|
||||
export interface SearchDocsInput {
|
||||
query: string;
|
||||
@@ -197,6 +201,12 @@ export function ToolIcon({
|
||||
);
|
||||
}
|
||||
|
||||
export function AccordionIcon({ toolType }: { toolType: DocsToolType }) {
|
||||
const IconComponent =
|
||||
toolType === "tool-get_doc_page" ? ArticleIcon : FileTextIcon;
|
||||
return <IconComponent size={32} weight="light" />;
|
||||
}
|
||||
|
||||
export function toDocsUrl(path: string): string {
|
||||
const urlPath = path.includes(".")
|
||||
? path.slice(0, path.lastIndexOf("."))
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import type { ToolUIPart } from "ai";
|
||||
import Link from "next/link";
|
||||
import React, { useState } from "react";
|
||||
import { getGetWorkspaceDownloadFileByIdUrl } from "@/app/api/__generated__/endpoints/workspace/workspace";
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import {
|
||||
ContentBadge,
|
||||
ContentCard,
|
||||
ContentCardHeader,
|
||||
ContentCardSubtitle,
|
||||
ContentCardTitle,
|
||||
ContentCodeBlock,
|
||||
ContentGrid,
|
||||
ContentLink,
|
||||
ContentMessage,
|
||||
ContentSuggestionsList,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import {
|
||||
formatMaybeJson,
|
||||
getAnimationText,
|
||||
@@ -13,6 +24,7 @@ import {
|
||||
isAgentOutputResponse,
|
||||
isErrorResponse,
|
||||
isNoResultsResponse,
|
||||
AccordionIcon,
|
||||
ToolIcon,
|
||||
type ViewAgentOutputToolOutput,
|
||||
} from "./helpers";
|
||||
@@ -30,22 +42,24 @@ interface Props {
|
||||
}
|
||||
|
||||
function getAccordionMeta(output: ViewAgentOutputToolOutput): {
|
||||
badgeText: string;
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description?: string;
|
||||
} {
|
||||
const icon = <AccordionIcon />;
|
||||
|
||||
if (isAgentOutputResponse(output)) {
|
||||
const status = output.execution?.status;
|
||||
return {
|
||||
badgeText: "Agent output",
|
||||
icon,
|
||||
title: output.agent_name,
|
||||
description: status ? `Status: ${status}` : output.message,
|
||||
};
|
||||
}
|
||||
if (isNoResultsResponse(output)) {
|
||||
return { badgeText: "Agent output", title: "No results" };
|
||||
return { icon, title: "No results" };
|
||||
}
|
||||
return { badgeText: "Agent output", title: "Error" };
|
||||
return { icon, title: "Error" };
|
||||
}
|
||||
|
||||
function resolveWorkspaceUrl(src: string): string {
|
||||
@@ -157,111 +171,106 @@ export function ViewAgentOutputTool({ part }: Props) {
|
||||
{hasExpandableContent && output && (
|
||||
<ToolAccordion {...getAccordionMeta(output)}>
|
||||
{isAgentOutputResponse(output) && (
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
{output.library_agent_link && (
|
||||
<Link
|
||||
href={output.library_agent_link}
|
||||
className="shrink-0 text-xs font-medium text-purple-600 hover:text-purple-700"
|
||||
>
|
||||
Open
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<ContentGrid>
|
||||
<ContentCardHeader
|
||||
className="gap-3"
|
||||
action={
|
||||
output.library_agent_link ? (
|
||||
<ContentLink href={output.library_agent_link}>
|
||||
Open
|
||||
</ContentLink>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
</ContentCardHeader>
|
||||
|
||||
{output.execution ? (
|
||||
<div className="grid gap-2">
|
||||
<div className="rounded-2xl border bg-background p-3">
|
||||
<p className="text-xs font-medium text-foreground">
|
||||
<ContentGrid>
|
||||
<ContentCard>
|
||||
<ContentCardTitle className="text-xs">
|
||||
Execution
|
||||
</p>
|
||||
<p className="mt-1 truncate text-xs text-muted-foreground">
|
||||
</ContentCardTitle>
|
||||
<ContentCardSubtitle className="mt-1">
|
||||
{output.execution.execution_id}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
</ContentCardSubtitle>
|
||||
<ContentCardSubtitle className="mt-1">
|
||||
Status: {output.execution.status}
|
||||
</p>
|
||||
</div>
|
||||
</ContentCardSubtitle>
|
||||
</ContentCard>
|
||||
|
||||
{output.execution.inputs_summary && (
|
||||
<div className="rounded-2xl border bg-background p-3">
|
||||
<p className="text-xs font-medium text-foreground">
|
||||
<ContentCard>
|
||||
<ContentCardTitle className="text-xs">
|
||||
Inputs summary
|
||||
</p>
|
||||
</ContentCardTitle>
|
||||
<pre className="mt-2 whitespace-pre-wrap text-xs text-muted-foreground">
|
||||
{formatMaybeJson(output.execution.inputs_summary)}
|
||||
</pre>
|
||||
</div>
|
||||
</ContentCard>
|
||||
)}
|
||||
|
||||
{Object.entries(output.execution.outputs ?? {}).map(
|
||||
([key, items]) => {
|
||||
const mediaContent = renderOutputValue(items);
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className="rounded-2xl border bg-background p-3"
|
||||
>
|
||||
<ContentCard key={key}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="truncate text-xs font-medium text-foreground">
|
||||
<ContentCardTitle className="text-xs">
|
||||
{key}
|
||||
</p>
|
||||
<span className="shrink-0 rounded-full border bg-muted px-2 py-0.5 text-[11px] text-muted-foreground">
|
||||
</ContentCardTitle>
|
||||
<ContentBadge>
|
||||
{items.length} item
|
||||
{items.length === 1 ? "" : "s"}
|
||||
</span>
|
||||
</ContentBadge>
|
||||
</div>
|
||||
{mediaContent || (
|
||||
<pre className="mt-2 whitespace-pre-wrap text-xs text-muted-foreground">
|
||||
{formatMaybeJson(items.slice(0, 3))}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
</ContentCard>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
) : (
|
||||
<div className="rounded-2xl border bg-background p-3">
|
||||
<p className="text-sm text-foreground">
|
||||
No execution selected.
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<ContentCard>
|
||||
<ContentMessage>No execution selected.</ContentMessage>
|
||||
<ContentCardSubtitle className="mt-1">
|
||||
Try asking for a specific run or execution_id.
|
||||
</p>
|
||||
</div>
|
||||
</ContentCardSubtitle>
|
||||
</ContentCard>
|
||||
)}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isNoResultsResponse(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.suggestions && output.suggestions.length > 0 && (
|
||||
<ul className="mt-1 list-disc space-y-1 pl-5 text-xs text-muted-foreground">
|
||||
{output.suggestions.slice(0, 5).map((s) => (
|
||||
<li key={s}>{s}</li>
|
||||
))}
|
||||
</ul>
|
||||
<ContentSuggestionsList
|
||||
items={output.suggestions}
|
||||
className="mt-1"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{isErrorResponse(output) && (
|
||||
<div className="grid gap-2">
|
||||
<p className="text-sm text-foreground">{output.message}</p>
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.error && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{formatMaybeJson(output.error)}
|
||||
</pre>
|
||||
</ContentCodeBlock>
|
||||
)}
|
||||
{output.details && (
|
||||
<pre className="whitespace-pre-wrap rounded-2xl border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
<ContentCodeBlock>
|
||||
{formatMaybeJson(output.details)}
|
||||
</pre>
|
||||
</ContentCodeBlock>
|
||||
)}
|
||||
</div>
|
||||
</ContentGrid>
|
||||
)}
|
||||
</ToolAccordion>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { EyeIcon } from "@phosphor-icons/react";
|
||||
import type { AgentOutputResponse } from "@/app/api/__generated__/models/agentOutputResponse";
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import type { NoResultsResponse } from "@/app/api/__generated__/models/noResultsResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import { EyeIcon, MonitorIcon } from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
|
||||
export interface ViewAgentOutputInput {
|
||||
agent_name?: string;
|
||||
@@ -144,6 +144,10 @@ export function ToolIcon({
|
||||
);
|
||||
}
|
||||
|
||||
export function AccordionIcon() {
|
||||
return <MonitorIcon size={32} weight="light" />;
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
if (typeof value === "string") return value;
|
||||
try {
|
||||
|
||||
@@ -307,7 +307,7 @@ export const MessageResponse = memo(
|
||||
({ className, ...props }: MessageResponseProps) => (
|
||||
<Streamdown
|
||||
className={cn(
|
||||
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
|
||||
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_pre]:!bg-white",
|
||||
className,
|
||||
)}
|
||||
plugins={{ code, mermaid, math, cjk }}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { As, Variant, variantElementMap, variants } from "./helpers";
|
||||
|
||||
type CustomProps = {
|
||||
@@ -22,7 +23,7 @@ export function Text({
|
||||
}: TextProps) {
|
||||
const variantClasses = variants[size || variant] || variants.body;
|
||||
const Element = outerAs || variantElementMap[variant];
|
||||
const combinedClassName = `${variantClasses} ${className}`.trim();
|
||||
const combinedClassName = cn(variantClasses, className);
|
||||
|
||||
return React.createElement(
|
||||
Element,
|
||||
|
||||
Reference in New Issue
Block a user