@@ -272,6 +272,17 @@ const TopicHistogram: FC = () => {
272272
273273 // State for API key
274274 const [ apiKey , setApiKey ] = useState < string > ( '' ) ;
275+ // Add state to track if we need to fetch explanation after API key is set
276+ const [ pendingExplanationTopic , setPendingExplanationTopic ] = useState < string | null > ( null ) ;
277+
278+ // Add cleanup effect for API key
279+ useEffect ( ( ) => {
280+ // Clear API key and pending explanation when component unmounts
281+ return ( ) => {
282+ setApiKey ( '' ) ;
283+ setPendingExplanationTopic ( null ) ;
284+ } ;
285+ } , [ ] ) ;
275286
276287 // Add API key input modal - initialize to false instead of !apiKey
277288 const [ showApiKeyModal , setShowApiKeyModal ] = useState ( false ) ;
@@ -281,109 +292,68 @@ const TopicHistogram: FC = () => {
281292
282293 // Function to handle topic click
283294 const handleTopicClick = ( topic : string ) => {
284- console . log ( 'Topic clicked:' , topic ) ;
285- console . log ( 'Current API key:' , apiKey ? 'Present' : 'Missing' ) ;
286-
287295 if ( ! apiKey ) {
288- console . log ( 'No API key, showing modal' ) ;
289- // Show API key modal if no key is set
296+ // Store the topic that was clicked for later explanation
297+ setPendingExplanationTopic ( topic ) ;
290298 setShowApiKeyModal ( true ) ;
291299 return ;
292300 }
293301
294- console . log ( 'Starting explanation fetch for topic:' , topic ) ;
295- // Show explanation modal immediately with loading state
302+ // If we have an API key, fetch explanation immediately
296303 setSelectedTopicForExplanation ( topic ) ;
297304 setTopicExplanation ( "" ) ; // Clear previous explanation
298305 setIsLoadingExplanation ( true ) ; // Set loading state
299- // Then fetch the explanation
300306 fetchTopicExplanation ( topic ) ;
301307 } ;
302308
303309 // Function to save API key and fetch explanation if a topic was clicked
304310 const saveApiKey = ( key : string ) => {
305- setApiKey ( key ) ;
311+ // Trim the key to remove any accidental whitespace
312+ const trimmedKey = key . trim ( ) ;
313+ setApiKey ( trimmedKey ) ;
306314 setShowApiKeyModal ( false ) ;
307315
308- // If there was a topic waiting for explanation, fetch it now
309- if ( selectedTopicForExplanation ) {
310- fetchTopicExplanation ( selectedTopicForExplanation ) ;
316+ // If there was a pending topic waiting for explanation, fetch it now
317+ if ( pendingExplanationTopic ) {
318+ setSelectedTopicForExplanation ( pendingExplanationTopic ) ;
319+ setTopicExplanation ( "" ) ; // Clear previous explanation
320+ setIsLoadingExplanation ( true ) ; // Set loading state
321+ fetchTopicExplanation ( pendingExplanationTopic ) ;
322+ setPendingExplanationTopic ( null ) ; // Clear the pending topic
311323 }
312324 } ;
313325
314- // Effect to check if search term and topic are provided
315- useEffect ( ( ) => {
316- if ( ! searchTerm ) {
317- notify ( {
318- message : "No search term provided. Please enter a topic to search." ,
319- type : "warning"
320- } ) ;
321- navigate ( '/' ) ;
322- } else {
323- setOriginalTopic ( userTopic ) ;
324- }
325- } , [ searchTerm , userTopic , navigate , notify ] ) ;
326-
327- // Effect to handle topic extraction when search term changes
328- useEffect ( ( ) => {
329- // Add a check for extractedTopics length to prevent refetching
330- if ( ! userTopic || extractedTopics . length > 0 ) return ;
331-
332- setIsLoading ( true ) ;
326+ // Function to clear API key
327+ const clearApiKey = ( ) => {
328+ setApiKey ( '' ) ;
329+ setShowApiKeyModal ( false ) ;
330+ setSelectedTopicForExplanation ( null ) ;
331+ setPendingExplanationTopic ( null ) ; // Also clear any pending explanation
332+ } ;
333333
334- fetch ( API_ENDPOINTS . PROCESS_TOPICS , {
335- method : 'POST' ,
336- headers : {
337- 'Content-Type' : 'application/json' ,
338- } ,
339- body : JSON . stringify ( {
340- topic : userTopic ,
341- searchTerm : searchTerm
342- } )
343- } )
344- . then ( response => {
345- if ( ! response . ok ) {
346- throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
347- }
348- return response . json ( ) ;
349- } )
350- . then ( data => {
351- if ( data . success && data . data . length === 0 ) {
352- notify ( {
353- message : "No topics found for this search. Try a different topic." ,
354- type : "warning"
355- } ) ;
356- }
357- setExtractedTopics ( data . data || [ ] ) ;
358- } )
359- . catch ( error => {
360- console . error ( 'Fetch error:' , error ) ;
361- notify ( {
362- message : "Failed to fetch topics. Please try again." ,
363- type : "error"
364- } ) ;
365- setExtractedTopics ( [ ] ) ;
366- } )
367- . finally ( ( ) => {
368- setIsLoading ( false ) ;
369- } ) ;
370- } , [ userTopic ] ) ;
334+ // Function to handle API key input change
335+ const handleApiKeyChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
336+ // Clear any existing key when user starts typing
337+ if ( apiKey ) {
338+ setApiKey ( '' ) ;
339+ setPendingExplanationTopic ( null ) ; // Clear pending explanation when key is cleared
340+ }
341+ setApiKey ( e . target . value ) ;
342+ } ;
371343
372344 // Function to fetch topic explanation
373345 const fetchTopicExplanation = async ( topic : string ) => {
374- console . log ( 'Fetching explanation for topic:' , topic ) ;
375346 if ( ! apiKey ) {
376- console . log ( 'No API key in fetchTopicExplanation' ) ;
377347 notify ( {
378348 message : "Please set your Google API key first" ,
379349 type : "warning"
380350 } ) ;
381- setSelectedTopicForExplanation ( null ) ; // Close modal if no API key
351+ setSelectedTopicForExplanation ( null ) ;
352+ setPendingExplanationTopic ( topic ) ; // Store the topic for later
382353 return ;
383354 }
384355
385356 try {
386- console . log ( 'Making API request to:' , API_ENDPOINTS . EXPLAIN_TOPIC ) ;
387357 const response = await fetch ( API_ENDPOINTS . EXPLAIN_TOPIC , {
388358 method : 'POST' ,
389359 headers : {
@@ -397,15 +367,14 @@ const TopicHistogram: FC = () => {
397367 } )
398368 } ) ;
399369
400- console . log ( 'Response status:' , response . status ) ;
401370 if ( ! response . ok ) {
402371 throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
403372 }
404373
405374 const data = await response . json ( ) ;
406- console . log ( 'Response data:' , data ) ;
407375 if ( data . success ) {
408- setTopicExplanation ( data . explanation ) ;
376+ const explanation = typeof data . explanation === 'object' ? data . explanation . explanation : data . explanation ;
377+ setTopicExplanation ( explanation || "No explanation available." ) ;
409378 } else {
410379 throw new Error ( data . message || 'Failed to get explanation' ) ;
411380 }
@@ -416,6 +385,7 @@ const TopicHistogram: FC = () => {
416385 type : "error"
417386 } ) ;
418387 setTopicExplanation ( "Sorry, I couldn't generate an explanation for this topic at the moment." ) ;
388+ // Don't clear the selected topic on error, so user can try again
419389 } finally {
420390 setIsLoadingExplanation ( false ) ;
421391 }
@@ -431,7 +401,7 @@ const TopicHistogram: FC = () => {
431401 const handleRequestSuggestions = async ( model : string , prompt : string , apiKey : string , topics : string [ ] ) => {
432402 setLlmSuggestionsState ( [ ] ) ; // Clear previous suggestions immediately
433403 try {
434- console . log ( 'Requesting AI suggestions with:' , { model, prompt, apiKey, topics } ) ;
404+ // console.log('Requesting AI suggestions with:', { model, prompt, apiKey, topics });
435405 const response = await fetch ( API_ENDPOINTS . AI_PROCESS , {
436406 method : 'POST' ,
437407 headers : {
@@ -451,7 +421,7 @@ const TopicHistogram: FC = () => {
451421 }
452422
453423 const data = await response . json ( ) ;
454- console . log ( 'Received AI suggestions:' , data ) ;
424+ // console.log('Received AI suggestions:', data);
455425
456426 if ( data . success && Array . isArray ( data . result ) ) {
457427 // Update state synchronously
@@ -488,7 +458,7 @@ const TopicHistogram: FC = () => {
488458 } , 100 ) ;
489459 } ) ;
490460
491- console . log ( 'State updated with suggestions:' , data . result ) ;
461+ // console.log('State updated with suggestions:', data.result);
492462 } else {
493463 throw new Error ( 'Invalid response format from AI service' ) ;
494464 }
@@ -508,6 +478,64 @@ const TopicHistogram: FC = () => {
508478 window . location . href = window . location . origin ;
509479 } ;
510480
481+ // Effect to handle topic extraction when search term changes
482+ useEffect ( ( ) => {
483+ // Add a check for extractedTopics length to prevent refetching
484+ if ( ! userTopic || extractedTopics . length > 0 ) return ;
485+
486+ setIsLoading ( true ) ;
487+
488+ fetch ( API_ENDPOINTS . PROCESS_TOPICS , {
489+ method : 'POST' ,
490+ headers : {
491+ 'Content-Type' : 'application/json' ,
492+ } ,
493+ body : JSON . stringify ( {
494+ topic : userTopic ,
495+ searchTerm : searchTerm
496+ } )
497+ } )
498+ . then ( response => {
499+ if ( ! response . ok ) {
500+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
501+ }
502+ return response . json ( ) ;
503+ } )
504+ . then ( data => {
505+ if ( data . success && data . data . length === 0 ) {
506+ notify ( {
507+ message : "No topics found for this search. Try a different topic." ,
508+ type : "warning"
509+ } ) ;
510+ }
511+ setExtractedTopics ( data . data || [ ] ) ;
512+ } )
513+ . catch ( error => {
514+ console . error ( 'Fetch error:' , error ) ;
515+ notify ( {
516+ message : "Failed to fetch topics. Please try again." ,
517+ type : "error"
518+ } ) ;
519+ setExtractedTopics ( [ ] ) ;
520+ } )
521+ . finally ( ( ) => {
522+ setIsLoading ( false ) ;
523+ } ) ;
524+ } , [ userTopic ] ) ;
525+
526+ // Effect to check if search term and topic are provided
527+ useEffect ( ( ) => {
528+ if ( ! searchTerm ) {
529+ notify ( {
530+ message : "No search term provided. Please enter a topic to search." ,
531+ type : "warning"
532+ } ) ;
533+ navigate ( '/' ) ;
534+ } else {
535+ setOriginalTopic ( userTopic ) ;
536+ }
537+ } , [ searchTerm , userTopic , navigate , notify ] ) ;
538+
511539 return (
512540 < main className = "container-fluid py-4" style = { { height : '100vh' , overflowY : 'auto' } } >
513541 { /* API Key Modal */ }
@@ -520,10 +548,7 @@ const TopicHistogram: FC = () => {
520548 < button
521549 type = "button"
522550 className = "btn-close"
523- onClick = { ( ) => {
524- setShowApiKeyModal ( false ) ;
525- setSelectedTopicForExplanation ( null ) ;
526- } }
551+ onClick = { clearApiKey }
527552 aria-label = "Close"
528553 />
529554 </ div >
@@ -535,11 +560,19 @@ const TopicHistogram: FC = () => {
535560 className = "form-control"
536561 id = "apiKey"
537562 value = { apiKey }
538- onChange = { ( e ) => setApiKey ( e . target . value ) }
563+ onChange = { handleApiKeyChange }
539564 placeholder = "Enter your Google Gemini API key"
565+ autoComplete = "off"
540566 />
541567 < div className = "form-text" >
542- Your Google Gemini API key is required to get topic explanations. It is stored locally in your browser and is never shared with our servers.
568+ Your Google Gemini API key is required to get topic explanations.
569+ < strong > Important security notes:</ strong >
570+ < ul className = "mt-2 mb-0" >
571+ < li > The key is stored only in memory and is cleared when you close the page</ li >
572+ < li > It is never saved to disk or sent to our servers</ li >
573+ < li > It is only used to make direct API calls to Google's services</ li >
574+ < li > Please use a key with appropriate restrictions set in Google Cloud Console</ li >
575+ </ ul >
543576 < br />
544577 < a href = "https://ai.google.dev/" target = "_blank" rel = "noopener noreferrer" >
545578 Get your API key from Google AI Studio
@@ -551,18 +584,15 @@ const TopicHistogram: FC = () => {
551584 < button
552585 type = "button"
553586 className = "btn btn-secondary"
554- onClick = { ( ) => {
555- setShowApiKeyModal ( false ) ;
556- setSelectedTopicForExplanation ( null ) ;
557- } }
587+ onClick = { clearApiKey }
558588 >
559589 Cancel
560590 </ button >
561591 < button
562592 type = "button"
563593 className = "btn btn-primary"
564594 onClick = { ( ) => saveApiKey ( apiKey ) }
565- disabled = { ! apiKey }
595+ disabled = { ! apiKey . trim ( ) }
566596 >
567597 Save & Continue
568598 </ button >
0 commit comments