refactor: update dependencies and streamline assistant-ui package usages

This commit is contained in:
Anish Sarkar 2026-03-24 02:22:51 +05:30
parent fed3a3b436
commit b8f3f41326
40 changed files with 886 additions and 1110 deletions

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
@ -457,12 +457,10 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const CreateConfluencePageToolUI = makeAssistantToolUI<
export const CreateConfluencePageToolUI = ({ args, result }: ToolCallMessagePartProps<
{ title: string; content?: string; space_id?: string },
CreateConfluencePageResult
>({
toolName: "create_confluence_page",
render: function CreateConfluencePageUI({ args, result }) {
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
@ -494,5 +492,4 @@ export const CreateConfluencePageToolUI = makeAssistantToolUI<
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { CornerDownLeftIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
@ -396,12 +396,10 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const DeleteConfluencePageToolUI = makeAssistantToolUI<
export const DeleteConfluencePageToolUI = ({ result }: ToolCallMessagePartProps<
{ page_title_or_id: string; delete_from_kb?: boolean },
DeleteConfluencePageResult
>({
toolName: "delete_confluence_page",
render: function DeleteConfluencePageUI({ result }) {
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
@ -435,5 +433,4 @@ export const DeleteConfluencePageToolUI = makeAssistantToolUI<
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
@ -493,16 +493,14 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const UpdateConfluencePageToolUI = makeAssistantToolUI<
export const UpdateConfluencePageToolUI = ({ args, result }: ToolCallMessagePartProps<
{
page_title_or_id: string;
new_title?: string;
new_content?: string;
},
UpdateConfluencePageResult
>({
toolName: "update_confluence_page",
render: function UpdateConfluencePageUI({ args, result }) {
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
@ -535,5 +533,4 @@ export const UpdateConfluencePageToolUI = makeAssistantToolUI<
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { Brain, CheckCircle2, Loader2, Search, Sparkles } from "lucide-react";
import type { FC, ReactNode } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@ -317,12 +317,7 @@ const SmartChainOfThought: FC<SmartChainOfThoughtProps> = ({ steps }) => {
* when the deepagent is processing a query. It shows thinking steps
* in a collapsible, hierarchical format.
*/
export const DeepAgentThinkingToolUI = makeAssistantToolUI<
DeepAgentThinkingArgs,
DeepAgentThinkingResult
>({
toolName: "deepagent_thinking",
render: function DeepAgentThinkingUI({ result, status }) {
export const DeepAgentThinkingToolUI = ({ result, status }: ToolCallMessagePartProps<DeepAgentThinkingArgs, DeepAgentThinkingResult>) => {
// Loading state - tool is still running
if (status.type === "running" || status.type === "requires-action") {
return <ThinkingLoadingState status={result?.status ?? undefined} />;
@ -349,8 +344,7 @@ export const DeepAgentThinkingToolUI = makeAssistantToolUI<
<SmartChainOfThought steps={result.steps} />
</div>
);
},
});
};
// ============================================================================
// Public Components

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { AlertCircleIcon, ImageIcon } from "lucide-react";
import { z } from "zod";
import {
@ -103,9 +103,7 @@ function ParsedImage({ result }: { result: unknown }) {
* - Hover overlay effects
* - Click to open full size
*/
export const DisplayImageToolUI = makeAssistantToolUI<DisplayImageArgs, DisplayImageResult>({
toolName: "display_image",
render: function DisplayImageUI({ args, result, status }) {
export const DisplayImageToolUI = ({ args, result, status }: ToolCallMessagePartProps<DisplayImageArgs, DisplayImageResult>) => {
const src = args.src || "Unknown";
// Loading state - tool is still running
@ -154,8 +152,7 @@ export const DisplayImageToolUI = makeAssistantToolUI<DisplayImageArgs, DisplayI
</ImageErrorBoundary>
</div>
);
},
});
};
export {
DisplayImageArgsSchema,

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useParams, usePathname } from "next/navigation";
import { useCallback, useEffect, useRef, useState } from "react";
import { z } from "zod";
@ -372,12 +372,7 @@ function PodcastStatusPoller({ podcastId, title }: { podcastId: number; title: s
*
* It polls for task completion and auto-updates when the podcast is ready.
*/
export const GeneratePodcastToolUI = makeAssistantToolUI<
GeneratePodcastArgs,
GeneratePodcastResult
>({
toolName: "generate_podcast",
render: function GeneratePodcastUI({ args, result, status }) {
export const GeneratePodcastToolUI = ({ args, result, status }: ToolCallMessagePartProps<GeneratePodcastArgs, GeneratePodcastResult>) => {
const title = args.podcast_title || "SurfSense Podcast";
// Loading state - tool is still running (agent processing)
@ -462,5 +457,4 @@ export const GeneratePodcastToolUI = makeAssistantToolUI<
// Fallback - missing required data
return <PodcastErrorState title={title} error="Missing podcast ID" />;
},
});
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useAtomValue, useSetAtom } from "jotai";
import { Dot } from "lucide-react";
import { useParams, usePathname } from "next/navigation";
@ -273,9 +273,7 @@ function ReportCard({
* Generate Report Tool UI renders custom UI inline in chat
* when the generate_report tool is called by the agent.
*/
export const GenerateReportToolUI = makeAssistantToolUI<GenerateReportArgs, GenerateReportResult>({
toolName: "generate_report",
render: function GenerateReportUI({ args, result, status }) {
export const GenerateReportToolUI = ({ args, result, status }: ToolCallMessagePartProps<GenerateReportArgs, GenerateReportResult>) => {
const params = useParams();
const pathname = usePathname();
const isPublicRoute = pathname?.startsWith("/public/");
@ -332,5 +330,4 @@ export const GenerateReportToolUI = makeAssistantToolUI<GenerateReportArgs, Gene
}
return <ReportErrorState title={topic} error="Missing report ID" />;
},
});
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pen, UserIcon, UsersIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
@ -466,42 +466,42 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const CreateGmailDraftToolUI = makeAssistantToolUI<
export const CreateGmailDraftToolUI = ({
args,
result,
}: ToolCallMessagePartProps<
{ to: string; subject: string; body: string; cc?: string; bcc?: string },
CreateGmailDraftResult
>({
toolName: "create_gmail_draft",
render: function CreateGmailDraftUI({ args, result }) {
if (!result) return null;
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, MailIcon, Pen, UserIcon, UsersIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
@ -464,42 +464,42 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const SendGmailEmailToolUI = makeAssistantToolUI<
export const SendGmailEmailToolUI = ({
args,
result,
}: ToolCallMessagePartProps<
{ to: string; subject: string; body: string; cc?: string; bcc?: string },
SendGmailEmailResult
>({
toolName: "send_gmail_email",
render: function SendGmailEmailUI({ args, result }) {
if (!result) return null;
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { CalendarIcon, CornerDownLeftIcon, MailIcon, UserIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
@ -379,43 +379,42 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const TrashGmailEmailToolUI = makeAssistantToolUI<
export const TrashGmailEmailToolUI = ({
result,
}: ToolCallMessagePartProps<
{ email_subject_or_id: string; delete_from_kb?: boolean },
TrashGmailEmailResult
>({
toolName: "trash_gmail_email",
render: function TrashGmailEmailUI({ result }) {
if (!result) return null;
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, MailIcon, Pen, UserIcon, UsersIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
@ -508,7 +508,10 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const UpdateGmailDraftToolUI = makeAssistantToolUI<
export const UpdateGmailDraftToolUI = ({
args,
result,
}: ToolCallMessagePartProps<
{
draft_subject_or_id: string;
body: string;
@ -518,42 +521,39 @@ export const UpdateGmailDraftToolUI = makeAssistantToolUI<
bcc?: string;
},
UpdateGmailDraftResult
>({
toolName: "update_gmail_draft",
render: function UpdateGmailDraftUI({ args, result }) {
if (!result) return null;
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
})
);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
})
);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { ClockIcon, CornerDownLeftIcon, GlobeIcon, MapPinIcon, Pen, UsersIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
@ -606,7 +606,10 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const CreateCalendarEventToolUI = makeAssistantToolUI<
export const CreateCalendarEventToolUI = ({
args,
result,
}: ToolCallMessagePartProps<
{
summary: string;
start_datetime: string;
@ -616,39 +619,36 @@ export const CreateCalendarEventToolUI = makeAssistantToolUI<
attendees?: string[];
},
CreateCalendarEventResult
>({
toolName: "create_calendar_event",
render: function CreateCalendarEventUI({ args, result }) {
if (!result) return null;
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { CalendarIcon, ClockIcon, CornerDownLeftIcon, MapPinIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
@ -431,44 +431,43 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const DeleteCalendarEventToolUI = makeAssistantToolUI<
export const DeleteCalendarEventToolUI = ({
result,
}: ToolCallMessagePartProps<
{ event_title_or_id: string; delete_from_kb?: boolean },
DeleteCalendarEventResult
>({
toolName: "delete_calendar_event",
render: function DeleteCalendarEventUI({ result }) {
if (!result) return null;
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isWarningResult(result)) return <WarningCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isWarningResult(result)) return <WarningCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import {
ArrowRightIcon,
@ -653,7 +653,10 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const UpdateCalendarEventToolUI = makeAssistantToolUI<
export const UpdateCalendarEventToolUI = ({
args,
result,
}: ToolCallMessagePartProps<
{
event_ref: string;
new_summary?: string;
@ -664,40 +667,37 @@ export const UpdateCalendarEventToolUI = makeAssistantToolUI<
new_attendees?: string[];
},
UpdateCalendarEventResult
>({
toolName: "update_calendar_event",
render: function UpdateCalendarEventUI({ args, result }) {
if (!result) return null;
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, FileIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
@ -492,44 +492,38 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const CreateGoogleDriveFileToolUI = makeAssistantToolUI<
{ name: string; file_type: string; content?: string },
CreateGoogleDriveFileResult
>({
toolName: "create_google_drive_file",
render: function CreateGoogleDriveFileUI({ args, result }) {
if (!result) return null;
export const CreateGoogleDriveFileToolUI = ({ args, result }: ToolCallMessagePartProps<{ name: string; file_type: string; content?: string }, CreateGoogleDriveFileResult>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { CornerDownLeftIcon, InfoIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
@ -410,46 +410,40 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const DeleteGoogleDriveFileToolUI = makeAssistantToolUI<
{ file_name: string; delete_from_kb?: boolean },
DeleteGoogleDriveFileResult
>({
toolName: "delete_google_drive_file",
render: function DeleteGoogleDriveFileUI({ result }) {
if (!result) return null;
export const DeleteGoogleDriveFileToolUI = ({ result }: ToolCallMessagePartProps<{ file_name: string; delete_from_kb?: boolean }, DeleteGoogleDriveFileResult>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isInsufficientPermissionsResult(result))
return <InsufficientPermissionsCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isWarningResult(result)) return <WarningCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isWarningResult(result)) return <WarningCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
@ -536,7 +536,7 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const CreateJiraIssueToolUI = makeAssistantToolUI<
export const CreateJiraIssueToolUI = ({ args, result }: ToolCallMessagePartProps<
{
project_key: string;
summary: string;
@ -545,9 +545,7 @@ export const CreateJiraIssueToolUI = makeAssistantToolUI<
priority?: string;
},
CreateJiraIssueResult
>({
toolName: "create_jira_issue",
render: function CreateJiraIssueUI({ args, result }) {
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
@ -579,5 +577,4 @@ export const CreateJiraIssueToolUI = makeAssistantToolUI<
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { CornerDownLeftIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
@ -393,12 +393,10 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const DeleteJiraIssueToolUI = makeAssistantToolUI<
export const DeleteJiraIssueToolUI = ({ result }: ToolCallMessagePartProps<
{ issue_title_or_key: string; delete_from_kb?: boolean },
DeleteJiraIssueResult
>({
toolName: "delete_jira_issue",
render: function DeleteJiraIssueUI({ result }) {
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
@ -432,5 +430,4 @@ export const DeleteJiraIssueToolUI = makeAssistantToolUI<
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
@ -553,7 +553,7 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const UpdateJiraIssueToolUI = makeAssistantToolUI<
export const UpdateJiraIssueToolUI = ({ args, result }: ToolCallMessagePartProps<
{
issue_title_or_key: string;
new_summary?: string;
@ -561,9 +561,7 @@ export const UpdateJiraIssueToolUI = makeAssistantToolUI<
new_priority?: string;
},
UpdateJiraIssueResult
>({
toolName: "update_jira_issue",
render: function UpdateJiraIssueUI({ args, result }) {
>) => {
if (!result) return null;
if (isInterruptResult(result)) {
@ -596,5 +594,4 @@ export const UpdateJiraIssueToolUI = makeAssistantToolUI<
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
@ -605,40 +605,34 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const CreateLinearIssueToolUI = makeAssistantToolUI<
{ title: string; description?: string },
CreateLinearIssueResult
>({
toolName: "create_linear_issue",
render: function CreateLinearIssueUI({ args, result }) {
if (!result) return null;
export const CreateLinearIssueToolUI = ({ args, result }: ToolCallMessagePartProps<{ title: string; description?: string }, CreateLinearIssueResult>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { CornerDownLeftIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
@ -360,42 +360,36 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const DeleteLinearIssueToolUI = makeAssistantToolUI<
{ issue_ref: string; delete_from_kb?: boolean },
DeleteLinearIssueResult
>({
toolName: "delete_linear_issue",
render: function DeleteLinearIssueUI({ result }) {
if (!result) return null;
export const DeleteLinearIssueToolUI = ({ result }: ToolCallMessagePartProps<{ issue_ref: string; delete_from_kb?: boolean }, DeleteLinearIssueResult>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isWarningResult(result)) return <WarningCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isWarningResult(result)) return <WarningCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
@ -739,49 +739,43 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const UpdateLinearIssueToolUI = makeAssistantToolUI<
{
issue_ref: string;
new_title?: string;
new_description?: string;
new_state_name?: string;
new_assignee_email?: string;
new_priority?: number;
new_label_names?: string[];
},
UpdateLinearIssueResult
>({
toolName: "update_linear_issue",
render: function UpdateLinearIssueUI({ args, result }) {
if (!result) return null;
export const UpdateLinearIssueToolUI = ({ args, result }: ToolCallMessagePartProps<{
issue_ref: string;
new_title?: string;
new_description?: string;
new_state_name?: string;
new_assignee_email?: string;
new_priority?: number;
new_label_names?: string[];
}, UpdateLinearIssueResult>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
window.dispatchEvent(
new CustomEvent("hitl-decision", { detail: { decisions: [decision] } })
);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
if (isNotFoundResult(result)) return <NotFoundCard result={result} />;
if (isAuthErrorResult(result)) return <AuthErrorCard result={result} />;
if (isErrorResult(result)) return <ErrorCard result={result} />;
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { AlertCircleIcon, ExternalLinkIcon, LinkIcon } from "lucide-react";
import { z } from "zod";
import {
@ -111,9 +111,7 @@ function ParsedMediaCard({ result }: { result: unknown }) {
* - Domain name
* - Clickable link to open in new tab
*/
export const LinkPreviewToolUI = makeAssistantToolUI<LinkPreviewArgs, LinkPreviewResult>({
toolName: "link_preview",
render: function LinkPreviewUI({ args, result, status }) {
export const LinkPreviewToolUI = ({ args, result, status }: ToolCallMessagePartProps<LinkPreviewArgs, LinkPreviewResult>) => {
const url = args.url || "Unknown URL";
// Loading state - tool is still running
@ -162,8 +160,7 @@ export const LinkPreviewToolUI = makeAssistantToolUI<LinkPreviewArgs, LinkPrevie
</MediaCardErrorBoundary>
</div>
);
},
});
};
// ============================================================================
// Multi Link Preview Schemas
@ -195,12 +192,7 @@ const MultiLinkPreviewResultSchema = z.object({
type MultiLinkPreviewArgs = z.infer<typeof MultiLinkPreviewArgsSchema>;
type MultiLinkPreviewResult = z.infer<typeof MultiLinkPreviewResultSchema>;
export const MultiLinkPreviewToolUI = makeAssistantToolUI<
MultiLinkPreviewArgs,
MultiLinkPreviewResult
>({
toolName: "multi_link_preview",
render: function MultiLinkPreviewUI({ args, result, status }) {
export const MultiLinkPreviewToolUI = ({ args, result, status }: ToolCallMessagePartProps<MultiLinkPreviewArgs, MultiLinkPreviewResult>) => {
const urls = args.urls || [];
// Loading state
@ -244,8 +236,7 @@ export const MultiLinkPreviewToolUI = makeAssistantToolUI<
))}
</div>
);
},
});
};
export {
LinkPreviewArgsSchema,

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
@ -445,46 +445,40 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const CreateNotionPageToolUI = makeAssistantToolUI<
{ title: string; content: string },
CreateNotionPageResult
>({
toolName: "create_notion_page",
render: function CreateNotionPageUI({ args, result }) {
if (!result) return null;
export const CreateNotionPageToolUI = ({ args, result }: ToolCallMessagePartProps<{ title: string; content: string }, CreateNotionPageResult>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (isAuthErrorResult(result)) {
return <AuthErrorCard result={result} />;
}
if (isAuthErrorResult(result)) {
return <AuthErrorCard result={result} />;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isErrorResult(result)) {
return <ErrorCard result={result} />;
}
if (isErrorResult(result)) {
return <ErrorCard result={result} />;
}
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { CornerDownLeftIcon } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
@ -372,53 +372,47 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const DeleteNotionPageToolUI = makeAssistantToolUI<
{ page_title: string; delete_from_kb?: boolean },
DeleteNotionPageResult
>({
toolName: "delete_notion_page",
render: function DeleteNotionPageUI({ result }) {
if (!result) return null;
export const DeleteNotionPageToolUI = ({ result }: ToolCallMessagePartProps<{ page_title: string; delete_from_kb?: boolean }, DeleteNotionPageResult>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isInfoResult(result)) {
return <InfoCard result={result} />;
}
if (isInfoResult(result)) {
return <InfoCard result={result} />;
}
if (isWarningResult(result)) {
return <WarningCard result={result} />;
}
if (isWarningResult(result)) {
return <WarningCard result={result} />;
}
if (isAuthErrorResult(result)) {
return <AuthErrorCard result={result} />;
}
if (isAuthErrorResult(result)) {
return <AuthErrorCard result={result} />;
}
if (isErrorResult(result)) {
return <ErrorCard result={result} />;
}
if (isErrorResult(result)) {
return <ErrorCard result={result} />;
}
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { useSetAtom } from "jotai";
import { CornerDownLeftIcon, Pen } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
@ -395,50 +395,44 @@ function SuccessCard({ result }: { result: SuccessResult }) {
);
}
export const UpdateNotionPageToolUI = makeAssistantToolUI<
{ page_title: string; content: string },
UpdateNotionPageResult
>({
toolName: "update_notion_page",
render: function UpdateNotionPageUI({ args, result }) {
if (!result) return null;
export const UpdateNotionPageToolUI = ({ args, result }: ToolCallMessagePartProps<{ page_title: string; content: string }, UpdateNotionPageResult>) => {
if (!result) return null;
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (isInterruptResult(result)) {
return (
<ApprovalCard
args={args}
interruptData={result}
onDecision={(decision) => {
const event = new CustomEvent("hitl-decision", {
detail: { decisions: [decision] },
});
window.dispatchEvent(event);
}}
/>
);
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (
typeof result === "object" &&
result !== null &&
"status" in result &&
(result as { status: string }).status === "rejected"
) {
return null;
}
if (isInfoResult(result)) {
return <InfoCard result={result} />;
}
if (isInfoResult(result)) {
return <InfoCard result={result} />;
}
if (isAuthErrorResult(result)) {
return <AuthErrorCard result={result} />;
}
if (isAuthErrorResult(result)) {
return <AuthErrorCard result={result} />;
}
if (isErrorResult(result)) {
return <ErrorCard result={result} />;
}
if (isErrorResult(result)) {
return <ErrorCard result={result} />;
}
return <SuccessCard result={result as SuccessResult} />;
},
});
return <SuccessCard result={result as SuccessResult} />;
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import {
AlertCircleIcon,
CheckCircle2Icon,
@ -380,9 +380,7 @@ function ExecuteCompleted({
// Tool UI
// ============================================================================
export const SandboxExecuteToolUI = makeAssistantToolUI<ExecuteArgs, ExecuteResult>({
toolName: "execute",
render: function SandboxExecuteUI({ args, result, status }) {
export const SandboxExecuteToolUI = ({ args, result, status }: ToolCallMessagePartProps<ExecuteArgs, ExecuteResult>) => {
const command = args.command || "…";
if (status.type === "running" || status.type === "requires-action") {
@ -414,7 +412,6 @@ export const SandboxExecuteToolUI = makeAssistantToolUI<ExecuteArgs, ExecuteResu
const parsed = parseExecuteResult(result);
const threadId = result.thread_id || null;
return <ExecuteCompleted command={command} parsed={parsed} threadId={threadId} />;
},
});
};
export { ExecuteArgsSchema, ExecuteResultSchema, type ExecuteArgs, type ExecuteResult };

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { AlertCircleIcon, FileTextIcon } from "lucide-react";
import { z } from "zod";
import {
@ -104,9 +104,7 @@ function ParsedArticle({ result }: { result: unknown }) {
* - Word count
* - Link to original source
*/
export const ScrapeWebpageToolUI = makeAssistantToolUI<ScrapeWebpageArgs, ScrapeWebpageResult>({
toolName: "scrape_webpage",
render: function ScrapeWebpageUI({ args, result, status }) {
export const ScrapeWebpageToolUI = ({ args, result, status }: ToolCallMessagePartProps<ScrapeWebpageArgs, ScrapeWebpageResult>) => {
const url = args.url || "Unknown URL";
// Loading state - tool is still running
@ -155,8 +153,7 @@ export const ScrapeWebpageToolUI = makeAssistantToolUI<ScrapeWebpageArgs, Scrape
</ArticleErrorBoundary>
</div>
);
},
});
};
export {
ScrapeWebpageArgsSchema,

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import { BrainIcon, CheckIcon, Loader2Icon, SearchIcon, XIcon } from "lucide-react";
import { z } from "zod";
@ -80,9 +80,7 @@ function CategoryBadge({ category }: { category: string }) {
// Save Memory Tool UI
// ============================================================================
export const SaveMemoryToolUI = makeAssistantToolUI<SaveMemoryArgs, SaveMemoryResult>({
toolName: "save_memory",
render: function SaveMemoryUI({ args, result, status }) {
export const SaveMemoryToolUI = ({ args, result, status }: ToolCallMessagePartProps<SaveMemoryArgs, SaveMemoryResult>) => {
const isRunning = status.type === "running" || status.type === "requires-action";
const isComplete = status.type === "complete";
const isError = result?.status === "error";
@ -159,16 +157,13 @@ export const SaveMemoryToolUI = makeAssistantToolUI<SaveMemoryArgs, SaveMemoryRe
}
return null;
},
});
};
// ============================================================================
// Recall Memory Tool UI
// ============================================================================
export const RecallMemoryToolUI = makeAssistantToolUI<RecallMemoryArgs, RecallMemoryResult>({
toolName: "recall_memory",
render: function RecallMemoryUI({ args, result, status }) {
export const RecallMemoryToolUI = ({ args, result, status }: ToolCallMessagePartProps<RecallMemoryArgs, RecallMemoryResult>) => {
const isRunning = status.type === "running" || status.type === "requires-action";
const isComplete = status.type === "complete";
const isError = result?.status === "error";
@ -263,8 +258,7 @@ export const RecallMemoryToolUI = makeAssistantToolUI<RecallMemoryArgs, RecallMe
}
return null;
},
});
};
// ============================================================================
// Exports

View file

@ -1,7 +1,7 @@
"use client";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { makeAssistantToolUI } from "@assistant-ui/react";
import type { ToolCallMessagePartProps } from "@assistant-ui/react";
import {
AlertCircleIcon,
Download,
@ -617,12 +617,10 @@ function StatusPoller({
return <ErrorState title={title} error="Unexpected state" />;
}
export const GenerateVideoPresentationToolUI = makeAssistantToolUI<
export const GenerateVideoPresentationToolUI = ({ args, result, status }: ToolCallMessagePartProps<
GenerateVideoPresentationArgs,
GenerateVideoPresentationResult
>({
toolName: "generate_video_presentation",
render: function GenerateVideoPresentationUI({ args, result, status }) {
>) => {
const params = useParams();
const pathname = usePathname();
const isPublicRoute = pathname?.startsWith("/public/");
@ -705,5 +703,4 @@ export const GenerateVideoPresentationToolUI = makeAssistantToolUI<
}
return <ErrorState title={title} error="Missing presentation ID" />;
},
});
};

View file

@ -1,6 +1,6 @@
"use client";
import { makeAssistantToolUI, useAssistantState } from "@assistant-ui/react";
import { type ToolCallMessagePartProps, useAuiState } from "@assistant-ui/react";
import { useAtomValue, useSetAtom } from "jotai";
import { useEffect, useMemo } from "react";
import { z } from "zod";
@ -63,96 +63,98 @@ function WriteTodosLoading() {
* only the FIRST component renders. Subsequent updates just update the
* shared state, and the first component reads from it.
*/
export const WriteTodosToolUI = makeAssistantToolUI<WriteTodosData, WriteTodosData>({
toolName: "write_todos",
render: function WriteTodosUI({ args, result, status, toolCallId }) {
const updatePlanState = useSetAtom(updatePlanStateAtom);
const planStates = useAtomValue(planStatesAtom);
export const WriteTodosToolUI = ({
args,
result,
status,
toolCallId,
}: ToolCallMessagePartProps<WriteTodosData, WriteTodosData>) => {
const updatePlanState = useSetAtom(updatePlanStateAtom);
const planStates = useAtomValue(planStatesAtom);
// Check if the THREAD is running
const isThreadRunning = useAssistantState(({ thread }) => thread.isRunning);
// Check if the THREAD is running
const isThreadRunning = useAuiState(({ thread }) => thread.isRunning);
// Use result if available, otherwise args (for streaming)
const data = result || args;
const hasTodos = data?.todos && data.todos.length > 0;
// Use result if available, otherwise args (for streaming)
const data = result || args;
const hasTodos = data?.todos && data.todos.length > 0;
// Fixed title for all plans in conversation
const planTitle = "Plan";
// Fixed title for all plans in conversation
const planTitle = "Plan";
// SYNCHRONOUS ownership check
const isOwner = useMemo(() => {
return registerPlanOwner(planTitle, toolCallId);
}, [planTitle, toolCallId]);
// SYNCHRONOUS ownership check
const isOwner = useMemo(() => {
return registerPlanOwner(planTitle, toolCallId);
}, [planTitle, toolCallId]);
// Get canonical title
const canonicalTitle = useMemo(() => getCanonicalPlanTitle(planTitle), [planTitle]);
// Get canonical title
const canonicalTitle = useMemo(() => getCanonicalPlanTitle(planTitle), [planTitle]);
// Register/update the plan state
useEffect(() => {
if (hasTodos) {
const normalizedPlan = parseSerializablePlan({ todos: data.todos });
updatePlanState({
id: normalizedPlan.id,
title: canonicalTitle,
todos: normalizedPlan.todos,
toolCallId,
});
}
}, [data, hasTodos, canonicalTitle, updatePlanState, toolCallId]);
// Get the current plan state
const currentPlanState = planStates.get(canonicalTitle);
// If we're NOT the owner, render nothing
if (!isOwner) {
return null;
// Register/update the plan state
useEffect(() => {
if (hasTodos) {
const normalizedPlan = parseSerializablePlan({ todos: data.todos });
updatePlanState({
id: normalizedPlan.id,
title: canonicalTitle,
todos: normalizedPlan.todos,
toolCallId,
});
}
}, [data, hasTodos, canonicalTitle, updatePlanState, toolCallId]);
// Loading state
if (status.type === "running" || status.type === "requires-action") {
if (hasTodos) {
const plan = parseSerializablePlan({ todos: data.todos });
return (
<div className="my-4">
<PlanErrorBoundary>
<Plan {...plan} showProgress={true} isStreaming={isThreadRunning} />
</PlanErrorBoundary>
</div>
);
}
return <WriteTodosLoading />;
// Get the current plan state
const currentPlanState = planStates.get(canonicalTitle);
// If we're NOT the owner, render nothing
if (!isOwner) {
return null;
}
// Loading state
if (status.type === "running" || status.type === "requires-action") {
if (hasTodos) {
const plan = parseSerializablePlan({ todos: data.todos });
return (
<div className="my-4">
<PlanErrorBoundary>
<Plan {...plan} showProgress={true} isStreaming={isThreadRunning} />
</PlanErrorBoundary>
</div>
);
}
return <WriteTodosLoading />;
}
// Incomplete/cancelled state
if (status.type === "incomplete") {
if (currentPlanState || hasTodos) {
const plan = currentPlanState || parseSerializablePlan({ todos: data?.todos || [] });
return (
<div className="my-4">
<PlanErrorBoundary>
<Plan {...plan} showProgress={true} isStreaming={isThreadRunning} />
</PlanErrorBoundary>
</div>
);
}
return null;
// Incomplete/cancelled state
if (status.type === "incomplete") {
if (currentPlanState || hasTodos) {
const plan = currentPlanState || parseSerializablePlan({ todos: data?.todos || [] });
return (
<div className="my-4">
<PlanErrorBoundary>
<Plan {...plan} showProgress={true} isStreaming={isThreadRunning} />
</PlanErrorBoundary>
</div>
);
}
return null;
}
// Success - render the plan
const planToRender =
currentPlanState || (hasTodos ? parseSerializablePlan({ todos: data.todos }) : null);
if (!planToRender) {
return <WriteTodosLoading />;
}
// Success - render the plan
const planToRender =
currentPlanState || (hasTodos ? parseSerializablePlan({ todos: data.todos }) : null);
if (!planToRender) {
return <WriteTodosLoading />;
}
return (
<div className="my-4">
<PlanErrorBoundary>
<Plan {...planToRender} showProgress={true} isStreaming={isThreadRunning} />
</PlanErrorBoundary>
</div>
);
},
});
return (
<div className="my-4">
<PlanErrorBoundary>
<Plan {...planToRender} showProgress={true} isStreaming={isThreadRunning} />
</PlanErrorBoundary>
</div>
);
};
export { WriteTodosSchema, type WriteTodosData };