From bebaa6d27d8c5571ecd82236b677d68ae9357e64 Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:29:01 +0100 Subject: [PATCH 01/13] fix: add `prompt` to prompts query --- packages/shared/src/graphql/prompt.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/shared/src/graphql/prompt.ts b/packages/shared/src/graphql/prompt.ts index 6764970f16..87f3292649 100644 --- a/packages/shared/src/graphql/prompt.ts +++ b/packages/shared/src/graphql/prompt.ts @@ -28,6 +28,7 @@ export const PROMPTS_QUERY = gql` id label description + prompt flags { icon color From c278b7eb5c737beb760a2e2f9e77b1a067f86274 Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:40:41 +0100 Subject: [PATCH 02/13] fix: add stale time to chat session --- packages/shared/src/hooks/chat/useChatSession.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/hooks/chat/useChatSession.ts b/packages/shared/src/hooks/chat/useChatSession.ts index eaceac35cf..fa0d1a70a4 100644 --- a/packages/shared/src/hooks/chat/useChatSession.ts +++ b/packages/shared/src/hooks/chat/useChatSession.ts @@ -3,7 +3,7 @@ import { useQueryClient, useQuery } from '@tanstack/react-query'; import { useAuthContext } from '../../contexts/AuthContext'; import type { Search } from '../../graphql/search'; import { getSearchSession } from '../../graphql/search'; -import { generateQueryKey, RequestKey } from '../../lib/query'; +import { generateQueryKey, RequestKey, StaleTime } from '../../lib/query'; import type { UseChatSessionProps, UseChatSession } from './types'; export const useChatSession = ({ @@ -27,6 +27,7 @@ export const useChatSession = ({ return getSearchSession(id); }, enabled: !!id, + staleTime: StaleTime.OneHour, }); return { From 714ce3f19ed50b7b820404df72b9058dba683257 Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:16:15 +0100 Subject: [PATCH 03/13] refactor: rename `value` to `prompt` --- packages/shared/src/hooks/chat/useChatStream.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/shared/src/hooks/chat/useChatStream.ts b/packages/shared/src/hooks/chat/useChatStream.ts index 13bc0bedfc..db5837f63b 100644 --- a/packages/shared/src/hooks/chat/useChatStream.ts +++ b/packages/shared/src/hooks/chat/useChatStream.ts @@ -33,8 +33,8 @@ export const useChatStream = (): UseChatStream => { const [sessionId, setSessionId] = useState(null); const executePrompt = useCallback( - async (value: string) => { - if (!value) { + async (prompt: string) => { + if (!prompt) { return; } @@ -87,7 +87,7 @@ export const useChatStream = (): UseChatStream => { ...payload, createdAt: new Date(), status: data.status, - prompt: value, + prompt, }), ); @@ -164,7 +164,7 @@ export const useChatStream = (): UseChatStream => { logErrorEvent(code); }; - const source = await sendSearchQuery(value, accessToken?.token); + const source = await sendSearchQuery(prompt, accessToken?.token); source.addEventListener('message', onMessage); source.addEventListener('error', onError); sourceRef.current = source; @@ -183,8 +183,8 @@ export const useChatStream = (): UseChatStream => { return { id: sessionId, handleSubmit: useCallback( - (_, value: string) => { - executePrompt(value); + (_, prompt: string) => { + executePrompt(prompt); }, [executePrompt], ), From aaee799ad23f5f262dac6569e73f102b3fe40002 Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:39:10 +0100 Subject: [PATCH 04/13] fix: move `usePromptButtons` to correct directory --- .../shared/src/components/post/smartPrompts/PromptButtons.tsx | 2 +- packages/shared/src/hooks/{feed => prompt}/usePromptButtons.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/shared/src/hooks/{feed => prompt}/usePromptButtons.ts (100%) diff --git a/packages/shared/src/components/post/smartPrompts/PromptButtons.tsx b/packages/shared/src/components/post/smartPrompts/PromptButtons.tsx index 7b95e7a073..d649774169 100644 --- a/packages/shared/src/components/post/smartPrompts/PromptButtons.tsx +++ b/packages/shared/src/components/post/smartPrompts/PromptButtons.tsx @@ -14,7 +14,7 @@ import { usePromptsQuery } from '../../../hooks/prompt/usePromptsQuery'; import { ElementPlaceholder } from '../../ElementPlaceholder'; import type { PromptFlags } from '../../../graphql/prompt'; import { PromptDisplay } from '../../../graphql/prompt'; -import { usePromptButtons } from '../../../hooks/feed/usePromptButtons'; +import { usePromptButtons } from '../../../hooks/prompt/usePromptButtons'; import { useViewSize, ViewSize } from '../../../hooks'; import { SimpleTooltip } from '../../tooltips'; import { promptColorMap, PromptIconMap } from './common'; diff --git a/packages/shared/src/hooks/feed/usePromptButtons.ts b/packages/shared/src/hooks/prompt/usePromptButtons.ts similarity index 100% rename from packages/shared/src/hooks/feed/usePromptButtons.ts rename to packages/shared/src/hooks/prompt/usePromptButtons.ts From 6435ad3bb28b09bdd3c6a6105c3ec48006f97d17 Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:39:56 +0100 Subject: [PATCH 05/13] refactor: remove double useCallback --- packages/shared/src/hooks/chat/types.ts | 3 +-- packages/shared/src/hooks/chat/useChatStream.ts | 7 +------ packages/webapp/components/search/SearchChatPage.tsx | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/shared/src/hooks/chat/types.ts b/packages/shared/src/hooks/chat/types.ts index b48c4f53e5..2a23632031 100644 --- a/packages/shared/src/hooks/chat/types.ts +++ b/packages/shared/src/hooks/chat/types.ts @@ -1,5 +1,4 @@ import type { QueryKey } from '@tanstack/react-query'; -import type { MouseEvent } from 'react'; import type { Search, SearchChunkSource } from '../../graphql/search'; export interface UseChatProps { @@ -32,7 +31,7 @@ export interface UseChat { queryKey: QueryKey; data: Search; isLoading: boolean; - handleSubmit(event: MouseEvent, value: string): void; + handleSubmit(prompt: string): Promise; } export interface CreatePayload { diff --git a/packages/shared/src/hooks/chat/useChatStream.ts b/packages/shared/src/hooks/chat/useChatStream.ts index db5837f63b..af67b6f2c5 100644 --- a/packages/shared/src/hooks/chat/useChatStream.ts +++ b/packages/shared/src/hooks/chat/useChatStream.ts @@ -182,11 +182,6 @@ export const useChatStream = (): UseChatStream => { return { id: sessionId, - handleSubmit: useCallback( - (_, prompt: string) => { - executePrompt(prompt); - }, - [executePrompt], - ), + handleSubmit: executePrompt, }; }; diff --git a/packages/webapp/components/search/SearchChatPage.tsx b/packages/webapp/components/search/SearchChatPage.tsx index 79889df8b6..c1f8265504 100644 --- a/packages/webapp/components/search/SearchChatPage.tsx +++ b/packages/webapp/components/search/SearchChatPage.tsx @@ -56,7 +56,7 @@ const SearchPage = (): ReactElement => { return; } - handleSubmit(undefined, query); + handleSubmit(query); }, [sessionIdQuery, query, handleSubmit]); const seo: NextSeoProps = { From b0faec574d3c45abefc20029f2cbc77c2346a331 Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:08:03 +0100 Subject: [PATCH 06/13] fix: add prompt to Prompt --- packages/shared/src/graphql/prompt.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/shared/src/graphql/prompt.ts b/packages/shared/src/graphql/prompt.ts index 87f3292649..94045154fd 100644 --- a/packages/shared/src/graphql/prompt.ts +++ b/packages/shared/src/graphql/prompt.ts @@ -9,6 +9,7 @@ export type PromptFlags = { export type Prompt = { id: string; label: string; + prompt: string; description?: string; createdAt: Date; updatedAt: Date; From 01d4982912ebba87e545a6e6fc864e75d4883852 Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:59:20 +0100 Subject: [PATCH 07/13] feat: query magni with smart prompt --- .../post/smartPrompts/SmartPrompt.tsx | 3 +- .../post/smartPrompts/SmartPromptResponse.tsx | 69 +++++++++ packages/shared/src/graphql/search.ts | 26 +++- .../shared/src/hooks/prompt/useSmartPrompt.ts | 142 ++++++++++++++++++ 4 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 packages/shared/src/components/post/smartPrompts/SmartPromptResponse.tsx create mode 100644 packages/shared/src/hooks/prompt/useSmartPrompt.ts diff --git a/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx b/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx index 510712d2a3..2220d9112e 100644 --- a/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx +++ b/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx @@ -10,6 +10,7 @@ import { PostUpgradeToPlus } from '../../plus/PostUpgradeToPlus'; import { TargetId } from '../../../lib/log'; import ShowMoreContent from '../../cards/common/ShowMoreContent'; import { Button, ButtonSize, ButtonVariant } from '../../buttons/Button'; +import { SmartPromptResponse } from './SmartPromptResponse'; export const SmartPrompt = ({ post }: { post: Post }): ReactElement => { const { isPlus, showPlusSubscription } = usePlusSubscription(); @@ -69,7 +70,7 @@ export const SmartPrompt = ({ post }: { post: Post }): ReactElement => { - Smart prompt - {activePrompt} + diff --git a/packages/shared/src/components/post/smartPrompts/SmartPromptResponse.tsx b/packages/shared/src/components/post/smartPrompts/SmartPromptResponse.tsx new file mode 100644 index 0000000000..903694a5ea --- /dev/null +++ b/packages/shared/src/components/post/smartPrompts/SmartPromptResponse.tsx @@ -0,0 +1,69 @@ +import React, { useEffect, useMemo } from 'react'; +import type { ReactElement } from 'react'; +import type { Post } from '../../../graphql/posts'; +import { RenderMarkdown } from '../../RenderMarkdown'; +import { SearchProgressBar } from '../../search'; +import Alert, { AlertType } from '../../widgets/Alert'; +import { isNullOrUndefined } from '../../../lib/func'; +import { labels } from '../../../lib'; +import { usePromptsQuery } from '../../../hooks/prompt/usePromptsQuery'; +import { useSmartPrompt } from '../../../hooks/prompt/useSmartPrompt'; + +type SmartPromptResponseProps = { + post: Post; + activePrompt: string; +}; + +export const SmartPromptResponse = ({ + post, + activePrompt, +}: SmartPromptResponseProps): ReactElement => { + const { data: prompts } = usePromptsQuery(); + const prompt = useMemo( + () => prompts?.find((p) => p.id === activePrompt), + [activePrompt, prompts], + ); + + const { + executePrompt, + data, + isPending: isChatLoading, + } = useSmartPrompt({ post, prompt }); + + useEffect(() => { + if (!prompt.prompt || data) { + return; + } + + executePrompt(prompt.prompt + new Date().getTime()); + }, [prompt, executePrompt, data]); + + return ( +
+ {!!data?.chunks?.[0]?.steps && ( +
+ + {!!data?.chunks?.[0]?.status && ( +
+ {data?.chunks?.[0]?.status} +
+ )} +
+ )} + {!isNullOrUndefined(data?.chunks?.[0]?.error?.code) && ( + + )} + +
+ ); +}; diff --git a/packages/shared/src/graphql/search.ts b/packages/shared/src/graphql/search.ts index df884f2b9b..35f300f756 100644 --- a/packages/shared/src/graphql/search.ts +++ b/packages/shared/src/graphql/search.ts @@ -365,6 +365,12 @@ export const getSearchUrl = (params: SearchUrlParams): string => { export const searchQueryUrl = `${apiUrl}/search/query`; +export const sendPrompt = async ( + params: URLSearchParams, +): Promise => { + return new EventSource(`${searchQueryUrl}?${params}`); +}; + export const sendSearchQuery = async ( query: string, token: string, @@ -374,7 +380,25 @@ export const sendSearchQuery = async ( token, }); - return new EventSource(`${searchQueryUrl}?${params}`); + return sendPrompt(params); +}; + +export const sendSmartPromptQuery = async ({ + query, + token, + post, +}: { + query: string; + token: string; + post: Post; +}): Promise => { + const params = new URLSearchParams({ + prompt: query, + token, + postId: post.id, + }); + + return sendPrompt(params); }; export type SearchSuggestion = { diff --git a/packages/shared/src/hooks/prompt/useSmartPrompt.ts b/packages/shared/src/hooks/prompt/useSmartPrompt.ts new file mode 100644 index 0000000000..787a064e61 --- /dev/null +++ b/packages/shared/src/hooks/prompt/useSmartPrompt.ts @@ -0,0 +1,142 @@ +import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useAuthContext } from '../../contexts/AuthContext'; +import { generateQueryKey, RequestKey, StaleTime } from '../../lib/query'; +import type { Post } from '../../graphql/posts'; +import type { Prompt } from '../../graphql/prompt'; +import type { + Search, + SearchChunk, + SearchChunkError, +} from '../../graphql/search'; +import { + initializeSearchSession, + searchErrorCodeToMessage, + sendSmartPromptQuery, + updateSearchData, +} from '../../graphql/search'; +import type { CreatePayload, TokenPayload, UseChatMessage } from '../chat'; +import { UseChatMessageType } from '../chat'; + +export const useSmartPrompt = ({ + post, + prompt, +}: { + post: Post; + prompt: Prompt; +}): { + executePrompt: (value: string) => Promise; + data: Search; + isPending: boolean; +} => { + const { user, accessToken } = useAuthContext(); + const client = useQueryClient(); + const sourceRef = useRef(); + + const queryKey = useMemo( + () => generateQueryKey(RequestKey.Prompts, user, post.id, prompt.id), + [post.id, prompt.id, user], + ); + + const { data, isPending } = useQuery({ + queryKey, + enabled: !!prompt.prompt, + staleTime: StaleTime.OneHour, + }); + + const executePrompt = useCallback( + async (value: string) => { + if (!value) { + return; + } + + if (sourceRef.current?.OPEN) { + sourceRef.current.close(); + } + + const setSearchQuery = (chunk: Partial) => { + client.setQueryData(queryKey, (previous) => + updateSearchData(previous, chunk), + ); + }; + + const onMessage = (event: MessageEvent) => { + const messageData: UseChatMessage = JSON.parse(event.data); + + switch (messageData.type) { + case UseChatMessageType.SessionCreated: { + const payload = messageData.payload as CreatePayload; + + client.setQueryData( + queryKey, + initializeSearchSession({ + ...payload, + createdAt: new Date(), + status: messageData.status, + prompt: value, + }), + ); + + break; + } + case UseChatMessageType.StatusUpdated: + setSearchQuery({ status: messageData.status }); + break; + case UseChatMessageType.NewTokenReceived: + setSearchQuery({ + response: (messageData.payload as TokenPayload).token, + }); + break; + case UseChatMessageType.Completed: { + setSearchQuery({ completedAt: new Date() }); + sourceRef.current?.close(); + break; + } + case UseChatMessageType.Error: { + const errorPayload = messageData.payload as SearchChunkError; + const message = + errorPayload.message || + searchErrorCodeToMessage[errorPayload.code]; + + setSearchQuery({ + error: { ...errorPayload, message }, + progress: -1, + }); + + sourceRef.current?.close(); + break; + } + case UseChatMessageType.SessionFound: + client.setQueryData(queryKey, messageData.payload as Search); + sourceRef.current?.close(); + break; + default: + break; + } + }; + + const source = await sendSmartPromptQuery({ + query: value, + token: accessToken?.token, + post, + }); + source.addEventListener('message', onMessage); + sourceRef.current = source; + }, + [accessToken?.token, client, post, queryKey], + ); + + useEffect(() => { + return () => { + if (sourceRef.current?.OPEN) { + sourceRef.current.close(); + } + }; + }, []); + + return { + executePrompt, + data, + isPending, + }; +}; From c1b29551b811531d455236698322bf53ac083877 Mon Sep 17 00:00:00 2001 From: Ole-Martin Bratteng <1681525+omBratteng@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:08:41 +0100 Subject: [PATCH 08/13] feat: send custom prompt to magni --- .../post/smartPrompts/CustomPrompt.tsx | 85 +++++++++++++++++++ .../post/smartPrompts/SmartPrompt.tsx | 27 +----- .../post/smartPrompts/SmartPromptResponse.tsx | 8 +- 3 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 packages/shared/src/components/post/smartPrompts/CustomPrompt.tsx diff --git a/packages/shared/src/components/post/smartPrompts/CustomPrompt.tsx b/packages/shared/src/components/post/smartPrompts/CustomPrompt.tsx new file mode 100644 index 0000000000..6fe27ea583 --- /dev/null +++ b/packages/shared/src/components/post/smartPrompts/CustomPrompt.tsx @@ -0,0 +1,85 @@ +import React, { useCallback, useMemo } from 'react'; +import type { ReactElement } from 'react'; +import { Button, ButtonSize, ButtonVariant } from '../../buttons/Button'; +import type { Post } from '../../../graphql/posts'; +import { useSmartPrompt } from '../../../hooks/prompt/useSmartPrompt'; +import { usePromptsQuery } from '../../../hooks/prompt/usePromptsQuery'; +import { PromptDisplay } from '../../../graphql/prompt'; +import { SearchProgressBar } from '../../search'; +import { isNullOrUndefined } from '../../../lib/func'; +import Alert, { AlertType } from '../../widgets/Alert'; +import { labels } from '../../../lib'; +import { RenderMarkdown } from '../../RenderMarkdown'; + +type CustomPromptProps = { + post: Post; +}; + +export const CustomPrompt = ({ post }: CustomPromptProps): ReactElement => { + const { data: prompts } = usePromptsQuery(); + const prompt = useMemo( + () => prompts?.find((p) => p.id === PromptDisplay.CustomPrompt), + [prompts], + ); + const { executePrompt, data, isPending } = useSmartPrompt({ post, prompt }); + const onSubmitCustomPrompt = useCallback( + (e) => { + e.preventDefault(); + + executePrompt(e.target[0].value); + }, + [executePrompt], + ); + + if (!data) { + return ( +
+