@@ -21,44 +21,44 @@ import { generateEmbedding } from '@/lib/aisearch/embeddings'
2121import { getDbWorker } from '@/lib/aisearch/dbWorker'
2222
2323function cosineSimilarity ( a : number [ ] , b : number [ ] ) : number {
24- if ( a . some ( isNaN ) || b . some ( isNaN ) ) {
25- console . error ( "NaN values detected in vectors:" ) ;
26- console . error ( "Vector A NaN indices:" , a . map ( ( val , i ) => isNaN ( val ) ? i : null ) . filter ( x => x !== null ) ) ;
27- console . error ( "Vector B NaN indices:" , b . map ( ( val , i ) => isNaN ( val ) ? i : null ) . filter ( x => x !== null ) ) ;
28- throw new Error ( "Invalid vectors containing NaN values" ) ;
29- }
30-
31- const dotProduct = a . reduce ( ( sum , val , i ) => sum + val * b [ i ] , 0 ) ;
32- const magnitudeA = Math . sqrt ( a . reduce ( ( sum , val ) => sum + val * val , 0 ) ) ;
33- const magnitudeB = Math . sqrt ( b . reduce ( ( sum , val ) => sum + val * val , 0 ) ) ;
34- return dotProduct / ( magnitudeA * magnitudeB ) ;
24+ if ( a . some ( isNaN ) || b . some ( isNaN ) ) {
25+ console . error ( "NaN values detected in vectors:" ) ;
26+ console . error ( "Vector A NaN indices:" , a . map ( ( val , i ) => isNaN ( val ) ? i : null ) . filter ( x => x !== null ) ) ;
27+ console . error ( "Vector B NaN indices:" , b . map ( ( val , i ) => isNaN ( val ) ? i : null ) . filter ( x => x !== null ) ) ;
28+ throw new Error ( "Invalid vectors containing NaN values" ) ;
29+ }
30+
31+ const dotProduct = a . reduce ( ( sum , val , i ) => sum + val * b [ i ] , 0 ) ;
32+ const magnitudeA = Math . sqrt ( a . reduce ( ( sum , val ) => sum + val * val , 0 ) ) ;
33+ const magnitudeB = Math . sqrt ( b . reduce ( ( sum , val ) => sum + val * val , 0 ) ) ;
34+ return dotProduct / ( magnitudeA * magnitudeB ) ;
3535}
3636
3737export default function Home ( ) {
3838 const [ query , setQuery ] = useState ( '' )
3939 const [ aiResponse , setAiResponse ] = useState ( '' )
40- const [ loaderText , setLoaderText ] = useState ( 'Loading database ...' )
40+ const [ loaderText , setLoaderText ] = useState ( 'Loading database ...' )
4141 const [ isLoading , setIsLoading ] = useState ( false )
4242 const [ error , setError ] = useState < string | null > ( null )
4343 const recommendedArticles = getRecommendedArticles ( )
4444 const searchConfig = getSearchConfig ( )
4545 const headerConfig = getHeaderConfig ( )
4646 const config = getAkiradocsConfig ( )
4747 const [ sources , setSources ] = useState < Source [ ] > ( [ ] )
48- const handleGenerateEmbedding = useCallback ( async ( text : string ) => {
49- try {
50- setIsLoading ( true ) ;
51- // console.log("Loading model for embedding");
52- const embedding = await generateEmbedding ( text , ( progress ) => { } ) ;
53- return embedding ;
54- } catch ( error ) {
55- console . error ( 'Error generating embedding:' , error ) ;
56- throw error ;
57- }
58- finally {
59- setLoaderText ( 'Searching database for relevant information ...' )
60- }
61- } , [ ] ) ;
48+ const handleGenerateEmbedding = useCallback ( async ( text : string ) => {
49+ try {
50+ setIsLoading ( true ) ;
51+ // console.log("Loading model for embedding");
52+ const embedding = await generateEmbedding ( text , ( progress ) => { } ) ;
53+ return embedding ;
54+ } catch ( error ) {
55+ console . error ( 'Error generating embedding:' , error ) ;
56+ throw error ;
57+ }
58+ finally {
59+ setLoaderText ( 'Searching database for relevant information ...' )
60+ }
61+ } , [ ] ) ;
6262
6363 // If AI Search is disabled, show the disabled message
6464 if ( ! config . navigation . header . items . find ( ( item : any ) => item . href === '/aiSearch' ) ?. show ) {
@@ -104,96 +104,98 @@ export default function Home() {
104104 setIsLoading ( true )
105105 setError ( null )
106106 setSources ( [ ] )
107-
108- const startTime = performance . now ( )
109-
110- try {
111- // Generate embedding for the query
112- const queryEmbedding = await handleGenerateEmbedding ( query ) ;
113- // console.log("Query embedding:", queryEmbedding)
114- // Get database worker
115- const worker = await getDbWorker ( ) ;
116-
117- // Get all documents
118- const allDocs = await worker . db . query ( `
107+
108+ const startTime = performance . now ( )
109+
110+ try {
111+ // Generate embedding for the query
112+ const queryEmbedding = await handleGenerateEmbedding ( query ) ;
113+ // console.log("Query embedding:", queryEmbedding)
114+ // Get database worker
115+ const worker = await getDbWorker ( ) ;
116+
117+ // Get all documents
118+ const allDocs = await worker . db . query ( `
119119 SELECT path, content, embedding
120120 FROM documents
121121 WHERE embedding IS NOT NULL
122122 ` ) ;
123123
124- // Calculate similarity scores and filter results
125- const similarityThreshold = 0.5 ;
126- const scoredDocs = allDocs
127- . map ( ( doc : any ) => {
128- // Clean the embedding string and parse it
129- const cleanEmbeddingStr = doc . embedding . replace ( / [ \[ \] ] / g, '' ) ; // Remove square brackets
130- const embeddingArray = cleanEmbeddingStr
131- . split ( ',' )
132- . map ( ( val : string ) => {
133- const parsed = parseFloat ( val . trim ( ) ) ;
134- if ( isNaN ( parsed ) ) {
135- console . error ( `Invalid embedding value found: "${ val } "` ) ;
136- }
137- return parsed ;
138- } ) ;
139-
140- return {
141- ...doc ,
142- similarity_score : cosineSimilarity ( queryEmbedding , embeddingArray )
143- } ;
144- } )
145- . filter ( ( doc : any ) => doc . similarity_score > similarityThreshold )
146- . sort ( ( a : any , b : any ) => b . similarity_score - a . similarity_score )
147- . slice ( 0 , 5 ) ;
148-
149- console . log ( "RAG top 5 results:" , scoredDocs ) ;
150-
151- // If no relevant documents found, return early
152- if ( scoredDocs . length === 0 ) {
153- setAiResponse ( "I cannot answer this question from the given documentation. The available content doesn't seem relevant enough to provide a accurate answer." ) ;
154- setIsLoading ( false ) ;
155- return ;
156- }
157-
158- setLoaderText ( 'Loading the AI model ...' )
159-
160- // Combine relevant documents into context
161- const docsContext = scoredDocs
162- . map ( ( doc : any ) => `
124+ // Calculate similarity scores and filter results
125+ const similarityThreshold = 0.5 ;
126+ const scoredDocs = allDocs
127+ . map ( ( doc : any ) => {
128+ // Clean the embedding string and parse it
129+ const cleanEmbeddingStr = doc . embedding . replace ( / [ \[ \] ] / g, '' ) ; // Remove square brackets
130+ const embeddingArray = cleanEmbeddingStr
131+ . split ( ',' )
132+ . map ( ( val : string ) => {
133+ const parsed = parseFloat ( val . trim ( ) ) ;
134+ if ( isNaN ( parsed ) ) {
135+ console . error ( `Invalid embedding value found: "${ val } "` ) ;
136+ }
137+ return parsed ;
138+ } ) ;
139+
140+ return {
141+ ...doc ,
142+ similarity_score : cosineSimilarity ( queryEmbedding , embeddingArray )
143+ } ;
144+ } )
145+ . filter ( ( doc : any ) => doc . similarity_score > similarityThreshold )
146+ . sort ( ( a : any , b : any ) => b . similarity_score - a . similarity_score )
147+ . slice ( 0 , 5 ) ;
148+
149+ console . log ( "RAG top 5 results:" , scoredDocs ) ;
150+
151+ // If no relevant documents found, return early
152+ if ( scoredDocs . length === 0 ) {
153+ setAiResponse ( "I cannot answer this question from the given documentation. The available content doesn't seem relevant enough to provide a accurate answer." ) ;
154+ setIsLoading ( false ) ;
155+ return ;
156+ }
157+
158+ setLoaderText ( 'Loading the AI model ...' )
159+
160+ // Combine relevant documents into context
161+ const docsContext = scoredDocs
162+ . map ( ( doc : any ) => `
163163 Source: ${ doc . path }
164164 --- Content ---
165165 ${ doc . content }
166166 --- End of Content ---
167167 ` )
168- . join ( '\n' ) ;
168+ . join ( '\n' ) ;
169169
170170 const engine = await CreateMLCEngine (
171171 "Llama-3.2-1B-Instruct-q4f16_1-MLC" ,
172- { initProgressCallback : ( progress : any ) => {
173- console . log ( progress )
174- setLoaderText ( `Loading the AI model ${ Math . round ( progress . progress * 100 ) } % ...` )
175- } } ,
176172 {
177- context_window_size : 20000 ,
173+ initProgressCallback : ( progress : any ) => {
174+ console . log ( progress )
175+ setLoaderText ( `Loading the AI model ${ Math . round ( progress . progress * 100 ) } % ...` )
176+ }
177+ } ,
178+ {
179+ context_window_size : 20000 ,
178180 }
179181 ) ;
180182
181183
182184
183- const engineLoadTime = performance . now ( ) // Track engine load time
184- console . log ( `Time taken for engine initialization: ${ ( engineLoadTime - startTime ) / 1000 } s` )
185- setLoaderText ( 'Processing information and generating AI response ...' )
185+ const engineLoadTime = performance . now ( ) // Track engine load time
186+ console . log ( `Time taken for engine initialization: ${ ( engineLoadTime - startTime ) / 1000 } s` )
187+ setLoaderText ( 'Processing information and generating AI response ...' )
186188 const messages = [
187- {
188- role : "system" ,
189- content : `You are a technical documentation assistant for AkiraDocs. Your purpose is to:
189+ {
190+ role : "system" ,
191+ content : `You are a technical documentation assistant for AkiraDocs. Your purpose is to:
1901921. Provide accurate, helpful answers using ONLY the provided documentation
1911932. Stay positive and factual based on the documentation provided.
1921943. Make sure the markdown answer is pretty, clean and easy to read.`
193195 } ,
194- {
195- role : "user" ,
196- content : `
196+ {
197+ role : "user" ,
198+ content : `
197199 Please provide a helpful answer which is short and concise to the following question using only the provided documentation.
198200
199201 Question: ${ query }
@@ -221,41 +223,43 @@ export default function Home() {
221223 }
222224 ] ;
223225
224- // console.log("Messages:", messages)
226+ // console.log("Messages:", messages)
225227
226- const chunks = await engine . chat . completions . create ( {
228+ const chunks = await engine . chat . completions . create ( {
227229 messages : messages as ChatCompletionMessageParam [ ] ,
228230 stream : true ,
229- stream_options : { include_usage : true } ,
230- max_tokens : 500 ,
231- temperature : 0.7 ,
232- top_p : 0.95 ,
233- frequency_penalty : 0.5 ,
234- presence_penalty : 0.5 ,
231+ stream_options : { include_usage : false } ,
232+ max_tokens : 300 ,
233+ temperature : 0.5 ,
234+ top_p : 0.8 ,
235+ frequency_penalty : 0.3 ,
236+ presence_penalty : 0.3 ,
235237 } ) ;
236238
237239 let aiContent = "" ;
238240 for await ( const chunk of chunks ) {
239241 const newContent = chunk . choices [ 0 ] ?. delta . content || "" ;
240242 aiContent += newContent ;
241-
242- // Process partial content for streaming
243- const { cleanResponse } = extractSources ( aiContent ) ;
244- setAiResponse ( cleanResponse ) ;
245- }
246-
247- // Only extract and set sources after streaming is complete
248- const { sources } = extractSources ( aiContent ) ;
243+
244+ // Process partial content for streaming
245+ const { cleanResponse } = extractSources ( aiContent ) ;
246+ setAiResponse ( cleanResponse ) ;
247+ setIsLoading ( false )
248+ }
249+
250+
251+ // Only extract and set sources after streaming is complete
252+ const { sources } = extractSources ( aiContent ) ;
249253 setSources ( sources ) ;
250-
251- const endTime = performance . now ( ) // Track total time
252- console . log ( `Total time taken for AI search: ${ ( endTime - startTime ) / 1000 } s` )
253254
254- } catch ( error ) {
255- console . error ( 'Search error:' , error ) ;
256- setError ( error instanceof Error ? error . message : 'An error occurred' ) ;
255+ const endTime = performance . now ( ) // Track total time
256+ console . log ( `Total time taken for AI search: ${ ( endTime - startTime ) / 1000 } s` )
257+
258+ } catch ( error ) {
259+ console . error ( 'Search error:' , error ) ;
260+ setError ( error instanceof Error ? error . message : 'An error occurred' ) ;
257261 } finally {
258- setIsLoading ( false ) ;
262+ setIsLoading ( false ) ;
259263 }
260264 }
261265
@@ -265,49 +269,49 @@ export default function Home() {
265269
266270 return (
267271 < div className = "flex flex-col min-h-screen" >
268- < Header { ...headerConfig } currentLocale = { `en` } currentType = { `aiSearch` } />
269- < div className = "min-h-screen py-12 px-4 sm:px-6 lg:px-8" >
270-
271- < div className = "max-w-4xl mx-auto" >
272- < SearchHeader
273- logo = { searchConfig . logo }
274- title = { searchConfig . title }
275- description = { searchConfig . description }
276- />
277- < div className = "flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 justify-center items-center mb-12" >
278- < SearchBar
279- query = { query }
280- onQueryChange = { setQuery }
281- onSubmit = { handleSearch }
272+ < Header { ...headerConfig } currentLocale = { `en` } currentType = { `aiSearch` } />
273+ < div className = "min-h-screen py-12 px-4 sm:px-6 lg:px-8" >
274+
275+ < div className = "max-w-4xl mx-auto" >
276+ < SearchHeader
277+ logo = { searchConfig . logo }
278+ title = { searchConfig . title }
279+ description = { searchConfig . description }
282280 />
283- < LegacyDocsToggle />
284- </ div >
285-
286- < AnimatePresence >
287- { isLoading ? (
288- < div className = "flex flex-col justify-center items-center space-y-4 py-12" >
289- < AILoader />
290- < p className = "text-muted-foreground text-sm animate-pulse" >
291- { loaderText }
292- </ p >
293- </ div >
294- ) : error ? (
295- < div className = "text-center p-4 rounded-lg bg-red-50 text-red-800" >
296- < p className = "text-lg font-medium mb-2" > Error</ p >
297- < p > { error } </ p >
298- </ div >
299- ) : aiResponse ? (
300- < AIResponse
301- response = { aiResponse }
302- sources = { sources }
303- onBack = { handleBack }
281+ < div className = "flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 justify-center items-center mb-12" >
282+ < SearchBar
283+ query = { query }
284+ onQueryChange = { setQuery }
285+ onSubmit = { handleSearch }
304286 />
305- ) : recommendedArticles && (
306- < RecommendedArticles articles = { recommendedArticles } />
307- ) }
308- </ AnimatePresence >
287+ < LegacyDocsToggle />
288+ </ div >
289+
290+ < AnimatePresence >
291+ { isLoading ? (
292+ < div className = "flex flex-col justify-center items-center space-y-4 py-12" >
293+ < AILoader />
294+ < p className = "text-muted-foreground text-sm animate-pulse" >
295+ { loaderText }
296+ </ p >
297+ </ div >
298+ ) : error ? (
299+ < div className = "text-center p-4 rounded-lg bg-red-50 text-red-800" >
300+ < p className = "text-lg font-medium mb-2" > Error</ p >
301+ < p > { error } </ p >
302+ </ div >
303+ ) : aiResponse ? (
304+ < AIResponse
305+ response = { aiResponse }
306+ sources = { sources }
307+ onBack = { handleBack }
308+ />
309+ ) : recommendedArticles && (
310+ < RecommendedArticles articles = { recommendedArticles } />
311+ ) }
312+ </ AnimatePresence >
313+ </ div >
309314 </ div >
310315 </ div >
311- </ div >
312316 )
313- }
317+ }
0 commit comments