Spaces:
Running
Running
# app.py | |
import gradio as gr | |
import time | |
import datetime | |
# --- Core Browser Logic (Slightly modified for Gradio) --- | |
# Changes: | |
# - Removed print() statements. Methods now return status/log messages. | |
# - Removed time.sleep() as Gradio handles user feedback. | |
class Tab: | |
"""Represents a single browser tab with its own history.""" | |
def __init__(self, homepage): | |
self.history = [] | |
self.current_index = -1 | |
self.homepage = homepage | |
self.navigate(self.homepage) | |
def navigate(self, url): | |
"""Navigates to a new URL, clearing any 'forward' history.""" | |
if self.current_index < len(self.history) - 1: | |
self.history = self.history[:self.current_index + 1] | |
self.history.append(url) | |
self.current_index += 1 | |
return f"Navigating to {url}..." | |
def go_back(self): | |
if self.current_index > 0: | |
self.current_index -= 1 | |
return f"Going back to {self.current_url()}" | |
return "No more history to go back to." | |
def go_forward(self): | |
if self.current_index < len(self.history) - 1: | |
self.current_index += 1 | |
return f"Going forward to {self.current_url()}" | |
return "No more history to go forward to." | |
def current_url(self): | |
return self.history[self.current_index] if self.current_index != -1 else "about:blank" | |
class PseudoBrowser: | |
"""Simulates a web browser's core functionality.""" | |
def __init__(self): | |
self.homepage = "https://www.startpage.com" | |
self.tabs = [Tab(self.homepage)] | |
self.active_tab_index = 0 | |
self.bookmarks = set() | |
self.global_history = [] | |
self.cache = {} | |
def _get_active_tab(self): | |
return self.tabs[self.active_tab_index] | |
def _fetch_content(self, url, refresh=False): | |
"""Simulates fetching content, using a cache.""" | |
log = "" | |
if url in self.cache and not refresh: | |
log += f"⚡️ Loading '{url}' from cache.\n" | |
content = self.cache[url] | |
else: | |
log += f"☁️ Fetching '{url}' from network...\n" | |
content = f"<html><body><h1>Welcome to {url}</h1><p><i>Content 'rendered' at {datetime.datetime.now().strftime('%H:%M:%S')}</i></p></body></html>" | |
self.cache[url] = content | |
return content, log | |
def open_url(self, url): | |
tab = self._get_active_tab() | |
log = tab.navigate(url) | |
self.global_history.append((datetime.datetime.now(), url)) | |
content, fetch_log = self._fetch_content(url) | |
return content, log + "\n" + fetch_log | |
def back(self): | |
tab = self._get_active_tab() | |
log = tab.go_back() | |
content, fetch_log = self._fetch_content(tab.current_url()) | |
return content, log + "\n" + fetch_log | |
def forward(self): | |
tab = self._get_active_tab() | |
log = tab.go_forward() | |
content, fetch_log = self._fetch_content(tab.current_url()) | |
return content, log + "\n" + fetch_log | |
def refresh(self): | |
tab = self._get_active_tab() | |
url = tab.current_url() | |
log = f"🔄 Refreshing {url}..." | |
content, fetch_log = self._fetch_content(url, refresh=True) | |
return content, log + "\n" + fetch_log | |
def new_tab(self): | |
new_tab = Tab(self.homepage) | |
self.tabs.append(new_tab) | |
self.active_tab_index = len(self.tabs) - 1 | |
content, fetch_log = self._fetch_content(new_tab.current_url()) | |
return content, f"✨ New tab opened.\n" + fetch_log | |
def close_tab(self): | |
if len(self.tabs) == 1: | |
return None, "❌ Cannot close the last tab!" | |
closed_tab = self.tabs.pop(self.active_tab_index) | |
log = f"💣 Tab ({closed_tab.current_url()}) closed." | |
if self.active_tab_index >= len(self.tabs): | |
self.active_tab_index = len(self.tabs) - 1 | |
active_tab = self._get_active_tab() | |
content, fetch_log = self._fetch_content(active_tab.current_url()) | |
return content, log + "\n" + fetch_log | |
def switch_tab(self, tab_label): | |
# The label is "Tab 0: https://..." | |
index = int(tab_label.split(":")[0].replace("Tab", "").strip()) | |
if 0 <= index < len(self.tabs): | |
self.active_tab_index = index | |
tab = self._get_active_tab() | |
content, fetch_log = self._fetch_content(tab.current_url()) | |
return content, f"Switched to Tab {index}.\n" + fetch_log | |
return None, "❌ Invalid tab index." | |
def add_bookmark(self): | |
url = self._get_active_tab().current_url() | |
if url not in self.bookmarks: | |
self.bookmarks.add(url) | |
return f"⭐ Bookmarked: {url}" | |
return f"ℹ️ Already bookmarked: {url}" | |
# --- Gradio UI and Event Handlers --- | |
def update_ui_components(browser: PseudoBrowser): | |
"""Generates all UI component values from the browser state.""" | |
# Main Content | |
active_tab = browser._get_active_tab() | |
content, _ = browser._fetch_content(active_tab.current_url()) | |
url_text = active_tab.current_url() | |
# Tab Selector | |
tab_choices = [f"Tab {i}: {tab.current_url()}" for i, tab in enumerate(browser.tabs)] | |
active_tab_label = f"Tab {browser.active_tab_index}: {active_tab.current_url()}" | |
# History | |
history_md = "### 📜 Global History\n" | |
if not browser.global_history: | |
history_md += "_(empty)_" | |
else: | |
for ts, url in reversed(browser.global_history[-10:]): # Show last 10 | |
history_md += f"- `{ts.strftime('%H:%M:%S')}`: {url}\n" | |
# Bookmarks | |
bookmarks_md = "### 📚 Bookmarks\n" | |
if not browser.bookmarks: | |
bookmarks_md += "_(empty)_" | |
else: | |
for bm in sorted(list(browser.bookmarks)): | |
bookmarks_md += f"- ⭐ {bm}\n" | |
return { | |
html_view: gr.HTML(value=content), | |
url_textbox: gr.Textbox(value=url_text), | |
tab_selector: gr.Radio(choices=tab_choices, value=active_tab_label, label="Active Tabs"), | |
history_display: gr.Markdown(history_md), | |
bookmarks_display: gr.Markdown(bookmarks_md) | |
} | |
# --- Event Handlers --- | |
def handle_go(browser_state, url): | |
content, log = browser_state.open_url(url) | |
return { | |
**update_ui_components(browser_state), | |
log_display: gr.Textbox(log) | |
} | |
def handle_back(browser_state): | |
content, log = browser_state.back() | |
return { | |
**update_ui_components(browser_state), | |
log_display: gr.Textbox(log) | |
} | |
def handle_forward(browser_state): | |
content, log = browser_state.forward() | |
return { | |
**update_ui_components(browser_state), | |
log_display: gr.Textbox(log) | |
} | |
def handle_refresh(browser_state): | |
content, log = browser_state.refresh() | |
return { | |
**update_ui_components(browser_state), | |
log_display: gr.Textbox(log) | |
} | |
def handle_new_tab(browser_state): | |
content, log = browser_state.new_tab() | |
return { | |
**update_ui_components(browser_state), | |
log_display: gr.Textbox(log) | |
} | |
def handle_close_tab(browser_state): | |
content, log = browser_state.close_tab() | |
# Update UI components. If content is None, it means the last tab wasn't closed. | |
updated_components = update_ui_components(browser_state) | |
if content is not None: | |
updated_components[html_view] = gr.HTML(value=content) | |
return { | |
**updated_components, | |
log_display: gr.Textbox(log) | |
} | |
def handle_switch_tab(browser_state, tab_label): | |
content, log = browser_state.switch_tab(tab_label) | |
return { | |
**update_ui_components(browser_state), | |
log_display: gr.Textbox(log) | |
} | |
def handle_add_bookmark(browser_state): | |
log = browser_state.add_bookmark() | |
return { | |
**update_ui_components(browser_state), | |
log_display: gr.Textbox(log) | |
} | |
# --- Gradio Interface Layout --- | |
with gr.Blocks(theme=gr.themes.Soft(), title="Pseudo Browser") as demo: | |
browser_state = gr.State(PseudoBrowser()) | |
gr.Markdown("# 🌐 Pseudo Browser Demo") | |
gr.Markdown("A simulation of a web browser's state and actions, built with Python and Gradio.") | |
with gr.Row(): | |
with gr.Column(scale=3): | |
# URL and Navigation Controls | |
with gr.Row(): | |
back_btn = gr.Button("◀") | |
forward_btn = gr.Button("▶") | |
refresh_btn = gr.Button("🔄") | |
url_textbox = gr.Textbox(label="URL", placeholder="https://example.com", interactive=True) | |
go_btn = gr.Button("Go", variant="primary") | |
bookmark_btn = gr.Button("⭐") | |
# Main "rendered" content view | |
html_view = gr.HTML(value="<html><body><h1>Welcome!</h1></body></html>") | |
# Log display | |
log_display = gr.Textbox(label="Log", interactive=False) | |
with gr.Column(scale=1): | |
# Tab Management | |
with gr.Row(): | |
new_tab_btn = gr.Button("➕ New Tab") | |
close_tab_btn = gr.Button("❌ Close Active Tab") | |
tab_selector = gr.Radio(choices=[], label="Active Tabs", interactive=True) | |
# Accordion for History and Bookmarks | |
with gr.Accordion("History & Bookmarks", open=True): | |
history_display = gr.Markdown("...") | |
bookmarks_display = gr.Markdown("...") | |
# --- Component Wiring --- | |
# Define all outputs that can be updated | |
outputs = [html_view, url_textbox, tab_selector, history_display, bookmarks_display, log_display] | |
# Initial load | |
demo.load( | |
fn=lambda state: {**update_ui_components(state), log_display: "🚀 Browser Initialized!"}, | |
inputs=[browser_state], | |
outputs=outputs | |
) | |
# Wire up buttons | |
go_btn.click(handle_go, [browser_state, url_textbox], outputs) | |
url_textbox.submit(handle_go, [browser_state, url_textbox], outputs) | |
back_btn.click(handle_back, [browser_state], outputs) | |
forward_btn.click(handle_forward, [browser_state], outputs) | |
refresh_btn.click(handle_refresh, [browser_state], outputs) | |
new_tab_btn.click(handle_new_tab, [browser_state], outputs) | |
close_tab_btn.click(handle_close_tab, [browser_state], outputs) | |
tab_selector.input(handle_switch_tab, [browser_state, tab_selector], outputs) | |
bookmark_btn.click(handle_add_bookmark, [browser_state], outputs) | |
demo.launch() |