|
|
|
|
|
import gradio as gr |
|
from bs4 import BeautifulSoup |
|
from sentence_transformers import SentenceTransformer |
|
import faiss |
|
import numpy as np |
|
import requests |
|
import time |
|
import re |
|
import base64 |
|
import logging |
|
import os |
|
import sys |
|
import concurrent.futures |
|
from concurrent.futures import ThreadPoolExecutor |
|
import threading |
|
|
|
|
|
import openai |
|
|
|
|
|
import urllib3 |
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
logger.setLevel(logging.INFO) |
|
|
|
|
|
console_handler = logging.StreamHandler(sys.stdout) |
|
console_handler.setLevel(logging.INFO) |
|
|
|
|
|
formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s') |
|
console_handler.setFormatter(formatter) |
|
|
|
|
|
logger.addHandler(console_handler) |
|
|
|
|
|
logger.info("Initializing variables and models") |
|
embedding_model = SentenceTransformer('all-MiniLM-L6-v2') |
|
faiss_index = None |
|
bookmarks = [] |
|
fetch_cache = {} |
|
|
|
|
|
GROQ_RPM = 30 |
|
GROQ_TPM = 40000 |
|
SECONDS_PER_MINUTE = 60 |
|
MIN_TIME_BETWEEN_CALLS = SECONDS_PER_MINUTE / GROQ_RPM |
|
MAX_CONCURRENT_CALLS = 3 |
|
TOKEN_BUFFER = 0.9 |
|
|
|
|
|
api_lock = threading.Lock() |
|
request_times = [] |
|
token_usage = [] |
|
LLM_SEMAPHORE = threading.Semaphore(MAX_CONCURRENT_CALLS) |
|
|
|
|
|
CATEGORIES = [ |
|
"Social Media", |
|
"News and Media", |
|
"Education and Learning", |
|
"Entertainment", |
|
"Shopping and E-commerce", |
|
"Finance and Banking", |
|
"Technology", |
|
"Health and Fitness", |
|
"Travel and Tourism", |
|
"Food and Recipes", |
|
"Sports", |
|
"Arts and Culture", |
|
"Government and Politics", |
|
"Business and Economy", |
|
"Science and Research", |
|
"Personal Blogs and Journals", |
|
"Job Search and Careers", |
|
"Music and Audio", |
|
"Videos and Movies", |
|
"Reference and Knowledge Bases", |
|
"Dead Link", |
|
"Uncategorized", |
|
] |
|
|
|
|
|
GROQ_API_KEY = os.getenv('GROQ_API_KEY') |
|
|
|
if not GROQ_API_KEY: |
|
logger.error("GROQ_API_KEY environment variable not set.") |
|
|
|
openai.api_key = GROQ_API_KEY |
|
openai.api_base = "https://api.groq.com/openai/v1" |
|
|
|
def manage_rate_limits(): |
|
""" |
|
Manage both request and token rate limits. |
|
Returns the time to wait (if any) before making next request. |
|
""" |
|
current_time = time.time() |
|
minute_ago = current_time - SECONDS_PER_MINUTE |
|
|
|
|
|
global request_times, token_usage |
|
request_times = [t for t in request_times if t > minute_ago] |
|
token_usage = [t for t, _ in token_usage if t > minute_ago] |
|
|
|
|
|
if len(request_times) >= GROQ_RPM: |
|
oldest_request = request_times[0] |
|
return max(0, SECONDS_PER_MINUTE - (current_time - oldest_request)) |
|
|
|
|
|
total_tokens = sum(tokens for _, tokens in token_usage) |
|
if total_tokens >= GROQ_TPM * TOKEN_BUFFER: |
|
return 1.0 |
|
|
|
return 0 |
|
|
|
def estimate_tokens(text): |
|
"""Estimate tokens in text using GPT-3 tokenizer approximation""" |
|
return len(text.split()) * 1.3 |
|
def extract_main_content(soup): |
|
""" |
|
Extract the main content from a webpage while filtering out boilerplate content. |
|
""" |
|
if not soup: |
|
return "" |
|
|
|
|
|
for element in soup(['script', 'style', 'header', 'footer', 'nav', 'aside', 'form', 'noscript']): |
|
element.decompose() |
|
|
|
|
|
p_tags = soup.find_all('p') |
|
if p_tags: |
|
content = ' '.join([p.get_text(strip=True, separator=' ') for p in p_tags]) |
|
else: |
|
|
|
content = soup.get_text(separator=' ', strip=True) |
|
|
|
|
|
content = re.sub(r'\s+', ' ', content) |
|
|
|
|
|
words = content.split() |
|
if len(words) > 1500: |
|
content = ' '.join(words[:1500]) |
|
|
|
return content |
|
|
|
def get_page_metadata(soup): |
|
""" |
|
Extract metadata from the webpage including title, description, and keywords. |
|
""" |
|
metadata = { |
|
'title': '', |
|
'description': '', |
|
'keywords': '' |
|
} |
|
|
|
if not soup: |
|
return metadata |
|
|
|
|
|
title_tag = soup.find('title') |
|
if title_tag and title_tag.string: |
|
metadata['title'] = title_tag.string.strip() |
|
|
|
|
|
meta_desc = ( |
|
soup.find('meta', attrs={'name': 'description'}) or |
|
soup.find('meta', attrs={'property': 'og:description'}) or |
|
soup.find('meta', attrs={'name': 'twitter:description'}) |
|
) |
|
if meta_desc: |
|
metadata['description'] = meta_desc.get('content', '').strip() |
|
|
|
|
|
meta_keywords = soup.find('meta', attrs={'name': 'keywords'}) |
|
if meta_keywords: |
|
metadata['keywords'] = meta_keywords.get('content', '').strip() |
|
|
|
|
|
if not metadata['title']: |
|
og_title = soup.find('meta', attrs={'property': 'og:title'}) |
|
if og_title: |
|
metadata['title'] = og_title.get('content', '').strip() |
|
|
|
return metadata |
|
|
|
def fetch_url_info(bookmark): |
|
""" |
|
Fetch information about a URL. |
|
""" |
|
url = bookmark['url'] |
|
if url in fetch_cache: |
|
with api_lock: |
|
bookmark.update(fetch_cache[url]) |
|
return |
|
|
|
try: |
|
logger.info(f"Fetching URL info for: {url}") |
|
headers = { |
|
'User-Agent': 'Mozilla/5.0', |
|
'Accept-Language': 'en-US,en;q=0.9', |
|
} |
|
response = requests.get(url, headers=headers, timeout=5, verify=False, allow_redirects=True) |
|
bookmark['etag'] = response.headers.get('ETag', 'N/A') |
|
bookmark['status_code'] = response.status_code |
|
|
|
content = response.text |
|
logger.info(f"Fetched content length for {url}: {len(content)} characters") |
|
|
|
if response.status_code >= 500: |
|
bookmark['dead_link'] = True |
|
bookmark['description'] = '' |
|
bookmark['html_content'] = '' |
|
logger.warning(f"Dead link detected: {url} with status {response.status_code}") |
|
else: |
|
bookmark['dead_link'] = False |
|
bookmark['html_content'] = content |
|
bookmark['description'] = '' |
|
logger.info(f"Fetched information for {url}") |
|
|
|
except requests.exceptions.Timeout: |
|
bookmark['dead_link'] = False |
|
bookmark['etag'] = 'N/A' |
|
bookmark['status_code'] = 'Timeout' |
|
bookmark['description'] = '' |
|
bookmark['html_content'] = '' |
|
bookmark['slow_link'] = True |
|
logger.warning(f"Timeout while fetching {url}. Marking as 'Slow'.") |
|
except Exception as e: |
|
bookmark['dead_link'] = True |
|
bookmark['etag'] = 'N/A' |
|
bookmark['status_code'] = 'Error' |
|
bookmark['description'] = '' |
|
bookmark['html_content'] = '' |
|
logger.error(f"Error fetching URL info for {url}: {e}", exc_info=True) |
|
finally: |
|
with api_lock: |
|
fetch_cache[url] = { |
|
'etag': bookmark.get('etag'), |
|
'status_code': bookmark.get('status_code'), |
|
'dead_link': bookmark.get('dead_link'), |
|
'description': bookmark.get('description'), |
|
'html_content': bookmark.get('html_content', ''), |
|
'slow_link': bookmark.get('slow_link', False), |
|
} |
|
|
|
def process_bookmarks_batch(bookmarks_batch): |
|
"""Process a batch of bookmarks with controlled rate limiting""" |
|
for bookmark in bookmarks_batch: |
|
with LLM_SEMAPHORE: |
|
while True: |
|
with api_lock: |
|
wait_time = manage_rate_limits() |
|
if wait_time <= 0: |
|
break |
|
logger.info(f"Rate limiting: Waiting for {wait_time:.2f} seconds...") |
|
time.sleep(wait_time) |
|
|
|
try: |
|
html_content = bookmark.get('html_content', '') |
|
soup = BeautifulSoup(html_content, 'html.parser') |
|
metadata = get_page_metadata(soup) |
|
main_content = extract_main_content(soup) |
|
|
|
|
|
content = f"Title: {metadata['title']}\nURL: {bookmark['url']}" |
|
if len(main_content) > 1000: |
|
main_content = main_content[:1000] + "..." |
|
|
|
prompt = f"""Analyze this webpage: |
|
{content} |
|
Content: {main_content} |
|
Provide in format: |
|
Summary: [2 sentences max] |
|
Category: [{', '.join(CATEGORIES)}]""" |
|
|
|
|
|
input_tokens = estimate_tokens(prompt) |
|
max_tokens = 150 |
|
total_tokens = input_tokens + max_tokens |
|
|
|
|
|
response = openai.ChatCompletion.create( |
|
model='llama-3.1-70b-versatile', |
|
messages=[{"role": "user", "content": prompt}], |
|
max_tokens=max_tokens, |
|
temperature=0.5, |
|
) |
|
|
|
|
|
with api_lock: |
|
current_time = time.time() |
|
request_times.append(current_time) |
|
token_usage.append((current_time, total_tokens)) |
|
|
|
content = response['choices'][0]['message']['content'].strip() |
|
|
|
|
|
summary_match = re.search(r"Summary:\s*(.*?)(?:\n|$)", content) |
|
category_match = re.search(r"Category:\s*(.*?)(?:\n|$)", content) |
|
|
|
bookmark['summary'] = summary_match.group(1).strip() if summary_match else 'No summary available.' |
|
|
|
if category_match: |
|
category = category_match.group(1).strip().strip('"') |
|
bookmark['category'] = category if category in CATEGORIES else 'Uncategorized' |
|
else: |
|
bookmark['category'] = 'Uncategorized' |
|
|
|
|
|
if 'social media' in bookmark['url'].lower() or 'twitter' in bookmark['url'].lower() or 'x.com' in bookmark['url'].lower(): |
|
bookmark['category'] = 'Social Media' |
|
elif 'wikipedia' in bookmark['url'].lower(): |
|
bookmark['category'] = 'Reference and Knowledge Bases' |
|
|
|
logger.info(f"Successfully processed bookmark: {bookmark['url']}") |
|
break |
|
|
|
except openai.error.RateLimitError as e: |
|
wait_time = int(e.headers.get('Retry-After', 5)) |
|
logger.warning(f"Rate limit hit, waiting {wait_time} seconds...") |
|
time.sleep(wait_time) |
|
except Exception as e: |
|
logger.error(f"Error processing bookmark: {e}") |
|
bookmark['summary'] = 'Processing failed.' |
|
bookmark['category'] = 'Uncategorized' |
|
break |
|
def vectorize_and_index(bookmarks_list): |
|
""" |
|
Create vector embeddings for bookmarks and build FAISS index with ID mapping. |
|
""" |
|
global faiss_index |
|
logger.info("Vectorizing summaries and building FAISS index") |
|
try: |
|
summaries = [bookmark['summary'] for bookmark in bookmarks_list] |
|
embeddings = embedding_model.encode(summaries) |
|
dimension = embeddings.shape[1] |
|
index = faiss.IndexIDMap(faiss.IndexFlatL2(dimension)) |
|
ids = np.array([bookmark['id'] for bookmark in bookmarks_list], dtype=np.int64) |
|
index.add_with_ids(np.array(embeddings).astype('float32'), ids) |
|
faiss_index = index |
|
logger.info("FAISS index built successfully with IDs") |
|
return index |
|
except Exception as e: |
|
logger.error(f"Error in vectorizing and indexing: {e}", exc_info=True) |
|
raise |
|
|
|
def display_bookmarks(): |
|
""" |
|
Generate HTML display for bookmarks. |
|
""" |
|
logger.info("Generating HTML display for bookmarks") |
|
cards = '' |
|
for i, bookmark in enumerate(bookmarks): |
|
index = i + 1 |
|
if bookmark.get('dead_link'): |
|
status = "β Dead Link" |
|
card_style = "border: 2px solid red;" |
|
text_style = "color: white;" |
|
elif bookmark.get('slow_link'): |
|
status = "β³ Slow Response" |
|
card_style = "border: 2px solid orange;" |
|
text_style = "color: white;" |
|
else: |
|
status = "β
Active" |
|
card_style = "border: 2px solid green;" |
|
text_style = "color: white;" |
|
|
|
title = bookmark['title'] |
|
url = bookmark['url'] |
|
etag = bookmark.get('etag', 'N/A') |
|
summary = bookmark.get('summary', '') |
|
category = bookmark.get('category', 'Uncategorized') |
|
|
|
|
|
from html import escape |
|
title = escape(title) |
|
url = escape(url) |
|
summary = escape(summary) |
|
category = escape(category) |
|
|
|
card_html = f''' |
|
<div class="card" style="{card_style} padding: 10px; margin: 10px; border-radius: 5px; background-color: #1e1e1e;"> |
|
<div class="card-content"> |
|
<h3 style="{text_style}">{index}. {title} {status}</h3> |
|
<p style="{text_style}"><strong>Category:</strong> {category}</p> |
|
<p style="{text_style}"><strong>URL:</strong> <a href="{url}" target="_blank" style="{text_style}">{url}</a></p> |
|
<p style="{text_style}"><strong>ETag:</strong> {etag}</p> |
|
<p style="{text_style}"><strong>Summary:</strong> {summary}</p> |
|
</div> |
|
</div> |
|
''' |
|
cards += card_html |
|
logger.info("HTML display generated") |
|
return cards |
|
|
|
def process_uploaded_file(file, state_bookmarks): |
|
""" |
|
Process uploaded file with optimized batch processing |
|
""" |
|
global bookmarks, faiss_index |
|
logger.info("Processing uploaded file") |
|
|
|
if file is None: |
|
logger.warning("No file uploaded") |
|
return "Please upload a bookmarks HTML file.", '', state_bookmarks, display_bookmarks(), gr.update(choices=[]) |
|
|
|
try: |
|
file_content = file.decode('utf-8') |
|
bookmarks = parse_bookmarks(file_content) |
|
|
|
if not bookmarks: |
|
return "No bookmarks found in the file.", '', state_bookmarks, display_bookmarks(), gr.update(choices=[]) |
|
|
|
|
|
for idx, bookmark in enumerate(bookmarks): |
|
bookmark['id'] = idx |
|
|
|
|
|
with ThreadPoolExecutor(max_workers=10) as executor: |
|
executor.map(fetch_url_info, bookmarks) |
|
|
|
|
|
batch_size = min(MAX_CONCURRENT_CALLS, len(bookmarks)) |
|
batches = [bookmarks[i:i + batch_size] for i in range(0, len(bookmarks), batch_size)] |
|
|
|
with ThreadPoolExecutor(max_workers=MAX_CONCURRENT_CALLS) as executor: |
|
executor.map(process_bookmarks_batch, batches) |
|
|
|
|
|
faiss_index = vectorize_and_index(bookmarks) |
|
|
|
|
|
bookmark_html = display_bookmarks() |
|
choices = [f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})" |
|
for i, bookmark in enumerate(bookmarks)] |
|
state_bookmarks = bookmarks.copy() |
|
|
|
return "β
Processing complete!", bookmark_html, state_bookmarks, bookmark_html, gr.update(choices=choices) |
|
|
|
except Exception as e: |
|
logger.error(f"Error processing file: {e}") |
|
return f"Error processing file: {str(e)}", '', state_bookmarks, display_bookmarks(), gr.update(choices=[]) |
|
|
|
def parse_bookmarks(file_content): |
|
""" |
|
Parse bookmarks from HTML file. |
|
""" |
|
logger.info("Parsing bookmarks") |
|
try: |
|
soup = BeautifulSoup(file_content, 'html.parser') |
|
extracted_bookmarks = [] |
|
for link in soup.find_all('a'): |
|
url = link.get('href') |
|
title = link.text.strip() |
|
if url and title: |
|
if url.startswith('http://') or url.startswith('https://'): |
|
extracted_bookmarks.append({'url': url, 'title': title}) |
|
else: |
|
logger.info(f"Skipping non-http/https URL: {url}") |
|
logger.info(f"Extracted {len(extracted_bookmarks)} bookmarks") |
|
return extracted_bookmarks |
|
except Exception as e: |
|
logger.error("Error parsing bookmarks: %s", e, exc_info=True) |
|
raise |
|
|
|
def delete_selected_bookmarks(selected_indices, state_bookmarks): |
|
""" |
|
Delete selected bookmarks and remove their vectors from the FAISS index. |
|
""" |
|
global bookmarks, faiss_index |
|
if not selected_indices: |
|
return "β οΈ No bookmarks selected.", gr.update(choices=[]), display_bookmarks() |
|
|
|
ids_to_delete = [] |
|
indices_to_delete = [] |
|
for s in selected_indices: |
|
idx = int(s.split('.')[0]) - 1 |
|
if 0 <= idx < len(bookmarks): |
|
bookmark_id = bookmarks[idx]['id'] |
|
ids_to_delete.append(bookmark_id) |
|
indices_to_delete.append(idx) |
|
logger.info(f"Deleting bookmark at index {idx + 1}") |
|
|
|
|
|
if faiss_index is not None and ids_to_delete: |
|
faiss_index.remove_ids(np.array(ids_to_delete, dtype=np.int64)) |
|
|
|
|
|
for idx in sorted(indices_to_delete, reverse=True): |
|
bookmarks.pop(idx) |
|
|
|
message = "ποΈ Selected bookmarks deleted successfully." |
|
logger.info(message) |
|
choices = [f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})" |
|
for i, bookmark in enumerate(bookmarks)] |
|
|
|
|
|
state_bookmarks = bookmarks.copy() |
|
|
|
return message, gr.update(choices=choices), display_bookmarks() |
|
|
|
def edit_selected_bookmarks_category(selected_indices, new_category, state_bookmarks): |
|
""" |
|
Edit category of selected bookmarks. |
|
""" |
|
if not selected_indices: |
|
return "β οΈ No bookmarks selected.", gr.update(choices=[]), display_bookmarks(), state_bookmarks |
|
if not new_category: |
|
return "β οΈ No new category selected.", gr.update(choices=[]), display_bookmarks(), state_bookmarks |
|
|
|
indices = [int(s.split('.')[0])-1 for s in selected_indices] |
|
for idx in indices: |
|
if 0 <= idx < len(bookmarks): |
|
bookmarks[idx]['category'] = new_category |
|
logger.info(f"Updated category for bookmark {idx + 1} to {new_category}") |
|
|
|
message = "βοΈ Category updated for selected bookmarks." |
|
logger.info(message) |
|
|
|
|
|
choices = [f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})" |
|
for i, bookmark in enumerate(bookmarks)] |
|
|
|
|
|
state_bookmarks = bookmarks.copy() |
|
|
|
return message, gr.update(choices=choices), display_bookmarks(), state_bookmarks |
|
def export_bookmarks(): |
|
""" |
|
Export bookmarks to an HTML file. |
|
""" |
|
if not bookmarks: |
|
logger.warning("No bookmarks to export") |
|
return None |
|
|
|
try: |
|
logger.info("Exporting bookmarks to HTML") |
|
soup = BeautifulSoup("<!DOCTYPE NETSCAPE-Bookmark-file-1><Title>Bookmarks</Title><H1>Bookmarks</H1>", 'html.parser') |
|
dl = soup.new_tag('DL') |
|
for bookmark in bookmarks: |
|
dt = soup.new_tag('DT') |
|
a = soup.new_tag('A', href=bookmark['url']) |
|
a.string = bookmark['title'] |
|
dt.append(a) |
|
dl.append(dt) |
|
soup.append(dl) |
|
html_content = str(soup) |
|
output_file = "exported_bookmarks.html" |
|
with open(output_file, 'w', encoding='utf-8') as f: |
|
f.write(html_content) |
|
logger.info("Bookmarks exported successfully") |
|
return output_file |
|
except Exception as e: |
|
logger.error(f"Error exporting bookmarks: {e}", exc_info=True) |
|
return None |
|
|
|
def chatbot_response(user_query, chat_history): |
|
""" |
|
Generate chatbot response using the FAISS index and embeddings. |
|
""" |
|
if not bookmarks or faiss_index is None: |
|
logger.warning("No bookmarks available for chatbot") |
|
chat_history.append({"role": "assistant", "content": "β οΈ No bookmarks available. Please upload and process your bookmarks first."}) |
|
return chat_history |
|
|
|
logger.info(f"Chatbot received query: {user_query}") |
|
|
|
try: |
|
chat_history.append({"role": "user", "content": user_query}) |
|
|
|
with LLM_SEMAPHORE: |
|
while True: |
|
with api_lock: |
|
wait_time = manage_rate_limits() |
|
if wait_time <= 0: |
|
break |
|
logger.info(f"Rate limiting: Waiting for {wait_time:.2f} seconds...") |
|
time.sleep(wait_time) |
|
|
|
try: |
|
|
|
query_vector = embedding_model.encode([user_query]).astype('float32') |
|
k = 5 |
|
distances, ids = faiss_index.search(query_vector, k) |
|
ids = ids.flatten() |
|
|
|
id_to_bookmark = {bookmark['id']: bookmark for bookmark in bookmarks} |
|
matching_bookmarks = [id_to_bookmark.get(id) for id in ids if id in id_to_bookmark] |
|
|
|
if not matching_bookmarks: |
|
answer = "No relevant bookmarks found for your query." |
|
chat_history.append({"role": "assistant", "content": answer}) |
|
return chat_history |
|
|
|
|
|
bookmarks_info = "\n".join([ |
|
f"Title: {bookmark['title']}\nURL: {bookmark['url']}\nSummary: {bookmark['summary']}" |
|
for bookmark in matching_bookmarks |
|
]) |
|
|
|
prompt = f"""User Query: "{user_query}" |
|
Found Bookmarks: |
|
{bookmarks_info} |
|
Provide a helpful, concise response.""" |
|
|
|
|
|
input_tokens = estimate_tokens(prompt) |
|
max_tokens = 300 |
|
total_tokens = input_tokens + max_tokens |
|
|
|
response = openai.ChatCompletion.create( |
|
model='llama-3.1-70b-versatile', |
|
messages=[{"role": "user", "content": prompt}], |
|
max_tokens=max_tokens, |
|
temperature=0.7, |
|
) |
|
|
|
|
|
with api_lock: |
|
current_time = time.time() |
|
request_times.append(current_time) |
|
token_usage.append((current_time, total_tokens)) |
|
|
|
answer = response['choices'][0]['message']['content'].strip() |
|
logger.info("Chatbot response generated") |
|
|
|
chat_history.append({"role": "assistant", "content": answer}) |
|
return chat_history |
|
|
|
except openai.error.RateLimitError as e: |
|
wait_time = int(e.headers.get('Retry-After', 5)) |
|
logger.warning(f"Rate limit hit, waiting {wait_time} seconds...") |
|
time.sleep(wait_time) |
|
continue |
|
except Exception as e: |
|
error_message = f"β οΈ Error processing your query: {str(e)}" |
|
logger.error(error_message, exc_info=True) |
|
chat_history.append({"role": "assistant", "content": error_message}) |
|
return chat_history |
|
|
|
except Exception as e: |
|
error_message = f"β οΈ Error processing your query: {str(e)}" |
|
logger.error(error_message, exc_info=True) |
|
chat_history.append({"role": "assistant", "content": error_message}) |
|
return chat_history |
|
|
|
def build_app(): |
|
""" |
|
Build and launch the Gradio app. |
|
""" |
|
try: |
|
logger.info("Building Gradio app") |
|
with gr.Blocks(css="app.css") as demo: |
|
|
|
state_bookmarks = gr.State([]) |
|
|
|
|
|
gr.Markdown(""" |
|
# π SmartMarks - AI Browser Bookmarks Manager |
|
|
|
Welcome to **SmartMarks**, your intelligent assistant for managing browser bookmarks. SmartMarks leverages AI to help you organize, search, and interact with your bookmarks seamlessly. |
|
|
|
--- |
|
|
|
## π **How to Use SmartMarks** |
|
|
|
SmartMarks is divided into three main sections: |
|
|
|
1. **π Upload and Process Bookmarks:** Import your existing bookmarks and let SmartMarks analyze and categorize them for you. |
|
2. **π¬ Chat with Bookmarks:** Interact with your bookmarks using natural language queries to find relevant links effortlessly. |
|
3. **π οΈ Manage Bookmarks:** View, edit, delete, and export your bookmarks with ease. |
|
|
|
Navigate through the tabs to explore each feature in detail. |
|
""") |
|
|
|
|
|
with gr.Tab("Upload and Process Bookmarks"): |
|
gr.Markdown(""" |
|
## π **Upload and Process Bookmarks** |
|
|
|
### π **Steps to Upload and Process:** |
|
|
|
1. **Upload Bookmarks File:** |
|
- Click on the **"π Upload Bookmarks HTML File"** button. |
|
- Select your browser's exported bookmarks HTML file from your device. |
|
|
|
2. **Process Bookmarks:** |
|
- After uploading, click on the **"βοΈ Process Bookmarks"** button. |
|
- SmartMarks will parse your bookmarks, fetch additional information, generate summaries, and categorize each link based on predefined categories. |
|
|
|
3. **View Processed Bookmarks:** |
|
- Once processing is complete, your bookmarks will be displayed in an organized and visually appealing format below. |
|
""") |
|
|
|
upload = gr.File(label="π Upload Bookmarks HTML File", type='binary') |
|
process_button = gr.Button("βοΈ Process Bookmarks") |
|
output_text = gr.Textbox(label="β
Output", interactive=False) |
|
bookmark_display = gr.HTML(label="π Processed Bookmarks") |
|
|
|
|
|
with gr.Tab("Chat with Bookmarks"): |
|
gr.Markdown(""" |
|
## π¬ **Chat with Bookmarks** |
|
|
|
### π€ **How to Interact:** |
|
|
|
1. **Enter Your Query:** |
|
- In the **"βοΈ Ask about your bookmarks"** textbox, type your question or keyword related to your bookmarks. |
|
|
|
2. **Submit Your Query:** |
|
- Click the **"π¨ Send"** button to submit your query. |
|
|
|
3. **Receive AI-Driven Responses:** |
|
- SmartMarks will analyze your query and provide relevant bookmarks that match your request. |
|
|
|
4. **View Chat History:** |
|
- All your queries and the corresponding AI responses are displayed in the chat history. |
|
""") |
|
|
|
chatbot = gr.Chatbot(label="π¬ Chat with SmartMarks", type='messages') |
|
user_input = gr.Textbox( |
|
label="βοΈ Ask about your bookmarks", |
|
placeholder="e.g., Do I have any bookmarks about AI?" |
|
) |
|
chat_button = gr.Button("π¨ Send") |
|
|
|
chat_button.click( |
|
chatbot_response, |
|
inputs=[user_input, chatbot], |
|
outputs=chatbot |
|
) |
|
|
|
|
|
with gr.Tab("Manage Bookmarks"): |
|
gr.Markdown(""" |
|
## π οΈ **Manage Bookmarks** |
|
|
|
### ποΈ **Features:** |
|
|
|
1. **View Bookmarks:** |
|
- All your processed bookmarks are displayed here with their respective categories and summaries. |
|
|
|
2. **Select Bookmarks:** |
|
- Use the checkboxes next to each bookmark to select one, multiple, or all bookmarks you wish to manage. |
|
|
|
3. **Delete Selected Bookmarks:** |
|
- After selecting the desired bookmarks, click the **"ποΈ Delete Selected"** button to remove them from your list. |
|
|
|
4. **Edit Categories:** |
|
- Select the bookmarks you want to re-categorize. |
|
- Choose a new category from the dropdown menu labeled **"π New Category"**. |
|
- Click the **"βοΈ Edit Category"** button to update their categories. |
|
|
|
5. **Export Bookmarks:** |
|
- Click the **"πΎ Export"** button to download your updated bookmarks as an HTML file. |
|
|
|
6. **Refresh Bookmarks:** |
|
- Click the **"π Refresh Bookmarks"** button to ensure the latest state is reflected in the display. |
|
""") |
|
|
|
manage_output = gr.Textbox(label="π Status", interactive=False) |
|
|
|
|
|
bookmark_selector = gr.CheckboxGroup( |
|
label="β
Select Bookmarks", |
|
choices=[] |
|
) |
|
|
|
new_category = gr.Dropdown( |
|
label="π New Category", |
|
choices=CATEGORIES, |
|
value="Uncategorized" |
|
) |
|
bookmark_display_manage = gr.HTML(label="π Bookmarks") |
|
|
|
with gr.Row(): |
|
delete_button = gr.Button("ποΈ Delete Selected") |
|
edit_category_button = gr.Button("βοΈ Edit Category") |
|
export_button = gr.Button("πΎ Export") |
|
refresh_button = gr.Button("π Refresh Bookmarks") |
|
|
|
download_link = gr.File(label="π₯ Download Exported Bookmarks") |
|
|
|
|
|
process_button.click( |
|
process_uploaded_file, |
|
inputs=[upload, state_bookmarks], |
|
outputs=[output_text, bookmark_display, state_bookmarks, bookmark_display, bookmark_selector] |
|
) |
|
|
|
delete_button.click( |
|
delete_selected_bookmarks, |
|
inputs=[bookmark_selector, state_bookmarks], |
|
outputs=[manage_output, bookmark_selector, bookmark_display_manage] |
|
) |
|
|
|
edit_category_button.click( |
|
edit_selected_bookmarks_category, |
|
inputs=[bookmark_selector, new_category, state_bookmarks], |
|
outputs=[manage_output, bookmark_selector, bookmark_display_manage, state_bookmarks] |
|
) |
|
|
|
export_button.click( |
|
export_bookmarks, |
|
outputs=download_link |
|
) |
|
|
|
refresh_button.click( |
|
lambda state_bookmarks: ( |
|
[ |
|
f"{i+1}. {bookmark['title']} (Category: {bookmark['category']})" |
|
for i, bookmark in enumerate(state_bookmarks) |
|
], |
|
display_bookmarks() |
|
), |
|
inputs=[state_bookmarks], |
|
outputs=[bookmark_selector, bookmark_display_manage] |
|
) |
|
|
|
logger.info("Launching Gradio app") |
|
demo.launch(debug=True) |
|
except Exception as e: |
|
logger.error(f"Error building the app: {e}", exc_info=True) |
|
print(f"Error building the app: {e}") |
|
|
|
if __name__ == "__main__": |
|
build_app() |