fix(askai): Ask AI fixes for v5 (#2945)
This commit is contained in:
parent
b87c28fc87
commit
9fe6738a35
16 changed files with 217 additions and 111 deletions
20
.changeset/ready-clubs-serve.md
Normal file
20
.changeset/ready-clubs-serve.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
"@docsearch/react": patch
|
||||||
|
"@docsearch/css": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(askai): Ask AI fixes for v5
|
||||||
|
|
||||||
|
- `getMessageContent` now joins every assistant text part instead of stopping
|
||||||
|
at the first one, so copying an answer that interleaves text with tool calls
|
||||||
|
returns the complete response (#2782)
|
||||||
|
- Key Ask AI conversation storage by `appId` instead of `indexName`, fixes
|
||||||
|
triggering a new conversation in hybrid mode
|
||||||
|
- Negative feedback panel is labelled with `aria-labelledby` and reason chips
|
||||||
|
expose selection via `aria-pressed` (styling moved off the
|
||||||
|
`--selected` modifier)
|
||||||
|
- `Popover.Trigger` forwards refs, fixes sources panel not showing in some cases
|
||||||
|
- Add `--docsearch-error-soft-color`, `--docsearch-error-text-color`, and
|
||||||
|
`--docsearch-code-block-background` variables; restyle the Ask AI error panel
|
||||||
|
and markdown code blocks to use them
|
||||||
|
```
|
||||||
|
|
@ -317,7 +317,8 @@
|
||||||
"typescript/prefer-namespace-keyword": ["error"],
|
"typescript/prefer-namespace-keyword": ["error"],
|
||||||
"typescript/prefer-ts-expect-error": ["error"],
|
"typescript/prefer-ts-expect-error": ["error"],
|
||||||
"typescript/unified-signatures": ["error"],
|
"typescript/unified-signatures": ["error"],
|
||||||
"vitest/require-mock-type-parameters": "off"
|
"vitest/require-mock-type-parameters": "off",
|
||||||
|
"no-debugger": ["error"]
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
21
AGENTS.md
21
AGENTS.md
|
|
@ -78,27 +78,6 @@ bun run pw:run:webkit
|
||||||
|
|
||||||
## Code Style Guidelines
|
## Code Style Guidelines
|
||||||
|
|
||||||
### Imports
|
|
||||||
|
|
||||||
Imported modules must be ordered alphabetically with newlines between groups:
|
|
||||||
|
|
||||||
1. Built-in modules
|
|
||||||
2. External dependencies
|
|
||||||
3. Parent directory imports
|
|
||||||
4. Sibling imports
|
|
||||||
5. Index imports
|
|
||||||
|
|
||||||
Internal `@/**/*` paths go before parent imports.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Correct order
|
|
||||||
import type { AutocompleteOptions } from '@algolia/autocomplete-core';
|
|
||||||
import React, { type JSX } from 'react';
|
|
||||||
|
|
||||||
import { DocSearchButton } from './DocSearchButton';
|
|
||||||
import type { DocSearchHit } from './types';
|
|
||||||
```
|
|
||||||
|
|
||||||
### TypeScript
|
### TypeScript
|
||||||
|
|
||||||
- Use `type` imports for type-only imports: `import type { Foo } from './types'`
|
- Use `type` imports for type-only imports: `import type { Foo } from './types'`
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
/* eslint-disable react/react-in-jsx-scope */
|
/* eslint-disable react/react-in-jsx-scope */
|
||||||
import { version } from '@docsearch/react';
|
import { version } from '@docsearch/react';
|
||||||
import { DocSearchSidepanel } from '@docsearch/react/sidepanel';
|
|
||||||
import { useState, type JSX } from 'react';
|
import { useState, type JSX } from 'react';
|
||||||
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
@ -121,17 +120,6 @@ function App(): JSX.Element {
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DocSearchSidepanel
|
|
||||||
assistantId="ccdec697-e3fe-465b-a1c3-657e7bf18aef"
|
|
||||||
indexName="docsearch"
|
|
||||||
appId="PMZUYBQDAK"
|
|
||||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
|
||||||
panel={{
|
|
||||||
suggestedQuestions: true,
|
|
||||||
}}
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -201,19 +201,12 @@
|
||||||
|
|
||||||
.DocSearch-AskAiScreen-Answer-Footer {
|
.DocSearch-AskAiScreen-Answer-Footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-AskAiScreen-Actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
gap: var(--docsearch-spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
.DocSearch-AskAiScreen-Actions-Controls {
|
.DocSearch-AskAiScreen-Actions-Controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
@ -226,13 +219,16 @@
|
||||||
.DocSearch-Feedback-Panel {
|
.DocSearch-Feedback-Panel {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: var(--docsearch-spacing);
|
padding-block: var(--docsearch-spacing);
|
||||||
|
padding-inline: var(--docsearch-spacing);
|
||||||
|
margin-block-start: var(--docsearch-spacing);
|
||||||
border-top: 1px solid var(--docsearch-subtle-color);
|
border-top: 1px solid var(--docsearch-subtle-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 0.75rem;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
animation: fade-in 0.2s ease-in forwards;
|
animation: fade-in 0.2s ease-in forwards;
|
||||||
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-Feedback-Panel-Header {
|
.DocSearch-Feedback-Panel-Header {
|
||||||
|
|
@ -253,7 +249,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 4px;
|
padding-inline: 0.25rem;
|
||||||
|
padding-block: 0.25rem;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: none;
|
background: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -267,8 +264,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-Feedback-Panel-Close svg {
|
.DocSearch-Feedback-Panel-Close svg {
|
||||||
width: 16px;
|
width: 1rem;
|
||||||
height: 16px;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-Feedback-Panel-Reasons {
|
.DocSearch-Feedback-Panel-Reasons {
|
||||||
|
|
@ -280,8 +277,9 @@
|
||||||
.DocSearch-Feedback-Panel-Reason {
|
.DocSearch-Feedback-Panel-Reason {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 0.375rem;
|
||||||
padding: 6px 12px;
|
padding-block: 0.375rem;
|
||||||
|
padding-inline: 0.75rem;
|
||||||
border-radius: var(--docsearch-border-radius);
|
border-radius: var(--docsearch-border-radius);
|
||||||
border: 1px solid var(--docsearch-subtle-color);
|
border: 1px solid var(--docsearch-subtle-color);
|
||||||
background: var(--docsearch-hit-background);
|
background: var(--docsearch-hit-background);
|
||||||
|
|
@ -293,18 +291,13 @@
|
||||||
border-color 0.2s ease,
|
border-color 0.2s ease,
|
||||||
background-color 0.2s ease,
|
background-color 0.2s ease,
|
||||||
color 0.2s ease;
|
color 0.2s ease;
|
||||||
}
|
|
||||||
|
|
||||||
.DocSearch-Feedback-Panel-Reason:hover {
|
&:hover,
|
||||||
border-color: var(--docsearch-highlight-color);
|
&[aria-pressed='true'] {
|
||||||
background: var(--docsearch-hit-highlight-color);
|
border-color: var(--docsearch-highlight-color);
|
||||||
}
|
background: var(--docsearch-hit-highlight-color);
|
||||||
|
color: var(--docsearch-highlight-color);
|
||||||
.DocSearch-Feedback-Panel-Reason--selected {
|
}
|
||||||
border-color: var(--docsearch-highlight-color);
|
|
||||||
background: var(--docsearch-soft-primary-color);
|
|
||||||
color: var(--docsearch-highlight-color);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-Feedback-Panel-Reason svg {
|
.DocSearch-Feedback-Panel-Reason svg {
|
||||||
|
|
@ -365,8 +358,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 36px;
|
padding-block: 0.5em;
|
||||||
padding: 6px 15px;
|
padding-inline: 1em;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: var(--docsearch-border-radius);
|
border-radius: var(--docsearch-border-radius);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
|
@ -502,9 +495,9 @@
|
||||||
padding-inline: 1em;
|
padding-inline: 1em;
|
||||||
padding-block: 1em;
|
padding-block: 1em;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
background-color: var(--docsearch-background-color);
|
background-color: var(--docsearch-error-soft-color);
|
||||||
border-radius: var(--docsearch-modal-radius);
|
border-radius: var(--docsearch-border-radius);
|
||||||
color: var(--docsearch-text-color);
|
color: var(--docsearch-error-text-color);
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
@ -525,6 +518,7 @@
|
||||||
|
|
||||||
.DocSearch-AskAiScreen-Error p {
|
.DocSearch-AskAiScreen-Error p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
color: var(--docsearch-error-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-AskAiScreen-Error-Content {
|
.DocSearch-AskAiScreen-Error-Content {
|
||||||
|
|
@ -543,7 +537,7 @@
|
||||||
margin: 12px 0 8px;
|
margin: 12px 0 8px;
|
||||||
color: var(--docsearch-text-color);
|
color: var(--docsearch-text-color);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border: 1px solid #febdc5;
|
border: 1px solid var(--docsearch-error-soft-color);
|
||||||
animation: slide-down 0.3s ease-out;
|
animation: slide-down 0.3s ease-out;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
@ -679,7 +673,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-Markdown-Content p {
|
.DocSearch-Markdown-Content p {
|
||||||
margin: 1em 0;
|
margin-block: 1em;
|
||||||
|
margin-inline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-Markdown-Content p:last-child {
|
.DocSearch-Markdown-Content p:last-child {
|
||||||
|
|
@ -691,11 +686,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-Markdown-Content code {
|
.DocSearch-Markdown-Content code {
|
||||||
background-color: var(--docsearch-key-background);
|
background-color: var(--docsearch-code-block-background);
|
||||||
color: var(--docsearch-text-color);
|
color: var(--docsearch-text-color);
|
||||||
padding: 0.2em 0;
|
padding-block: 0.2em;
|
||||||
|
padding-inline: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 3px;
|
border-radius: var(--docsearch-border-radius);
|
||||||
font-family:
|
font-family:
|
||||||
ui-monospace,
|
ui-monospace,
|
||||||
SFMono-Regular,
|
SFMono-Regular,
|
||||||
|
|
@ -704,17 +700,19 @@
|
||||||
Consolas,
|
Consolas,
|
||||||
'Liberation Mono',
|
'Liberation Mono',
|
||||||
monospace;
|
monospace;
|
||||||
font-size: 0.9em;
|
font-size: 0.9rem;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-Markdown-Content pre {
|
.DocSearch-Markdown-Content pre {
|
||||||
background-color: var(--docsearch-key-background);
|
background-color: var(--docsearch-code-block-background);
|
||||||
color: var(--docsearch-text-color);
|
color: var(--docsearch-text-color);
|
||||||
padding: 1.2em;
|
padding-block: 1.2em;
|
||||||
border-radius: 6px;
|
padding-inline: 1.2em;
|
||||||
|
border-radius: var(--docsearch-border-radius);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin: 1.5em 0;
|
margin-block: 1.5em;
|
||||||
|
margin-inline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.DocSearch-Markdown-Content pre code {
|
.DocSearch-Markdown-Content pre code {
|
||||||
|
|
@ -722,7 +720,6 @@
|
||||||
color: inherit;
|
color: inherit;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.8em;
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
|
@ -1059,7 +1056,8 @@
|
||||||
|
|
||||||
.DocSearch-AskAiScreen-Sources {
|
.DocSearch-AskAiScreen-Sources {
|
||||||
padding-inline: 0;
|
padding-inline: 0;
|
||||||
margin-top: 0.5rem;
|
margin-block: 0;
|
||||||
|
margin-block-start: 0.5rem;
|
||||||
color: var(--docsearch-text-color);
|
color: var(--docsearch-text-color);
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -1100,11 +1098,12 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset-block-start: 8px;
|
inset-block-start: 8px;
|
||||||
inset-inline-end: 8px;
|
inset-inline-end: 8px;
|
||||||
background: var(--docsearch-key-background);
|
background: var(--docsearch-code-block-background);
|
||||||
color: var(--docsearch-text-color);
|
color: var(--docsearch-text-color);
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: 0.2em 0.6em;
|
padding-block: 0.2em;
|
||||||
font-size: 0.75em;
|
padding-inline: 0.6em;
|
||||||
|
font-size: 0.95em;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
--docsearch-subtle-color: rgb(214 214 231);
|
--docsearch-subtle-color: rgb(214 214 231);
|
||||||
--docsearch-text-color: rgb(35 38 59 / 100%);
|
--docsearch-text-color: rgb(35 38 59 / 100%);
|
||||||
--docsearch-error-color: #ef5350;
|
--docsearch-error-color: #ef5350;
|
||||||
|
--docsearch-error-soft-color: rgb(254 189 197);
|
||||||
|
--docsearch-error-text-color: rgb(122 28 43);
|
||||||
--docsearch-success-color: #e8f5e9;
|
--docsearch-success-color: #e8f5e9;
|
||||||
--docsearch-secondary-text-color: rgb(90 94 154 / 100%);
|
--docsearch-secondary-text-color: rgb(90 94 154 / 100%);
|
||||||
--docsearch-background-color: rgb(245 245 250);
|
--docsearch-background-color: rgb(245 245 250);
|
||||||
|
|
@ -88,6 +90,9 @@
|
||||||
0 0 0 1px rgb(33 36 61 / 5%),
|
0 0 0 1px rgb(33 36 61 / 5%),
|
||||||
0 4px 8px -2px rgb(33 36 61 / 25%);
|
0 4px 8px -2px rgb(33 36 61 / 25%);
|
||||||
--docsearch-popover-arrow-color: rgb(230 230 240);
|
--docsearch-popover-arrow-color: rgb(230 230 240);
|
||||||
|
|
||||||
|
/* markdown */
|
||||||
|
--docsearch-code-block-background: rgb(235 235 240);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Darkmode */
|
/* Darkmode */
|
||||||
|
|
@ -143,4 +148,7 @@
|
||||||
0 0 0 1px rgb(255 255 255 / 8%),
|
0 0 0 1px rgb(255 255 255 / 8%),
|
||||||
0 4px 8px -2px rgb(0 0 0 / 55%);
|
0 4px 8px -2px rgb(0 0 0 / 55%);
|
||||||
--docsearch-popover-arrow-color: var(--docsearch-hit-focus-background);
|
--docsearch-popover-arrow-color: var(--docsearch-hit-focus-background);
|
||||||
|
|
||||||
|
/* markdown */
|
||||||
|
--docsearch-code-block-background: rgb(33 33 57);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -717,6 +717,14 @@
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.DocSearch-Sidepanel .DocSearch-Markdown-Content code {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DocSearch-Sidepanel .DocSearch-CodeSnippet pre {
|
||||||
|
padding-block-start: 2.4em;
|
||||||
|
}
|
||||||
|
|
||||||
.DocSearch-Sidepanel .DocSearch-AskAiScreen-MessageContent-Tool-Query:hover {
|
.DocSearch-Sidepanel .DocSearch-AskAiScreen-MessageContent-Tool-Query:hover {
|
||||||
cursor: unset;
|
cursor: unset;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
|
||||||
|
|
@ -241,9 +241,7 @@ function AskAiExchangeCard({
|
||||||
<div className="DocSearch-AskAiScreen-Response-Container">
|
<div className="DocSearch-AskAiScreen-Response-Container">
|
||||||
<div className="DocSearch-AskAiScreen-Response">
|
<div className="DocSearch-AskAiScreen-Response">
|
||||||
<div className="DocSearch-AskAiScreen-Message DocSearch-AskAiScreen-Message--user">
|
<div className="DocSearch-AskAiScreen-Message DocSearch-AskAiScreen-Message--user">
|
||||||
<p className="DocSearch-AskAiScreen-Query">
|
<p className="DocSearch-AskAiScreen-Query">{userContent ?? ''}</p>
|
||||||
{userContent?.text ?? ''}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="DocSearch-AskAiScreen-Message DocSearch-AskAiScreen-Message--assistant">
|
<div className="DocSearch-AskAiScreen-Message DocSearch-AskAiScreen-Message--assistant">
|
||||||
<div className="DocSearch-AskAiScreen-MessageContent">
|
<div className="DocSearch-AskAiScreen-MessageContent">
|
||||||
|
|
@ -361,7 +359,7 @@ function AskAiExchangeCard({
|
||||||
<FeedbackActions
|
<FeedbackActions
|
||||||
id={messageId}
|
id={messageId}
|
||||||
showActions={showActions}
|
showActions={showActions}
|
||||||
latestAssistantMessageContent={assistantContent?.text || null}
|
latestAssistantMessageContent={assistantContent || null}
|
||||||
translations={translations}
|
translations={translations}
|
||||||
conversations={conversations}
|
conversations={conversations}
|
||||||
onFeedback={onFeedback}
|
onFeedback={onFeedback}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ export type ConversationScreenTranslations = Partial<
|
||||||
feedbackTagSafetyOrLegal: string;
|
feedbackTagSafetyOrLegal: string;
|
||||||
feedbackTagOther: string;
|
feedbackTagOther: string;
|
||||||
/** Error title shown if there is an error while chatting. */
|
/** Error title shown if there is an error while chatting. */
|
||||||
errorTitleText;
|
errorTitleText: string;
|
||||||
suggestedPromptsTitleText: string;
|
suggestedPromptsTitleText: string;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
@ -171,9 +171,7 @@ const ConversationExchange = React.forwardRef<
|
||||||
>
|
>
|
||||||
<div className="DocSearch-AskAiScreen-Response">
|
<div className="DocSearch-AskAiScreen-Response">
|
||||||
<div className="DocSearch-AskAiScreen-Message DocSearch-AskAiScreen-Message--user">
|
<div className="DocSearch-AskAiScreen-Message DocSearch-AskAiScreen-Message--user">
|
||||||
<p className="DocSearch-AskAiScreen-Query">
|
<p className="DocSearch-AskAiScreen-Query">{userContent ?? ''}</p>
|
||||||
{userContent?.text ?? ''}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="DocSearch-AskAiScreen-Message DocSearch-AskAiScreen-Message--assistant">
|
<div className="DocSearch-AskAiScreen-Message DocSearch-AskAiScreen-Message--assistant">
|
||||||
<div className="DocSearch-AskAiScreen-MessageContent">
|
<div className="DocSearch-AskAiScreen-MessageContent">
|
||||||
|
|
@ -288,7 +286,7 @@ const ConversationExchange = React.forwardRef<
|
||||||
isSidepanel={true}
|
isSidepanel={true}
|
||||||
id={messageId}
|
id={messageId}
|
||||||
showActions={showActions}
|
showActions={showActions}
|
||||||
latestAssistantMessageContent={assistantContent?.text || null}
|
latestAssistantMessageContent={assistantContent || null}
|
||||||
translations={translations}
|
translations={translations}
|
||||||
conversations={conversations}
|
conversations={conversations}
|
||||||
onFeedback={onFeedback}
|
onFeedback={onFeedback}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { render } from '@testing-library/react';
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
import type { UIMessage } from 'ai';
|
import type { UIMessage } from 'ai';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import '@testing-library/jest-dom/vitest';
|
import '@testing-library/jest-dom/vitest';
|
||||||
|
|
||||||
import { AskAiScreen } from '../AskAiScreen';
|
import { AskAiScreen } from '../AskAiScreen';
|
||||||
|
|
@ -17,6 +17,7 @@ const baseProps = {
|
||||||
setQuery: (): void => {},
|
setQuery: (): void => {},
|
||||||
messages: [],
|
messages: [],
|
||||||
status: 'ready' as const,
|
status: 'ready' as const,
|
||||||
|
tools: {},
|
||||||
disableUserPersonalization: false,
|
disableUserPersonalization: false,
|
||||||
resultsFooterComponent: null,
|
resultsFooterComponent: null,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
@ -47,4 +48,46 @@ describe('AskAiScreen', () => {
|
||||||
|
|
||||||
expect(getByText('oh no')).toBeInTheDocument();
|
expect(getByText('oh no')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('copies all assistant text parts around tool calls', () => {
|
||||||
|
const writeText = vi.fn();
|
||||||
|
Object.defineProperty(navigator, 'clipboard', {
|
||||||
|
configurable: true,
|
||||||
|
value: { writeText },
|
||||||
|
});
|
||||||
|
const messages: UIMessage[] = [
|
||||||
|
{
|
||||||
|
id: 'user-1',
|
||||||
|
role: 'user',
|
||||||
|
parts: [{ type: 'text', text: 'What is Docusaurus?' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'assistant-1',
|
||||||
|
role: 'assistant',
|
||||||
|
parts: [
|
||||||
|
{ type: 'text', text: 'Let me look that up.', state: 'done' },
|
||||||
|
{
|
||||||
|
type: 'tool-algolia_search_index',
|
||||||
|
toolCallId: 'tool-1',
|
||||||
|
state: 'output-available',
|
||||||
|
input: { query: 'Docusaurus', index: 'docs' },
|
||||||
|
output: { hits: [] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: 'Docusaurus is a static site generator.',
|
||||||
|
state: 'done',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
render(<AskAiScreen {...baseProps} messages={messages} />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTitle('Copy'));
|
||||||
|
|
||||||
|
expect(writeText).toHaveBeenCalledWith(
|
||||||
|
'Let me look that up.\n\nDocusaurus is a static site generator.'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ export function FeedbackActions({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="DocSearch-AskAiScreen-Actions">
|
<>
|
||||||
<div className="DocSearch-AskAiScreen-Actions-Controls">
|
<div className="DocSearch-AskAiScreen-Actions-Controls">
|
||||||
{isSidepanel ? copyButton : null}
|
{isSidepanel ? copyButton : null}
|
||||||
|
|
||||||
|
|
@ -259,7 +259,7 @@ export function FeedbackActions({
|
||||||
onClose={handleCloseNote}
|
onClose={handleCloseNote}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,6 +286,7 @@ function NegativeFeedbackPanel({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onClose,
|
onClose,
|
||||||
}: NegativeFeedbackPanelProps): JSX.Element {
|
}: NegativeFeedbackPanelProps): JSX.Element {
|
||||||
|
const feedbackTitleId = React.useId();
|
||||||
const {
|
const {
|
||||||
feedbackPanelTitle = 'What went wrong? (optional)',
|
feedbackPanelTitle = 'What went wrong? (optional)',
|
||||||
feedbackDetailsPlaceholder = 'Share some details...',
|
feedbackDetailsPlaceholder = 'Share some details...',
|
||||||
|
|
@ -297,10 +298,12 @@ function NegativeFeedbackPanel({
|
||||||
<div
|
<div
|
||||||
className="DocSearch-Feedback-Panel"
|
className="DocSearch-Feedback-Panel"
|
||||||
role="group"
|
role="group"
|
||||||
aria-label={feedbackPanelTitle}
|
aria-labelledby={feedbackTitleId}
|
||||||
>
|
>
|
||||||
<div className="DocSearch-Feedback-Panel-Header">
|
<div className="DocSearch-Feedback-Panel-Header">
|
||||||
<p className="DocSearch-Feedback-Panel-Title">{feedbackPanelTitle}</p>
|
<p className="DocSearch-Feedback-Panel-Title" id={feedbackTitleId}>
|
||||||
|
{feedbackPanelTitle}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="DocSearch-Feedback-Panel-Reasons">
|
<div className="DocSearch-Feedback-Panel-Reasons">
|
||||||
|
|
@ -310,9 +313,7 @@ function NegativeFeedbackPanel({
|
||||||
<button
|
<button
|
||||||
key={reason.value}
|
key={reason.value}
|
||||||
type="button"
|
type="button"
|
||||||
className={`DocSearch-Feedback-Panel-Reason${
|
className="DocSearch-Feedback-Panel-Reason"
|
||||||
isSelected ? ' DocSearch-Feedback-Panel-Reason--selected' : ''
|
|
||||||
}`}
|
|
||||||
aria-pressed={isSelected}
|
aria-pressed={isSelected}
|
||||||
onClick={() => onToggleTag(reason.value)}
|
onClick={() => onToggleTag(reason.value)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -156,9 +156,20 @@ describe('FeedbackActions', () => {
|
||||||
const { onFeedback } = renderComponent();
|
const { onFeedback } = renderComponent();
|
||||||
|
|
||||||
fireEvent.click(screen.getByTitle('Dislike'));
|
fireEvent.click(screen.getByTitle('Dislike'));
|
||||||
const tag = screen.getByText('Incorrect or incomplete');
|
const tag = screen.getByRole('button', {
|
||||||
|
name: 'Incorrect or incomplete',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tag).toHaveAttribute('aria-pressed', 'false');
|
||||||
|
|
||||||
fireEvent.click(tag);
|
fireEvent.click(tag);
|
||||||
|
|
||||||
|
expect(tag).toHaveAttribute('aria-pressed', 'true');
|
||||||
|
|
||||||
fireEvent.click(tag);
|
fireEvent.click(tag);
|
||||||
|
|
||||||
|
expect(tag).toHaveAttribute('aria-pressed', 'false');
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('Submit'));
|
fireEvent.click(screen.getByText('Submit'));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Popover as PopoverPrimitive } from '@base-ui/react/popover';
|
import { Popover as PopoverPrimitive } from '@base-ui/react/popover';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import type { JSX } from 'react';
|
||||||
|
|
||||||
import { CloseIcon } from '../../icons';
|
import { CloseIcon } from '../../icons';
|
||||||
|
|
||||||
|
|
@ -7,13 +8,13 @@ export function Popover({ ...props }: PopoverPrimitive.Root.Props) {
|
||||||
return <PopoverPrimitive.Root {...props} />;
|
return <PopoverPrimitive.Root {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PopoverTrigger({
|
const PopoverTrigger = React.forwardRef<
|
||||||
className,
|
HTMLButtonElement,
|
||||||
...props
|
PopoverPrimitive.Trigger.Props
|
||||||
}: PopoverPrimitive.Trigger.Props) {
|
>(({ className, ...props }, ref): JSX.Element => {
|
||||||
const cn = `DocSearch-Popover-Trigger${className ? ` ${className}` : ''}`;
|
const cn = `DocSearch-Popover-Trigger${className ? ` ${className}` : ''}`;
|
||||||
return <PopoverPrimitive.Trigger className={cn} {...props} />;
|
return <PopoverPrimitive.Trigger className={cn} ref={ref} {...props} />;
|
||||||
}
|
});
|
||||||
|
|
||||||
Popover.Trigger = PopoverTrigger;
|
Popover.Trigger = PopoverTrigger;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,6 @@ export const useAskAi: UseAskAi = ({
|
||||||
assistantId,
|
assistantId,
|
||||||
apiKey,
|
apiKey,
|
||||||
appId,
|
appId,
|
||||||
indexName,
|
|
||||||
tools = EMPTY_TOOLS,
|
tools = EMPTY_TOOLS,
|
||||||
searchParameters,
|
searchParameters,
|
||||||
memory,
|
memory,
|
||||||
|
|
@ -218,7 +217,7 @@ export const useAskAi: UseAskAi = ({
|
||||||
|
|
||||||
const conversations = useRef(
|
const conversations = useRef(
|
||||||
createStoredConversations<StoredAskAiState>({
|
createStoredConversations<StoredAskAiState>({
|
||||||
key: `__DOCSEARCH_ASKAI_CONVERSATIONS__${indexName}`,
|
key: `__DOCSEARCH_ASKAI_CONVERSATIONS__${appId}`,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
})
|
})
|
||||||
).current;
|
).current;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
isAIToolPart,
|
isAIToolPart,
|
||||||
isAlgoliaMCPSearchOutputPart,
|
isAlgoliaMCPSearchOutputPart,
|
||||||
sanitizeMessagesForRequest,
|
sanitizeMessagesForRequest,
|
||||||
|
getMessageContent,
|
||||||
} from '../ai';
|
} from '../ai';
|
||||||
|
|
||||||
function message(id: string, parts: AIMessagePart[]): AIMessage {
|
function message(id: string, parts: AIMessagePart[]): AIMessage {
|
||||||
|
|
@ -247,3 +248,50 @@ describe('getSearchToolQueries', () => {
|
||||||
expect(getSearchToolQueries(part)).toEqual(['foo']);
|
expect(getSearchToolQueries(part)).toEqual(['foo']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getMessageContent', () => {
|
||||||
|
it('joins the text parts around tool calls so copying returns the full answer', () => {
|
||||||
|
expect(
|
||||||
|
getMessageContent(
|
||||||
|
message('a1', [
|
||||||
|
{ type: 'text', text: 'Let me look that up.', state: 'done' },
|
||||||
|
{
|
||||||
|
type: 'tool-algolia_search_index',
|
||||||
|
toolCallId: 't1',
|
||||||
|
state: 'output-available',
|
||||||
|
input: {
|
||||||
|
query: 'foo',
|
||||||
|
index: 'bar',
|
||||||
|
},
|
||||||
|
output: { hits: [] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: 'Docusaurus is a static site generator.',
|
||||||
|
state: 'done',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
)
|
||||||
|
).toBe('Let me look that up.\n\nDocusaurus is a static site generator.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty string without a message or text parts', () => {
|
||||||
|
expect(getMessageContent(null)).toBe('');
|
||||||
|
expect(
|
||||||
|
getMessageContent(
|
||||||
|
message('a2', [
|
||||||
|
{
|
||||||
|
type: 'tool-algolia_search_index',
|
||||||
|
toolCallId: 't2',
|
||||||
|
state: 'output-available',
|
||||||
|
input: {
|
||||||
|
query: 'foo',
|
||||||
|
index: 'bar',
|
||||||
|
},
|
||||||
|
output: { hits: [] },
|
||||||
|
},
|
||||||
|
])
|
||||||
|
)
|
||||||
|
).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -113,10 +113,14 @@ export const buildDummyAskAiHit = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMessageContent = (
|
// answers can interleave text with tool calls, so join every text part
|
||||||
message: AIMessage | null
|
// instead of stopping at the first one
|
||||||
): TextUIPart | undefined =>
|
// see https://github.com/algolia/docsearch/issues/2782
|
||||||
message?.parts.find((part) => part.type === 'text');
|
export const getMessageContent = (message: AIMessage | null): string =>
|
||||||
|
(message?.parts ?? [])
|
||||||
|
.filter((part): part is TextUIPart => part.type === 'text')
|
||||||
|
.map((part) => part.text)
|
||||||
|
.join('\n\n');
|
||||||
|
|
||||||
/** Helper function to check if error is a thread depth error (AI-217). */
|
/** Helper function to check if error is a thread depth error (AI-217). */
|
||||||
export function isThreadDepthError(error?: Error): boolean {
|
export function isThreadDepthError(error?: Error): boolean {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue