Skip to main content
Glama
index.tsx7.44 kB
'use client'; import { Link } from '@components/Link/Link'; import { Container } from '@intlayer/design-system'; import { useAskDocQuestion, usePersistedStore, } from '@intlayer/design-system/hooks'; import { useIntlayer } from 'next-intlayer'; import { type FC, type ReactNode, useEffect, useRef, useState } from 'react'; import { PagesRoutes } from '@/Routes'; import { FileReference } from './FileReference'; import { FormSection } from './FormSection'; import { type ChatCompletionRequestMessage, MessagesList, } from './MessagesList'; const uuid = () => Math.random().toString(36).slice(2); type AskDocQuestionResult = | { success: boolean; status: number; data: { response: string; relatedFiles: string[]; } | null; message?: string; description?: string; error?: | { code: string; title: string; message: string; } | Array<{ code: string; title: string; message: string; }>; } | { response: string; relatedFiles: string[]; }; export type StoredValue = { question: string | undefined; answer: string | undefined; }; type ChatBotProps = { additionalButtons?: ReactNode; displayRelatedFiles?: boolean; stateReloaderTrigger?: any; isActive?: boolean; }; type DiscussionStore = { discussionId: string; storedPrompt: ChatCompletionRequestMessage[]; relatedFiles: string[]; }; export const ChatBot: FC<ChatBotProps> = ({ additionalButtons, displayRelatedFiles = true, stateReloaderTrigger, isActive = false, }) => { const [hasReachedRateLimit, setHasReachedRateLimit] = useState(false); const { mutate: askDocQuestion, isPending } = useAskDocQuestion(); const { firstMessageContent, rateLimitExceededMessage, signInButton } = useIntlayer('chat'); const isFirstRender = useRef(true); const [currentResponse, setCurrentResponse] = useState(''); const firstMessage: ChatCompletionRequestMessage = { role: 'system', content: firstMessageContent.content.value, }; const [discussion, setDiscussion, loadDiscussion] = usePersistedStore< DiscussionStore | undefined >('chat-bot-discussion-store'); const handleAskNewQuestion = (newQuestion: string) => { setCurrentResponse(''); setDiscussion( (discussion) => ({ ...discussion, discussionId: discussion?.discussionId ?? uuid(), storedPrompt: [ ...(discussion?.storedPrompt ?? []), { role: 'user' as const, content: newQuestion, timestamp: new Date(), }, ], }) as DiscussionStore ); const newMessages: ChatCompletionRequestMessage[] = [ ...(discussion?.storedPrompt ?? []), { role: 'user' as const, content: newQuestion, }, ]; askDocQuestion( { messages: newMessages, discussionId: discussion?.discussionId ?? '', onMessage: (chunk: string) => setCurrentResponse((prev) => prev + chunk), onDone: (response: AskDocQuestionResult) => { const responseData = 'data' in response ? response.data : response; if (!responseData) { console.error('Invalid response format:', response); return; } setDiscussion( (discussion) => ({ ...discussion, storedPrompt: [ ...(discussion?.storedPrompt ?? []), { role: 'assistant' as const, content: responseData.response, timestamp: new Date(), }, ], }) as DiscussionStore ); setDiscussion( (discussion) => ({ ...discussion, relatedFiles: [ ...new Set([ ...(discussion?.relatedFiles ?? []), ...(responseData.relatedFiles ?? []), ]), ], }) as DiscussionStore ); setCurrentResponse(''); }, }, { onSuccess: () => { setHasReachedRateLimit(false); }, onError: (errorMessage: any) => { let error: any; // If json is valid, parse it try { if (typeof errorMessage === 'undefined') return; if (typeof errorMessage.message === 'string') { error = errorMessage.message; } else { error = JSON.parse(errorMessage as any); } } catch (_e) { // If json is not valid, set error to the original errorMessage error = errorMessage; } // render toast for each error if there is more than one // otherwise render the toast with the error message // biome-ignore lint/complexity/noFlatMapIdentity: <Match the case if error is an array> [error] .flatMap((error) => error) .forEach((error) => { if (error.code === 'RATE_LIMIT_EXCEEDED_UNAUTHENTICATED') { setHasReachedRateLimit(true); } }); }, } ); }; const handleClear = () => { setDiscussion((discussion) => ({ ...discussion, discussionId: uuid(), storedPrompt: [], relatedFiles: [], })); setCurrentResponse(''); }; useEffect(() => { if (isFirstRender.current) { isFirstRender.current = false; loadDiscussion(); return; } if (typeof stateReloaderTrigger === 'undefined') return; loadDiscussion(); }, [stateReloaderTrigger]); return ( <div className="flex size-full flex-col items-center justify-between overflow-auto"> <div className="relative flex size-full flex-auto"> <div className="absolute inset-0 size-full"> <MessagesList storedPrompt={[ firstMessage, ...(discussion?.storedPrompt ?? []), ...(currentResponse ? [{ role: 'assistant' as const, content: currentResponse }] : []), ]} isLoading={isPending} /> </div> </div> <div className="w-full flex-1"> {displayRelatedFiles && ( <FileReference relatedFiles={discussion?.relatedFiles ?? []} /> )} {hasReachedRateLimit && ( <Container className="mx-auto mt-3 flex max-w-md flex-col gap-4 text-center text-sm" borderColor="neutral" border roundedSize="xl" padding="md" > <span>{rateLimitExceededMessage}</span> <Link href={PagesRoutes.Auth_SignIn} label={signInButton.label.value} color="text" variant="button-outlined" > {signInButton.text} </Link> </Container> )} <FormSection askNewQuestion={handleAskNewQuestion} clear={handleClear} nbMessages={(discussion?.storedPrompt ?? []).length} additionalButtons={additionalButtons} isActive={isActive} /> </div> </div> ); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server