File size: 3,688 Bytes
f56a29b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import type { ToolUIPart } from 'ai';
import { type ComponentProps, createContext, type ReactNode, useContext } from 'react';
type ToolUIPartApproval =
| {
id: string;
approved?: never;
reason?: never;
}
| {
id: string;
approved: boolean;
reason?: string;
}
| {
id: string;
approved: true;
reason?: string;
}
| {
id: string;
approved: true;
reason?: string;
}
| {
id: string;
approved: false;
reason?: string;
}
| undefined;
type ConfirmationContextValue = {
approval: ToolUIPartApproval;
state: ToolUIPart['state'];
};
const ConfirmationContext = createContext<ConfirmationContextValue | null>(null);
const useConfirmation = () => {
const context = useContext(ConfirmationContext);
if (!context) {
throw new Error('Confirmation components must be used within Confirmation');
}
return context;
};
export type ConfirmationProps = ComponentProps<typeof Alert> & {
approval?: ToolUIPartApproval;
state: ToolUIPart['state'];
};
export const Confirmation = ({ className, approval, state, ...props }: ConfirmationProps) => {
if (!approval || state === 'input-streaming' || state === 'input-available') {
return null;
}
return (
<ConfirmationContext.Provider value={{ approval, state }}>
<Alert className={cn('flex flex-col gap-2', className)} {...props} />
</ConfirmationContext.Provider>
);
};
export type ConfirmationTitleProps = ComponentProps<typeof AlertDescription>;
export const ConfirmationTitle = ({ className, ...props }: ConfirmationTitleProps) => (
<AlertDescription className={cn('inline', className)} {...props} />
);
export type ConfirmationRequestProps = {
children?: ReactNode;
};
export const ConfirmationRequest = ({ children }: ConfirmationRequestProps) => {
const { state } = useConfirmation();
// Only show when approval is requested
if (state !== 'approval-requested') {
return null;
}
return children;
};
export type ConfirmationAcceptedProps = {
children?: ReactNode;
};
export const ConfirmationAccepted = ({ children }: ConfirmationAcceptedProps) => {
const { approval, state } = useConfirmation();
// Only show when approved and in response states
if (
!approval?.approved ||
(state !== 'approval-responded' && state !== 'output-denied' && state !== 'output-available')
) {
return null;
}
return children;
};
export type ConfirmationRejectedProps = {
children?: ReactNode;
};
export const ConfirmationRejected = ({ children }: ConfirmationRejectedProps) => {
const { approval, state } = useConfirmation();
// Only show when rejected and in response states
if (
approval?.approved !== false ||
(state !== 'approval-responded' && state !== 'output-denied' && state !== 'output-available')
) {
return null;
}
return children;
};
export type ConfirmationActionsProps = ComponentProps<'div'>;
export const ConfirmationActions = ({ className, ...props }: ConfirmationActionsProps) => {
const { state } = useConfirmation();
// Only show when approval is requested
if (state !== 'approval-requested') {
return null;
}
return (
<div className={cn('flex items-center justify-end gap-2 self-end', className)} {...props} />
);
};
export type ConfirmationActionProps = ComponentProps<typeof Button>;
export const ConfirmationAction = (props: ConfirmationActionProps) => (
<Button className="h-8 px-3 text-sm" type="button" {...props} />
);
|