diff --git a/examples/demo-react/src/App.tsx b/examples/demo-react/src/App.tsx
index 24447bfc..faebfb28 100644
--- a/examples/demo-react/src/App.tsx
+++ b/examples/demo-react/src/App.tsx
@@ -7,7 +7,8 @@ import './App.css';
import '@docsearch/css/dist/style.css';
import '@docsearch/css/dist/sidepanel.css';
-import { AgentStudioExample } from './examples/agent-studio';
+import AgentStudio from './examples/agent-studio';
+import AgentStudioSidepanel from './examples/agent-studio-sidepanel';
import Basic from './examples/basic';
import BasicAskAI from './examples/basic-askai';
import Composable from './examples/composable';
@@ -102,7 +103,14 @@ function App(): JSX.Element {
+
+
+ Agent Studio sidepanel
+
diff --git a/examples/demo-react/src/examples/agent-studio-sidepanel.tsx b/examples/demo-react/src/examples/agent-studio-sidepanel.tsx
new file mode 100644
index 00000000..667d1471
--- /dev/null
+++ b/examples/demo-react/src/examples/agent-studio-sidepanel.tsx
@@ -0,0 +1,25 @@
+/* eslint-disable react/react-in-jsx-scope */
+import { DocSearch } from '@docsearch/core';
+import { SidepanelButton } from '@docsearch/sidepanel/button';
+import { Sidepanel } from '@docsearch/sidepanel/sidepanel';
+import type { JSX } from 'react';
+
+export default function AgentStudioSidepanel(): JSX.Element {
+ return (
+
+
+
+
+ );
+}
diff --git a/examples/demo-react/src/examples/agent-studio.tsx b/examples/demo-react/src/examples/agent-studio.tsx
index 6916e836..02efc55f 100644
--- a/examples/demo-react/src/examples/agent-studio.tsx
+++ b/examples/demo-react/src/examples/agent-studio.tsx
@@ -2,45 +2,25 @@
import { DocSearch } from '@docsearch/core';
import { DocSearchButton } from '@docsearch/modal/button';
import { DocSearchModal } from '@docsearch/modal/modal';
-import { SidepanelButton } from '@docsearch/sidepanel/button';
-import { Sidepanel } from '@docsearch/sidepanel/sidepanel';
import type { JSX } from 'react';
-export function AgentStudioExample(): JSX.Element {
+export default function AgentStudio(): JSX.Element {
return (
- <>
-
-
-
-
-
-
-
-
-
- >
+
+
+
+
);
}
diff --git a/packages/docsearch-react/src/AskAiScreen.tsx b/packages/docsearch-react/src/AskAiScreen.tsx
index 6c2aa2ce..24242bec 100644
--- a/packages/docsearch-react/src/AskAiScreen.tsx
+++ b/packages/docsearch-react/src/AskAiScreen.tsx
@@ -140,6 +140,8 @@ function AskAiExchangeCard({
isLastExchange &&
!displayParts.some((part) => part.type !== 'step-start');
+ const messageId = agentStudio ? assistantMessage?.id || exchange.id : userMessage?.id || exchange.id;
+
return (
@@ -236,12 +238,11 @@ function AskAiExchangeCard({
@@ -262,7 +263,6 @@ interface AskAiScreenFooterActionsProps {
translations: AskAiScreenTranslations;
conversations: StoredSearchPlugin
;
onFeedback?: (messageId: string, thumbs: 0 | 1) => Promise;
- agentStudio?: boolean;
}
export function AskAiScreenFooterActions({
@@ -272,7 +272,6 @@ export function AskAiScreenFooterActions({
translations,
conversations,
onFeedback,
- agentStudio,
}: AskAiScreenFooterActionsProps): JSX.Element | null {
// local state for feedback, initialised from stored conversations
const initialFeedback = React.useMemo(() => {
@@ -310,26 +309,25 @@ export function AskAiScreenFooterActions({
return (
- {!agentStudio &&
- (feedback === null ? (
- <>
- {saving ? (
-
- ) : (
- <>
-
handleFeedback('like')} />
- handleFeedback('dislike')} />
- >
- )}
- {savingError && (
- {savingError.message || 'An error occured'}
- )}
- >
- ) : (
-
- {thanksForFeedbackText}
-
- ))}
+ {feedback === null ? (
+ <>
+ {saving ? (
+
+ ) : (
+ <>
+ handleFeedback('like')} />
+ handleFeedback('dislike')} />
+ >
+ )}
+ {savingError && (
+ {savingError.message || 'An error occured'}
+ )}
+ >
+ ) : (
+
+ {thanksForFeedbackText}
+
+ )}
navigator.clipboard.writeText(latestAssistantMessageContent)}
@@ -373,7 +371,7 @@ export function AskAiScreen({ translations = {}, ...props }: AskAiScreenProps):
startNewConversationButtonText = 'Start a new conversation',
} = translations;
- const { messages, askAiError, status } = props;
+ const { messages, askAiError, status, agentStudio } = props;
// Check if there's a thread depth error
const hasThreadDepthError = useMemo(() => {
@@ -448,7 +446,7 @@ export function AskAiScreen({ translations = {}, ...props }: AskAiScreenProps):
loadingStatus={props.status}
translations={translations}
conversations={props.conversations}
- agentStudio={props.agentStudio}
+ agentStudio={agentStudio}
onSearchQueryClick={handleSearchQueryClick}
onFeedback={props.onFeedback}
/>
diff --git a/packages/docsearch-react/src/DocSearchModal.tsx b/packages/docsearch-react/src/DocSearchModal.tsx
index 9fe5022d..3777b2f1 100644
--- a/packages/docsearch-react/src/DocSearchModal.tsx
+++ b/packages/docsearch-react/src/DocSearchModal.tsx
@@ -20,13 +20,12 @@ import type { ScreenStateTranslations } from './ScreenState';
import { ScreenState } from './ScreenState';
import type { SearchBoxTranslations } from './SearchBox';
import { SearchBox } from './SearchBox';
-import { createStoredConversations, createStoredSearches } from './stored-searches';
+import { createStoredSearches } from './stored-searches';
import type {
DocSearchHit,
DocSearchState,
InternalDocSearchHit,
StoredAskAiMessage,
- StoredAskAiState,
StoredDocSearchHit,
SuggestedQuestionHit,
} from './types';
@@ -384,12 +383,6 @@ export function DocSearchModal({
const defaultIndexName = indexes[0].name;
// storage
- const conversations = React.useRef(
- createStoredConversations({
- key: `__DOCSEARCH_ASKAI_CONVERSATIONS__${askAiConfig?.indexName || defaultIndexName}`,
- limit: 10,
- }),
- ).current;
const favoriteSearches = React.useRef(
createStoredSearches({
key: `__DOCSEARCH_FAVORITE_SEARCHES__${defaultIndexName}`,
@@ -405,15 +398,16 @@ export function DocSearchModal({
const [stoppedStream, setStoppedStream] = React.useState(false);
- const { messages, status, setMessages, sendMessage, stopAskAiStreaming, askAiError, sendFeedback } = useAskAi({
- assistantId: askAiConfigurationId,
- apiKey: askAiConfig?.apiKey || apiKey,
- appId: askAiConfig?.appId || appId,
- indexName: askAiConfig?.indexName || defaultIndexName,
- searchParameters: askAiSearchParameters,
- useStagingEnv: askAiUseStagingEnv,
- agentStudio,
- });
+ const { messages, status, setMessages, sendMessage, stopAskAiStreaming, askAiError, sendFeedback, conversations } =
+ useAskAi({
+ assistantId: askAiConfigurationId,
+ apiKey: askAiConfig?.apiKey || apiKey,
+ appId: askAiConfig?.appId || appId,
+ indexName: askAiConfig?.indexName || defaultIndexName,
+ searchParameters: askAiSearchParameters,
+ useStagingEnv: askAiUseStagingEnv,
+ agentStudio,
+ });
const prevStatus = React.useRef(status);
React.useEffect(() => {
diff --git a/packages/docsearch-react/src/Sidepanel/ConversationActions.tsx b/packages/docsearch-react/src/Sidepanel/ConversationActions.tsx
index 22c6a167..e8864a57 100644
--- a/packages/docsearch-react/src/Sidepanel/ConversationActions.tsx
+++ b/packages/docsearch-react/src/Sidepanel/ConversationActions.tsx
@@ -14,7 +14,6 @@ interface ConversationActionsProps {
conversations: StoredSearchPlugin;
onFeedback?: (messageId: string, thumbs: 0 | 1) => Promise;
isSidepanel?: boolean;
- agentStudio?: boolean;
}
export function ConversationActions({
@@ -24,7 +23,6 @@ export function ConversationActions({
onFeedback,
latestAssistantMessageContent,
showActions,
- agentStudio,
}: ConversationActionsProps): JSX.Element | null {
const initialFeedback = React.useMemo(() => {
const message = conversations.getOne?.(id);
@@ -65,26 +63,25 @@ export function ConversationActions({
translations={translations}
onClick={() => navigator.clipboard.writeText(latestAssistantMessageContent)}
/>
- {!agentStudio &&
- (feedback === null ? (
- <>
- {saving ? (
-
- ) : (
- <>
- handleFeedback('like')} />
- handleFeedback('dislike')} />
- >
- )}
- {savingError && (
- {savingError.message || 'An error occured'}
- )}
- >
- ) : (
-
- {thanksForFeedbackText}
-
- ))}
+ {feedback === null ? (
+ <>
+ {saving ? (
+
+ ) : (
+ <>
+ handleFeedback('like')} />
+ handleFeedback('dislike')} />
+ >
+ )}
+ {savingError && (
+ {savingError.message || 'An error occured'}
+ )}
+ >
+ ) : (
+
+ {thanksForFeedbackText}
+
+ )}
);
}
diff --git a/packages/docsearch-react/src/Sidepanel/ConversationScreen.tsx b/packages/docsearch-react/src/Sidepanel/ConversationScreen.tsx
index 3a18589e..b12b3e93 100644
--- a/packages/docsearch-react/src/Sidepanel/ConversationScreen.tsx
+++ b/packages/docsearch-react/src/Sidepanel/ConversationScreen.tsx
@@ -120,6 +120,8 @@ const ConversationExchange = React.forwardRef
@@ -212,12 +214,11 @@ const ConversationExchange = React.forwardRef
diff --git a/packages/docsearch-react/src/askai.ts b/packages/docsearch-react/src/askai.ts
index 809e24a5..693c0d0f 100644
--- a/packages/docsearch-react/src/askai.ts
+++ b/packages/docsearch-react/src/askai.ts
@@ -3,6 +3,8 @@ import { ASK_AI_API_URL, BETA_ASK_AI_API_URL } from './constants';
// ... existing imports ...
const TOKEN_KEY = 'askai_token';
+export const agentStudioBaseUrl = (appId: string): string => `https://${appId}.algolia.net/agent-studio/1`;
+
type TokenPayload = { exp: number };
const decode = (token: string): TokenPayload => {
@@ -134,3 +136,37 @@ export const getAgentStudioErrorMessage = (error: Error): Error => {
return new Error(errorMessage);
};
+
+export const postAgentStudioFeedback = ({
+ agentId,
+ vote,
+ messageId,
+ appId,
+ apiKey,
+ abortSignal,
+}: {
+ agentId: string;
+ vote: 0 | 1;
+ messageId: string;
+ appId: string;
+ apiKey: string;
+ abortSignal: AbortSignal;
+}): Promise => {
+ const headers = new Headers();
+ headers.set('x-algolia-application-id', appId);
+ headers.set('x-algolia-api-key', apiKey);
+ headers.set('content-type', 'application/json');
+
+ const baseUrl = `${agentStudioBaseUrl(appId)}/feedback`;
+
+ return fetch(baseUrl, {
+ method: 'POST',
+ body: JSON.stringify({
+ messageId,
+ agentId,
+ vote,
+ }),
+ headers,
+ signal: abortSignal,
+ });
+};
diff --git a/packages/docsearch-react/src/useAskAi.ts b/packages/docsearch-react/src/useAskAi.ts
index a30b6d49..c77eab9c 100644
--- a/packages/docsearch-react/src/useAskAi.ts
+++ b/packages/docsearch-react/src/useAskAi.ts
@@ -3,7 +3,13 @@ import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai';
import { useCallback, useMemo, useRef } from 'react';
-import { getAgentStudioErrorMessage, getValidToken, postFeedback } from './askai';
+import {
+ agentStudioBaseUrl,
+ getAgentStudioErrorMessage,
+ getValidToken,
+ postAgentStudioFeedback,
+ postFeedback,
+} from './askai';
import type { Exchange } from './AskAiScreen';
import { ASK_AI_API_URL, BETA_ASK_AI_API_URL } from './constants';
import type { StoredSearchPlugin } from './stored-searches';
@@ -59,7 +65,7 @@ const getAgentStudioTransport = ({
searchParameters,
}: AgentStudioTransportParams): DefaultChatTransport => {
return new DefaultChatTransport({
- api: `https://${appId}.algolia.net/agent-studio/1/agents/${assistantId}/completions?stream=true&compatibilityMode=ai-sdk-5`,
+ api: `${agentStudioBaseUrl(appId)}/agents/${assistantId}/completions?stream=true&compatibilityMode=ai-sdk-5`,
headers: {
'x-algolia-application-id': appId,
'x-algolia-api-key': apiKey,
@@ -145,19 +151,28 @@ export const useAskAi: UseAskAi = ({ assistantId, apiKey, appId, indexName, useS
async (messageId: string, thumbs: 0 | 1): Promise => {
if (!assistantId) return;
- const res = await postFeedback({
- assistantId,
- thumbs,
- messageId,
- appId,
- abortSignal: abortControllerRef.current.signal,
- useStagingEnv,
- });
+ const res = await (params.agentStudio
+ ? postAgentStudioFeedback({
+ agentId: assistantId,
+ vote: thumbs,
+ messageId,
+ appId,
+ apiKey,
+ abortSignal: abortControllerRef.current.signal,
+ })
+ : postFeedback({
+ assistantId,
+ thumbs,
+ messageId,
+ appId,
+ abortSignal: abortControllerRef.current.signal,
+ useStagingEnv,
+ }));
if (res.status >= 300) throw new Error('Failed, try again later.');
conversations.addFeedback?.(messageId, thumbs === 1 ? 'like' : 'dislike');
},
- [assistantId, appId, conversations, useStagingEnv],
+ [assistantId, params.agentStudio, appId, apiKey, useStagingEnv, conversations],
);
const onStopStreaming = async (): Promise => {