@@ -21,44 +21,44 @@ import { generateEmbedding } from '@/lib/aisearch/embeddings'
21
21
import { getDbWorker } from '@/lib/aisearch/dbWorker'
22
22
23
23
function 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 ) ;
35
35
}
36
36
37
37
export default function Home ( ) {
38
38
const [ query , setQuery ] = useState ( '' )
39
39
const [ aiResponse , setAiResponse ] = useState ( '' )
40
- const [ loaderText , setLoaderText ] = useState ( 'Loading database ...' )
40
+ const [ loaderText , setLoaderText ] = useState ( 'Loading database ...' )
41
41
const [ isLoading , setIsLoading ] = useState ( false )
42
42
const [ error , setError ] = useState < string | null > ( null )
43
43
const recommendedArticles = getRecommendedArticles ( )
44
44
const searchConfig = getSearchConfig ( )
45
45
const headerConfig = getHeaderConfig ( )
46
46
const config = getAkiradocsConfig ( )
47
47
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
+ } , [ ] ) ;
62
62
63
63
// If AI Search is disabled, show the disabled message
64
64
if ( ! config . navigation . header . items . find ( ( item : any ) => item . href === '/aiSearch' ) ?. show ) {
@@ -104,96 +104,98 @@ export default function Home() {
104
104
setIsLoading ( true )
105
105
setError ( null )
106
106
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 ( `
119
119
SELECT path, content, embedding
120
120
FROM documents
121
121
WHERE embedding IS NOT NULL
122
122
` ) ;
123
123
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 ) => `
163
163
Source: ${ doc . path }
164
164
--- Content ---
165
165
${ doc . content }
166
166
--- End of Content ---
167
167
` )
168
- . join ( '\n' ) ;
168
+ . join ( '\n' ) ;
169
169
170
170
const engine = await CreateMLCEngine (
171
171
"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
- } } ,
176
172
{
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 ,
178
180
}
179
181
) ;
180
182
181
183
182
184
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 ...' )
186
188
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:
190
192
1. Provide accurate, helpful answers using ONLY the provided documentation
191
193
2. Stay positive and factual based on the documentation provided.
192
194
3. Make sure the markdown answer is pretty, clean and easy to read.`
193
195
} ,
194
- {
195
- role : "user" ,
196
- content : `
196
+ {
197
+ role : "user" ,
198
+ content : `
197
199
Please provide a helpful answer which is short and concise to the following question using only the provided documentation.
198
200
199
201
Question: ${ query }
@@ -221,41 +223,43 @@ export default function Home() {
221
223
}
222
224
] ;
223
225
224
- // console.log("Messages:", messages)
226
+ // console.log("Messages:", messages)
225
227
226
- const chunks = await engine . chat . completions . create ( {
228
+ const chunks = await engine . chat . completions . create ( {
227
229
messages : messages as ChatCompletionMessageParam [ ] ,
228
230
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 ,
235
237
} ) ;
236
238
237
239
let aiContent = "" ;
238
240
for await ( const chunk of chunks ) {
239
241
const newContent = chunk . choices [ 0 ] ?. delta . content || "" ;
240
242
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 ) ;
249
253
setSources ( sources ) ;
250
-
251
- const endTime = performance . now ( ) // Track total time
252
- console . log ( `Total time taken for AI search: ${ ( endTime - startTime ) / 1000 } s` )
253
254
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' ) ;
257
261
} finally {
258
- setIsLoading ( false ) ;
262
+ setIsLoading ( false ) ;
259
263
}
260
264
}
261
265
@@ -265,49 +269,49 @@ export default function Home() {
265
269
266
270
return (
267
271
< 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 }
282
280
/>
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 }
304
286
/>
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 >
309
314
</ div >
310
315
</ div >
311
- </ div >
312
316
)
313
- }
317
+ }
0 commit comments