mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-03 04:42:39 +02:00
refactor: update dependencies and streamline assistant-ui package usages
This commit is contained in:
parent
fed3a3b436
commit
b8f3f41326
40 changed files with 886 additions and 1110 deletions
|
|
@ -1,10 +1,9 @@
|
|||
import {
|
||||
ActionBarPrimitive,
|
||||
AssistantIf,
|
||||
AuiIf,
|
||||
ErrorPrimitive,
|
||||
MessagePrimitive,
|
||||
useAssistantState,
|
||||
useMessage,
|
||||
useAuiState,
|
||||
} from "@assistant-ui/react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { CheckIcon, CopyIcon, DownloadIcon, MessageSquare, RefreshCwIcon } from "lucide-react";
|
||||
|
|
@ -21,6 +20,22 @@ import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
|
|||
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
||||
import { CommentPanelContainer } from "@/components/chat-comments/comment-panel-container/comment-panel-container";
|
||||
import { CommentSheet } from "@/components/chat-comments/comment-sheet/comment-sheet";
|
||||
import { CreateConfluencePageToolUI, DeleteConfluencePageToolUI, UpdateConfluencePageToolUI } from "@/components/tool-ui/confluence";
|
||||
import { DeepAgentThinkingToolUI } from "@/components/tool-ui/deepagent-thinking";
|
||||
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
|
||||
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
|
||||
import { GenerateReportToolUI } from "@/components/tool-ui/generate-report";
|
||||
import { GenerateVideoPresentationToolUI } from "@/components/tool-ui/video-presentation";
|
||||
import { CreateGmailDraftToolUI, SendGmailEmailToolUI, TrashGmailEmailToolUI, UpdateGmailDraftToolUI } from "@/components/tool-ui/gmail";
|
||||
import { CreateCalendarEventToolUI, DeleteCalendarEventToolUI, UpdateCalendarEventToolUI } from "@/components/tool-ui/google-calendar";
|
||||
import { CreateGoogleDriveFileToolUI, DeleteGoogleDriveFileToolUI } from "@/components/tool-ui/google-drive";
|
||||
import { CreateJiraIssueToolUI, DeleteJiraIssueToolUI, UpdateJiraIssueToolUI } from "@/components/tool-ui/jira";
|
||||
import { CreateLinearIssueToolUI, DeleteLinearIssueToolUI, UpdateLinearIssueToolUI } from "@/components/tool-ui/linear";
|
||||
import { LinkPreviewToolUI, MultiLinkPreviewToolUI } from "@/components/tool-ui/link-preview";
|
||||
import { CreateNotionPageToolUI, DeleteNotionPageToolUI, UpdateNotionPageToolUI } from "@/components/tool-ui/notion";
|
||||
import { SandboxExecuteToolUI } from "@/components/tool-ui/sandbox-execute";
|
||||
import { ScrapeWebpageToolUI } from "@/components/tool-ui/scrape-webpage";
|
||||
import { RecallMemoryToolUI, SaveMemoryToolUI } from "@/components/tool-ui/user-memory";
|
||||
import { useComments } from "@/hooks/use-comments";
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
|
@ -42,13 +57,13 @@ const ThinkingStepsPart: FC = () => {
|
|||
const thinkingStepsMap = useContext(ThinkingStepsContext);
|
||||
|
||||
// Get the current message ID to look up thinking steps
|
||||
const messageId = useAssistantState(({ message }) => message?.id);
|
||||
const messageId = useAuiState(({ message }) => message?.id);
|
||||
const thinkingSteps = thinkingStepsMap.get(messageId) || [];
|
||||
|
||||
// Check if this specific message is currently streaming
|
||||
// A message is streaming if: thread is running AND this is the last assistant message
|
||||
const isThreadRunning = useAssistantState(({ thread }) => thread.isRunning);
|
||||
const isLastMessage = useAssistantState(({ message }) => message?.isLast ?? false);
|
||||
const isThreadRunning = useAuiState(({ thread }) => thread.isRunning);
|
||||
const isLastMessage = useAuiState(({ message }) => message?.isLast ?? false);
|
||||
const isMessageStreaming = isThreadRunning && isLastMessage;
|
||||
|
||||
if (thinkingSteps.length === 0) return null;
|
||||
|
|
@ -70,7 +85,43 @@ const AssistantMessageInner: FC = () => {
|
|||
<MessagePrimitive.Parts
|
||||
components={{
|
||||
Text: MarkdownText,
|
||||
tools: { Fallback: ToolFallback },
|
||||
tools: {
|
||||
by_name: {
|
||||
deepagent_thinking: DeepAgentThinkingToolUI,
|
||||
generate_report: GenerateReportToolUI,
|
||||
generate_podcast: GeneratePodcastToolUI,
|
||||
generate_video_presentation: GenerateVideoPresentationToolUI,
|
||||
link_preview: LinkPreviewToolUI,
|
||||
multi_link_preview: MultiLinkPreviewToolUI,
|
||||
display_image: DisplayImageToolUI,
|
||||
scrape_webpage: ScrapeWebpageToolUI,
|
||||
save_memory: SaveMemoryToolUI,
|
||||
recall_memory: RecallMemoryToolUI,
|
||||
execute: SandboxExecuteToolUI,
|
||||
create_notion_page: CreateNotionPageToolUI,
|
||||
update_notion_page: UpdateNotionPageToolUI,
|
||||
delete_notion_page: DeleteNotionPageToolUI,
|
||||
create_linear_issue: CreateLinearIssueToolUI,
|
||||
update_linear_issue: UpdateLinearIssueToolUI,
|
||||
delete_linear_issue: DeleteLinearIssueToolUI,
|
||||
create_google_drive_file: CreateGoogleDriveFileToolUI,
|
||||
delete_google_drive_file: DeleteGoogleDriveFileToolUI,
|
||||
create_calendar_event: CreateCalendarEventToolUI,
|
||||
update_calendar_event: UpdateCalendarEventToolUI,
|
||||
delete_calendar_event: DeleteCalendarEventToolUI,
|
||||
create_gmail_draft: CreateGmailDraftToolUI,
|
||||
update_gmail_draft: UpdateGmailDraftToolUI,
|
||||
send_gmail_email: SendGmailEmailToolUI,
|
||||
trash_gmail_email: TrashGmailEmailToolUI,
|
||||
create_jira_issue: CreateJiraIssueToolUI,
|
||||
update_jira_issue: UpdateJiraIssueToolUI,
|
||||
delete_jira_issue: DeleteJiraIssueToolUI,
|
||||
create_confluence_page: CreateConfluencePageToolUI,
|
||||
update_confluence_page: UpdateConfluencePageToolUI,
|
||||
delete_confluence_page: DeleteConfluencePageToolUI,
|
||||
},
|
||||
Fallback: ToolFallback,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<MessageError />
|
||||
|
|
@ -95,7 +146,7 @@ export const AssistantMessage: FC = () => {
|
|||
const messageRef = useRef<HTMLDivElement>(null);
|
||||
const commentPanelRef = useRef<HTMLDivElement>(null);
|
||||
const commentTriggerRef = useRef<HTMLButtonElement>(null);
|
||||
const messageId = useAssistantState(({ message }) => message?.id);
|
||||
const messageId = useAuiState(({ message }) => message?.id);
|
||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
||||
const dbMessageId = parseMessageId(messageId);
|
||||
const commentsEnabled = useAtomValue(commentsEnabledAtom);
|
||||
|
|
@ -104,8 +155,8 @@ export const AssistantMessage: FC = () => {
|
|||
const isMediumScreen = useMediaQuery("(min-width: 768px) and (max-width: 1023px)");
|
||||
const isDesktop = useMediaQuery("(min-width: 1024px)");
|
||||
|
||||
const isThreadRunning = useAssistantState(({ thread }) => thread.isRunning);
|
||||
const isLastMessage = useAssistantState(({ message }) => message?.isLast ?? false);
|
||||
const isThreadRunning = useAuiState(({ thread }) => thread.isRunning);
|
||||
const isLastMessage = useAuiState(({ message }) => message?.isLast ?? false);
|
||||
const isMessageStreaming = isThreadRunning && isLastMessage;
|
||||
|
||||
const { data: commentsData, isSuccess: commentsLoaded } = useComments({
|
||||
|
|
@ -227,38 +278,38 @@ export const AssistantMessage: FC = () => {
|
|||
};
|
||||
|
||||
const AssistantActionBar: FC = () => {
|
||||
const { isLast } = useMessage();
|
||||
const isLast = useAuiState((s) => s.message.isLast);
|
||||
|
||||
return (
|
||||
<ActionBarPrimitive.Root
|
||||
<ActionBarPrimitive.Root
|
||||
hideWhenRunning
|
||||
autohide="not-last"
|
||||
autohideFloat="single-branch"
|
||||
className="aui-assistant-action-bar-root -ml-1 col-start-3 row-start-2 flex gap-1 text-muted-foreground md:data-floating:absolute md:data-floating:rounded-md md:data-floating:p-1 [&>button]:opacity-100 md:[&>button]:opacity-[var(--aui-button-opacity,1)]"
|
||||
>
|
||||
<ActionBarPrimitive.Copy asChild>
|
||||
<ActionBarPrimitive.Copy asChild>
|
||||
<TooltipIconButton tooltip="Copy">
|
||||
<AssistantIf condition={({ message }) => message.isCopied}>
|
||||
<AuiIf condition={({ message }) => message.isCopied}>
|
||||
<CheckIcon />
|
||||
</AssistantIf>
|
||||
<AssistantIf condition={({ message }) => !message.isCopied}>
|
||||
</AuiIf>
|
||||
<AuiIf condition={({ message }) => !message.isCopied}>
|
||||
<CopyIcon />
|
||||
</AssistantIf>
|
||||
</AuiIf>
|
||||
</TooltipIconButton>
|
||||
</ActionBarPrimitive.Copy>
|
||||
<ActionBarPrimitive.ExportMarkdown asChild>
|
||||
<ActionBarPrimitive.ExportMarkdown asChild>
|
||||
<TooltipIconButton tooltip="Download">
|
||||
<DownloadIcon />
|
||||
</TooltipIconButton>
|
||||
</ActionBarPrimitive.ExportMarkdown>
|
||||
{/* Only allow regenerating the last assistant message */}
|
||||
{isLast && (
|
||||
{/* Only allow regenerating the last assistant message */}
|
||||
{isLast && (
|
||||
<ActionBarPrimitive.Reload asChild>
|
||||
<TooltipIconButton tooltip="Refresh">
|
||||
<RefreshCwIcon />
|
||||
</TooltipIconButton>
|
||||
</ActionBarPrimitive.Reload>
|
||||
)}
|
||||
</ActionBarPrimitive.Root>
|
||||
);
|
||||
</ActionBarPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { useAssistantState, useThreadViewport } from "@assistant-ui/react";
|
||||
import { ChevronRightIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import { createContext, useCallback, useEffect, useState } from "react";
|
||||
import { ChainOfThoughtItem } from "@/components/prompt-kit/chain-of-thought";
|
||||
import { TextShimmerLoader } from "@/components/prompt-kit/loader";
|
||||
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
|
||||
|
|
@ -154,52 +153,3 @@ export const ThinkingStepsDisplay: FC<{ steps: ThinkingStep[]; isThreadRunning?:
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that handles auto-scroll when thinking steps update.
|
||||
* Uses useThreadViewport to scroll to bottom when thinking steps change,
|
||||
* ensuring the user always sees the latest content during streaming.
|
||||
*/
|
||||
export const ThinkingStepsScrollHandler: FC = () => {
|
||||
const thinkingStepsMap = useContext(ThinkingStepsContext);
|
||||
const viewport = useThreadViewport();
|
||||
const isRunning = useAssistantState(({ thread }) => thread.isRunning);
|
||||
// Track the serialized state to detect any changes
|
||||
const prevStateRef = useRef<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
// Only act during streaming
|
||||
if (!isRunning) {
|
||||
prevStateRef.current = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Serialize the thinking steps state to detect any changes
|
||||
// This catches new steps, status changes, and item additions
|
||||
let stateString = "";
|
||||
thinkingStepsMap.forEach((steps, msgId) => {
|
||||
steps.forEach((step) => {
|
||||
stateString += `${msgId}:${step.id}:${step.status}:${step.items?.length || 0};`;
|
||||
});
|
||||
});
|
||||
|
||||
// If state changed at all during streaming, scroll
|
||||
if (stateString !== prevStateRef.current && stateString !== "") {
|
||||
prevStateRef.current = stateString;
|
||||
|
||||
// Multiple attempts to ensure scroll happens after DOM updates
|
||||
const scrollAttempt = () => {
|
||||
try {
|
||||
viewport.scrollToBottom();
|
||||
} catch {
|
||||
// Ignore errors - viewport might not be ready
|
||||
}
|
||||
};
|
||||
|
||||
// Delayed attempts to handle async DOM updates
|
||||
requestAnimationFrame(scrollAttempt);
|
||||
setTimeout(scrollAttempt, 100);
|
||||
}
|
||||
}, [thinkingStepsMap, viewport, isRunning]);
|
||||
|
||||
return null; // This component doesn't render anything
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import {
|
||||
ActionBarPrimitive,
|
||||
AssistantIf,
|
||||
AuiIf,
|
||||
BranchPickerPrimitive,
|
||||
ComposerPrimitive,
|
||||
ErrorPrimitive,
|
||||
MessagePrimitive,
|
||||
ThreadPrimitive,
|
||||
useAssistantState,
|
||||
useComposerRuntime,
|
||||
useAui,
|
||||
useAuiState,
|
||||
} from "@assistant-ui/react";
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import {
|
||||
|
|
@ -125,19 +125,19 @@ export const Thread: FC<ThreadProps> = ({ messageThinkingSteps = new Map() }) =>
|
|||
|
||||
const ThreadContent: FC = () => {
|
||||
return (
|
||||
<ThreadPrimitive.Root
|
||||
<ThreadPrimitive.Root
|
||||
className="aui-root aui-thread-root @container flex h-full min-h-0 flex-col bg-main-panel"
|
||||
style={{
|
||||
["--thread-max-width" as string]: "44rem",
|
||||
}}
|
||||
>
|
||||
<ThreadPrimitive.Viewport
|
||||
<ThreadPrimitive.Viewport
|
||||
turnAnchor="top"
|
||||
className="aui-thread-viewport relative flex flex-1 min-h-0 flex-col overflow-y-auto px-4 pt-4"
|
||||
>
|
||||
<AssistantIf condition={({ thread }) => thread.isEmpty}>
|
||||
<AuiIf condition={({ thread }) => thread.isEmpty}>
|
||||
<ThreadWelcome />
|
||||
</AssistantIf>
|
||||
</AuiIf>
|
||||
|
||||
<ThreadPrimitive.Messages
|
||||
components={{
|
||||
|
|
@ -152,15 +152,15 @@ const ThreadContent: FC = () => {
|
|||
style={{ paddingBottom: "max(1rem, env(safe-area-inset-bottom))" }}
|
||||
>
|
||||
<ThreadScrollToBottom />
|
||||
<AssistantIf condition={({ thread }) => !thread.isEmpty}>
|
||||
<AuiIf condition={({ thread }) => !thread.isEmpty}>
|
||||
<div className="fade-in slide-in-from-bottom-4 animate-in duration-500 ease-out fill-mode-both">
|
||||
<Composer />
|
||||
</div>
|
||||
</AssistantIf>
|
||||
</AuiIf>
|
||||
</ThreadPrimitive.ViewportFooter>
|
||||
</ThreadPrimitive.Viewport>
|
||||
</ThreadPrimitive.Root>
|
||||
);
|
||||
</ThreadPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
||||
const ThreadScrollToBottom: FC = () => {
|
||||
|
|
@ -327,11 +327,11 @@ const Composer: FC = () => {
|
|||
const editorContainerRef = useRef<HTMLDivElement>(null);
|
||||
const documentPickerRef = useRef<DocumentMentionPickerRef>(null);
|
||||
const { search_space_id, chat_id } = useParams();
|
||||
const composerRuntime = useComposerRuntime();
|
||||
const aui = useAui();
|
||||
const hasAutoFocusedRef = useRef(false);
|
||||
|
||||
const isThreadEmpty = useAssistantState(({ thread }) => thread.isEmpty);
|
||||
const isThreadRunning = useAssistantState(({ thread }) => thread.isRunning);
|
||||
const isThreadEmpty = useAuiState(({ thread }) => thread.isEmpty);
|
||||
const isThreadRunning = useAuiState(({ thread }) => thread.isRunning);
|
||||
|
||||
// Cycling placeholder state - only cycles in new chats
|
||||
const [placeholderIndex, setPlaceholderIndex] = useState(0);
|
||||
|
|
@ -378,7 +378,7 @@ const Composer: FC = () => {
|
|||
// hooks never fire their own network requests (eliminates N+1 API calls).
|
||||
// Return a primitive string from the selector so useSyncExternalStore can
|
||||
// compare snapshots by value and avoid infinite re-render loops.
|
||||
const assistantIdsKey = useAssistantState(({ thread }) =>
|
||||
const assistantIdsKey = useAuiState(({ thread }) =>
|
||||
thread.messages
|
||||
.filter((m) => m.role === "assistant" && m.id?.startsWith("msg-"))
|
||||
.map((m) => m.id?.replace("msg-", ""))
|
||||
|
|
@ -414,9 +414,9 @@ const Composer: FC = () => {
|
|||
// Sync editor text with assistant-ui composer runtime
|
||||
const handleEditorChange = useCallback(
|
||||
(text: string) => {
|
||||
composerRuntime.setText(text);
|
||||
aui.composer().setText(text);
|
||||
},
|
||||
[composerRuntime]
|
||||
[aui]
|
||||
);
|
||||
|
||||
// Open document picker when @ mention is triggered
|
||||
|
|
@ -469,7 +469,7 @@ const Composer: FC = () => {
|
|||
return;
|
||||
}
|
||||
if (!showDocumentPopover) {
|
||||
composerRuntime.send();
|
||||
aui.composer().send();
|
||||
editorRef.current?.clear();
|
||||
setMentionedDocuments([]);
|
||||
setSidebarDocs([]);
|
||||
|
|
@ -478,7 +478,7 @@ const Composer: FC = () => {
|
|||
showDocumentPopover,
|
||||
isThreadRunning,
|
||||
isBlockedByOtherUser,
|
||||
composerRuntime,
|
||||
aui,
|
||||
setMentionedDocuments,
|
||||
setSidebarDocs,
|
||||
]);
|
||||
|
|
@ -591,7 +591,7 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 2;
|
||||
setToolsScrollPos(atTop ? "top" : atBottom ? "bottom" : "middle");
|
||||
}, []);
|
||||
const isComposerTextEmpty = useAssistantState(({ composer }) => {
|
||||
const isComposerTextEmpty = useAuiState(({ composer }) => {
|
||||
const text = composer.text?.trim() || "";
|
||||
return text.length === 0;
|
||||
});
|
||||
|
|
@ -702,8 +702,8 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
const isSendDisabled = isComposerEmpty || !hasModelConfigured || isBlockedByOtherUser;
|
||||
|
||||
return (
|
||||
<div className="aui-composer-action-wrapper relative mx-3 mb-2 flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="aui-composer-action-wrapper relative mx-3 mb-2 flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
{!isDesktop ? (
|
||||
<>
|
||||
<DropdownMenu>
|
||||
|
|
@ -1007,16 +1007,14 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!hasModelConfigured && (
|
||||
{!hasModelConfigured && (
|
||||
<div className="flex items-center gap-1.5 text-amber-600 dark:text-amber-400 text-xs">
|
||||
<AlertCircle className="size-3" />
|
||||
<span>Select a model</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<AssistantIf condition={({ thread }) => !thread.isRunning}>
|
||||
<div className="flex items-center gap-2">
|
||||
<AuiIf condition={({ thread }) => !thread.isRunning}>
|
||||
<ComposerPrimitive.Send asChild disabled={isSendDisabled}>
|
||||
<TooltipIconButton
|
||||
tooltip={
|
||||
|
|
@ -1042,9 +1040,9 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
<ArrowUpIcon className="aui-composer-send-icon size-4" />
|
||||
</TooltipIconButton>
|
||||
</ComposerPrimitive.Send>
|
||||
</AssistantIf>
|
||||
</AuiIf>
|
||||
|
||||
<AssistantIf condition={({ thread }) => thread.isRunning}>
|
||||
<AuiIf condition={({ thread }) => thread.isRunning}>
|
||||
<ComposerPrimitive.Cancel asChild>
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -1056,10 +1054,10 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
<SquareIcon className="aui-composer-cancel-icon size-3 fill-current" />
|
||||
</Button>
|
||||
</ComposerPrimitive.Cancel>
|
||||
</AssistantIf>
|
||||
</AuiIf>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** Convert snake_case tool names to human-readable labels */
|
||||
|
|
@ -1151,13 +1149,13 @@ const ThinkingStepsPart: FC = () => {
|
|||
const thinkingStepsMap = useContext(ThinkingStepsContext);
|
||||
|
||||
// Get the current message ID to look up thinking steps
|
||||
const messageId = useAssistantState(({ message }) => message?.id);
|
||||
const messageId = useAuiState(({ message }) => message?.id);
|
||||
const thinkingSteps = thinkingStepsMap.get(messageId) || [];
|
||||
|
||||
// Check if this specific message is currently streaming
|
||||
// A message is streaming if: thread is running AND this is the last assistant message
|
||||
const isThreadRunning = useAssistantState(({ thread }) => thread.isRunning);
|
||||
const isLastMessage = useAssistantState(({ message }) => message?.isLast ?? false);
|
||||
const isThreadRunning = useAuiState(({ thread }) => thread.isRunning);
|
||||
const isLastMessage = useAuiState(({ message }) => message?.isLast ?? false);
|
||||
const isMessageStreaming = isThreadRunning && isLastMessage;
|
||||
|
||||
if (thinkingSteps.length === 0) return null;
|
||||
|
|
@ -1195,34 +1193,34 @@ const AssistantMessageInner: FC = () => {
|
|||
|
||||
const AssistantActionBar: FC = () => {
|
||||
return (
|
||||
<ActionBarPrimitive.Root
|
||||
<ActionBarPrimitive.Root
|
||||
hideWhenRunning
|
||||
autohide="not-last"
|
||||
autohideFloat="single-branch"
|
||||
className="aui-assistant-action-bar-root -ml-1 col-start-3 row-start-2 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
|
||||
>
|
||||
<ActionBarPrimitive.Copy asChild>
|
||||
<ActionBarPrimitive.Copy asChild>
|
||||
<TooltipIconButton tooltip="Copy">
|
||||
<AssistantIf condition={({ message }) => message.isCopied}>
|
||||
<AuiIf condition={({ message }) => message.isCopied}>
|
||||
<CheckIcon />
|
||||
</AssistantIf>
|
||||
<AssistantIf condition={({ message }) => !message.isCopied}>
|
||||
</AuiIf>
|
||||
<AuiIf condition={({ message }) => !message.isCopied}>
|
||||
<CopyIcon />
|
||||
</AssistantIf>
|
||||
</AuiIf>
|
||||
</TooltipIconButton>
|
||||
</ActionBarPrimitive.Copy>
|
||||
<ActionBarPrimitive.ExportMarkdown asChild>
|
||||
<ActionBarPrimitive.ExportMarkdown asChild>
|
||||
<TooltipIconButton tooltip="Export as Markdown">
|
||||
<DownloadIcon />
|
||||
</TooltipIconButton>
|
||||
</ActionBarPrimitive.ExportMarkdown>
|
||||
<ActionBarPrimitive.Reload asChild>
|
||||
<ActionBarPrimitive.Reload asChild>
|
||||
<TooltipIconButton tooltip="Refresh">
|
||||
<RefreshCwIcon />
|
||||
</TooltipIconButton>
|
||||
</ActionBarPrimitive.Reload>
|
||||
</ActionBarPrimitive.Root>
|
||||
);
|
||||
</ActionBarPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
||||
const EditComposer: FC = () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ActionBarPrimitive, MessagePrimitive, useAssistantState } from "@assistant-ui/react";
|
||||
import { ActionBarPrimitive, MessagePrimitive, useAuiState } from "@assistant-ui/react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { FileText, Pen } from "lucide-react";
|
||||
import { type FC, useState } from "react";
|
||||
|
|
@ -42,10 +42,10 @@ const UserAvatar: FC<AuthorMetadata> = ({ displayName, avatarUrl }) => {
|
|||
};
|
||||
|
||||
export const UserMessage: FC = () => {
|
||||
const messageId = useAssistantState(({ message }) => message?.id);
|
||||
const messageId = useAuiState(({ message }) => message?.id);
|
||||
const messageDocumentsMap = useAtomValue(messageDocumentsMapAtom);
|
||||
const mentionedDocs = messageId ? messageDocumentsMap[messageId] : undefined;
|
||||
const metadata = useAssistantState(({ message }) => message?.metadata);
|
||||
const metadata = useAuiState(({ message }) => message?.metadata);
|
||||
const author = metadata?.custom?.author as AuthorMetadata | undefined;
|
||||
|
||||
return (
|
||||
|
|
@ -93,13 +93,13 @@ export const UserMessage: FC = () => {
|
|||
};
|
||||
|
||||
const UserActionBar: FC = () => {
|
||||
const isThreadRunning = useAssistantState(({ thread }) => thread.isRunning);
|
||||
const isThreadRunning = useAuiState(({ thread }) => thread.isRunning);
|
||||
|
||||
// Get current message ID
|
||||
const currentMessageId = useAssistantState(({ message }) => message?.id);
|
||||
const currentMessageId = useAuiState(({ message }) => message?.id);
|
||||
|
||||
// Find the last user message ID in the thread (computed once, memoized by selector)
|
||||
const lastUserMessageId = useAssistantState(({ thread }) => {
|
||||
const lastUserMessageId = useAuiState(({ thread }) => {
|
||||
const messages = thread.messages;
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
if (messages[i].role === "user") {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,6 @@
|
|||
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
||||
import { Navbar } from "@/components/homepage/navbar";
|
||||
import { ReportPanel } from "@/components/report-panel/report-panel";
|
||||
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
|
||||
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
|
||||
import { GenerateReportToolUI } from "@/components/tool-ui/generate-report";
|
||||
import { GenerateVideoPresentationToolUI } from "@/components/tool-ui/video-presentation";
|
||||
import { LinkPreviewToolUI } from "@/components/tool-ui/link-preview";
|
||||
import { ScrapeWebpageToolUI } from "@/components/tool-ui/scrape-webpage";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { usePublicChat } from "@/hooks/use-public-chat";
|
||||
import { usePublicChatRuntime } from "@/hooks/use-public-chat-runtime";
|
||||
|
|
@ -45,14 +39,6 @@ export function PublicChatView({ shareToken }: PublicChatViewProps) {
|
|||
<main className="min-h-screen bg-main-panel text-foreground overflow-x-hidden">
|
||||
<Navbar scrolledBgClassName={navbarScrolledBg} />
|
||||
<AssistantRuntimeProvider runtime={runtime}>
|
||||
{/* Tool UIs for rendering tool results */}
|
||||
<GeneratePodcastToolUI />
|
||||
<GenerateReportToolUI />
|
||||
<GenerateVideoPresentationToolUI />
|
||||
<LinkPreviewToolUI />
|
||||
<DisplayImageToolUI />
|
||||
<ScrapeWebpageToolUI />
|
||||
|
||||
<div className="flex h-screen pt-16 overflow-hidden">
|
||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
<PublicThread footer={<PublicChatFooter shareToken={shareToken} />} />
|
||||
|
|
|
|||
|
|
@ -2,16 +2,22 @@
|
|||
|
||||
import {
|
||||
ActionBarPrimitive,
|
||||
AssistantIf,
|
||||
AuiIf,
|
||||
MessagePrimitive,
|
||||
ThreadPrimitive,
|
||||
useAssistantState,
|
||||
useAuiState,
|
||||
} from "@assistant-ui/react";
|
||||
import { CheckIcon, CopyIcon } from "lucide-react";
|
||||
import { type FC, type ReactNode, useState } from "react";
|
||||
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
|
||||
import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
|
||||
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
||||
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
|
||||
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
|
||||
import { GenerateReportToolUI } from "@/components/tool-ui/generate-report";
|
||||
import { GenerateVideoPresentationToolUI } from "@/components/tool-ui/video-presentation";
|
||||
import { LinkPreviewToolUI } from "@/components/tool-ui/link-preview";
|
||||
import { ScrapeWebpageToolUI } from "@/components/tool-ui/scrape-webpage";
|
||||
|
||||
interface PublicThreadProps {
|
||||
footer?: ReactNode;
|
||||
|
|
@ -93,7 +99,7 @@ const UserAvatar: FC<AuthorMetadata & { hasError: boolean; onError: () => void }
|
|||
};
|
||||
|
||||
const PublicUserMessage: FC = () => {
|
||||
const metadata = useAssistantState(({ message }) => message?.metadata);
|
||||
const metadata = useAuiState(({ message }) => message?.metadata);
|
||||
const author = metadata?.custom?.author as AuthorMetadata | undefined;
|
||||
|
||||
return (
|
||||
|
|
@ -139,7 +145,17 @@ const PublicAssistantMessage: FC = () => {
|
|||
<MessagePrimitive.Parts
|
||||
components={{
|
||||
Text: MarkdownText,
|
||||
tools: { Fallback: ToolFallback },
|
||||
tools: {
|
||||
by_name: {
|
||||
generate_podcast: GeneratePodcastToolUI,
|
||||
generate_report: GenerateReportToolUI,
|
||||
generate_video_presentation: GenerateVideoPresentationToolUI,
|
||||
link_preview: LinkPreviewToolUI,
|
||||
display_image: DisplayImageToolUI,
|
||||
scrape_webpage: ScrapeWebpageToolUI,
|
||||
},
|
||||
Fallback: ToolFallback,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -153,21 +169,21 @@ const PublicAssistantMessage: FC = () => {
|
|||
|
||||
const PublicAssistantActionBar: FC = () => {
|
||||
return (
|
||||
<ActionBarPrimitive.Root
|
||||
<ActionBarPrimitive.Root
|
||||
autohide="not-last"
|
||||
autohideFloat="single-branch"
|
||||
className="aui-assistant-action-bar-root -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm"
|
||||
>
|
||||
<ActionBarPrimitive.Copy asChild>
|
||||
<ActionBarPrimitive.Copy asChild>
|
||||
<TooltipIconButton tooltip="Copy">
|
||||
<AssistantIf condition={({ message }) => message.isCopied}>
|
||||
<AuiIf condition={({ message }) => message.isCopied}>
|
||||
<CheckIcon />
|
||||
</AssistantIf>
|
||||
<AssistantIf condition={({ message }) => !message.isCopied}>
|
||||
</AuiIf>
|
||||
<AuiIf condition={({ message }) => !message.isCopied}>
|
||||
<CopyIcon />
|
||||
</AssistantIf>
|
||||
</AuiIf>
|
||||
</TooltipIconButton>
|
||||
</ActionBarPrimitive.Copy>
|
||||
</ActionBarPrimitive.Root>
|
||||
);
|
||||
</ActionBarPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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" />;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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" />;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" />;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue