Skip to content
2 changes: 1 addition & 1 deletion apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2202,7 +2202,7 @@ export default function ChatView(props: ChatViewProps) {
</Button>
<Button
size="xs"
variant="ghost"
variant="default"
onClick={() =>
void runProjectCloneAction("Failed to retry clone", () =>
retryProjectClone({ environmentId, input: { projectId } }),
Expand Down
7 changes: 2 additions & 5 deletions apps/web/src/components/chat/ComposerBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ function Row({
layout = "inline",
...props
}: useRender.ComponentProps<"div"> & {
layout?: "inline" | "wrap-actions" | "wrap-actions-narrow" | "approval";
layout?: "inline" | "wrap-actions" | "approval";
}) {
const rowProps = {
className: cn(
Expand All @@ -192,8 +192,6 @@ function Row({
"[&:is(button)]:cursor-pointer [&:is(button)]:rounded-[0.5rem] [&:is(button)]:focus-visible:outline-2 [&:is(button)]:focus-visible:-outline-offset-2 [&:is(button)]:focus-visible:outline-ring",
layout === "wrap-actions" &&
"@max-[400px]:*:data-[slot=composer-banner-content]:min-h-(--composer-banner-icon-column)",
layout === "wrap-actions-narrow" &&
"@max-[320px]:*:data-[slot=composer-banner-content]:min-h-(--composer-banner-icon-column)",
layout === "approval" && "items-start gap-x-2 gap-y-3",
className,
),
Expand Down Expand Up @@ -257,8 +255,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-[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",
"@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",
className,
)}
{...props}
Expand Down
28 changes: 12 additions & 16 deletions apps/web/src/components/chat/ComposerBannerStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,24 +270,17 @@ function ComposerBannerStackAlert({
variant={item.variant}
density="comfortable"
>
<ComposerBanner.Row layout="wrap-actions-narrow">
<ComposerBanner.Row layout="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",
)}
>
<ComposerBanner.Content className="flex-col items-start gap-0.5 py-0.5">
<span className="w-full min-w-0 font-medium whitespace-normal wrap-anywhere">
{item.title}
</span>
{item.description ? (
<>
<span className="min-w-0 shrink-[9999] truncate text-muted-foreground @max-[400px]:sr-only">
{item.description}
</span>
<span className="flex w-full min-w-0 items-start gap-1">
<span className="min-w-0 truncate text-muted-foreground">{item.description}</span>
<Popover>
<PopoverTrigger
openOnHover
Expand All @@ -296,21 +289,24 @@ 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="-my-1 flex-none text-muted-foreground hover:text-foreground"
/>
}
>
<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
5 changes: 3 additions & 2 deletions apps/web/src/components/chat/ComposerTasksBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@ function TaskSummary({
className={progress.completedSteps >= progress.totalSteps ? "text-success" : undefined}
data-composer-task-progress="true"
>
{progress.completedSteps}/{progress.totalSteps} complete
{progress.completedSteps}/{progress.totalSteps}
<span className="sr-only"> complete</span>
</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