confirmation.tsx 3.6 KB

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