siddhartharya commited on
Commit
dea9f7b
Β·
verified Β·
1 Parent(s): 20e84c1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +290 -150
app.py CHANGED
@@ -34,9 +34,8 @@ console_handler.setFormatter(formatter)
34
  # Add the handler to the logger
35
  logger.addHandler(console_handler)
36
 
37
- # Initialize models and variables
38
- logger.info("Initializing models and variables")
39
- embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
40
  faiss_index = None
41
  bookmarks = []
42
  fetch_cache = {}
@@ -79,6 +78,17 @@ if not GROQ_API_KEY:
79
  openai.api_key = GROQ_API_KEY
80
  openai.api_base = "https://api.groq.com/openai/v1"
81
 
 
 
 
 
 
 
 
 
 
 
 
82
  def extract_main_content(soup):
83
  """
84
  Extract the main content from a webpage while filtering out boilerplate content.
@@ -377,17 +387,18 @@ def vectorize_and_index(bookmarks_list):
377
  """
378
  Create vector embeddings for bookmarks and build FAISS index with ID mapping.
379
  """
 
380
  logger.info("Vectorizing summaries and building FAISS index")
381
  try:
382
  summaries = [bookmark['summary'] for bookmark in bookmarks_list]
383
- embeddings = embedding_model.encode(summaries)
384
  dimension = embeddings.shape[1]
385
- index = faiss.IndexIDMap(faiss.IndexFlatL2(dimension))
 
386
  # Assign unique IDs to each bookmark
387
  ids = np.array([bookmark['id'] for bookmark in bookmarks_list], dtype=np.int64)
388
- index.add_with_ids(np.array(embeddings).astype('float32'), ids)
389
  logger.info("FAISS index built successfully with IDs")
390
- return index
391
  except Exception as e:
392
  logger.error(f"Error in vectorizing and indexing: {e}", exc_info=True)
393
  raise
@@ -441,7 +452,7 @@ def display_bookmarks():
441
  logger.info("HTML display generated")
442
  return cards
443
 
444
- def process_uploaded_file(file):
445
  """
446
  Process the uploaded bookmarks file.
447
  """
@@ -450,23 +461,47 @@ def process_uploaded_file(file):
450
 
451
  if file is None:
452
  logger.warning("No file uploaded")
453
- return "Please upload a bookmarks HTML file.", '', gr.update(choices=[]), display_bookmarks()
 
 
 
 
 
 
454
 
455
  try:
456
  file_content = file.decode('utf-8')
457
  except UnicodeDecodeError as e:
458
- logger.error(f"Error decoding the file: {e}", exc_info=True)
459
- return "Error decoding the file. Please ensure it's a valid HTML file.", '', gr.update(choices=[]), display_bookmarks()
 
 
 
 
 
 
460
 
461
  try:
462
  bookmarks = parse_bookmarks(file_content)
463
  except Exception as e:
464
- logger.error(f"Error parsing bookmarks: {e}", exc_info=True)
465
- return "Error parsing the bookmarks HTML file.", '', gr.update(choices=[]), display_bookmarks()
 
 
 
 
 
 
466
 
467
  if not bookmarks:
468
  logger.warning("No bookmarks found in the uploaded file")
469
- return "No bookmarks found in the uploaded file.", '', gr.update(choices=[]), display_bookmarks()
 
 
 
 
 
 
470
 
471
  # Assign unique IDs to bookmarks
472
  for idx, bookmark in enumerate(bookmarks):
@@ -477,91 +512,129 @@ def process_uploaded_file(file):
477
  with ThreadPoolExecutor(max_workers=20) as executor:
478
  executor.map(fetch_url_info, bookmarks)
479
 
480
- # Process bookmarks concurrently with LLM calls
481
- logger.info("Processing bookmarks with LLM concurrently")
482
  with ThreadPoolExecutor(max_workers=5) as executor:
483
  executor.map(generate_summary_and_assign_category, bookmarks)
484
 
485
  try:
486
- faiss_index = vectorize_and_index(bookmarks)
487
  except Exception as e:
488
  logger.error(f"Error building FAISS index: {e}", exc_info=True)
489
- return "Error building search index.", '', gr.update(choices=[]), display_bookmarks()
 
 
 
 
 
 
490
 
491
  message = f"βœ… Successfully processed {len(bookmarks)} bookmarks."
492
  logger.info(message)
493
 
494
  # Generate displays and updates
495
  bookmark_html = display_bookmarks()
496
- choices = [f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})"
497
- for i, bookmark in enumerate(bookmarks)]
498
-
499
- return message, bookmark_html, gr.update(choices=choices), bookmark_html
 
 
 
 
 
500
 
501
- def delete_selected_bookmarks(selected_indices):
502
  """
503
  Delete selected bookmarks and remove their vectors from the FAISS index.
504
  """
505
  global bookmarks, faiss_index
506
  if not selected_indices:
507
- return "⚠️ No bookmarks selected.", gr.update(choices=[]), display_bookmarks()
508
 
509
  ids_to_delete = []
510
  indices_to_delete = []
511
  for s in selected_indices:
512
- idx = int(s.split('.')[0]) - 1
513
- if 0 <= idx < len(bookmarks):
514
- bookmark_id = bookmarks[idx]['id']
515
- ids_to_delete.append(bookmark_id)
516
- indices_to_delete.append(idx)
517
- logger.info(f"Deleting bookmark at index {idx + 1}")
 
 
 
518
 
519
  # Remove vectors from FAISS index
520
  if faiss_index is not None and ids_to_delete:
521
  faiss_index.remove_ids(np.array(ids_to_delete, dtype=np.int64))
522
 
523
  # Remove bookmarks from the list (reverse order to avoid index shifting)
 
524
  for idx in sorted(indices_to_delete, reverse=True):
525
  bookmarks.pop(idx)
526
 
527
  message = "πŸ—‘οΈ Selected bookmarks deleted successfully."
528
  logger.info(message)
529
- choices = [f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})"
530
- for i, bookmark in enumerate(bookmarks)]
531
 
532
- return message, gr.update(choices=choices), display_bookmarks()
 
 
 
 
 
533
 
534
- def edit_selected_bookmarks_category(selected_indices, new_category):
535
  """
536
  Edit category of selected bookmarks.
537
  """
538
  if not selected_indices:
539
- return "⚠️ No bookmarks selected.", gr.update(choices=[]), display_bookmarks()
 
 
 
 
 
540
  if not new_category:
541
- return "⚠️ No new category selected.", gr.update(choices=[]), display_bookmarks()
 
 
 
 
 
542
 
543
- indices = [int(s.split('.')[0])-1 for s in selected_indices]
544
- for idx in indices:
545
- if 0 <= idx < len(bookmarks):
546
- bookmarks[idx]['category'] = new_category
547
- logger.info(f"Updated category for bookmark {idx + 1} to {new_category}")
 
 
 
 
548
 
549
  message = "✏️ Category updated for selected bookmarks."
550
  logger.info(message)
551
 
552
  # Update choices and display
553
- choices = [f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})"
554
- for i, bookmark in enumerate(bookmarks)]
555
 
556
- return message, gr.update(choices=choices), display_bookmarks()
 
 
 
 
 
557
 
558
- def export_bookmarks():
559
  """
560
  Export bookmarks to an HTML file.
561
  """
 
562
  if not bookmarks:
563
  logger.warning("No bookmarks to export")
564
- return None # Return None instead of a message
565
 
566
  try:
567
  logger.info("Exporting bookmarks to HTML")
@@ -575,30 +648,36 @@ def export_bookmarks():
575
  dl.append(dt)
576
  soup.append(dl)
577
  html_content = str(soup)
578
- # Save to a temporary file
579
- output_file = "exported_bookmarks.html"
580
- with open(output_file, 'w', encoding='utf-8') as f:
581
- f.write(html_content)
582
  logger.info("Bookmarks exported successfully")
583
- return output_file # Return the file path
584
  except Exception as e:
585
- logger.error(f"Error exporting bookmarks: {e}", exc_info=True)
586
- return None # Return None in case of error
587
 
588
- def chatbot_response(user_query, chat_history):
589
  """
590
  Generate chatbot response using the FAISS index and embeddings, maintaining chat history.
591
  """
592
- if not bookmarks or faiss_index is None:
 
 
 
 
 
593
  logger.warning("No bookmarks available for chatbot")
594
- chat_history.append((user_query, "⚠️ No bookmarks available. Please upload and process your bookmarks first."))
595
- return chat_history
596
 
597
  logger.info(f"Chatbot received query: {user_query}")
598
 
599
  try:
 
 
 
600
  # Encode the user query
601
- query_vector = embedding_model.encode([user_query]).astype('float32')
602
 
603
  # Search the FAISS index
604
  k = 5 # Number of results to return
@@ -610,65 +689,49 @@ def chatbot_response(user_query, chat_history):
610
  matching_bookmarks = [id_to_bookmark.get(id) for id in ids if id in id_to_bookmark]
611
 
612
  if not matching_bookmarks:
613
- answer = "No relevant bookmarks found for your query."
614
- chat_history.append((user_query, answer))
615
- return chat_history
616
 
617
  # Format the response
618
- bookmarks_info = "\n".join([
619
- f"Title: {bookmark['title']}\nURL: {bookmark['url']}\nSummary: {bookmark['summary']}"
620
  for bookmark in matching_bookmarks
621
  ])
622
 
623
- # Use the LLM via Groq Cloud API to generate a response
624
  prompt = f"""
625
  A user asked: "{user_query}"
626
  Based on the bookmarks below, provide a helpful answer to the user's query, referencing the relevant bookmarks.
 
627
  Bookmarks:
628
  {bookmarks_info}
 
629
  Provide a concise and helpful response.
630
  """
631
 
632
- # Estimate tokens
633
- def estimate_tokens(text):
634
- return len(text) / 4 # Approximate token estimation
635
-
636
- prompt_tokens = estimate_tokens(prompt)
637
- max_tokens = 300 # Adjust as needed
638
- total_tokens = prompt_tokens + max_tokens
639
-
640
- # Calculate required delay
641
- tokens_per_minute = 60000 # Adjust based on your rate limit
642
- tokens_per_second = tokens_per_minute / 60
643
- required_delay = total_tokens / tokens_per_second
644
- sleep_time = max(required_delay, 1)
645
-
646
  response = openai.ChatCompletion.create(
647
  model='llama-3.1-70b-versatile', # Using the specified model
648
  messages=[
649
  {"role": "user", "content": prompt}
650
  ],
651
- max_tokens=int(max_tokens),
652
  temperature=0.7,
653
  )
654
  answer = response['choices'][0]['message']['content'].strip()
655
  logger.info("Chatbot response generated")
656
- time.sleep(sleep_time)
657
-
658
- # Append the interaction to chat history
659
- chat_history.append((user_query, answer))
660
- return chat_history
661
 
662
  except openai.error.RateLimitError as e:
663
  wait_time = int(e.headers.get("Retry-After", 5))
664
  logger.warning(f"Rate limit reached. Waiting for {wait_time} seconds before retrying...")
665
  time.sleep(wait_time)
666
- return chatbot_response(user_query, chat_history) # Retry after waiting
667
  except Exception as e:
668
  error_message = f"⚠️ Error processing your query: {str(e)}"
669
  logger.error(error_message, exc_info=True)
670
- chat_history.append((user_query, error_message))
671
- return chat_history
672
 
673
  def build_app():
674
  """
@@ -676,103 +739,180 @@ def build_app():
676
  """
677
  try:
678
  logger.info("Building Gradio app")
679
- with gr.Blocks(css="app.css") as demo:
 
 
 
 
680
  # General Overview
681
  gr.Markdown("""
682
- # πŸ“š SmartMarks - AI Browser Bookmarks Manager
683
- Welcome to **SmartMarks**, your intelligent assistant for managing browser bookmarks. SmartMarks leverages AI to help you organize, search, and interact with your bookmarks seamlessly.
684
- ---
685
- ## πŸš€ **How to Use SmartMarks**
686
- SmartMarks is divided into three main sections:
687
- 1. **πŸ“‚ Upload and Process Bookmarks:** Import your existing bookmarks and let SmartMarks analyze and categorize them for you.
688
- 2. **πŸ’¬ Chat with Bookmarks:** Interact with your bookmarks using natural language queries to find relevant links effortlessly.
689
- 3. **πŸ› οΈ Manage Bookmarks:** View, edit, delete, and export your bookmarks with ease.
690
- """)
 
 
 
 
 
 
 
691
 
692
  # Upload and Process Bookmarks Tab
693
  with gr.Tab("Upload and Process Bookmarks"):
694
  gr.Markdown("""
695
- ## πŸ“‚ **Upload and Process Bookmarks**
696
- ### πŸ“ **Steps:**
697
- 1. Click on the "Upload Bookmarks HTML File" button
698
- 2. Select your bookmarks file
699
- 3. Click "Process Bookmarks" to analyze and organize your bookmarks
700
- """)
701
-
702
- upload = gr.File(label="πŸ“ Upload Bookmarks HTML File", type='binary')
 
 
 
 
 
 
 
 
 
703
  process_button = gr.Button("βš™οΈ Process Bookmarks")
704
  output_text = gr.Textbox(label="βœ… Output", interactive=False)
705
  bookmark_display = gr.HTML(label="πŸ“„ Processed Bookmarks")
706
 
 
 
 
 
 
 
707
  # Chat with Bookmarks Tab
708
  with gr.Tab("Chat with Bookmarks"):
709
  gr.Markdown("""
710
- ## πŸ’¬ **Chat with Bookmarks**
711
- Ask questions about your bookmarks and get relevant results.
712
- """)
713
-
714
- chatbot = gr.Chatbot(label="πŸ’¬ Chat with SmartMarks")
715
- user_input = gr.Textbox(
716
- label="✍️ Ask about your bookmarks",
717
- placeholder="e.g., Do I have any bookmarks about AI?"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
718
  )
719
- chat_button = gr.Button("πŸ“¨ Send")
720
 
721
  # Manage Bookmarks Tab
722
  with gr.Tab("Manage Bookmarks"):
723
  gr.Markdown("""
724
- ## πŸ› οΈ **Manage Bookmarks**
725
- Select bookmarks to delete or edit their categories.
726
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
727
 
728
  manage_output = gr.Textbox(label="πŸ”„ Status", interactive=False)
729
  bookmark_selector = gr.CheckboxGroup(
730
  label="βœ… Select Bookmarks",
731
- choices=[]
732
- )
733
- new_category = gr.Dropdown(
734
- label="πŸ†• New Category",
735
- choices=CATEGORIES,
736
- value="Uncategorized"
737
  )
 
738
  bookmark_display_manage = gr.HTML(label="πŸ“„ Bookmarks")
739
 
740
  with gr.Row():
741
  delete_button = gr.Button("πŸ—‘οΈ Delete Selected")
742
  edit_category_button = gr.Button("✏️ Edit Category")
743
  export_button = gr.Button("πŸ’Ύ Export")
 
744
 
745
- download_link = gr.File(label="πŸ“₯ Download Exported Bookmarks")
746
-
747
- # Set up event handlers
748
- process_button.click(
749
- process_uploaded_file,
750
- inputs=upload,
751
- outputs=[output_text, bookmark_display, bookmark_selector, bookmark_display_manage]
752
- )
753
 
754
- chat_button.click(
755
- chatbot_response,
756
- inputs=[user_input, chatbot],
757
- outputs=chatbot
758
- )
 
759
 
760
- delete_button.click(
761
- delete_selected_bookmarks,
762
- inputs=bookmark_selector,
763
- outputs=[manage_output, bookmark_selector, bookmark_display_manage]
764
- )
765
 
766
- edit_category_button.click(
767
- edit_selected_bookmarks_category,
768
- inputs=[bookmark_selector, new_category],
769
- outputs=[manage_output, bookmark_selector, bookmark_display_manage]
770
- )
771
 
772
- export_button.click(
773
- export_bookmarks,
774
- outputs=download_link
775
- )
 
 
 
 
 
 
776
 
777
  logger.info("Launching Gradio app")
778
  demo.launch(debug=True)
 
34
  # Add the handler to the logger
35
  logger.addHandler(console_handler)
36
 
37
+ # Initialize variables
38
+ logger.info("Initializing variables")
 
39
  faiss_index = None
40
  bookmarks = []
41
  fetch_cache = {}
 
78
  openai.api_key = GROQ_API_KEY
79
  openai.api_base = "https://api.groq.com/openai/v1"
80
 
81
+ # Global variables for models to enable lazy loading
82
+ embedding_model = None
83
+
84
+ def get_embedding_model():
85
+ global embedding_model
86
+ if embedding_model is None:
87
+ logger.info("Loading SentenceTransformer model...")
88
+ embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
89
+ logger.info("SentenceTransformer model loaded.")
90
+ return embedding_model
91
+
92
  def extract_main_content(soup):
93
  """
94
  Extract the main content from a webpage while filtering out boilerplate content.
 
387
  """
388
  Create vector embeddings for bookmarks and build FAISS index with ID mapping.
389
  """
390
+ global faiss_index
391
  logger.info("Vectorizing summaries and building FAISS index")
392
  try:
393
  summaries = [bookmark['summary'] for bookmark in bookmarks_list]
394
+ embeddings = get_embedding_model().encode(summaries).astype('float32')
395
  dimension = embeddings.shape[1]
396
+ if faiss_index is None:
397
+ faiss_index = faiss.IndexIDMap(faiss.IndexFlatL2(dimension))
398
  # Assign unique IDs to each bookmark
399
  ids = np.array([bookmark['id'] for bookmark in bookmarks_list], dtype=np.int64)
400
+ faiss_index.add_with_ids(embeddings, ids)
401
  logger.info("FAISS index built successfully with IDs")
 
402
  except Exception as e:
403
  logger.error(f"Error in vectorizing and indexing: {e}", exc_info=True)
404
  raise
 
452
  logger.info("HTML display generated")
453
  return cards
454
 
455
+ def process_uploaded_file(file, state_bookmarks):
456
  """
457
  Process the uploaded bookmarks file.
458
  """
 
461
 
462
  if file is None:
463
  logger.warning("No file uploaded")
464
+ return (
465
+ "⚠️ Please upload a bookmarks HTML file.",
466
+ "",
467
+ gr.update(choices=[]),
468
+ display_bookmarks(),
469
+ state_bookmarks # Return the unchanged state
470
+ )
471
 
472
  try:
473
  file_content = file.decode('utf-8')
474
  except UnicodeDecodeError as e:
475
+ logger.error(f"Error decoding the file: {e}")
476
+ return (
477
+ "⚠️ Error decoding the file. Please ensure it's a valid HTML file.",
478
+ "",
479
+ gr.update(choices=[]),
480
+ display_bookmarks(),
481
+ state_bookmarks # Return the unchanged state
482
+ )
483
 
484
  try:
485
  bookmarks = parse_bookmarks(file_content)
486
  except Exception as e:
487
+ logger.error(f"Error parsing bookmarks: {e}")
488
+ return (
489
+ "⚠️ Error parsing the bookmarks HTML file.",
490
+ "",
491
+ gr.update(choices=[]),
492
+ display_bookmarks(),
493
+ state_bookmarks # Return the unchanged state
494
+ )
495
 
496
  if not bookmarks:
497
  logger.warning("No bookmarks found in the uploaded file")
498
+ return (
499
+ "⚠️ No bookmarks found in the uploaded file.",
500
+ "",
501
+ gr.update(choices=[]),
502
+ display_bookmarks(),
503
+ state_bookmarks # Return the unchanged state
504
+ )
505
 
506
  # Assign unique IDs to bookmarks
507
  for idx, bookmark in enumerate(bookmarks):
 
512
  with ThreadPoolExecutor(max_workers=20) as executor:
513
  executor.map(fetch_url_info, bookmarks)
514
 
515
+ # Generate summaries and assign categories
516
+ logger.info("Generating summaries and assigning categories")
517
  with ThreadPoolExecutor(max_workers=5) as executor:
518
  executor.map(generate_summary_and_assign_category, bookmarks)
519
 
520
  try:
521
+ vectorize_and_index(bookmarks)
522
  except Exception as e:
523
  logger.error(f"Error building FAISS index: {e}", exc_info=True)
524
+ return (
525
+ "⚠️ Error building search index.",
526
+ "",
527
+ gr.update(choices=[]),
528
+ display_bookmarks(),
529
+ state_bookmarks # Return the unchanged state
530
+ )
531
 
532
  message = f"βœ… Successfully processed {len(bookmarks)} bookmarks."
533
  logger.info(message)
534
 
535
  # Generate displays and updates
536
  bookmark_html = display_bookmarks()
537
+ choices = [f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})" for i, bookmark in enumerate(bookmarks)]
538
+
539
+ return (
540
+ message,
541
+ bookmark_html,
542
+ gr.update(choices=choices, value=[]),
543
+ bookmark_html,
544
+ bookmarks.copy() # Return the updated state
545
+ )
546
 
547
+ def delete_selected_bookmarks(selected_indices, state_bookmarks):
548
  """
549
  Delete selected bookmarks and remove their vectors from the FAISS index.
550
  """
551
  global bookmarks, faiss_index
552
  if not selected_indices:
553
+ return "⚠️ No bookmarks selected.", gr.update(choices=[]), display_bookmarks(), state_bookmarks
554
 
555
  ids_to_delete = []
556
  indices_to_delete = []
557
  for s in selected_indices:
558
+ try:
559
+ idx = int(s.split('.')[0]) - 1
560
+ if 0 <= idx < len(state_bookmarks):
561
+ bookmark_id = state_bookmarks[idx]['id']
562
+ ids_to_delete.append(bookmark_id)
563
+ indices_to_delete.append(idx)
564
+ logger.info(f"Deleting bookmark at index {idx + 1}")
565
+ except ValueError:
566
+ logger.error(f"Invalid selection format: {s}")
567
 
568
  # Remove vectors from FAISS index
569
  if faiss_index is not None and ids_to_delete:
570
  faiss_index.remove_ids(np.array(ids_to_delete, dtype=np.int64))
571
 
572
  # Remove bookmarks from the list (reverse order to avoid index shifting)
573
+ bookmarks = state_bookmarks.copy()
574
  for idx in sorted(indices_to_delete, reverse=True):
575
  bookmarks.pop(idx)
576
 
577
  message = "πŸ—‘οΈ Selected bookmarks deleted successfully."
578
  logger.info(message)
579
+ choices = [f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})" for i, bookmark in enumerate(bookmarks)]
 
580
 
581
+ return (
582
+ message,
583
+ gr.update(choices=choices, value=[]),
584
+ display_bookmarks(),
585
+ bookmarks.copy() # Return the updated state
586
+ )
587
 
588
+ def edit_selected_bookmarks_category(selected_indices, new_category, state_bookmarks):
589
  """
590
  Edit category of selected bookmarks.
591
  """
592
  if not selected_indices:
593
+ return (
594
+ "⚠️ No bookmarks selected.",
595
+ gr.update(choices=[]),
596
+ display_bookmarks(),
597
+ state_bookmarks
598
+ )
599
  if not new_category:
600
+ return (
601
+ "⚠️ No new category selected.",
602
+ gr.update(choices=[]),
603
+ display_bookmarks(),
604
+ state_bookmarks
605
+ )
606
 
607
+ bookmarks = state_bookmarks.copy()
608
+ for s in selected_indices:
609
+ try:
610
+ idx = int(s.split('.')[0]) - 1
611
+ if 0 <= idx < len(bookmarks):
612
+ bookmarks[idx]['category'] = new_category
613
+ logger.info(f"Updated category for bookmark {idx + 1} to {new_category}")
614
+ except ValueError:
615
+ logger.error(f"Invalid selection format: {s}")
616
 
617
  message = "✏️ Category updated for selected bookmarks."
618
  logger.info(message)
619
 
620
  # Update choices and display
621
+ choices = [f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})" for i, bookmark in enumerate(bookmarks)]
 
622
 
623
+ return (
624
+ message,
625
+ gr.update(choices=choices, value=[]),
626
+ display_bookmarks(),
627
+ bookmarks.copy() # Return the updated state
628
+ )
629
 
630
+ def export_bookmarks(state_bookmarks):
631
  """
632
  Export bookmarks to an HTML file.
633
  """
634
+ bookmarks = state_bookmarks
635
  if not bookmarks:
636
  logger.warning("No bookmarks to export")
637
+ return "⚠️ No bookmarks to export."
638
 
639
  try:
640
  logger.info("Exporting bookmarks to HTML")
 
648
  dl.append(dt)
649
  soup.append(dl)
650
  html_content = str(soup)
651
+ # Encode the HTML content to base64 for download
652
+ b64 = base64.b64encode(html_content.encode()).decode()
653
+ href = f'data:text/html;base64,{b64}'
 
654
  logger.info("Bookmarks exported successfully")
655
+ return f'<a href="{href}" download="exported_bookmarks.html">πŸ’Ύ Download Exported Bookmarks</a>'
656
  except Exception as e:
657
+ logger.error(f"Error exporting bookmarks: {e}")
658
+ return "⚠️ Error exporting bookmarks."
659
 
660
+ def chatbot_response(user_query, chat_history, state_bookmarks):
661
  """
662
  Generate chatbot response using the FAISS index and embeddings, maintaining chat history.
663
  """
664
+ if not GROQ_API_KEY:
665
+ logger.warning("GROQ_API_KEY not set.")
666
+ return chat_history + [("System", "⚠️ API key not set. Please set the GROQ_API_KEY environment variable in the Hugging Face Space settings.")]
667
+
668
+ bookmarks = state_bookmarks
669
+ if not bookmarks:
670
  logger.warning("No bookmarks available for chatbot")
671
+ return chat_history + [("System", "⚠️ No bookmarks available. Please upload and process your bookmarks first.")]
 
672
 
673
  logger.info(f"Chatbot received query: {user_query}")
674
 
675
  try:
676
+ # Ensure embedding model is loaded
677
+ model = get_embedding_model()
678
+
679
  # Encode the user query
680
+ query_vector = model.encode([user_query]).astype('float32')
681
 
682
  # Search the FAISS index
683
  k = 5 # Number of results to return
 
689
  matching_bookmarks = [id_to_bookmark.get(id) for id in ids if id in id_to_bookmark]
690
 
691
  if not matching_bookmarks:
692
+ response_text = "No relevant bookmarks found for your query."
693
+ logger.info(response_text)
694
+ return chat_history + [("System", response_text)]
695
 
696
  # Format the response
697
+ bookmarks_info = "\n\n".join([
698
+ f"**Title:** {bookmark['title']}\n**URL:** {bookmark['url']}\n**Summary:** {bookmark['summary']}"
699
  for bookmark in matching_bookmarks
700
  ])
701
 
702
+ # Construct the prompt
703
  prompt = f"""
704
  A user asked: "{user_query}"
705
  Based on the bookmarks below, provide a helpful answer to the user's query, referencing the relevant bookmarks.
706
+
707
  Bookmarks:
708
  {bookmarks_info}
709
+
710
  Provide a concise and helpful response.
711
  """
712
 
713
+ # Call the LLM via Groq Cloud API
 
 
 
 
 
 
 
 
 
 
 
 
 
714
  response = openai.ChatCompletion.create(
715
  model='llama-3.1-70b-versatile', # Using the specified model
716
  messages=[
717
  {"role": "user", "content": prompt}
718
  ],
719
+ max_tokens=300,
720
  temperature=0.7,
721
  )
722
  answer = response['choices'][0]['message']['content'].strip()
723
  logger.info("Chatbot response generated")
724
+ return chat_history + [("User", user_query), ("System", answer)]
 
 
 
 
725
 
726
  except openai.error.RateLimitError as e:
727
  wait_time = int(e.headers.get("Retry-After", 5))
728
  logger.warning(f"Rate limit reached. Waiting for {wait_time} seconds before retrying...")
729
  time.sleep(wait_time)
730
+ return chatbot_response(user_query, chat_history, state_bookmarks) # Retry after waiting
731
  except Exception as e:
732
  error_message = f"⚠️ Error processing your query: {str(e)}"
733
  logger.error(error_message, exc_info=True)
734
+ return chat_history + [("System", error_message)]
 
735
 
736
  def build_app():
737
  """
 
739
  """
740
  try:
741
  logger.info("Building Gradio app")
742
+ with gr.Blocks(css="app.css") as demo: # Load external CSS file
743
+ # Shared states
744
+ state_bookmarks = gr.State([])
745
+ chat_history = gr.State([])
746
+
747
  # General Overview
748
  gr.Markdown("""
749
+ # πŸ“š SmartMarks - AI Browser Bookmarks Manager
750
+
751
+ Welcome to **SmartMarks**, your intelligent assistant for managing browser bookmarks. SmartMarks leverages AI to help you organize, search, and interact with your bookmarks seamlessly. Whether you're looking to categorize your links, retrieve information quickly, or maintain an updated list, SmartMarks has you covered.
752
+
753
+ ---
754
+
755
+ ## πŸš€ **How to Use SmartMarks**
756
+
757
+ SmartMarks is divided into three main sections:
758
+
759
+ 1. **πŸ“‚ Upload and Process Bookmarks:** Import your existing bookmarks and let SmartMarks analyze and categorize them for you.
760
+ 2. **πŸ’¬ Chat with Bookmarks:** Interact with your bookmarks using natural language queries to find relevant links effortlessly.
761
+ 3. **πŸ› οΈ Manage Bookmarks:** View, edit, delete, and export your bookmarks with ease.
762
+
763
+ Navigate through the tabs to explore each feature in detail.
764
+ """)
765
 
766
  # Upload and Process Bookmarks Tab
767
  with gr.Tab("Upload and Process Bookmarks"):
768
  gr.Markdown("""
769
+ ## πŸ“‚ **Upload and Process Bookmarks**
770
+
771
+ ### πŸ“ **Steps to Upload and Process:**
772
+
773
+ 1. **Upload Bookmarks File:**
774
+ - Click on the **"πŸ“ Upload Bookmarks HTML File"** button.
775
+ - Select your browser's exported bookmarks HTML file from your device.
776
+
777
+ 2. **Process Bookmarks:**
778
+ - After uploading, click on the **"βš™οΈ Process Bookmarks"** button.
779
+ - SmartMarks will parse your bookmarks, fetch additional information, generate summaries, and categorize each link based on predefined categories.
780
+
781
+ 3. **View Processed Bookmarks:**
782
+ - Once processing is complete, your bookmarks will be displayed in an organized and visually appealing format below.
783
+ """)
784
+
785
+ upload = gr.File(label="πŸ“ Upload Bookmarks HTML File", type='file')
786
  process_button = gr.Button("βš™οΈ Process Bookmarks")
787
  output_text = gr.Textbox(label="βœ… Output", interactive=False)
788
  bookmark_display = gr.HTML(label="πŸ“„ Processed Bookmarks")
789
 
790
+ process_button.click(
791
+ process_uploaded_file,
792
+ inputs=[upload, state_bookmarks],
793
+ outputs=[output_text, bookmark_display, None, None, state_bookmarks]
794
+ )
795
+
796
  # Chat with Bookmarks Tab
797
  with gr.Tab("Chat with Bookmarks"):
798
  gr.Markdown("""
799
+ ## πŸ’¬ **Chat with Bookmarks**
800
+
801
+ ### πŸ€– **How to Interact:**
802
+
803
+ 1. **Enter Your Query:**
804
+ - In the **"✍️ Ask about your bookmarks"** textbox, type your question or keyword related to your bookmarks. For example, "Do I have any bookmarks about GenerativeAI?"
805
+
806
+ 2. **Submit Your Query:**
807
+ - Click the **"πŸ“¨ Send"** button to submit your query.
808
+
809
+ 3. **Receive AI-Driven Responses:**
810
+ - SmartMarks will analyze your query and provide relevant bookmarks that match your request, making it easier to find specific links without manual searching.
811
+
812
+ 4. **View Chat History:**
813
+ - All your queries and the corresponding AI responses are displayed in the chat history for your reference.
814
+ """)
815
+
816
+ with gr.Row():
817
+ chat_history_display = gr.Chatbot(label="πŸ—¨οΈ Chat History", type="messages")
818
+ with gr.Column(scale=1):
819
+ chat_input = gr.Textbox(
820
+ label="✍️ Ask about your bookmarks",
821
+ placeholder="e.g., Do I have any bookmarks about GenerativeAI?",
822
+ lines=1,
823
+ interactive=True
824
+ )
825
+ chat_button = gr.Button("πŸ“¨ Send")
826
+
827
+ # When user presses Enter in chat_input
828
+ chat_input.submit(
829
+ chatbot_response,
830
+ inputs=[chat_input, chat_history_display, state_bookmarks],
831
+ outputs=chat_history_display
832
+ )
833
+
834
+ # When user clicks Send button
835
+ chat_button.click(
836
+ chatbot_response,
837
+ inputs=[chat_input, chat_history_display, state_bookmarks],
838
+ outputs=chat_history_display
839
  )
 
840
 
841
  # Manage Bookmarks Tab
842
  with gr.Tab("Manage Bookmarks"):
843
  gr.Markdown("""
844
+ ## πŸ› οΈ **Manage Bookmarks**
845
+
846
+ ### πŸ—‚οΈ **Features:**
847
+
848
+ 1. **View Bookmarks:**
849
+ - All your processed bookmarks are displayed here with their respective categories and summaries.
850
+
851
+ 2. **Select Bookmarks:**
852
+ - Use the checkboxes next to each bookmark to select one, multiple, or all bookmarks you wish to manage.
853
+
854
+ 3. **Delete Selected Bookmarks:**
855
+ - After selecting the desired bookmarks, click the **"πŸ—‘οΈ Delete Selected"** button to remove them from your list.
856
+
857
+ 4. **Edit Categories:**
858
+ - Select the bookmarks you want to re-categorize.
859
+ - Choose a new category from the dropdown menu labeled **"πŸ†• New Category"**.
860
+ - Click the **"✏️ Edit Category"** button to update their categories.
861
+
862
+ 5. **Export Bookmarks:**
863
+ - Click the **"πŸ’Ύ Export"** button to download your updated bookmarks as an HTML file.
864
+ - This file can be uploaded back to your browser to reflect the changes made within SmartMarks.
865
+
866
+ 6. **Refresh Bookmarks:**
867
+ - Click the **"πŸ”„ Refresh Bookmarks"** button to ensure the latest state is reflected in the display.
868
+ """)
869
 
870
  manage_output = gr.Textbox(label="πŸ”„ Status", interactive=False)
871
  bookmark_selector = gr.CheckboxGroup(
872
  label="βœ… Select Bookmarks",
873
+ choices=[],
874
+ value=[]
 
 
 
 
875
  )
876
+ new_category_input = gr.Dropdown(label="πŸ†• New Category", choices=CATEGORIES, value="Uncategorized")
877
  bookmark_display_manage = gr.HTML(label="πŸ“„ Bookmarks")
878
 
879
  with gr.Row():
880
  delete_button = gr.Button("πŸ—‘οΈ Delete Selected")
881
  edit_category_button = gr.Button("✏️ Edit Category")
882
  export_button = gr.Button("πŸ’Ύ Export")
883
+ refresh_button = gr.Button("πŸ”„ Refresh Bookmarks")
884
 
885
+ download_link = gr.HTML(label="πŸ“₯ Download Exported Bookmarks")
 
 
 
 
 
 
 
886
 
887
+ # Define button actions
888
+ delete_button.click(
889
+ delete_selected_bookmarks,
890
+ inputs=[bookmark_selector, state_bookmarks],
891
+ outputs=[manage_output, bookmark_selector, bookmark_display_manage, state_bookmarks]
892
+ )
893
 
894
+ edit_category_button.click(
895
+ edit_selected_bookmarks_category,
896
+ inputs=[bookmark_selector, new_category_input, state_bookmarks],
897
+ outputs=[manage_output, bookmark_selector, bookmark_display_manage, state_bookmarks]
898
+ )
899
 
900
+ export_button.click(
901
+ export_bookmarks,
902
+ inputs=[state_bookmarks],
903
+ outputs=download_link
904
+ )
905
 
906
+ refresh_button.click(
907
+ lambda bookmarks: (
908
+ [
909
+ f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})" for i, bookmark in enumerate(bookmarks)
910
+ ],
911
+ display_bookmarks()
912
+ ),
913
+ inputs=[state_bookmarks],
914
+ outputs=[bookmark_selector, bookmark_display_manage]
915
+ )
916
 
917
  logger.info("Launching Gradio app")
918
  demo.launch(debug=True)