a transcript with nothing in it can say nothing
"Send a message to start" is right on the chat screen and wrong under a setup card that already says what to do — it contradicts the Start button above it and takes 200px of height doing so, which is what squeezed the new-commander pane into a scroller a third of the panel tall. `emptyState` is optional and three-valued on purpose: undefined keeps the default, a node replaces it, null shows nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
import type { UseChatType } from '../../../hooks/useChat';
|
import type { UseChatType } from '../../../hooks/useChat';
|
||||||
import { useEmbeddableChat } from './useEmbeddableChat';
|
import { useEmbeddableChat } from './useEmbeddableChat';
|
||||||
import { MessageList } from '../components/MessageList';
|
import { MessageList } from '../components/MessageList';
|
||||||
@@ -5,6 +6,14 @@ import { InputArea } from '../components/InputArea';
|
|||||||
|
|
||||||
type EmbeddableChatProps = {
|
type EmbeddableChatProps = {
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
|
/**
|
||||||
|
* What the transcript shows before there is anything in it. `null` shows nothing.
|
||||||
|
*
|
||||||
|
* The default, "Send a message to start", is true on the chat screen and wrong wherever something else
|
||||||
|
* already explains what to do — a new commander has a setup card and a Start button above it, so the
|
||||||
|
* message contradicts them and takes 200px of height while doing it.
|
||||||
|
*/
|
||||||
|
emptyState?: ReactNode;
|
||||||
/** Which panel this is — see `queue-store`. Absent on the screen, where there is only one chat. */
|
/** Which panel this is — see `queue-store`. Absent on the screen, where there is only one chat. */
|
||||||
panelId?: string;
|
panelId?: string;
|
||||||
initialModel?: string | null;
|
initialModel?: string | null;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
import type { KeyboardEvent } from 'react';
|
import type { KeyboardEvent } from 'react';
|
||||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||||
import { useUserVisibleModels } from 'state/useModels';
|
import { useUserVisibleModels } from 'state/useModels';
|
||||||
@@ -51,6 +52,7 @@ type UseEmbeddableChatParams = {
|
|||||||
contextId?: string;
|
contextId?: string;
|
||||||
/** This session is a commander, per `GET /chat/sessions/:id`. See the note where it is surfaced. */
|
/** This session is a commander, per `GET /chat/sessions/:id`. See the note where it is surfaced. */
|
||||||
isCommander?: boolean;
|
isCommander?: boolean;
|
||||||
|
emptyState?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -622,6 +624,7 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams, onMessageComp
|
|||||||
would lose the brand-new composer, which has no session yet.
|
would lose the brand-new composer, which has no session yet.
|
||||||
*/
|
*/
|
||||||
isCommander: params.context === 'command-hq' || params.isCommander === true,
|
isCommander: params.context === 'command-hq' || params.isCommander === true,
|
||||||
|
emptyState: params.emptyState,
|
||||||
/*
|
/*
|
||||||
Claude's transcript id, which is what a commander is keyed on everywhere — the Command HQ row, the
|
Claude's transcript id, which is what a commander is keyed on everywhere — the Command HQ row, the
|
||||||
record of what it spawned, the delete cascade. `sessionId` above is OFFICER's handle for the same
|
record of what it spawned, the delete cascade. `sessionId` above is OFFICER's handle for the same
|
||||||
|
|||||||
@@ -107,11 +107,17 @@ export const MessageList = ({ manager }: MessageListProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div ref={scrollViewportRef} className="h-full overflow-y-auto">
|
<div ref={scrollViewportRef} className="h-full overflow-y-auto">
|
||||||
{rows.length === 0 && !isGenerating && (
|
{rows.length === 0 &&
|
||||||
<div className="flex items-center justify-center h-full min-h-[200px] text-muted-foreground text-sm">
|
!isGenerating &&
|
||||||
Send a message to start
|
// `undefined` means nobody said, so keep the default. `null` means "show nothing", which is what
|
||||||
</div>
|
// a screen that already explains itself above the transcript passes.
|
||||||
)}
|
(manager.emptyState !== undefined ? (
|
||||||
|
manager.emptyState
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center h-full min-h-[200px] text-muted-foreground text-sm">
|
||||||
|
Send a message to start
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
{rows.length > 0 && (
|
{rows.length > 0 && (
|
||||||
<ToolOpenProvider>
|
<ToolOpenProvider>
|
||||||
<div className="relative w-full" style={{ height: virtualizer.getTotalSize() }}>
|
<div className="relative w-full" style={{ height: virtualizer.getTotalSize() }}>
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ function NewCommander({
|
|||||||
!started && !ready ? { reason: !name.trim() ? 'Name it first…' : 'Choose a working directory first…' } : false
|
!started && !ready ? { reason: !name.trim() ? 'Name it first…' : 'Choose a working directory first…' } : false
|
||||||
}
|
}
|
||||||
className={!started ? 'shrink-0' : 'min-h-0 flex-1'}
|
className={!started ? 'shrink-0' : 'min-h-0 flex-1'}
|
||||||
|
{...(!started ? { emptyState: null } : {})}
|
||||||
/>
|
/>
|
||||||
<DirPickerModal open={pickerOpen} onClose={() => setPickerOpen(false)} onSelect={setCwd} />
|
<DirPickerModal open={pickerOpen} onClose={() => setPickerOpen(false)} onSelect={setCwd} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user