| author | |
| committer | |
| log | 238715fd6fb1e5106db1e310eed7c389304b4840 |
| tree | 00ce625f75cd3108319fd4bd440d2e8cef89886a |
| parent | b03bc4b52d184c5f2367d702e043586a0e9fcdaa |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: ss <ss@barekey.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>8 files changed, 122 insertions(+), 64 deletions(-)
apps/web/src/components/ChatView.tsx+4| ... | @@ -1843,7 +1843,11 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -1843,7 +1843,11 @@ export default function ChatView(props: ChatViewProps) { |
| 1843 | // `codex_personal`) surfaces its own status/message in the banner rather | 1843 | // `codex_personal`) surfaces its own status/message in the banner rather |
| 1844 | // than the default Codex's. Falls back to first-match-by-kind when no | 1844 | // than the default Codex's. Falls back to first-match-by-kind when no |
| 1845 | // saved instance id is available or the instance no longer exists. | 1845 | // saved instance id is available or the instance no longer exists. |
| 1846 | const selectedProviderInstanceId = | ||
| 1847 | providerStatuses.find((status) => status.instanceId === selectedProviderByThreadId) | ||
| 1848 | ?.instanceId ?? null; | ||
| 1846 | const activeProviderInstanceId = | 1849 | const activeProviderInstanceId = |
| 1850 | selectedProviderInstanceId ?? | ||
| 1847 | activeThread?.session?.providerInstanceId ?? | 1851 | activeThread?.session?.providerInstanceId ?? |
| 1848 | activeThread?.modelSelection.instanceId ?? | 1852 | activeThread?.modelSelection.instanceId ?? |
| 1849 | activeProject?.defaultModelSelection?.instanceId ?? | 1853 | activeProject?.defaultModelSelection?.instanceId ?? |
apps/web/src/components/chat/ProviderStatusBanner.tsx+31-16| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | import { type ServerProvider } from "@t3tools/contracts"; | 1 | import { type ServerProvider } from "@t3tools/contracts"; |
| 2 | import { memo } from "react"; | 2 | import { memo } from "react"; |
| 3 | import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; | 3 | import { InfoIcon } from "lucide-react"; |
| 4 | import { CircleAlertIcon } from "lucide-react"; | 4 | import { cn } from "~/lib/utils"; |
| 5 | import { formatProviderDriverKindLabel } from "../../providerModels"; | 5 | import { formatProviderDriverKindLabel } from "../../providerModels"; |
| 6 | 6 | ||
| 7 | export const ProviderStatusBanner = memo(function ProviderStatusBanner({ | 7 | export const ProviderStatusBanner = memo(function ProviderStatusBanner({ |
| ... | @@ -13,22 +13,37 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({ | ... | @@ -13,22 +13,37 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({ |
| 13 | return null; | 13 | return null; |
| 14 | } | 14 | } |
| 15 | 15 | ||
| 16 | const providerLabel = status.displayName?.trim() || formatProviderDriverKindLabel(status.driver); | 16 | const providerName = status.displayName?.trim() || formatProviderDriverKindLabel(status.driver); |
| 17 | const defaultMessage = | 17 | const isUnauthenticated = status.status === "error" && status.auth.status === "unauthenticated"; |
| 18 | status.status === "error" | 18 | const title = isUnauthenticated |
| 19 | ? `${providerLabel} provider is unavailable.` | 19 | ? `${providerName} is unauthenticated` |
| 20 | : `${providerLabel} provider has limited availability.`; | 20 | : `${providerName} provider status`; |
| 21 | const title = `${providerLabel} provider status`; | 21 | const message = isUnauthenticated |
| 22 | ? "Sign in via the CLI to authenticate again." | ||
| 23 | : (status.message ?? | ||
| 24 | (status.status === "error" | ||
| 25 | ? `${providerName} provider is unavailable.` | ||
| 26 | : `${providerName} provider has limited availability.`)); | ||
| 22 | 27 | ||
| 23 | return ( | 28 | return ( |
| 24 | <div className="pt-3 mx-auto max-w-3xl"> | 29 | <div className="mx-auto w-fit max-w-[calc(100%-2rem)] pt-3"> |
| 25 | <Alert variant={status.status === "error" ? "error" : "warning"}> | 30 | <div |
| 26 | <CircleAlertIcon /> | 31 | className={cn( |
| 27 | <AlertTitle>{title}</AlertTitle> | 32 | "inline-flex items-center gap-3 rounded-xl border px-3.5 py-3 text-card-foreground text-sm", |
| 28 | <AlertDescription className="line-clamp-3" title={status.message ?? defaultMessage}> | 33 | status.status === "warning" |
| 29 | {status.message ?? defaultMessage} | 34 | ? "border-warning/32 bg-warning/4 [&_svg]:text-warning" |
| 30 | </AlertDescription> | 35 | : "border-destructive/32 bg-destructive/4 text-destructive-foreground [&_svg]:text-destructive", |
| 31 | </Alert> | 36 | )} |
| 37 | role="alert" | ||
| 38 | > | ||
| 39 | <InfoIcon className="size-4 shrink-0" aria-hidden /> | ||
| 40 | <div className="flex min-w-0 flex-col gap-1"> | ||
| 41 | <div className="font-medium">{title}</div> | ||
| 42 | <div className="line-clamp-3 text-muted-foreground" title={message}> | ||
| 43 | {message} | ||
| 44 | </div> | ||
| 45 | </div> | ||
| 46 | </div> | ||
| 32 | </div> | 47 | </div> |
| 33 | ); | 48 | ); |
| 34 | }); | 49 | }); |
apps/web/src/components/chat/ThreadErrorBanner.tsx+4-8| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { memo } from "react"; | 1 | import { memo } from "react"; |
| 2 | import { Alert, AlertAction, AlertDescription } from "../ui/alert"; | 2 | import { Alert, AlertAction, AlertDescription } from "../ui/alert"; |
| 3 | import { Button } from "../ui/button"; | ||
| 3 | import { CircleAlertIcon, XIcon } from "lucide-react"; | 4 | import { CircleAlertIcon, XIcon } from "lucide-react"; |
| 4 | 5 | ||
| 5 | export const ThreadErrorBanner = memo(function ThreadErrorBanner({ | 6 | export const ThreadErrorBanner = memo(function ThreadErrorBanner({ |
| ... | @@ -19,14 +20,9 @@ export const ThreadErrorBanner = memo(function ThreadErrorBanner({ | ... | @@ -19,14 +20,9 @@ export const ThreadErrorBanner = memo(function ThreadErrorBanner({ |
| 19 | </AlertDescription> | 20 | </AlertDescription> |
| 20 | {onDismiss && ( | 21 | {onDismiss && ( |
| 21 | <AlertAction> | 22 | <AlertAction> |
| 22 | <button | 23 | <Button variant="ghost" size="icon-xs" aria-label="Dismiss error" onClick={onDismiss}> |
| 23 | type="button" | 24 | <XIcon className="text-destructive" /> |
| 24 | aria-label="Dismiss error" | 25 | </Button> |
| 25 | className="inline-flex size-6 items-center justify-center rounded-md text-destructive/60 transition-colors hover:text-destructive" | ||
| 26 | onClick={onDismiss} | ||
| 27 | > | ||
| 28 | <XIcon className="size-3.5" /> | ||
| 29 | </button> | ||
| 30 | </AlertAction> | 26 | </AlertAction> |
| 31 | )} | 27 | )} |
| 32 | </Alert> | 28 | </Alert> |
apps/web/src/components/ui/alert-dialog.tsx+3-2| ... | @@ -17,8 +17,9 @@ function AlertDialogTrigger(props: AlertDialogPrimitive.Trigger.Props) { | ... | @@ -17,8 +17,9 @@ function AlertDialogTrigger(props: AlertDialogPrimitive.Trigger.Props) { |
| 17 | function AlertDialogBackdrop({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) { | 17 | function AlertDialogBackdrop({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) { |
| 18 | return ( | 18 | return ( |
| 19 | <AlertDialogPrimitive.Backdrop | 19 | <AlertDialogPrimitive.Backdrop |
| 20 | forceRender | ||
| 20 | className={cn( | 21 | className={cn( |
| 21 | "fixed inset-0 z-50 transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", | 22 | "fixed inset-0 z-50 bg-background/60 backdrop-blur-xs transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", |
| 22 | className, | 23 | className, |
| 23 | )} | 24 | )} |
| 24 | data-slot="alert-dialog-backdrop" | 25 | data-slot="alert-dialog-backdrop" |
| ... | @@ -31,7 +32,7 @@ function AlertDialogViewport({ className, ...props }: AlertDialogPrimitive.Viewp | ... | @@ -31,7 +32,7 @@ function AlertDialogViewport({ className, ...props }: AlertDialogPrimitive.Viewp |
| 31 | return ( | 32 | return ( |
| 32 | <AlertDialogPrimitive.Viewport | 33 | <AlertDialogPrimitive.Viewport |
| 33 | className={cn( | 34 | className={cn( |
| 34 | "fixed inset-0 z-50 grid grid-rows-[1fr_auto_3fr] justify-items-center bg-white/48 p-4 backdrop-blur-[2px] dark:bg-black/28", | 35 | "fixed inset-0 z-50 grid grid-rows-[1fr_auto_1fr] justify-items-center p-4", |
| 35 | className, | 36 | className, |
| 36 | )} | 37 | )} |
| 37 | data-slot="alert-dialog-viewport" | 38 | data-slot="alert-dialog-viewport" |
apps/web/src/components/ui/alert.tsx+75-34| ... | @@ -1,55 +1,101 @@ | ... | @@ -1,55 +1,101 @@ |
| 1 | import { cva, type VariantProps } from "class-variance-authority"; | 1 | import { cva, type VariantProps } from "class-variance-authority"; |
| 2 | import { Children, isValidElement } from "react"; | ||
| 2 | import type * as React from "react"; | 3 | import type * as React from "react"; |
| 3 | 4 | ||
| 4 | import { cn } from "~/lib/utils"; | 5 | import { cn } from "~/lib/utils"; |
| 5 | 6 | ||
| 6 | const alertVariants = cva( | 7 | const alertVariants = cva("relative rounded-xl border px-3.5 py-3 text-card-foreground text-sm", { |
| 7 | "relative grid w-full items-start gap-x-2 gap-y-0.5 rounded-xl border px-3.5 py-3 text-card-foreground text-sm has-[>svg]:has-data-[slot=alert-action]:grid-cols-[calc(var(--spacing)*4)_1fr_auto] has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-data-[slot=alert-action]:grid-cols-[1fr_auto] has-[>svg]:gap-x-2 [&>svg]:h-lh [&>svg]:w-4", | 8 | defaultVariants: { |
| 8 | { | 9 | variant: "default", |
| 9 | defaultVariants: { | 10 | }, |
| 10 | variant: "default", | 11 | variants: { |
| 11 | }, | 12 | variant: { |
| 12 | variants: { | 13 | default: "bg-transparent dark:bg-input/32 [&_svg]:text-muted-foreground", |
| 13 | variant: { | 14 | error: |
| 14 | default: "bg-transparent dark:bg-input/32 [&>svg]:text-muted-foreground", | 15 | "border-destructive/32 bg-destructive/4 text-destructive-foreground [&_[data-slot=alert-description]]:text-destructive-foreground/80 [&_svg]:text-destructive", |
| 15 | error: "border-destructive/32 bg-destructive/4 [&>svg]:text-destructive", | 16 | info: "border-info/32 bg-info/4 [&_svg]:text-info", |
| 16 | info: "border-info/32 bg-info/4 [&>svg]:text-info", | 17 | success: "border-success/32 bg-success/4 [&_svg]:text-success", |
| 17 | success: "border-success/32 bg-success/4 [&>svg]:text-success", | 18 | warning: "border-warning/32 bg-warning/4 [&_svg]:text-warning", |
| 18 | warning: "border-warning/32 bg-warning/4 [&>svg]:text-warning", | ||
| 19 | }, | ||
| 20 | }, | 19 | }, |
| 21 | }, | 20 | }, |
| 22 | ); | 21 | }); |
| 22 | |||
| 23 | function alertChildSlot(child: React.ReactElement): string | undefined { | ||
| 24 | const propsSlot = (child.props as Record<string, string | undefined>)["data-slot"]; | ||
| 25 | if (propsSlot) { | ||
| 26 | return propsSlot; | ||
| 27 | } | ||
| 28 | |||
| 29 | const type = child.type as { displayName?: string; name?: string }; | ||
| 30 | switch (type.displayName ?? type.name) { | ||
| 31 | case "AlertAction": | ||
| 32 | return "alert-action"; | ||
| 33 | case "AlertTitle": | ||
| 34 | return "alert-title"; | ||
| 35 | case "AlertDescription": | ||
| 36 | return "alert-description"; | ||
| 37 | default: | ||
| 38 | return undefined; | ||
| 39 | } | ||
| 40 | } | ||
| 23 | 41 | ||
| 24 | function Alert({ | 42 | function Alert({ |
| 25 | className, | 43 | className, |
| 26 | variant, | 44 | variant, |
| 45 | children, | ||
| 27 | ...props | 46 | ...props |
| 28 | }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) { | 47 | }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) { |
| 48 | const icon: React.ReactNode[] = []; | ||
| 49 | const content: React.ReactNode[] = []; | ||
| 50 | const action: React.ReactNode[] = []; | ||
| 51 | |||
| 52 | Children.forEach(children, (child) => { | ||
| 53 | if (!isValidElement(child)) { | ||
| 54 | content.push(child); | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | const slot = alertChildSlot(child); | ||
| 58 | if (slot === "alert-action") { | ||
| 59 | action.push(child); | ||
| 60 | } else if (slot === "alert-title" || slot === "alert-description") { | ||
| 61 | content.push(child); | ||
| 62 | } else { | ||
| 63 | icon.push(child); | ||
| 64 | } | ||
| 65 | }); | ||
| 66 | |||
| 29 | return ( | 67 | return ( |
| 30 | <div | 68 | <div |
| 31 | className={cn(alertVariants({ variant }), className)} | 69 | className={cn(alertVariants({ variant }), className)} |
| 32 | data-slot="alert" | 70 | data-slot="alert" |
| 33 | role="alert" | 71 | role="alert" |
| 34 | {...props} | 72 | {...props} |
| 35 | /> | 73 | > |
| 74 | <div className="flex items-center gap-2"> | ||
| 75 | {icon.length > 0 && ( | ||
| 76 | <div className="flex size-4 shrink-0 items-center justify-center [&>svg]:size-full"> | ||
| 77 | {icon} | ||
| 78 | </div> | ||
| 79 | )} | ||
| 80 | {content.length > 0 && ( | ||
| 81 | <div className="flex min-w-0 flex-1 flex-col gap-0.5">{content}</div> | ||
| 82 | )} | ||
| 83 | {action.length > 0 && ( | ||
| 84 | <div className="flex shrink-0 items-center self-center">{action}</div> | ||
| 85 | )} | ||
| 86 | </div> | ||
| 87 | </div> | ||
| 36 | ); | 88 | ); |
| 37 | } | 89 | } |
| 38 | 90 | ||
| 39 | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { | 91 | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { |
| 40 | return ( | 92 | return <div className={cn("font-medium", className)} data-slot="alert-title" {...props} />; |
| 41 | <div | ||
| 42 | className={cn("font-medium [svg~&]:col-start-2", className)} | ||
| 43 | data-slot="alert-title" | ||
| 44 | {...props} | ||
| 45 | /> | ||
| 46 | ); | ||
| 47 | } | 93 | } |
| 48 | 94 | ||
| 49 | function AlertDescription({ className, ...props }: React.ComponentProps<"div">) { | 95 | function AlertDescription({ className, ...props }: React.ComponentProps<"div">) { |
| 50 | return ( | 96 | return ( |
| 51 | <div | 97 | <div |
| 52 | className={cn("flex flex-col gap-2.5 text-muted-foreground [svg~&]:col-start-2", className)} | 98 | className={cn("flex flex-col gap-2.5 text-muted-foreground", className)} |
| 53 | data-slot="alert-description" | 99 | data-slot="alert-description" |
| 54 | {...props} | 100 | {...props} |
| 55 | /> | 101 | /> |
| ... | @@ -57,16 +103,11 @@ function AlertDescription({ className, ...props }: React.ComponentProps<"div">) | ... | @@ -57,16 +103,11 @@ function AlertDescription({ className, ...props }: React.ComponentProps<"div">) |
| 57 | } | 103 | } |
| 58 | 104 | ||
| 59 | function AlertAction({ className, ...props }: React.ComponentProps<"div">) { | 105 | function AlertAction({ className, ...props }: React.ComponentProps<"div">) { |
| 60 | return ( | 106 | return <div className={cn("flex gap-1", className)} data-slot="alert-action" {...props} />; |
| 61 | <div | ||
| 62 | className={cn( | ||
| 63 | "flex gap-1 max-sm:col-start-2 max-sm:mt-2 sm:row-start-1 sm:row-end-3 sm:self-center sm:[[data-slot=alert-description]~&]:col-start-2 sm:[[data-slot=alert-title]~&]:col-start-2 sm:[svg~&]:col-start-2 sm:[svg~[data-slot=alert-description]~&]:col-start-3 sm:[svg~[data-slot=alert-title]~&]:col-start-3", | ||
| 64 | className, | ||
| 65 | )} | ||
| 66 | data-slot="alert-action" | ||
| 67 | {...props} | ||
| 68 | /> | ||
| 69 | ); | ||
| 70 | } | 107 | } |
| 71 | 108 | ||
| 109 | AlertTitle.displayName = "AlertTitle"; | ||
| 110 | AlertDescription.displayName = "AlertDescription"; | ||
| 111 | AlertAction.displayName = "AlertAction"; | ||
| 112 | |||
| 72 | export { Alert, AlertTitle, AlertDescription, AlertAction }; | 113 | export { Alert, AlertTitle, AlertDescription, AlertAction }; |
apps/web/src/components/ui/command.tsx+1-1| ... | @@ -30,7 +30,7 @@ function CommandDialogBackdrop({ className, ...props }: CommandDialogPrimitive.B | ... | @@ -30,7 +30,7 @@ function CommandDialogBackdrop({ className, ...props }: CommandDialogPrimitive.B |
| 30 | return ( | 30 | return ( |
| 31 | <CommandDialogPrimitive.Backdrop | 31 | <CommandDialogPrimitive.Backdrop |
| 32 | className={cn( | 32 | className={cn( |
| 33 | "fixed inset-0 z-50 bg-background/60 transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", | 33 | "fixed inset-0 z-50 bg-background/60 backdrop-blur-xs transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", |
| 34 | className, | 34 | className, |
| 35 | )} | 35 | )} |
| 36 | data-slot="command-dialog-backdrop" | 36 | data-slot="command-dialog-backdrop" |
apps/web/src/components/ui/dialog.tsx+3-2| ... | @@ -23,8 +23,9 @@ function DialogClose(props: DialogPrimitive.Close.Props) { | ... | @@ -23,8 +23,9 @@ function DialogClose(props: DialogPrimitive.Close.Props) { |
| 23 | function DialogBackdrop({ className, ...props }: DialogPrimitive.Backdrop.Props) { | 23 | function DialogBackdrop({ className, ...props }: DialogPrimitive.Backdrop.Props) { |
| 24 | return ( | 24 | return ( |
| 25 | <DialogPrimitive.Backdrop | 25 | <DialogPrimitive.Backdrop |
| 26 | forceRender | ||
| 26 | className={cn( | 27 | className={cn( |
| 27 | "fixed inset-0 z-50 transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", | 28 | "fixed inset-0 z-50 bg-background/60 backdrop-blur-xs transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", |
| 28 | className, | 29 | className, |
| 29 | )} | 30 | )} |
| 30 | data-slot="dialog-backdrop" | 31 | data-slot="dialog-backdrop" |
| ... | @@ -37,7 +38,7 @@ function DialogViewport({ className, ...props }: DialogPrimitive.Viewport.Props) | ... | @@ -37,7 +38,7 @@ function DialogViewport({ className, ...props }: DialogPrimitive.Viewport.Props) |
| 37 | return ( | 38 | return ( |
| 38 | <DialogPrimitive.Viewport | 39 | <DialogPrimitive.Viewport |
| 39 | className={cn( | 40 | className={cn( |
| 40 | "fixed inset-0 z-50 grid grid-rows-[1fr_auto_3fr] justify-items-center bg-white/48 p-4 backdrop-blur-[2px] dark:bg-black/28", | 41 | "fixed inset-0 z-50 grid grid-rows-[1fr_auto_1fr] justify-items-center p-4", |
| 41 | className, | 42 | className, |
| 42 | )} | 43 | )} |
| 43 | data-slot="dialog-viewport" | 44 | data-slot="dialog-viewport" |
apps/web/src/components/ui/sheet.tsx+1-1| ... | @@ -22,7 +22,7 @@ function SheetBackdrop({ className, ...props }: SheetPrimitive.Backdrop.Props) { | ... | @@ -22,7 +22,7 @@ function SheetBackdrop({ className, ...props }: SheetPrimitive.Backdrop.Props) { |
| 22 | return ( | 22 | return ( |
| 23 | <SheetPrimitive.Backdrop | 23 | <SheetPrimitive.Backdrop |
| 24 | className={cn( | 24 | className={cn( |
| 25 | "fixed inset-0 z-50 bg-black/32 backdrop-blur-sm transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", | 25 | "fixed inset-0 z-50 bg-background/60 backdrop-blur-xs transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", |
| 26 | className, | 26 | className, |
| 27 | )} | 27 | )} |
| 28 | data-slot="sheet-backdrop" | 28 | data-slot="sheet-backdrop" |