Skip to content

Commit 318f4c4

Browse files
committed
Address PR review: validate agent config, scroll only when at bottom, make pre-tool hiding opt-out
1 parent df29c11 commit 318f4c4

3 files changed

Lines changed: 61 additions & 16 deletions

File tree

src/agent-chat/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,15 @@ import { AgentChatWidget } from "@site/src/agent-chat";
4444

4545
## Key props (`AgentChat`)
4646

47-
| Prop | Purpose |
48-
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49-
| `config` | `{ appId, agentId, apiKey, host?, stream? }` connection details. |
50-
| `welcomeMessage` | Shown on the empty state. |
51-
| `suggestions` | Starter prompt buttons. |
52-
| `placeholder` | Input placeholder. |
53-
| `markdownClassName` | Class applied to rendered answers so they inherit the host's prose styling (e.g. Docusaurus `"markdown"`). |
54-
| `codeBlock` | Component used for fenced code. Defaults to a built-in copyable block; pass the host's highlighter (e.g. Docusaurus `@theme/CodeBlock`) for syntax highlighting. |
47+
| Prop | Purpose |
48+
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49+
| `config` | `{ appId, agentId, apiKey, host?, stream? }` connection details. |
50+
| `welcomeMessage` | Shown on the empty state. |
51+
| `suggestions` | Starter prompt buttons. |
52+
| `placeholder` | Input placeholder. |
53+
| `markdownClassName` | Class applied to rendered answers so they inherit the host's prose styling (e.g. Docusaurus `"markdown"`). |
54+
| `codeBlock` | Component used for fenced code. Defaults to a built-in copyable block; pass the host's highlighter (e.g. Docusaurus `@theme/CodeBlock`) for syntax highlighting. |
55+
| `hideStatusUntilToolCall` | Hide a streaming reply until a tool call occurs, suppressing pre-search "status" chatter. Defaults to `true`. Set `false` for agents that answer without tools. |
5556

5657
`AgentChatWidget` accepts all of the above plus `title`.
5758

src/agent-chat/react/AgentChat.tsx

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ export interface AgentChatProps {
3131
* for syntax highlighting that matches the site.
3232
*/
3333
codeBlock?: CodeBlockComponent;
34+
/**
35+
* While an assistant message is streaming, hide its text until a tool call
36+
* has occurred. This suppresses the "Let me find that…" status chatter that
37+
* tool-using agents emit before each search. Defaults to `true`.
38+
*
39+
* Set to `false` for agents that answer **without** calling tools, otherwise
40+
* their reply won't appear until streaming finishes.
41+
*/
42+
hideStatusUntilToolCall?: boolean;
3443
}
3544

3645
/** Collect every non-empty text segment of a message, in order. */
@@ -69,11 +78,16 @@ function hasToolCall(message: UIMessage): boolean {
6978
/**
7079
* Text to show for an assistant message. While it's still streaming, the
7180
* pre-tool status chatter ("Let me find that…") would otherwise flash in and
72-
* then vanish once a tool call arrives, so we reveal nothing until a tool call
73-
* has happened. Once streaming finishes, show the final answer regardless.
81+
* then vanish once a tool call arrives, so (when `hideUntilTool`) we reveal
82+
* nothing until a tool call has happened. Once streaming finishes, show the
83+
* final answer regardless.
7484
*/
75-
function visibleAnswer(message: UIMessage, streaming: boolean): string[] {
76-
if (streaming && !hasToolCall(message)) {
85+
function visibleAnswer(
86+
message: UIMessage,
87+
streaming: boolean,
88+
hideUntilTool: boolean,
89+
): string[] {
90+
if (streaming && hideUntilTool && !hasToolCall(message)) {
7791
return [];
7892
}
7993
return answerParts(message);
@@ -84,16 +98,18 @@ function Message({
8498
markdownClassName,
8599
codeBlock,
86100
streaming,
101+
hideStatusUntilToolCall,
87102
}: {
88103
message: UIMessage;
89104
markdownClassName?: string;
90105
codeBlock?: CodeBlockComponent;
91106
streaming: boolean;
107+
hideStatusUntilToolCall: boolean;
92108
}) {
93109
const isUser = message.role === "user";
94110
const segments = isUser
95111
? textParts(message)
96-
: visibleAnswer(message, streaming);
112+
: visibleAnswer(message, streaming, hideStatusUntilToolCall);
97113
if (segments.length === 0) {
98114
return null;
99115
}
@@ -144,18 +160,31 @@ export function AgentChat({
144160
suggestions,
145161
markdownClassName,
146162
codeBlock,
163+
hideStatusUntilToolCall = true,
147164
}: AgentChatProps) {
148165
const { messages, sendMessage, status, error, stop, regenerate } =
149166
useAgentChat(config);
150167
const [input, setInput] = useState("");
151168
const scrollRef = useRef<HTMLDivElement>(null);
169+
// Whether the user was at the bottom of the log before the latest update.
170+
const atBottomRef = useRef(true);
152171

153172
const isBusy = status === "submitted" || status === "streaming";
154173
const isEmpty = messages.length === 0;
155174

156-
useEffect(() => {
175+
const handleScroll = () => {
157176
const el = scrollRef.current;
158177
if (el) {
178+
atBottomRef.current =
179+
el.scrollHeight - el.scrollTop - el.clientHeight < 60;
180+
}
181+
};
182+
183+
// Auto-scroll on new content, but only if the user hasn't scrolled up to
184+
// read earlier messages.
185+
useEffect(() => {
186+
const el = scrollRef.current;
187+
if (el && atBottomRef.current) {
159188
el.scrollTop = el.scrollHeight;
160189
}
161190
}, [messages, status]);
@@ -185,13 +214,16 @@ export function AgentChat({
185214
// (covers: awaiting first token, and running tools before the answer starts).
186215
const showThinking =
187216
isBusy &&
188-
(!last || last.role === "user" || visibleAnswer(last, true).length === 0);
217+
(!last ||
218+
last.role === "user" ||
219+
visibleAnswer(last, true, hideStatusUntilToolCall).length === 0);
189220

190221
return (
191222
<div className={clsx(styles.container, className)}>
192223
<div
193224
className={styles.messages}
194225
ref={scrollRef}
226+
onScroll={handleScroll}
195227
role="log"
196228
aria-live="polite"
197229
>
@@ -224,6 +256,7 @@ export function AgentChat({
224256
markdownClassName={markdownClassName}
225257
codeBlock={codeBlock}
226258
streaming={message.id === streamingId}
259+
hideStatusUntilToolCall={hideStatusUntilToolCall}
227260
/>
228261
))}
229262

src/hooks/useAgentChatConfig.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,16 @@ import type { AgentChatConfig } from "@site/src/agent-chat";
1010
*/
1111
export function useAgentChatConfig(): AgentChatConfig {
1212
const { siteConfig } = useDocusaurusContext();
13-
return siteConfig.customFields!.agentChat as AgentChatConfig;
13+
const config = siteConfig.customFields?.agentChat as
14+
| AgentChatConfig
15+
| undefined;
16+
17+
if (!config?.appId || !config?.agentId || !config?.apiKey) {
18+
throw new Error(
19+
"Agent Studio config is missing. Set `customFields.agentChat` " +
20+
"({ appId, agentId, apiKey }) in docusaurus.config.ts.",
21+
);
22+
}
23+
24+
return config;
1425
}

0 commit comments

Comments
 (0)