Skip to content

Commit d70f243

Browse files
authored
fix(registry): Fixes missing files and deps for Search Ask AI experience (#8)
1 parent 497c528 commit d70f243

4 files changed

Lines changed: 32 additions & 3 deletions

File tree

apps/docs/public/r/registry.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@
8080
"algoliasearch@^5",
8181
"react-instantsearch@^7.16.2",
8282
"tw-animate-css",
83-
"lucide-react"
83+
"lucide-react",
84+
"marked"
8485
],
8586
"css": {
8687
"@import \"tw-animate-css\"": {},
@@ -114,6 +115,10 @@
114115
"path": "src/registry/experiences/search-askai/hooks/use-askai.ts",
115116
"type": "registry:hook"
116117
},
118+
{
119+
"path": "src/registry/experiences/search-askai/hooks/use-suggested-questions.ts",
120+
"type": "registry:hook"
121+
},
117122
{
118123
"path": "src/registry/experiences/search-askai/page.tsx",
119124
"type": "registry:page",
@@ -187,6 +192,10 @@
187192
"path": "src/registry/experiences/sidepanel-askai/hooks/use-askai.ts",
188193
"type": "registry:hook"
189194
},
195+
{
196+
"path": "src/registry/experiences/sidepanel-askai/hooks/use-suggested-questions.ts",
197+
"type": "registry:hook"
198+
},
190199
{
191200
"path": "src/registry/experiences/sidepanel-askai/page.tsx",
192201
"type": "registry:page",

apps/docs/public/r/search-ai.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"algoliasearch@^5",
1111
"react-instantsearch@^7.16.2",
1212
"tw-animate-css",
13-
"lucide-react"
13+
"lucide-react",
14+
"marked"
1415
],
1516
"devDependencies": [
1617
"tw-animate-css"
@@ -39,6 +40,11 @@
3940
"content": "import { useChat } from \"@ai-sdk/react\";\nimport {\n DefaultChatTransport,\n lastAssistantMessageIsCompleteWithToolCalls,\n} from \"ai\";\nimport { useMemo } from \"react\";\n\nexport interface AskAIConfig {\n applicationId: string;\n apiKey: string;\n indexName: string;\n assistantId: string;\n}\n\nexport function useAskai(config: AskAIConfig) {\n if (!config) {\n throw new Error(\"config is required for useAskai\");\n }\n\n const baseUrl = \"https://askai.algolia.com\";\n\n const transport = useMemo(() => {\n return new DefaultChatTransport({\n api: `${baseUrl}/chat`,\n headers: async () => {\n const token = await getValidToken({ assistantId: config.assistantId });\n return {\n \"x-algolia-api-key\": config.apiKey,\n \"x-algolia-application-id\": config.applicationId,\n \"x-algolia-index-name\": config.indexName,\n \"x-algolia-assistant-id\": config.assistantId,\n \"x-ai-sdk-version\": \"v5\",\n authorization: `TOKEN ${token}`,\n } as Record<string, string>;\n },\n });\n }, [\n config.apiKey,\n config.applicationId,\n config.indexName,\n config.assistantId,\n ]);\n\n const chat = useChat({\n transport,\n sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,\n async onToolCall({ toolCall }) {\n if (toolCall.dynamic) return;\n },\n });\n\n const isGenerating =\n chat.status === \"submitted\" || chat.status === \"streaming\";\n\n return {\n ...chat,\n isGenerating,\n };\n}\n\nconst BASE_ASKAI_URL = \"https://askai.algolia.com\";\nconst TOKEN_KEY = \"askai_token\";\n\ntype TokenPayload = { exp: number };\n\nconst decode = (token: string): TokenPayload => {\n const [b64] = token.split(\".\");\n return JSON.parse(atob(b64));\n};\n\nconst isExpired = (token?: string | null): boolean => {\n if (!token) return true;\n try {\n const { exp } = decode(token);\n // refresh 30 s before the backend rejects it\n return Date.now() / 1000 > exp - 30;\n } catch {\n return true;\n }\n};\n\nlet inflight: Promise<string> | null = null;\n\n// call /token once, cache the promise while it’s running\n// eslint-disable-next-line require-await\nexport const getValidToken = async ({\n assistantId,\n}: {\n assistantId: string;\n}): Promise<string> => {\n const cached = sessionStorage.getItem(TOKEN_KEY);\n if (!isExpired(cached)) return cached as string;\n\n if (!inflight) {\n inflight = fetch(`${BASE_ASKAI_URL}/chat/token`, {\n method: \"POST\",\n headers: {\n \"x-algolia-assistant-id\": assistantId,\n \"content-type\": \"application/json\",\n },\n })\n .then((r) => r.json())\n .then(({ token }) => {\n sessionStorage.setItem(TOKEN_KEY, token);\n return token;\n })\n .finally(() => {\n inflight = null;\n });\n }\n\n return inflight;\n};\n\nexport const postFeedback = async ({\n assistantId,\n thumbs,\n messageId,\n appId,\n}: {\n assistantId: string;\n thumbs: 0 | 1;\n messageId: string;\n appId: string;\n}): Promise<Response> => {\n const headers = new Headers();\n headers.set(\"x-algolia-assistant-id\", assistantId);\n headers.set(\"content-type\", \"application/json\");\n\n const token = await getValidToken({ assistantId });\n headers.set(\"authorization\", `TOKEN ${token}`);\n\n return fetch(`${BASE_ASKAI_URL}/chat/feedback`, {\n method: \"POST\",\n body: JSON.stringify({\n appId,\n messageId,\n thumbs,\n }),\n headers,\n });\n};\n",
4041
"type": "registry:hook"
4142
},
43+
{
44+
"path": "src/registry/experiences/search-askai/hooks/use-suggested-questions.ts",
45+
"content": "import type { Hit, LiteClient, SearchResponse } from \"algoliasearch/lite\";\nimport { useEffect, useRef, useState } from \"react\";\n\nexport const SUGGESTED_QUETIONS_INDEX_NAME =\n \"algolia_ask_ai_suggested_questions\";\n\ntype SuggestedQuestion = {\n appId: string;\n assistantId: string;\n question: string;\n locale?: string;\n state: \"published\";\n source: string;\n order: number;\n};\n\nexport type SuggestedQuestionHit = Hit<SuggestedQuestion>;\n\ntype UseSuggestedQuestionsProps = {\n assistantId: string | null;\n suggestedQuestionsEnabled?: boolean;\n searchClient: LiteClient;\n isOpen?: boolean;\n};\n\nexport const useSuggestedQuestions = ({\n assistantId,\n suggestedQuestionsEnabled = false,\n searchClient,\n isOpen = false,\n}: UseSuggestedQuestionsProps): SuggestedQuestionHit[] => {\n const [suggestedQuestions, setSuggestedQuestions] = useState<\n SuggestedQuestionHit[]\n >([]);\n const hasFetchedRef = useRef(false);\n\n useEffect(() => {\n // Only fetch once when sidepanel opens\n if (hasFetchedRef.current || !isOpen) {\n return;\n }\n\n const getSuggestedQuestions = async (): Promise<void> => {\n if (!suggestedQuestionsEnabled || !assistantId || assistantId === \"\") {\n return;\n }\n\n try {\n const { results } = await searchClient.search<SuggestedQuestion>({\n requests: [\n {\n indexName: SUGGESTED_QUETIONS_INDEX_NAME,\n filters: `state:published AND assistantId:${assistantId}`,\n hitsPerPage: 3,\n },\n ],\n });\n\n const result = results[0] as SearchResponse<SuggestedQuestion>;\n setSuggestedQuestions(result.hits);\n hasFetchedRef.current = true;\n } catch (error) {\n console.error(\"Failed to fetch suggested questions:\", error);\n }\n };\n\n getSuggestedQuestions();\n }, [suggestedQuestionsEnabled, assistantId, isOpen, searchClient]);\n\n return suggestedQuestions;\n};\n",
46+
"type": "registry:hook"
47+
},
4248
{
4349
"path": "src/registry/experiences/search-askai/page.tsx",
4450
"content": "\"use client\";\n\nimport SearchAskAI from \"@/registry/experiences/search-askai/components/search-ai\";\n\nexport default function Page() {\n return (\n <div className=\"flex items-center justify-center min-h-[100px] md:min-h-[400px] relative p-4\">\n <SearchAskAI\n applicationId=\"06YAZFOHSQ\"\n apiKey=\"94b6afdc316917b6e6cdf2763fa561df\"\n indexName=\"algolia_podcast_sample_dataset\"\n assistantId=\"UpR727VnXnoG\"\n attributes={{\n primaryText: \"title\",\n secondaryText: \"description\",\n tertiaryText: \"itunesAuthor\",\n image: \"imageUrl\",\n url: \"url\",\n }}\n />\n </div>\n );\n}\n",

apps/docs/public/r/sidepanel-askai.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
"content": "import { useChat } from \"@ai-sdk/react\";\nimport {\n DefaultChatTransport,\n lastAssistantMessageIsCompleteWithToolCalls,\n} from \"ai\";\nimport { useMemo } from \"react\";\n\nexport interface AskAIConfig {\n applicationId: string;\n apiKey: string;\n indexName: string;\n assistantId: string;\n}\n\nexport function useAskai(config: AskAIConfig) {\n if (!config) {\n throw new Error(\"config is required for useAskai\");\n }\n\n const baseUrl = \"https://askai.algolia.com\";\n\n const transport = useMemo(() => {\n return new DefaultChatTransport({\n api: `${baseUrl}/chat`,\n headers: async () => {\n const token = await getValidToken({ assistantId: config.assistantId });\n return {\n \"x-algolia-api-key\": config.apiKey,\n \"x-algolia-application-id\": config.applicationId,\n \"x-algolia-index-name\": config.indexName,\n \"x-algolia-assistant-id\": config.assistantId,\n \"x-ai-sdk-version\": \"v5\",\n authorization: `TOKEN ${token}`,\n } as Record<string, string>;\n },\n });\n }, [\n config.apiKey,\n config.applicationId,\n config.indexName,\n config.assistantId,\n ]);\n\n const chat = useChat({\n transport,\n sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,\n async onToolCall({ toolCall }) {\n if (toolCall.dynamic) return;\n },\n });\n\n const isGenerating =\n chat.status === \"submitted\" || chat.status === \"streaming\";\n\n return {\n ...chat,\n isGenerating,\n };\n}\n\nconst BASE_ASKAI_URL = \"https://askai.algolia.com\";\nconst TOKEN_KEY = \"askai_token\";\n\ntype TokenPayload = { exp: number };\n\nconst decode = (token: string): TokenPayload => {\n const [b64] = token.split(\".\");\n return JSON.parse(atob(b64));\n};\n\nconst isExpired = (token?: string | null): boolean => {\n if (!token) return true;\n try {\n const { exp } = decode(token);\n // refresh 30 s before the backend rejects it\n return Date.now() / 1000 > exp - 30;\n } catch {\n return true;\n }\n};\n\nlet inflight: Promise<string> | null = null;\n\n// call /token once, cache the promise while it’s running\n// eslint-disable-next-line require-await\nexport const getValidToken = async ({\n assistantId,\n}: {\n assistantId: string;\n}): Promise<string> => {\n const cached = sessionStorage.getItem(TOKEN_KEY);\n if (!isExpired(cached)) return cached as string;\n\n if (!inflight) {\n inflight = fetch(`${BASE_ASKAI_URL}/chat/token`, {\n method: \"POST\",\n headers: {\n \"x-algolia-assistant-id\": assistantId,\n \"content-type\": \"application/json\",\n },\n })\n .then((r) => r.json())\n .then(({ token }) => {\n sessionStorage.setItem(TOKEN_KEY, token);\n return token;\n })\n .finally(() => {\n inflight = null;\n });\n }\n\n return inflight;\n};\n\nexport const postFeedback = async ({\n assistantId,\n thumbs,\n messageId,\n appId,\n}: {\n assistantId: string;\n thumbs: 0 | 1;\n messageId: string;\n appId: string;\n}): Promise<Response> => {\n const headers = new Headers();\n headers.set(\"x-algolia-assistant-id\", assistantId);\n headers.set(\"content-type\", \"application/json\");\n\n const token = await getValidToken({ assistantId });\n headers.set(\"authorization\", `TOKEN ${token}`);\n\n return fetch(`${BASE_ASKAI_URL}/chat/feedback`, {\n method: \"POST\",\n body: JSON.stringify({\n appId,\n messageId,\n thumbs,\n }),\n headers,\n });\n};\n",
2929
"type": "registry:hook"
3030
},
31+
{
32+
"path": "src/registry/experiences/sidepanel-askai/hooks/use-suggested-questions.ts",
33+
"content": "import type { Hit, LiteClient, SearchResponse } from \"algoliasearch/lite\";\nimport { useEffect, useRef, useState } from \"react\";\n\nexport const SUGGESTED_QUETIONS_INDEX_NAME =\n \"algolia_ask_ai_suggested_questions\";\n\ntype SuggestedQuestion = {\n appId: string;\n assistantId: string;\n question: string;\n locale?: string;\n state: \"published\";\n source: string;\n order: number;\n};\n\nexport type SuggestedQuestionHit = Hit<SuggestedQuestion>;\n\ntype UseSuggestedQuestionsProps = {\n assistantId: string | null;\n suggestedQuestionsEnabled?: boolean;\n searchClient: LiteClient;\n isOpen?: boolean;\n};\n\nexport const useSuggestedQuestions = ({\n assistantId,\n suggestedQuestionsEnabled = false,\n searchClient,\n isOpen = false,\n}: UseSuggestedQuestionsProps): SuggestedQuestionHit[] => {\n const [suggestedQuestions, setSuggestedQuestions] = useState<\n SuggestedQuestionHit[]\n >([]);\n const hasFetchedRef = useRef(false);\n\n useEffect(() => {\n // Only fetch once when sidepanel opens\n if (hasFetchedRef.current || !isOpen) {\n return;\n }\n\n const getSuggestedQuestions = async (): Promise<void> => {\n if (!suggestedQuestionsEnabled || !assistantId || assistantId === \"\") {\n return;\n }\n\n try {\n const { results } = await searchClient.search<SuggestedQuestion>({\n requests: [\n {\n indexName: SUGGESTED_QUETIONS_INDEX_NAME,\n filters: `state:published AND assistantId:${assistantId}`,\n hitsPerPage: 3,\n },\n ],\n });\n\n const result = results[0] as SearchResponse<SuggestedQuestion>;\n setSuggestedQuestions(result.hits);\n hasFetchedRef.current = true;\n } catch (error) {\n console.error(\"Failed to fetch suggested questions:\", error);\n }\n };\n\n getSuggestedQuestions();\n }, [suggestedQuestionsEnabled, assistantId, isOpen, searchClient]);\n\n return suggestedQuestions;\n};\n",
34+
"type": "registry:hook"
35+
},
3136
{
3237
"path": "src/registry/experiences/sidepanel-askai/page.tsx",
3338
"content": "\"use client\";\n\nimport SidepanelExperience from \"@/registry/experiences/sidepanel-askai/components/sidepanel-askai\";\n\nexport default function Page() {\n return (\n <div className=\"flex items-center justify-center min-h-[100px] md:min-h-[400px] relative p-4\">\n <SidepanelExperience\n applicationId=\"06YAZFOHSQ\"\n apiKey=\"94b6afdc316917b6e6cdf2763fa561df\"\n indexName=\"algolia_podcast_sample_dataset\"\n assistantId=\"UpR727VnXnoG\"\n />\n </div>\n );\n}\n",

apps/docs/registry.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@
8080
"algoliasearch@^5",
8181
"react-instantsearch@^7.16.2",
8282
"tw-animate-css",
83-
"lucide-react"
83+
"lucide-react",
84+
"marked"
8485
],
8586
"css": {
8687
"@import \"tw-animate-css\"": {},
@@ -114,6 +115,10 @@
114115
"path": "src/registry/experiences/search-askai/hooks/use-askai.ts",
115116
"type": "registry:hook"
116117
},
118+
{
119+
"path": "src/registry/experiences/search-askai/hooks/use-suggested-questions.ts",
120+
"type": "registry:hook"
121+
},
117122
{
118123
"path": "src/registry/experiences/search-askai/page.tsx",
119124
"type": "registry:page",
@@ -187,6 +192,10 @@
187192
"path": "src/registry/experiences/sidepanel-askai/hooks/use-askai.ts",
188193
"type": "registry:hook"
189194
},
195+
{
196+
"path": "src/registry/experiences/sidepanel-askai/hooks/use-suggested-questions.ts",
197+
"type": "registry:hook"
198+
},
190199
{
191200
"path": "src/registry/experiences/sidepanel-askai/page.tsx",
192201
"type": "registry:page",

0 commit comments

Comments
 (0)