diff --git a/.changeset/ready-clubs-serve.md b/.changeset/ready-clubs-serve.md new file mode 100644 index 00000000..34c8173e --- /dev/null +++ b/.changeset/ready-clubs-serve.md @@ -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 +``` diff --git a/.oxlintrc.json b/.oxlintrc.json index 263b1f40..b0e55f20 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -317,7 +317,8 @@ "typescript/prefer-namespace-keyword": ["error"], "typescript/prefer-ts-expect-error": ["error"], "typescript/unified-signatures": ["error"], - "vitest/require-mock-type-parameters": "off" + "vitest/require-mock-type-parameters": "off", + "no-debugger": ["error"] }, "overrides": [ { diff --git a/AGENTS.md b/AGENTS.md index eeb033d0..bbad5ad1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -78,27 +78,6 @@ bun run pw:run:webkit ## 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 - Use `type` imports for type-only imports: `import type { Foo } from './types'` diff --git a/examples/demo-react/src/App.tsx b/examples/demo-react/src/App.tsx index 2f554e44..fc57713b 100644 --- a/examples/demo-react/src/App.tsx +++ b/examples/demo-react/src/App.tsx @@ -1,6 +1,5 @@ /* eslint-disable react/react-in-jsx-scope */ import { version } from '@docsearch/react'; -import { DocSearchSidepanel } from '@docsearch/react/sidepanel'; import { useState, type JSX } from 'react'; import './App.css'; @@ -121,17 +120,6 @@ function App(): JSX.Element { - - ); } diff --git a/packages/docsearch-css/src/_askai.css b/packages/docsearch-css/src/_askai.css index 1404cf3d..69d1e1ce 100644 --- a/packages/docsearch-css/src/_askai.css +++ b/packages/docsearch-css/src/_askai.css @@ -201,19 +201,12 @@ .DocSearch-AskAiScreen-Answer-Footer { display: flex; - flex-direction: row; + flex-flow: row wrap; align-items: center; justify-content: space-between; gap: 8px; } -.DocSearch-AskAiScreen-Actions { - display: flex; - flex-direction: column; - width: 100%; - gap: var(--docsearch-spacing); -} - .DocSearch-AskAiScreen-Actions-Controls { display: flex; flex-direction: row; @@ -226,13 +219,16 @@ .DocSearch-Feedback-Panel { flex-basis: 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); display: flex; flex-direction: column; - gap: 12px; + gap: 0.75rem; text-align: start; animation: fade-in 0.2s ease-in forwards; + font-size: 0.875rem; } .DocSearch-Feedback-Panel-Header { @@ -253,7 +249,8 @@ display: flex; align-items: center; justify-content: center; - padding: 4px; + padding-inline: 0.25rem; + padding-block: 0.25rem; border: 0; background: none; cursor: pointer; @@ -267,8 +264,8 @@ } .DocSearch-Feedback-Panel-Close svg { - width: 16px; - height: 16px; + width: 1rem; + height: 1rem; } .DocSearch-Feedback-Panel-Reasons { @@ -280,8 +277,9 @@ .DocSearch-Feedback-Panel-Reason { display: inline-flex; align-items: center; - gap: 6px; - padding: 6px 12px; + gap: 0.375rem; + padding-block: 0.375rem; + padding-inline: 0.75rem; border-radius: var(--docsearch-border-radius); border: 1px solid var(--docsearch-subtle-color); background: var(--docsearch-hit-background); @@ -293,18 +291,13 @@ border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease; -} -.DocSearch-Feedback-Panel-Reason:hover { - border-color: var(--docsearch-highlight-color); - background: var(--docsearch-hit-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; + &:hover, + &[aria-pressed='true'] { + border-color: var(--docsearch-highlight-color); + background: var(--docsearch-hit-highlight-color); + color: var(--docsearch-highlight-color); + } } .DocSearch-Feedback-Panel-Reason svg { @@ -365,8 +358,8 @@ display: flex; align-items: center; justify-content: center; - min-height: 36px; - padding: 6px 15px; + padding-block: 0.5em; + padding-inline: 1em; border: 0; border-radius: var(--docsearch-border-radius); font-family: inherit; @@ -502,9 +495,9 @@ padding-inline: 1em; padding-block: 1em; gap: 8px; - background-color: var(--docsearch-background-color); - border-radius: var(--docsearch-modal-radius); - color: var(--docsearch-text-color); + background-color: var(--docsearch-error-soft-color); + border-radius: var(--docsearch-border-radius); + color: var(--docsearch-error-text-color); font-size: 1em; font-weight: 400; flex-direction: row; @@ -525,6 +518,7 @@ .DocSearch-AskAiScreen-Error p { margin: 0; + color: var(--docsearch-error-text-color); } .DocSearch-AskAiScreen-Error-Content { @@ -543,7 +537,7 @@ margin: 12px 0 8px; color: var(--docsearch-text-color); font-size: 12px; - border: 1px solid #febdc5; + border: 1px solid var(--docsearch-error-soft-color); animation: slide-down 0.3s ease-out; width: 100%; } @@ -679,7 +673,8 @@ } .DocSearch-Markdown-Content p { - margin: 1em 0; + margin-block: 1em; + margin-inline: 0; } .DocSearch-Markdown-Content p:last-child { @@ -691,11 +686,12 @@ } .DocSearch-Markdown-Content code { - background-color: var(--docsearch-key-background); + background-color: var(--docsearch-code-block-background); color: var(--docsearch-text-color); - padding: 0.2em 0; + padding-block: 0.2em; + padding-inline: 0; margin: 0; - border-radius: 3px; + border-radius: var(--docsearch-border-radius); font-family: ui-monospace, SFMono-Regular, @@ -704,17 +700,19 @@ Consolas, 'Liberation Mono', monospace; - font-size: 0.9em; + font-size: 0.9rem; letter-spacing: normal; } .DocSearch-Markdown-Content pre { - background-color: var(--docsearch-key-background); + background-color: var(--docsearch-code-block-background); color: var(--docsearch-text-color); - padding: 1.2em; - border-radius: 6px; + padding-block: 1.2em; + padding-inline: 1.2em; + border-radius: var(--docsearch-border-radius); overflow-x: auto; - margin: 1.5em 0; + margin-block: 1.5em; + margin-inline: 0; } .DocSearch-Markdown-Content pre code { @@ -722,7 +720,6 @@ color: inherit; padding: 0; margin: 0; - font-size: 0.8em; border-radius: 0; white-space: pre-wrap; overflow-wrap: break-word; @@ -1059,7 +1056,8 @@ .DocSearch-AskAiScreen-Sources { padding-inline: 0; - margin-top: 0.5rem; + margin-block: 0; + margin-block-start: 0.5rem; color: var(--docsearch-text-color); list-style-type: none; display: flex; @@ -1100,11 +1098,12 @@ position: absolute; inset-block-start: 8px; inset-inline-end: 8px; - background: var(--docsearch-key-background); + background: var(--docsearch-code-block-background); color: var(--docsearch-text-color); border: 0; - padding: 0.2em 0.6em; - font-size: 0.75em; + padding-block: 0.2em; + padding-inline: 0.6em; + font-size: 0.95em; border-radius: 4px; cursor: pointer; transition: opacity 0.2s ease; diff --git a/packages/docsearch-css/src/_variables.css b/packages/docsearch-css/src/_variables.css index 75e37d3b..8fe577a1 100644 --- a/packages/docsearch-css/src/_variables.css +++ b/packages/docsearch-css/src/_variables.css @@ -7,6 +7,8 @@ --docsearch-subtle-color: rgb(214 214 231); --docsearch-text-color: rgb(35 38 59 / 100%); --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-secondary-text-color: rgb(90 94 154 / 100%); --docsearch-background-color: rgb(245 245 250); @@ -88,6 +90,9 @@ 0 0 0 1px rgb(33 36 61 / 5%), 0 4px 8px -2px rgb(33 36 61 / 25%); --docsearch-popover-arrow-color: rgb(230 230 240); + + /* markdown */ + --docsearch-code-block-background: rgb(235 235 240); } /* Darkmode */ @@ -143,4 +148,7 @@ 0 0 0 1px rgb(255 255 255 / 8%), 0 4px 8px -2px rgb(0 0 0 / 55%); --docsearch-popover-arrow-color: var(--docsearch-hit-focus-background); + + /* markdown */ + --docsearch-code-block-background: rgb(33 33 57); } diff --git a/packages/docsearch-css/src/sidepanel.css b/packages/docsearch-css/src/sidepanel.css index f680280b..5a9f56b5 100644 --- a/packages/docsearch-css/src/sidepanel.css +++ b/packages/docsearch-css/src/sidepanel.css @@ -717,6 +717,14 @@ 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 { cursor: unset; box-shadow: none; diff --git a/packages/docsearch-react/src/AskAiScreen.tsx b/packages/docsearch-react/src/AskAiScreen.tsx index d0f72c1b..219fafa9 100644 --- a/packages/docsearch-react/src/AskAiScreen.tsx +++ b/packages/docsearch-react/src/AskAiScreen.tsx @@ -241,9 +241,7 @@ function AskAiExchangeCard({
-

- {userContent?.text ?? ''} -

+

{userContent ?? ''}

@@ -361,7 +359,7 @@ function AskAiExchangeCard({ ; @@ -171,9 +171,7 @@ const ConversationExchange = React.forwardRef< >
-

- {userContent?.text ?? ''} -

+

{userContent ?? ''}

@@ -288,7 +286,7 @@ const ConversationExchange = React.forwardRef< isSidepanel={true} id={messageId} showActions={showActions} - latestAssistantMessageContent={assistantContent?.text || null} + latestAssistantMessageContent={assistantContent || null} translations={translations} conversations={conversations} onFeedback={onFeedback} diff --git a/packages/docsearch-react/src/__tests__/askai.test.tsx b/packages/docsearch-react/src/__tests__/askai.test.tsx index b25e1b93..36698126 100644 --- a/packages/docsearch-react/src/__tests__/askai.test.tsx +++ b/packages/docsearch-react/src/__tests__/askai.test.tsx @@ -1,7 +1,7 @@ -import { render } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import type { UIMessage } from 'ai'; import React from 'react'; -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; import '@testing-library/jest-dom/vitest'; import { AskAiScreen } from '../AskAiScreen'; @@ -17,6 +17,7 @@ const baseProps = { setQuery: (): void => {}, messages: [], status: 'ready' as const, + tools: {}, disableUserPersonalization: false, resultsFooterComponent: null, } as any; @@ -47,4 +48,46 @@ describe('AskAiScreen', () => { 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(); + + fireEvent.click(screen.getByTitle('Copy')); + + expect(writeText).toHaveBeenCalledWith( + 'Let me look that up.\n\nDocusaurus is a static site generator.' + ); + }); }); diff --git a/packages/docsearch-react/src/components/FeedbackActions.tsx b/packages/docsearch-react/src/components/FeedbackActions.tsx index b96c8f0a..4d16e3b7 100644 --- a/packages/docsearch-react/src/components/FeedbackActions.tsx +++ b/packages/docsearch-react/src/components/FeedbackActions.tsx @@ -214,7 +214,7 @@ export function FeedbackActions({ ); return ( -
+ <>
{isSidepanel ? copyButton : null} @@ -259,7 +259,7 @@ export function FeedbackActions({ onClose={handleCloseNote} /> ) : null} -
+ ); } @@ -286,6 +286,7 @@ function NegativeFeedbackPanel({ onSubmit, onClose, }: NegativeFeedbackPanelProps): JSX.Element { + const feedbackTitleId = React.useId(); const { feedbackPanelTitle = 'What went wrong? (optional)', feedbackDetailsPlaceholder = 'Share some details...', @@ -297,10 +298,12 @@ function NegativeFeedbackPanel({
-

{feedbackPanelTitle}

+

+ {feedbackPanelTitle} +

@@ -310,9 +313,7 @@ function NegativeFeedbackPanel({