Skip to content
2 changes: 2 additions & 0 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2200,6 +2200,7 @@ export default function ChatView(props: ChatViewProps) {
return {
id: `project-clone:${projectId}`,
variant: "info",
compact: true,
priority: "activity",
icon: <DownloadIcon />,
title: `Cloning ${name}`,
Expand All @@ -2223,6 +2224,7 @@ export default function ChatView(props: ChatViewProps) {
return {
id: `project-clone:${projectId}`,
variant: cancelled ? "warning" : "error",
compact: true,
Comment thread
Bil0000 marked this conversation as resolved.
icon: <DownloadIcon />,
title: cancelled ? `Cancelled cloning ${name}` : `Failed to clone ${name}`,
description: cancelled ? "Retry to bring in the repository." : activeProjectClone.error,
Comment thread
Bil0000 marked this conversation as resolved.
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/ComposerBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ function Actions({ className, ...props }: ComponentProps<"span">) {
className={cn(
"col-start-3 row-start-1 flex flex-wrap items-center justify-end gap-1",
"group-data-[composer-banner-layout=approval]/banner-row:self-center group-data-[composer-banner-layout=approval]/banner-row:gap-1.5 @max-[560px]:group-data-[composer-banner-layout=approval]/banner-row:col-start-2 @max-[560px]:group-data-[composer-banner-layout=approval]/banner-row:col-end-4 @max-[560px]:group-data-[composer-banner-layout=approval]/banner-row:row-start-2",
"@max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:col-start-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:col-end-4 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:row-start-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:-ms-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:justify-start",
"@max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:col-start-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:col-end-4 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:row-start-2 @max-[400px]:group-data-[composer-banner-layout=wrap-actions]/banner-row:has-[>:nth-child(2)]:justify-end",
"@max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:col-start-2 @max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:col-end-4 @max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:row-start-2 @max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:-ms-2 @max-[320px]:group-data-[composer-banner-layout=wrap-actions-narrow]/banner-row:has-[>:nth-child(2)]:justify-start",
className,
)}
Expand Down
35 changes: 20 additions & 15 deletions apps/web/src/components/chat/ComposerBannerStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface ComposerBannerStackItem {
readonly id: string;
readonly variant: ComposerBannerVariant;
readonly priority?: "urgent" | "activity" | "notice";
readonly compact?: boolean;
readonly icon: ReactNode;
readonly title: ReactNode;
readonly description?: ReactNode;
Expand Down Expand Up @@ -270,22 +271,20 @@ function ComposerBannerStackAlert({
variant={item.variant}
density="comfortable"
>
<ComposerBanner.Row layout="wrap-actions-narrow">
<ComposerBanner.Row layout={item.compact ? "wrap-actions-narrow" : "wrap-actions"}>
<ComposerBanner.Icon className="h-(--composer-banner-icon-column) self-start">
{item.icon}
</ComposerBanner.Icon>
<ComposerBanner.Content className="whitespace-nowrap">
<span
className={cn(
"min-w-0 font-medium leading-7 sm:leading-6",
typeof item.title === "string" && "truncate",
)}
>
{item.title}
</span>
<span className="min-w-0 truncate font-medium leading-7 sm:leading-6">{item.title}</span>
{item.description ? (
<>
<span className="min-w-0 shrink-[9999] truncate text-muted-foreground @max-[400px]:sr-only">
<span className={item.compact ? "contents" : "flex min-w-8 flex-1 items-center gap-1"}>
<span
className={cn(
"min-w-0 truncate text-muted-foreground",
item.compact && "shrink-[9999] @max-[400px]:sr-only",
)}
>
{item.description}
</span>
<Popover>
Expand All @@ -296,21 +295,27 @@ function ComposerBannerStackAlert({
size="icon-xs"
variant="ghost"
aria-label="Show notice details"
className="hidden flex-none text-muted-foreground hover:text-foreground @max-[400px]:inline-flex"
className={cn(
"flex-none text-muted-foreground hover:text-foreground",
item.compact && "hidden @max-[400px]:inline-flex",
)}
/>
}
>
<InfoIcon className="size-3.5" />
</PopoverTrigger>
<PopoverPopup
aria-label="Notice details"
tooltipStyle
side="top"
className="max-w-72 whitespace-normal text-pretty"
className="max-w-80 whitespace-normal text-pretty wrap-anywhere"
>
{item.description}
<ComposerBanner.Scroll className="max-h-[min(var(--available-height),24rem,40dvh)]">
{item.description}
</ComposerBanner.Scroll>
</PopoverPopup>
</Popover>
</>
</span>
) : null}
</ComposerBanner.Content>
{item.actions || item.onDismiss ? (
Expand Down
122 changes: 63 additions & 59 deletions apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,66 +228,70 @@ const ComposerPendingUserInputCard = memo(function ComposerPendingUserInputCard(
</ComposerBanner.Actions>
</CollapsibleTrigger>
<CollapsiblePanel>
<ComposerBanner.Body className="pe-1 pb-1">
<p className="text-sm text-foreground/85">{activeQuestion.question}</p>
{activeQuestion.multiSelect ? (
<p className="mt-1 text-secondary-label text-xs">Select one or more options.</p>
) : null}
<div className="mt-2 space-y-0.5">
{activeQuestion.options.map((option, index) => {
const optionValue = option.value ?? option.label;
const isOptimisticallySelected =
optimisticSingleSelect?.questionId === activeQuestion.id &&
optimisticSingleSelect.optionValue === optionValue;
const isSelected =
isOptimisticallySelected ||
(!customAnswerActive && progress.selectedOptionValues.includes(optionValue));
const shortcutKey = index < 9 ? index + 1 : null;
const className = cn(
"group flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left outline-none transition-colors duration-150 focus-visible:ring-1 focus-visible:ring-primary/25",
isSelected
? "bg-muted/55 text-foreground"
: "bg-transparent text-foreground/85 hover:bg-muted/30",
isResponding && "opacity-50 cursor-not-allowed",
!isResponding && "cursor-pointer",
);
const content = (
<>
<div className="min-w-0 flex-1 flex flex-col gap-0.5">
<span className="text-sm font-medium">{option.label}</span>
{option.description && option.description !== option.label ? (
<span className="text-secondary-label text-[11px]">{option.description}</span>
<ComposerBanner.Scroll>
<ComposerBanner.Body className="pe-1 pb-1 wrap-anywhere">
<p className="text-sm text-foreground/85">{activeQuestion.question}</p>
{activeQuestion.multiSelect ? (
<p className="mt-1 text-secondary-label text-xs">Select one or more options.</p>
) : null}
<div className="mt-2 space-y-0.5">
{activeQuestion.options.map((option, index) => {
const optionValue = option.value ?? option.label;
const isOptimisticallySelected =
optimisticSingleSelect?.questionId === activeQuestion.id &&
optimisticSingleSelect.optionValue === optionValue;
const isSelected =
isOptimisticallySelected ||
(!customAnswerActive && progress.selectedOptionValues.includes(optionValue));
const shortcutKey = index < 9 ? index + 1 : null;
const className = cn(
"group flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-left outline-none transition-colors duration-150 focus-visible:ring-1 focus-visible:ring-primary/25",
isSelected
? "bg-muted/55 text-foreground"
: "bg-transparent text-foreground/85 hover:bg-muted/30",
isResponding && "opacity-50 cursor-not-allowed",
!isResponding && "cursor-pointer",
);
const content = (
<>
<div className="min-w-0 flex-1 flex flex-col gap-0.5">
<span className="text-sm font-medium">{option.label}</span>
{option.description && option.description !== option.label ? (
<span className="text-secondary-label text-[11px]">
{option.description}
</span>
) : null}
</div>
{isSelected ? (
<CheckIcon className="size-3.5 shrink-0 text-primary" />
) : shortcutKey !== null ? (
<kbd
className={cn(
"flex size-5 shrink-0 items-center justify-center text-[10px] font-medium text-muted-foreground tabular-nums",
)}
>
{shortcutKey}
</kbd>
) : null}
</div>
{isSelected ? (
<CheckIcon className="size-3.5 shrink-0 text-primary" />
) : shortcutKey !== null ? (
<kbd
className={cn(
"flex size-5 shrink-0 items-center justify-center text-[10px] font-medium text-muted-foreground tabular-nums",
)}
>
{shortcutKey}
</kbd>
) : null}
</>
);
return (
<button
key={`${activeQuestion.id}:${optionValue}`}
type="button"
disabled={isResponding}
onClick={() => {
handleOptionSelection(activeQuestion.id, optionValue);
}}
className={className}
>
{content}
</button>
);
})}
</div>
</ComposerBanner.Body>
</>
);
return (
<button
key={`${activeQuestion.id}:${optionValue}`}
type="button"
disabled={isResponding}
onClick={() => {
handleOptionSelection(activeQuestion.id, optionValue);
}}
className={className}
>
{content}
</button>
);
})}
</div>
</ComposerBanner.Body>
</ComposerBanner.Scroll>
</CollapsiblePanel>
</Collapsible>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/chat/ComposerTasksBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function TaskSummary({
>
{progress.completedSteps}/{progress.totalSteps}
</ComposerBanner.Count>
<TaskSegments className="hidden w-20 sm:flex" steps={steps} />
<TaskSegments className="hidden w-20 @min-[560px]:flex" steps={steps} />
<ComposerBanner.ToggleIcon expanded={expanded} />
</ComposerBanner.Actions>
</>
Expand Down
Loading