Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5539,6 +5539,11 @@ export default function ChatView(props: ChatViewProps) {
}
useRightPanelStore.getState().toggleVisibility(activeThreadRef);
}, [activeThreadRef, closePreviewPanel, rightPanelOpen]);
const [threadPanelCustomizeRequested, setThreadPanelCustomizeRequested] = useState(false);
const handleThreadPanelCustomizeRequest = useCallback(
() => setThreadPanelCustomizeRequested(false),
[],
);
const toggleThreadPanel = useCallback(() => {
if (!activeThreadRef) return;
useRightPanelStore.getState().toggleThreadPanel(activeThreadRef, threadPanelPresentation);
Expand Down Expand Up @@ -7357,6 +7362,14 @@ export default function ChatView(props: ChatViewProps) {
return;
}

if (command === "threadPanel.customize") {
event.preventDefault();
event.stopPropagation();
if (!threadPanelOpen) toggleThreadPanel();
setThreadPanelCustomizeRequested(true);
return;
}

if (command === "threadPanel.toggle") {
event.preventDefault();
event.stopPropagation();
Expand Down Expand Up @@ -7541,6 +7554,7 @@ export default function ChatView(props: ChatViewProps) {
getShortcutContext,
toggleRightPanel,
toggleThreadPanel,
threadPanelOpen,
toggleTerminalVisibility,
composerRef,
]);
Expand Down Expand Up @@ -10298,6 +10312,8 @@ export default function ChatView(props: ChatViewProps) {
onAddProjectScript: saveProjectScript,
onUpdateProjectScript: updateProjectScript,
onDeleteProjectScript: deleteProjectScript,
customizeRequested: threadPanelCustomizeRequested,
onCustomizeRequestHandled: handleThreadPanelCustomizeRequest,
};
const panelToggleControlProps = {
terminalAvailable: activeProject !== null,
Expand Down
7 changes: 6 additions & 1 deletion apps/web/src/components/chat/ThreadAutomationsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const STATUS_DOT_CLASS: Record<ScheduledTask["lastRunStatus"], string> = {
* Renders nothing when the thread has no bound automations.
*/
export function ThreadAutomationsPanel(props: {
readonly alwaysVisible?: boolean;
readonly environmentId: EnvironmentId;
readonly threadId: ThreadId;
}) {
Expand All @@ -58,7 +59,7 @@ export function ThreadAutomationsPanel(props: {
// A load error must not look like "no automations" — this thread may have
// tasks whose controls would silently vanish. Only hide the section when we
// positively know there is nothing bound to it.
if (tasksQuery.error === null && boundTasks.length === 0) return null;
if (tasksQuery.error === null && boundTasks.length === 0 && !props.alwaysVisible) return null;

const reportFailure = (title: string, error: unknown) => {
toastManager.add(
Expand Down Expand Up @@ -131,6 +132,10 @@ export function ThreadAutomationsPanel(props: {
<p className="px-2.5 py-1.5 text-2xs text-destructive">
Could not load automations: {tasksQuery.error}
</p>
) : boundTasks.length === 0 ? (
<p className="px-2.5 py-1.5 text-2xs text-muted-foreground">
{tasksQuery.data === null ? "Loading automations…" : "No automations."}
</p>
) : null}

<ul className="m-0 list-none p-0">
Expand Down
26 changes: 17 additions & 9 deletions apps/web/src/components/chat/ThreadDetailsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@ export function ThreadDetailsCard({
handle,
onPresentationChange,
children,
editing = false,
contentKey = "",
}: {
threadRef: ScopedThreadRef;
anchor: RefObject<Element | null>;
handle: ReturnType<typeof PopoverCreateHandle>;
onPresentationChange: (presentation: ThreadPanelPresentation) => void;
children: (density: "full" | "compact" | "essential") => ReactNode;
editing?: boolean;
contentKey?: string;
children: (
density: "full" | "compact" | "essential",
renderCard: (content: ReactNode, action?: ReactNode) => ReactNode,
) => ReactNode;
}) {
const canvas = useChatCanvas();
const preferredPlacement = canvas
Expand All @@ -44,15 +51,15 @@ export function ThreadDetailsCard({
selectThreadPanelOpen(state.threadPanelVisibilityByThreadKey, threadRef, "popover"),
);
const [contentElement, setContentElement] = useState<HTMLDivElement | null>(null);
const measurementKey = `${threadRef.environmentId}:${threadRef.threadId}:${preferredPlacement?.width ?? "popup"}`;
const measurementKey = `${threadRef.environmentId}:${threadRef.threadId}:${preferredPlacement?.width ?? "popup"}:${editing}:${contentKey}`;
const [measurements, setMeasurements] = useState({
key: measurementKey,
heights: { full: 0, compact: 0 },
});
const contentHeights =
measurements.key === measurementKey ? measurements.heights : { full: 0, compact: 0 };
const height = placement?.height ?? Math.max(0, (canvas?.container.height ?? 0) - 52);
const density = resolveThreadDetailsCardDensity(height, contentHeights);
const density = editing ? "full" : resolveThreadDetailsCardDensity(height, contentHeights);
const reportDetailsCard = canvas?.reportDetailsCard;
const cardLeft = preferredPlacement?.x;
const cardRight = preferredPlacement
Expand Down Expand Up @@ -95,18 +102,19 @@ export function ThreadDetailsCard({
observer.observe(element);
return () => observer.disconnect();
}, [contentElement, density, measurementKey]);
const card = (
const renderCard = (content: ReactNode, action?: ReactNode) => (
<div
className={cn(
"dropdown-glass isolate contain-paint grid max-h-full grid-rows-[minmax(0,1fr)] overflow-hidden rounded-3xl",
"dropdown-glass isolate contain-paint group/thread-details relative grid max-h-full grid-rows-[minmax(0,1fr)] overflow-hidden rounded-3xl",
mode === "popover" &&
"max-h-[min(calc(100dvh-6.5rem),calc(var(--available-height,100dvh)-1rem))]",
)}
style={placement ? { maxHeight: height } : undefined}
data-thread-details-card
>
{action}
<ScrollArea scrollFade className="min-h-0">
<div ref={setContentElement}>{children(density)}</div>
<div ref={setContentElement}>{content}</div>
</ScrollArea>
</div>
);
Expand All @@ -132,7 +140,7 @@ export function ThreadDetailsCard({
data-density={density}
data-thread-details-panel="inline"
>
{card}
{children(density, renderCard)}
</aside>
) : null
) : (
Expand All @@ -146,8 +154,8 @@ export function ThreadDetailsCard({
variant="panel"
padding="none"
>
<div data-density={density} data-thread-details-panel="popover">
{card}
<div className="relative" data-density={density} data-thread-details-panel="popover">
{children(density, renderCard)}
</div>
</PopoverPopup>
)}
Expand Down
Loading
Loading