Skip to content

Commit b02e038

Browse files
committed
feat(agentStudio): new conversation fixes
1 parent ca599aa commit b02e038

File tree

9 files changed

+293
-85
lines changed

9 files changed

+293
-85
lines changed

packages/docsearch-core/src/DocSearch.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface DocSearchContext {
5454
isModalActive: boolean;
5555
onAskAiToggle: OnAskAiToggle;
5656
initialAskAiMessage: InitialAskAiMessage | undefined;
57+
clearInitialAskAiMessage: () => void;
5758
registerView: (view: View) => void;
5859
isHybridModeSupported: boolean;
5960
}
@@ -183,6 +184,10 @@ function DocSearchInner(
183184
[setDocsearchState, setInitialQuery],
184185
);
185186

187+
const clearInitialAskAiMessage = React.useCallback((): void => {
188+
setInitialAskAiMessage(undefined);
189+
}, []);
190+
186191
const registerView = React.useCallback(
187192
(view: View): void => {
188193
if (registeredViews.has(view)) return;
@@ -246,6 +251,7 @@ function DocSearchInner(
246251
isModalActive,
247252
onAskAiToggle,
248253
initialAskAiMessage,
254+
clearInitialAskAiMessage,
249255
registerView,
250256
isHybridModeSupported,
251257
}),
@@ -260,6 +266,7 @@ function DocSearchInner(
260266
isModalActive,
261267
onAskAiToggle,
262268
initialAskAiMessage,
269+
clearInitialAskAiMessage,
263270
registerView,
264271
isHybridModeSupported,
265272
],

packages/docsearch-css/src/sidepanel.css

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@
1919
--docsearch-sidepanel-hit-highlight-color: var(--docsearch-hit-highlight-color);
2020
--docsearch-sidepanel-button-background: var(--docsearch-sidepanel-background);
2121
--docsearch-sidepanel-button-background-dark: var(--docsearch-sidepanel-background);
22+
--docsearch-sidepanel-thread-depth-banner-bg: #fff1f2;
23+
--docsearch-sidepanel-thread-depth-banner-border: #fecdd3;
2224
}
2325

2426
html[data-theme='dark'] {
2527
--docsearch-sidepanel-text-base: var(--docsearch-text-color);
2628
--docsearch-sidepanel-primary-disabled: rgba(1, 45, 186, 0.60);
2729
--docsearch-sidepanel-button-background-dark: rgba(4, 4, 8, 1);
30+
--docsearch-sidepanel-thread-depth-banner-bg: rgb(239 83 80 / 12%);
31+
--docsearch-sidepanel-thread-depth-banner-border: rgb(254 205 211 / 35%);
2832
}
2933

3034
.DocSearch-SidepanelButton {
@@ -623,9 +627,38 @@ html[data-theme="dark"] .DocSearch-Sidepanel-Prompt--stop:hover {
623627
flex-direction: column;
624628
}
625629

626-
.DocSearch-Sidepanel-ConversationScreen-threadDepth {
627-
margin: 0 1rem 0.75rem;
630+
@keyframes docsearch-sidepanel-thread-depth-banner-enter {
631+
from {
632+
opacity: 0;
633+
transform: translateY(-10px);
634+
}
635+
636+
to {
637+
opacity: 1;
638+
transform: translateY(0);
639+
}
640+
}
641+
642+
.DocSearch-Sidepanel .DocSearch-Sidepanel-ThreadDepthBanner {
643+
display: flex;
644+
flex-direction: column;
645+
margin: 0;
628646
flex-shrink: 0;
647+
padding: 0.75rem 1rem;
648+
border-radius: 0.5rem;
649+
background-color: var(--docsearch-sidepanel-thread-depth-banner-bg);
650+
border: 1px solid var(--docsearch-sidepanel-thread-depth-banner-border);
651+
box-sizing: border-box;
652+
color: var(--docsearch-sidepanel-text-base);
653+
font-size: 0.75rem;
654+
line-height: 1rem;
655+
font-weight: 400;
656+
width: 100%;
657+
animation: docsearch-sidepanel-thread-depth-banner-enter 0.3s ease-out;
658+
}
659+
660+
.DocSearch-Sidepanel .DocSearch-Sidepanel-ThreadDepthBanner p {
661+
margin: 0;
629662
}
630663

631664
@media screen and (min-width: 769px) {

packages/docsearch-react/src/DocSearchModal.tsx

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,18 @@ export function DocSearchModal({
398398

399399
const [stoppedStream, setStoppedStream] = React.useState(false);
400400

401-
const { messages, status, setMessages, sendMessage, stopAskAiStreaming, askAiError, sendFeedback, conversations } =
402-
useAskAi({
401+
const {
402+
messages,
403+
status,
404+
sendMessage,
405+
stopAskAiStreaming,
406+
askAiError,
407+
sendFeedback,
408+
conversations,
409+
clearError,
410+
resetAskAiAbortScope,
411+
resetAskAiChatSession,
412+
} = useAskAi({
403413
assistantId: askAiConfigurationId,
404414
apiKey: askAiConfig?.apiKey || apiKey,
405415
appId: askAiConfig?.appId || appId,
@@ -423,9 +433,12 @@ export function DocSearchModal({
423433
};
424434
}
425435

426-
for (const part of messages[0].parts) {
427-
if (part.type === 'text') {
428-
conversations.add(buildDummyAskAiHit(part.text, messages));
436+
const first = messages[0];
437+
if (first?.parts) {
438+
for (const part of first.parts) {
439+
if (part.type === 'text') {
440+
conversations.add(buildDummyAskAiHit(part.text, messages));
441+
}
429442
}
430443
}
431444
}
@@ -533,6 +546,8 @@ export function DocSearchModal({
533546
if (isHybridModeSupported) return;
534547

535548
setStoppedStream(false);
549+
resetAskAiAbortScope();
550+
clearError();
536551

537552
const messageOptions: ChatRequestOptions = {};
538553

@@ -571,7 +586,16 @@ export function DocSearchModal({
571586
autocompleteRef.current.setQuery('');
572587
}
573588
},
574-
[onAskAiToggle, interceptAskAiEvent, sendMessage, askAiState, setAskAiState, isHybridModeSupported],
589+
[
590+
onAskAiToggle,
591+
interceptAskAiEvent,
592+
askAiState,
593+
setAskAiState,
594+
isHybridModeSupported,
595+
clearError,
596+
resetAskAiAbortScope,
597+
sendMessage,
598+
],
575599
);
576600

577601
// feedback handler
@@ -623,7 +647,10 @@ export function DocSearchModal({
623647
},
624648
onSelect({ item }): void {
625649
if (item.messages) {
626-
setMessages(item.messages as any);
650+
resetAskAiChatSession({
651+
kind: 'setMessages',
652+
messages: item.messages as AIMessage[],
653+
});
627654
onAskAiToggle(true);
628655
}
629656
},
@@ -793,14 +820,18 @@ export function DocSearchModal({
793820
};
794821
}, []);
795822

796-
// Refresh the autocomplete results when ask ai is toggled off
797-
// helps return to the previous ac state and start screen
823+
// Refresh autocomplete and rotate the chat session only when Ask AI is turned off — not on every
824+
// mount while inactive. `clearError` from `useChat` can change when `chatSessionId` changes; listing
825+
// it (and re-running `resetAskAiChatSession` on that) caused an infinite update loop.
826+
const prevIsAskAiActiveRef = React.useRef(isAskAiActive);
798827
React.useEffect(() => {
799-
if (!isAskAiActive) {
828+
if (prevIsAskAiActiveRef.current && !isAskAiActive) {
800829
autocomplete.refresh();
801-
setMessages([]);
830+
clearError();
831+
resetAskAiChatSession();
802832
}
803-
}, [isAskAiActive, autocomplete, setMessages]);
833+
prevIsAskAiActiveRef.current = isAskAiActive;
834+
}, [isAskAiActive, autocomplete, clearError, resetAskAiChatSession]);
804835

805836
// Track external state in order to manage internal askAiState
806837
React.useEffect(() => {
@@ -814,7 +845,8 @@ export function DocSearchModal({
814845
};
815846

816847
const handleNewConversation = (): void => {
817-
setMessages([]);
848+
clearError();
849+
resetAskAiChatSession();
818850
setAskAiState('new-conversation');
819851
};
820852

@@ -913,7 +945,10 @@ export function DocSearchModal({
913945
if (item.type === 'askAI' && item.query) {
914946
// if the item is askAI and the anchor is stored
915947
if (item.anchor === 'stored' && 'messages' in item) {
916-
setMessages(item.messages as any);
948+
resetAskAiChatSession({
949+
kind: 'setMessages',
950+
messages: item.messages as AIMessage[],
951+
});
917952
const initialMessage: InitialAskAiMessage = {
918953
query: item.query,
919954
messageId: (item.messages as StoredAskAiMessage[])[0].id,

packages/docsearch-react/src/Sidepanel.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,21 @@ function DocSearchSidepanelComp({
115115
panel: { portalContainer, ...panelProps } = {},
116116
...rootProps
117117
}: DocSearchSidepanelProps): JSX.Element {
118-
const { docsearchState, setDocsearchState, keyboardShortcuts, registerView, initialAskAiMessage } = useDocSearch();
118+
const {
119+
docsearchState,
120+
setDocsearchState,
121+
keyboardShortcuts,
122+
registerView,
123+
initialAskAiMessage,
124+
clearInitialAskAiMessage,
125+
} = useDocSearch();
119126

120127
const toggleSidepanelState = React.useCallback(() => {
121128
setDocsearchState(docsearchState === 'sidepanel' ? 'ready' : 'sidepanel');
122129
}, [docsearchState, setDocsearchState]);
123130

124131
const handleClose = (): void => {
132+
clearInitialAskAiMessage();
125133
setDocsearchState('ready');
126134
};
127135

packages/docsearch-react/src/Sidepanel/ConversationScreen.tsx

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ export type ConversationScreenProps = {
8080
handleFeedback?: (messageId: string, thumbs: 0 | 1) => Promise<void>;
8181
streamError?: Error;
8282
agentStudio?: boolean;
83-
showThreadDepthError?: boolean;
84-
onThreadDepthNewConversation?: () => void;
8583
};
8684

8785
type ConversationnExchangeProps = {
@@ -245,18 +243,9 @@ const ConversationExchange = React.forwardRef<HTMLDivElement, ConversationnExcha
245243
);
246244

247245
export const ConversationScreen = memo(
248-
({
249-
exchanges,
250-
translations = {},
251-
handleFeedback,
252-
showThreadDepthError = false,
253-
onThreadDepthNewConversation,
254-
...props
255-
}: ConversationScreenProps): JSX.Element => {
246+
({ exchanges, translations = {}, handleFeedback, ...props }: ConversationScreenProps): JSX.Element => {
256247
const {
257248
conversationDisclaimer = 'Answers are generated with AI which can make mistakes. Verify responses.',
258-
threadDepthExceededMessage = 'This conversation is now closed to keep responses accurate.',
259-
startNewConversationButtonText = 'Start a new conversation',
260249
} = translations;
261250

262251
const mostRecentExchangeRef = React.useRef<HTMLDivElement>(null);
@@ -274,23 +263,6 @@ export const ConversationScreen = memo(
274263

275264
return (
276265
<div className="DocSearch-Sidepanel-ConversationScreen">
277-
{showThreadDepthError && onThreadDepthNewConversation ? (
278-
<div className="DocSearch-AskAiScreen-MessageContent DocSearch-AskAiScreen-Error DocSearch-AskAiScreen-Error--ThreadDepth DocSearch-Sidepanel-ConversationScreen-threadDepth">
279-
<div className="DocSearch-AskAiScreen-Error-Content">
280-
<p>
281-
{threadDepthExceededMessage}{' '}
282-
<button
283-
type="button"
284-
className="DocSearch-ThreadDepthError-Link"
285-
onClick={onThreadDepthNewConversation}
286-
>
287-
{startNewConversationButtonText}
288-
</button>{' '}
289-
to continue.
290-
</p>
291-
</div>
292-
</div>
293-
) : null}
294266
<p className="DocSearch-Sidepanel-ConversationScreen-disclaimer">{conversationDisclaimer}</p>
295267

296268
{exchanges.slice().map((exchange, idx) => {

packages/docsearch-react/src/Sidepanel/PromptForm.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,25 @@ type Props = {
4444
onStopStreaming: () => void;
4545
/** When true, the prompt is disabled (same as modal SearchBox in Ask AI mode). */
4646
isThreadDepthError?: boolean;
47+
/**
48+
* Rendered above the textarea inside the prompt chrome (e.g. thread-depth AI-217 banner).
49+
*/
50+
threadDepthBanner?: React.ReactNode;
4751
};
4852

4953
const MAX_PROMPT_ROWS = 8;
5054

5155
export const PromptForm = React.forwardRef<HTMLTextAreaElement, Props>(
5256
(
53-
{ exchanges, isStreaming, translations = {}, onSend, onStopStreaming, isThreadDepthError = false },
57+
{
58+
exchanges,
59+
isStreaming,
60+
translations = {},
61+
onSend,
62+
onStopStreaming,
63+
isThreadDepthError = false,
64+
threadDepthBanner,
65+
},
5466
ref,
5567
): JSX.Element => {
5668
const isMobile = useIsMobile();
@@ -134,6 +146,7 @@ export const PromptForm = React.forwardRef<HTMLTextAreaElement, Props>(
134146

135147
return (
136148
<div className="DocSearch-Sidepanel-Prompt">
149+
{threadDepthBanner}
137150
<form
138151
className="DocSearch-Sidepanel-Prompt--form"
139152
onSubmit={(e) => {

0 commit comments

Comments
 (0)