🎄 merry christmas: ai sdk v6 beta + tool approval (#1361)
This commit is contained in:
parent
6e5b883cf2
commit
4d3ba8d9fe
21 changed files with 429 additions and 202 deletions
|
|
@ -25,6 +25,7 @@ import { PreviewAttachment } from "./preview-attachment";
|
|||
import { Weather } from "./weather";
|
||||
|
||||
const PurePreviewMessage = ({
|
||||
addToolApprovalResponse,
|
||||
chatId,
|
||||
message,
|
||||
vote,
|
||||
|
|
@ -34,6 +35,7 @@ const PurePreviewMessage = ({
|
|||
isReadonly,
|
||||
requiresScrollPadding: _requiresScrollPadding,
|
||||
}: {
|
||||
addToolApprovalResponse: UseChatHelpers<ChatMessage>["addToolApprovalResponse"];
|
||||
chatId: string;
|
||||
message: ChatMessage;
|
||||
vote: Vote | undefined;
|
||||
|
|
@ -76,9 +78,10 @@ const PurePreviewMessage = ({
|
|||
),
|
||||
"w-full":
|
||||
(message.role === "assistant" &&
|
||||
message.parts?.some(
|
||||
(message.parts?.some(
|
||||
(p) => p.type === "text" && p.text?.trim()
|
||||
)) ||
|
||||
) ||
|
||||
message.parts?.some((p) => p.type.startsWith("tool-")))) ||
|
||||
mode === "edit",
|
||||
"max-w-[calc(100%-2.5rem)] sm:max-w-[min(fit-content,80%)]":
|
||||
message.role === "user" && mode !== "edit",
|
||||
|
|
@ -122,7 +125,7 @@ const PurePreviewMessage = ({
|
|||
<div key={key}>
|
||||
<MessageContent
|
||||
className={cn({
|
||||
"w-fit break-words rounded-2xl px-3 py-2 text-right text-white":
|
||||
"wrap-break-word w-fit rounded-2xl px-3 py-2 text-right text-white":
|
||||
message.role === "user",
|
||||
"bg-transparent px-0 py-0 text-left":
|
||||
message.role === "assistant",
|
||||
|
|
@ -163,22 +166,95 @@ const PurePreviewMessage = ({
|
|||
|
||||
if (type === "tool-getWeather") {
|
||||
const { toolCallId, state } = part;
|
||||
const approvalId = (part as { approval?: { id: string } })
|
||||
.approval?.id;
|
||||
const isDenied =
|
||||
state === "output-denied" ||
|
||||
(state === "approval-responded" &&
|
||||
(part as { approval?: { approved?: boolean } }).approval
|
||||
?.approved === false);
|
||||
const widthClass = "w-[min(100%,450px)]";
|
||||
|
||||
if (state === "output-available") {
|
||||
return (
|
||||
<div className={widthClass} key={toolCallId}>
|
||||
<Weather weatherAtLocation={part.output} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isDenied) {
|
||||
return (
|
||||
<div className={widthClass} key={toolCallId}>
|
||||
<Tool className="w-full" defaultOpen={true}>
|
||||
<ToolHeader
|
||||
state="output-denied"
|
||||
type="tool-getWeather"
|
||||
/>
|
||||
<ToolContent>
|
||||
<div className="px-4 py-3 text-muted-foreground text-sm">
|
||||
Weather lookup was denied.
|
||||
</div>
|
||||
</ToolContent>
|
||||
</Tool>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (state === "approval-responded") {
|
||||
return (
|
||||
<div className={widthClass} key={toolCallId}>
|
||||
<Tool className="w-full" defaultOpen={true}>
|
||||
<ToolHeader state={state} type="tool-getWeather" />
|
||||
<ToolContent>
|
||||
<ToolInput input={part.input} />
|
||||
</ToolContent>
|
||||
</Tool>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tool defaultOpen={true} key={toolCallId}>
|
||||
<ToolHeader state={state} type="tool-getWeather" />
|
||||
<ToolContent>
|
||||
{state === "input-available" && (
|
||||
<ToolInput input={part.input} />
|
||||
)}
|
||||
{state === "output-available" && (
|
||||
<ToolOutput
|
||||
errorText={undefined}
|
||||
output={<Weather weatherAtLocation={part.output} />}
|
||||
/>
|
||||
)}
|
||||
</ToolContent>
|
||||
</Tool>
|
||||
<div className={widthClass} key={toolCallId}>
|
||||
<Tool className="w-full" defaultOpen={true}>
|
||||
<ToolHeader state={state} type="tool-getWeather" />
|
||||
<ToolContent>
|
||||
{(state === "input-available" ||
|
||||
state === "approval-requested") && (
|
||||
<ToolInput input={part.input} />
|
||||
)}
|
||||
{state === "approval-requested" && approvalId && (
|
||||
<div className="flex items-center justify-end gap-2 border-t px-4 py-3">
|
||||
<button
|
||||
className="rounded-md px-3 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground"
|
||||
onClick={() => {
|
||||
addToolApprovalResponse({
|
||||
id: approvalId,
|
||||
approved: false,
|
||||
reason: "User denied weather lookup",
|
||||
});
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Deny
|
||||
</button>
|
||||
<button
|
||||
className="rounded-md bg-primary px-3 py-1.5 text-primary-foreground text-sm transition-colors hover:bg-primary/90"
|
||||
onClick={() => {
|
||||
addToolApprovalResponse({
|
||||
id: approvalId,
|
||||
approved: true,
|
||||
});
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Allow
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</ToolContent>
|
||||
</Tool>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -285,22 +361,15 @@ const PurePreviewMessage = ({
|
|||
export const PreviewMessage = memo(
|
||||
PurePreviewMessage,
|
||||
(prevProps, nextProps) => {
|
||||
if (prevProps.isLoading !== nextProps.isLoading) {
|
||||
return false;
|
||||
if (
|
||||
prevProps.isLoading === nextProps.isLoading &&
|
||||
prevProps.message.id === nextProps.message.id &&
|
||||
prevProps.requiresScrollPadding === nextProps.requiresScrollPadding &&
|
||||
equal(prevProps.message.parts, nextProps.message.parts) &&
|
||||
equal(prevProps.vote, nextProps.vote)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (prevProps.message.id !== nextProps.message.id) {
|
||||
return false;
|
||||
}
|
||||
if (prevProps.requiresScrollPadding !== nextProps.requiresScrollPadding) {
|
||||
return false;
|
||||
}
|
||||
if (!equal(prevProps.message.parts, nextProps.message.parts)) {
|
||||
return false;
|
||||
}
|
||||
if (!equal(prevProps.vote, nextProps.vote)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue