mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-05 20:35:10 -05:00
chore: further fixes
This commit is contained in:
@@ -110,7 +110,7 @@ export function ChatSidebar() {
|
||||
<Text variant="h3" size="body-medium">
|
||||
Your chats
|
||||
</Text>
|
||||
<div className="relative left-5">
|
||||
<div className="relative left-6">
|
||||
<SidebarTrigger />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -57,7 +57,7 @@ export function MobileDrawer({
|
||||
<Drawer.Portal>
|
||||
<Drawer.Overlay className="fixed inset-0 z-[60] bg-black/10 backdrop-blur-sm" />
|
||||
<Drawer.Content className="fixed left-0 top-0 z-[70] flex h-full w-80 flex-col border-r border-zinc-200 bg-zinc-50">
|
||||
<div className="shrink-0 border-b border-zinc-200 p-4">
|
||||
<div className="shrink-0 border-b border-zinc-200 px-4 py-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Drawer.Title className="text-lg font-semibold text-zinc-800">
|
||||
Your chats
|
||||
@@ -68,7 +68,7 @@ export function MobileDrawer({
|
||||
aria-label="Close sessions"
|
||||
onClick={onClose}
|
||||
>
|
||||
<X width="1.25rem" height="1.25rem" />
|
||||
<X width="1rem" height="1rem" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function MorphingTextAnimation({ text }: Props) {
|
||||
export function MorphingTextAnimation({ text, className }: Props) {
|
||||
const letters = text.split("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={cn(className)}>
|
||||
<AnimatePresence mode="popLayout" initial={false}>
|
||||
<motion.div key={text} className="whitespace-nowrap">
|
||||
<motion.span className="inline-flex overflow-hidden">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CaretDownIcon } from "@phosphor-icons/react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { useId } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useToolAccordion } from "./useToolAccordion";
|
||||
|
||||
interface Props {
|
||||
@@ -36,12 +36,7 @@ export function ToolAccordion({
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"mt-2 w-full rounded-2xl border bg-background px-3 py-2",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className={cn("mt-2 w-full rounded-lg border px-3 py-2", className)}>
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={isExpanded}
|
||||
@@ -50,7 +45,7 @@ export function ToolAccordion({
|
||||
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="rounded-full border bg-muted px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
|
||||
<span className="px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
|
||||
{badgeText}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
isOperationInProgressOutput,
|
||||
isOperationPendingOutput,
|
||||
isOperationStartedOutput,
|
||||
StateIcon,
|
||||
ToolIcon,
|
||||
truncateText,
|
||||
type CreateAgentToolOutput,
|
||||
} from "./helpers";
|
||||
@@ -73,8 +73,12 @@ function getAccordionMeta(output: CreateAgentToolOutput): {
|
||||
export function CreateAgentTool({ part }: Props) {
|
||||
const text = getAnimationText(part);
|
||||
const { onSend } = useCopilotChatActions();
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
|
||||
const output = getCreateAgentToolOutput(part);
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isErrorOutput(output));
|
||||
const hasExpandableContent =
|
||||
part.state === "output-available" &&
|
||||
!!output &&
|
||||
@@ -99,8 +103,11 @@ export function CreateAgentTool({ part }: Props) {
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<StateIcon state={part.state} />
|
||||
<MorphingTextAnimation text={text} />
|
||||
<ToolIcon isStreaming={isStreaming} isError={isError} />
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasExpandableContent && output && (
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
CircleNotchIcon,
|
||||
XCircleIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
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";
|
||||
@@ -126,45 +122,47 @@ export function getAnimationText(part: {
|
||||
}): string {
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
return "Creating agent";
|
||||
case "input-available":
|
||||
return "Generating agent workflow";
|
||||
return "Creating a new agent";
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return "Agent created";
|
||||
if (!output) return "Creating a new agent";
|
||||
if (isOperationStartedOutput(output)) return "Agent creation started";
|
||||
if (isOperationPendingOutput(output)) return "Agent creation in progress";
|
||||
if (isOperationInProgressOutput(output))
|
||||
return "Agent creation already in progress";
|
||||
if (isAgentSavedOutput(output)) return `Saved: ${output.agent_name}`;
|
||||
if (isAgentPreviewOutput(output)) return `Preview: ${output.agent_name}`;
|
||||
if (isAgentSavedOutput(output)) return `Saved "${output.agent_name}"`;
|
||||
if (isAgentPreviewOutput(output)) return `Preview "${output.agent_name}"`;
|
||||
if (isClarificationNeededOutput(output)) return "Needs clarification";
|
||||
return "Error creating agent";
|
||||
}
|
||||
case "output-error":
|
||||
return "Error creating agent";
|
||||
default:
|
||||
return "Processing";
|
||||
return "Creating a new agent";
|
||||
}
|
||||
}
|
||||
|
||||
export function StateIcon({ state }: { state: ToolUIPart["state"] }) {
|
||||
switch (state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
className="h-4 w-4 animate-spin text-muted-foreground"
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
case "output-available":
|
||||
return <CheckCircleIcon className="h-4 w-4 text-green-500" />;
|
||||
case "output-error":
|
||||
return <XCircleIcon className="h-4 w-4 text-red-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
export function ToolIcon({
|
||||
isStreaming,
|
||||
isError,
|
||||
}: {
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<PlusIcon
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
isOperationInProgressOutput,
|
||||
isOperationPendingOutput,
|
||||
isOperationStartedOutput,
|
||||
StateIcon,
|
||||
ToolIcon,
|
||||
truncateText,
|
||||
type EditAgentToolOutput,
|
||||
} from "./helpers";
|
||||
@@ -73,8 +73,12 @@ function getAccordionMeta(output: EditAgentToolOutput): {
|
||||
export function EditAgentTool({ part }: Props) {
|
||||
const text = getAnimationText(part);
|
||||
const { onSend } = useCopilotChatActions();
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
|
||||
const output = getEditAgentToolOutput(part);
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isErrorOutput(output));
|
||||
const hasExpandableContent =
|
||||
part.state === "output-available" &&
|
||||
!!output &&
|
||||
@@ -99,8 +103,11 @@ export function EditAgentTool({ part }: Props) {
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<StateIcon state={part.state} />
|
||||
<MorphingTextAnimation text={text} />
|
||||
<ToolIcon isStreaming={isStreaming} isError={isError} />
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasExpandableContent && output && (
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
CircleNotchIcon,
|
||||
XCircleIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
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";
|
||||
@@ -126,45 +122,47 @@ export function getAnimationText(part: {
|
||||
}): string {
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
return "Editing agent";
|
||||
case "input-available":
|
||||
return "Updating agent workflow";
|
||||
return "Editing the agent";
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return "Agent updated";
|
||||
if (!output) return "Editing the agent";
|
||||
if (isOperationStartedOutput(output)) return "Agent update started";
|
||||
if (isOperationPendingOutput(output)) return "Agent update in progress";
|
||||
if (isOperationInProgressOutput(output))
|
||||
return "Agent update already in progress";
|
||||
if (isAgentSavedOutput(output)) return `Saved: ${output.agent_name}`;
|
||||
if (isAgentPreviewOutput(output)) return `Preview: ${output.agent_name}`;
|
||||
if (isAgentSavedOutput(output)) return `Saved "${output.agent_name}"`;
|
||||
if (isAgentPreviewOutput(output)) return `Preview "${output.agent_name}"`;
|
||||
if (isClarificationNeededOutput(output)) return "Needs clarification";
|
||||
return "Error editing agent";
|
||||
}
|
||||
case "output-error":
|
||||
return "Error editing agent";
|
||||
default:
|
||||
return "Processing";
|
||||
return "Editing the agent";
|
||||
}
|
||||
}
|
||||
|
||||
export function StateIcon({ state }: { state: ToolUIPart["state"] }) {
|
||||
switch (state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
className="h-4 w-4 animate-spin text-muted-foreground"
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
case "output-available":
|
||||
return <CheckCircleIcon className="h-4 w-4 text-green-500" />;
|
||||
case "output-error":
|
||||
return <XCircleIcon className="h-4 w-4 text-red-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
export function ToolIcon({
|
||||
isStreaming,
|
||||
isError,
|
||||
}: {
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<PencilLineIcon
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
getFindAgentsOutput,
|
||||
getSourceLabelFromToolType,
|
||||
isAgentsFoundOutput,
|
||||
StateIcon,
|
||||
isErrorOutput,
|
||||
ToolIcon,
|
||||
} from "./helpers";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
|
||||
@@ -28,6 +29,10 @@ interface Props {
|
||||
export function FindAgentsTool({ part }: Props) {
|
||||
const text = getAnimationText(part);
|
||||
const output = getFindAgentsOutput(part);
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isErrorOutput(output));
|
||||
|
||||
const query =
|
||||
typeof part.input === "object" && part.input !== null
|
||||
@@ -59,8 +64,11 @@ export function FindAgentsTool({ part }: Props) {
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<StateIcon state={part.state} />
|
||||
<MorphingTextAnimation text={text} />
|
||||
<ToolIcon toolType={part.type} isStreaming={isStreaming} isError={isError} />
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasAgents && agentsFoundOutput && (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ToolUIPart } from "ai";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
CircleNotchIcon,
|
||||
XCircleIcon,
|
||||
MagnifyingGlassIcon,
|
||||
SquaresFourIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import type { AgentInfo } from "@/app/api/__generated__/models/agentInfo";
|
||||
import type { AgentsFoundResponse } from "@/app/api/__generated__/models/agentsFoundResponse";
|
||||
@@ -99,52 +98,45 @@ export function getAnimationText(part: {
|
||||
input?: unknown;
|
||||
output?: unknown;
|
||||
}): string {
|
||||
const { label, source } = getSourceLabelFromToolType(part.type);
|
||||
const { source } = getSourceLabelFromToolType(part.type);
|
||||
const query = (part.input as FindAgentInput | undefined)?.query?.trim();
|
||||
|
||||
// Action phrase matching legacy ToolCallMessage
|
||||
const actionPhrase =
|
||||
source === "library"
|
||||
? "Looking for library agents"
|
||||
: "Looking for agents in the marketplace";
|
||||
|
||||
const queryText = query ? ` matching "${query}"` : "";
|
||||
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
return `Searching ${label.toLowerCase()} agents for you`;
|
||||
|
||||
case "input-available": {
|
||||
const query = (part.input as FindAgentInput | undefined)?.query?.trim();
|
||||
if (query) {
|
||||
return source === "library"
|
||||
? `Finding library agents matching "${query}"`
|
||||
: `Finding marketplace agents matching "${query}"`;
|
||||
}
|
||||
return source === "library" ? "Finding library agents" : "Finding agents";
|
||||
}
|
||||
case "input-available":
|
||||
return `${actionPhrase}${queryText}`;
|
||||
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
const query = (part.input as FindAgentInput | undefined)?.query?.trim();
|
||||
const scope = source === "library" ? "in your library" : "in marketplace";
|
||||
if (!output) {
|
||||
return query ? `Found agents ${scope} for "${query}"` : "Found agents";
|
||||
return `${actionPhrase}${queryText}`;
|
||||
}
|
||||
if (isNoResultsOutput(output)) {
|
||||
return query
|
||||
? `No agents found ${scope} for "${query}"`
|
||||
: `No agents found ${scope}`;
|
||||
return `No agents found${queryText}`;
|
||||
}
|
||||
if (isAgentsFoundOutput(output)) {
|
||||
const count = output.count ?? output.agents?.length ?? 0;
|
||||
const countText = `Found ${count} agent${count === 1 ? "" : "s"}`;
|
||||
if (query) return `${countText} ${scope} for "${query}"`;
|
||||
return `${countText} ${scope}`;
|
||||
return `Found ${count} agent${count === 1 ? "" : "s"}${queryText}`;
|
||||
}
|
||||
if (isErrorOutput(output)) {
|
||||
return `Error finding agents ${scope}`;
|
||||
return `Error finding agents${queryText}`;
|
||||
}
|
||||
return `Found agents ${scope}`;
|
||||
return `${actionPhrase}${queryText}`;
|
||||
}
|
||||
|
||||
case "output-error":
|
||||
return source === "library"
|
||||
? "Error finding agents in your library"
|
||||
: "Error finding agents in marketplace";
|
||||
return `Error finding agents${queryText}`;
|
||||
|
||||
default:
|
||||
return "Processing";
|
||||
return actionPhrase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,21 +150,30 @@ export function getAgentHref(agent: AgentInfo): string | null {
|
||||
return `/marketplace/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`;
|
||||
}
|
||||
|
||||
export function StateIcon({ state }: { state: ToolUIPart["state"] }) {
|
||||
switch (state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
className="h-4 w-4 animate-spin text-muted-foreground"
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
case "output-available":
|
||||
return <CheckCircleIcon className="h-4 w-4 text-green-500" />;
|
||||
case "output-error":
|
||||
return <XCircleIcon className="h-4 w-4 text-red-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
export function ToolIcon({
|
||||
toolType,
|
||||
isStreaming,
|
||||
isError,
|
||||
}: {
|
||||
toolType?: FindAgentsToolType;
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
const { source } = getSourceLabelFromToolType(toolType);
|
||||
const IconComponent =
|
||||
source === "library" ? MagnifyingGlassIcon : SquaresFourIcon;
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import type { BlockListResponse } from "@/app/api/__generated__/models/blockListResponse";
|
||||
import { ToolUIPart } from "ai";
|
||||
import { getAnimationText, StateIcon } from "./helpers";
|
||||
import { getAnimationText, ToolIcon } from "./helpers";
|
||||
|
||||
export interface FindBlockInput {
|
||||
query: string;
|
||||
@@ -25,11 +25,17 @@ interface Props {
|
||||
|
||||
export function FindBlocksTool({ part }: Props) {
|
||||
const text = getAnimationText(part);
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
const isError = part.state === "output-error";
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 py-2 text-sm text-muted-foreground">
|
||||
<StateIcon state={part.state} />
|
||||
<MorphingTextAnimation text={text} />
|
||||
<ToolIcon isStreaming={isStreaming} isError={isError} />
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,7 @@ import { ToolUIPart } from "ai";
|
||||
import type { BlockListResponse } from "@/app/api/__generated__/models/blockListResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import { FindBlockInput, FindBlockToolPart } from "./FindBlocks";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
CircleNotchIcon,
|
||||
XCircleIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import { PackageIcon } from "@phosphor-icons/react";
|
||||
|
||||
function parseOutput(output: unknown): BlockListResponse | null {
|
||||
if (!output) return null;
|
||||
@@ -29,46 +25,48 @@ function parseOutput(output: unknown): BlockListResponse | null {
|
||||
}
|
||||
|
||||
export function getAnimationText(part: FindBlockToolPart): string {
|
||||
const query = (part.input as FindBlockInput | undefined)?.query?.trim();
|
||||
const queryText = query ? ` matching "${query}"` : "";
|
||||
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
return "Searching blocks for you";
|
||||
|
||||
case "input-available": {
|
||||
const query = (part.input as FindBlockInput).query;
|
||||
return `Finding "${query}" blocks`;
|
||||
}
|
||||
case "input-available":
|
||||
return `Searching for blocks${queryText}`;
|
||||
|
||||
case "output-available": {
|
||||
const parsed = parseOutput(part.output);
|
||||
if (parsed) {
|
||||
return `Found ${parsed.count} "${(part.input as FindBlockInput).query}" blocks`;
|
||||
return `Found ${parsed.count} block${parsed.count === 1 ? "" : "s"}${queryText}`;
|
||||
}
|
||||
return "Found blocks";
|
||||
return `Searching for blocks${queryText}`;
|
||||
}
|
||||
|
||||
case "output-error":
|
||||
return "Error finding blocks";
|
||||
return `Error finding blocks${queryText}`;
|
||||
|
||||
default:
|
||||
return "Processing";
|
||||
return "Searching for blocks";
|
||||
}
|
||||
}
|
||||
|
||||
export function StateIcon({ state }: { state: ToolUIPart["state"] }) {
|
||||
switch (state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
className="h-4 w-4 animate-spin text-muted-foreground"
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
case "output-available":
|
||||
return <CheckCircleIcon className="h-4 w-4 text-green-500" />;
|
||||
case "output-error":
|
||||
return <XCircleIcon className="h-4 w-4 text-red-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
export function ToolIcon({
|
||||
isStreaming,
|
||||
isError,
|
||||
}: {
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<PackageIcon
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
isRunAgentExecutionStartedOutput,
|
||||
isRunAgentNeedLoginOutput,
|
||||
isRunAgentSetupRequirementsOutput,
|
||||
StateIcon,
|
||||
ToolIcon,
|
||||
type RunAgentToolOutput,
|
||||
} from "./helpers";
|
||||
|
||||
@@ -205,8 +205,12 @@ function coerceExpectedInputs(rawInputs: unknown): Array<{
|
||||
export function RunAgentTool({ part }: Props) {
|
||||
const text = getAnimationText(part);
|
||||
const { onSend } = useCopilotChatActions();
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
|
||||
const output = getRunAgentToolOutput(part);
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isRunAgentErrorOutput(output));
|
||||
const hasExpandableContent =
|
||||
part.state === "output-available" &&
|
||||
!!output &&
|
||||
@@ -225,8 +229,11 @@ export function RunAgentTool({ part }: Props) {
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<StateIcon state={part.state} />
|
||||
<MorphingTextAnimation text={text} />
|
||||
<ToolIcon isStreaming={isStreaming} isError={isError} />
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasExpandableContent && output && (
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
CircleNotchIcon,
|
||||
XCircleIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
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";
|
||||
@@ -128,24 +124,29 @@ export function getAnimationText(part: {
|
||||
}): string {
|
||||
const input = part.input as RunAgentInput | undefined;
|
||||
const agentIdentifier = getAgentIdentifierText(input);
|
||||
const mode = getExecutionModeText(input);
|
||||
const isSchedule = Boolean(
|
||||
input?.schedule_name?.trim() || input?.cron?.trim(),
|
||||
);
|
||||
const actionPhrase = isSchedule
|
||||
? "Scheduling the agent to run"
|
||||
: "Running the agent";
|
||||
const identifierText = agentIdentifier ? ` "${agentIdentifier}"` : "";
|
||||
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
return "Preparing to run agent";
|
||||
case "input-available":
|
||||
return agentIdentifier ? `${mode}: ${agentIdentifier}` : "Running agent";
|
||||
return `${actionPhrase}${identifierText}`;
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return "Agent run updated";
|
||||
if (!output) return `${actionPhrase}${identifierText}`;
|
||||
if (isRunAgentExecutionStartedOutput(output)) {
|
||||
return `Started: ${output.graph_name}`;
|
||||
return `Started "${output.graph_name}"`;
|
||||
}
|
||||
if (isRunAgentAgentDetailsOutput(output)) {
|
||||
return `Agent inputs: ${output.agent.name}`;
|
||||
return `Agent inputs needed for "${output.agent.name}"`;
|
||||
}
|
||||
if (isRunAgentSetupRequirementsOutput(output)) {
|
||||
return `Needs setup: ${output.setup_info.agent_name}`;
|
||||
return `Setup needed for "${output.setup_info.agent_name}"`;
|
||||
}
|
||||
if (isRunAgentNeedLoginOutput(output))
|
||||
return "Sign in required to run agent";
|
||||
@@ -154,27 +155,30 @@ export function getAnimationText(part: {
|
||||
case "output-error":
|
||||
return "Error running agent";
|
||||
default:
|
||||
return "Processing";
|
||||
return actionPhrase;
|
||||
}
|
||||
}
|
||||
|
||||
export function StateIcon({ state }: { state: ToolUIPart["state"] }) {
|
||||
switch (state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
className="h-4 w-4 animate-spin text-muted-foreground"
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
case "output-available":
|
||||
return <CheckCircleIcon className="h-4 w-4 text-green-500" />;
|
||||
case "output-error":
|
||||
return <XCircleIcon className="h-4 w-4 text-red-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
export function ToolIcon({
|
||||
isStreaming,
|
||||
isError,
|
||||
}: {
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<PlayIcon
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
isRunBlockBlockOutput,
|
||||
isRunBlockErrorOutput,
|
||||
isRunBlockSetupRequirementsOutput,
|
||||
StateIcon,
|
||||
ToolIcon,
|
||||
type RunBlockToolOutput,
|
||||
} from "./helpers";
|
||||
|
||||
@@ -190,8 +190,12 @@ function coerceExpectedInputs(rawInputs: unknown): Array<{
|
||||
export function RunBlockTool({ part }: Props) {
|
||||
const text = getAnimationText(part);
|
||||
const { onSend } = useCopilotChatActions();
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
|
||||
const output = getRunBlockToolOutput(part);
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isRunBlockErrorOutput(output));
|
||||
const hasExpandableContent =
|
||||
part.state === "output-available" &&
|
||||
!!output &&
|
||||
@@ -208,8 +212,11 @@ export function RunBlockTool({ part }: Props) {
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<StateIcon state={part.state} />
|
||||
<MorphingTextAnimation text={text} />
|
||||
<ToolIcon isStreaming={isStreaming} isError={isError} />
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasExpandableContent && output && (
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
CircleNotchIcon,
|
||||
XCircleIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
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";
|
||||
@@ -89,47 +85,50 @@ export function getAnimationText(part: {
|
||||
output?: unknown;
|
||||
}): string {
|
||||
const input = part.input as RunBlockInput | undefined;
|
||||
const blockLabel = getBlockLabel(input);
|
||||
const blockId = input?.block_id?.trim();
|
||||
const blockText = blockId ? ` "${blockId}"` : "";
|
||||
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
return "Preparing to run block";
|
||||
case "input-available":
|
||||
return blockLabel ? `Running ${blockLabel}` : "Running block";
|
||||
return `Running the block${blockText}`;
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return "Block run updated";
|
||||
if (!output) return `Running the block${blockText}`;
|
||||
if (isRunBlockBlockOutput(output))
|
||||
return `Block ran: ${output.block_name}`;
|
||||
return `Ran "${output.block_name}"`;
|
||||
if (isRunBlockSetupRequirementsOutput(output)) {
|
||||
return `Needs setup: ${output.setup_info.agent_name}`;
|
||||
return `Setup needed for "${output.setup_info.agent_name}"`;
|
||||
}
|
||||
return "Error running block";
|
||||
}
|
||||
case "output-error":
|
||||
return "Error running block";
|
||||
default:
|
||||
return "Processing";
|
||||
return "Running the block";
|
||||
}
|
||||
}
|
||||
|
||||
export function StateIcon({ state }: { state: ToolUIPart["state"] }) {
|
||||
switch (state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
className="h-4 w-4 animate-spin text-muted-foreground"
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
case "output-available":
|
||||
return <CheckCircleIcon className="h-4 w-4 text-green-500" />;
|
||||
case "output-error":
|
||||
return <XCircleIcon className="h-4 w-4 text-red-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
export function ToolIcon({
|
||||
isStreaming,
|
||||
isError,
|
||||
}: {
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<PlayIcon
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
isDocSearchResultsOutput,
|
||||
isErrorOutput,
|
||||
isNoResultsOutput,
|
||||
StateIcon,
|
||||
ToolIcon,
|
||||
toDocsUrl,
|
||||
type DocsToolType,
|
||||
} from "./helpers";
|
||||
@@ -40,6 +40,10 @@ function truncate(text: string, maxChars: number): string {
|
||||
export function SearchDocsTool({ part }: Props) {
|
||||
const output = getDocsToolOutput(part);
|
||||
const text = getAnimationText(part);
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isErrorOutput(output));
|
||||
|
||||
const normalized = useMemo(() => {
|
||||
if (!output) return null;
|
||||
@@ -80,8 +84,11 @@ export function SearchDocsTool({ part }: Props) {
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<StateIcon state={part.state} />
|
||||
<MorphingTextAnimation text={text} />
|
||||
<ToolIcon toolType={part.type} isStreaming={isStreaming} isError={isError} />
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasExpandableContent && normalized && (
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { ToolUIPart } from "ai";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
CircleNotchIcon,
|
||||
XCircleIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
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";
|
||||
@@ -123,51 +119,42 @@ export function getAnimationText(part: {
|
||||
}): string {
|
||||
switch (part.type) {
|
||||
case "tool-search_docs": {
|
||||
const query = (part.input as SearchDocsInput | undefined)?.query?.trim();
|
||||
const queryText = query ? ` for "${query}"` : "";
|
||||
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
return "Searching docs for you";
|
||||
case "input-available": {
|
||||
const query = (
|
||||
part.input as SearchDocsInput | undefined
|
||||
)?.query?.trim();
|
||||
return query ? `Searching docs for "${query}"` : "Searching docs";
|
||||
}
|
||||
case "input-available":
|
||||
return `Searching documentation${queryText}`;
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
const query = (
|
||||
part.input as SearchDocsInput | undefined
|
||||
)?.query?.trim();
|
||||
if (!output) return "Found documentation";
|
||||
if (!output) return `Searching documentation${queryText}`;
|
||||
if (isDocSearchResultsOutput(output)) {
|
||||
const count = output.count ?? output.results.length;
|
||||
return query
|
||||
? `Found ${count} doc result${count === 1 ? "" : "s"} for "${query}"`
|
||||
: `Found ${count} doc result${count === 1 ? "" : "s"}`;
|
||||
return `Found ${count} result${count === 1 ? "" : "s"}${queryText}`;
|
||||
}
|
||||
if (isNoResultsOutput(output)) {
|
||||
return query ? `No docs found for "${query}"` : "No docs found";
|
||||
return `No results found${queryText}`;
|
||||
}
|
||||
return "Error searching docs";
|
||||
return `Error searching documentation${queryText}`;
|
||||
}
|
||||
case "output-error":
|
||||
return "Error searching docs";
|
||||
return `Error searching documentation${queryText}`;
|
||||
default:
|
||||
return "Processing";
|
||||
return "Searching documentation";
|
||||
}
|
||||
}
|
||||
case "tool-get_doc_page": {
|
||||
const path = (part.input as GetDocPageInput | undefined)?.path?.trim();
|
||||
const pathText = path ? ` "${path}"` : "";
|
||||
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
return "Loading documentation page";
|
||||
case "input-available": {
|
||||
const path = (
|
||||
part.input as GetDocPageInput | undefined
|
||||
)?.path?.trim();
|
||||
return path ? `Loading "${path}"` : "Loading documentation page";
|
||||
}
|
||||
case "input-available":
|
||||
return `Loading documentation page${pathText}`;
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return "Loaded documentation page";
|
||||
if (!output) return `Loading documentation page${pathText}`;
|
||||
if (isDocPageOutput(output)) return `Loaded "${output.title}"`;
|
||||
if (isNoResultsOutput(output)) return "Documentation page not found";
|
||||
return "Error loading documentation page";
|
||||
@@ -175,7 +162,7 @@ export function getAnimationText(part: {
|
||||
case "output-error":
|
||||
return "Error loading documentation page";
|
||||
default:
|
||||
return "Processing";
|
||||
return "Loading documentation page";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,23 +170,31 @@ export function getAnimationText(part: {
|
||||
return "Processing";
|
||||
}
|
||||
|
||||
export function StateIcon({ state }: { state: ToolUIPart["state"] }) {
|
||||
switch (state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
className="h-4 w-4 animate-spin text-muted-foreground"
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
case "output-available":
|
||||
return <CheckCircleIcon className="h-4 w-4 text-green-500" />;
|
||||
case "output-error":
|
||||
return <XCircleIcon className="h-4 w-4 text-red-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
export function ToolIcon({
|
||||
toolType,
|
||||
isStreaming,
|
||||
isError,
|
||||
}: {
|
||||
toolType: DocsToolType;
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
const IconComponent =
|
||||
toolType === "tool-get_doc_page" ? FileTextIcon : FileMagnifyingGlassIcon;
|
||||
|
||||
return (
|
||||
<IconComponent
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function toDocsUrl(path: string): string {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
isAgentOutputResponse,
|
||||
isErrorResponse,
|
||||
isNoResultsResponse,
|
||||
StateIcon,
|
||||
ToolIcon,
|
||||
type ViewAgentOutputToolOutput,
|
||||
} from "./helpers";
|
||||
|
||||
@@ -48,8 +48,12 @@ function getAccordionMeta(output: ViewAgentOutputToolOutput): {
|
||||
|
||||
export function ViewAgentOutputTool({ part }: Props) {
|
||||
const text = getAnimationText(part);
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
|
||||
const output = getViewAgentOutputToolOutput(part);
|
||||
const isError =
|
||||
part.state === "output-error" || (!!output && isErrorResponse(output));
|
||||
const hasExpandableContent =
|
||||
part.state === "output-available" &&
|
||||
!!output &&
|
||||
@@ -60,8 +64,11 @@ export function ViewAgentOutputTool({ part }: Props) {
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<StateIcon state={part.state} />
|
||||
<MorphingTextAnimation text={text} />
|
||||
<ToolIcon isStreaming={isStreaming} isError={isError} />
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasExpandableContent && output && (
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import type { ToolUIPart } from "ai";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
CircleNotchIcon,
|
||||
XCircleIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
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";
|
||||
@@ -102,19 +98,19 @@ export function getAnimationText(part: {
|
||||
}): string {
|
||||
const input = part.input as ViewAgentOutputInput | undefined;
|
||||
const agent = getAgentIdentifierText(input);
|
||||
const agentText = agent ? ` "${agent}"` : "";
|
||||
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
return "Looking up agent outputs";
|
||||
case "input-available":
|
||||
return agent ? `Loading outputs: ${agent}` : "Loading agent outputs";
|
||||
return `Retrieving agent output${agentText}`;
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (!output) return "Loaded agent outputs";
|
||||
if (!output) return `Retrieving agent output${agentText}`;
|
||||
if (isAgentOutputResponse(output)) {
|
||||
if (output.execution)
|
||||
return `Loaded output (${output.execution.status})`;
|
||||
return "Loaded agent outputs";
|
||||
return `Retrieved output (${output.execution.status})`;
|
||||
return "Retrieved agent output";
|
||||
}
|
||||
if (isNoResultsResponse(output)) return "No outputs found";
|
||||
return "Error loading agent output";
|
||||
@@ -122,27 +118,30 @@ export function getAnimationText(part: {
|
||||
case "output-error":
|
||||
return "Error loading agent output";
|
||||
default:
|
||||
return "Processing";
|
||||
return "Retrieving agent output";
|
||||
}
|
||||
}
|
||||
|
||||
export function StateIcon({ state }: { state: ToolUIPart["state"] }) {
|
||||
switch (state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return (
|
||||
<CircleNotchIcon
|
||||
className="h-4 w-4 animate-spin text-muted-foreground"
|
||||
weight="bold"
|
||||
/>
|
||||
);
|
||||
case "output-available":
|
||||
return <CheckCircleIcon className="h-4 w-4 text-green-500" />;
|
||||
case "output-error":
|
||||
return <XCircleIcon className="h-4 w-4 text-red-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
export function ToolIcon({
|
||||
isStreaming,
|
||||
isError,
|
||||
}: {
|
||||
isStreaming?: boolean;
|
||||
isError?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<EyeIcon
|
||||
size={14}
|
||||
weight="regular"
|
||||
className={
|
||||
isError
|
||||
? "text-red-500"
|
||||
: isStreaming
|
||||
? "text-neutral-500"
|
||||
: "text-neutral-400"
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function formatMaybeJson(value: unknown): string {
|
||||
|
||||
Reference in New Issue
Block a user