WHG2023
fix: Use markdown for images to prevent JS errors
2a4ee49
import gradio as gr
import os
import requests
import json
from typing import Dict, List, Generator, Optional
from dotenv import load_dotenv
import base64
import tempfile
import re
import urllib.parse
from datetime import datetime
# Load environment variables from .env file
load_dotenv()
# --- Data Logging (with consent) ---
def log_user_data(invention_disclosure: str):
"""Logs the user's invention disclosure to a file if consent is given."""
try:
with open("user_data_log.txt", "a") as f:
log_entry = {
"timestamp": datetime.now().isoformat(),
"invention_disclosure": invention_disclosure
}
f.write(json.dumps(log_entry) + "\n")
except Exception as e:
print(f"Error logging user data: {e}")
# --- New Agentic Backend Integration ---
try:
from real_ai_agents_implementation import AgenticNegotiator, NegotiationState
AGENTIC_BACKEND_AVAILABLE = True
except ImportError as e:
print(f"Failed to import agentic backend: {e}")
AGENTIC_BACKEND_AVAILABLE = False
# Keep Gemini Image Generator for LaTeX compilation
try:
from gemini_image_generator import GeminiImageGenerator
GEMINI_IMAGE_AVAILABLE = True
except ImportError:
GEMINI_IMAGE_AVAILABLE = False
def compile_latex_to_image(latex_code: str) -> Optional[str]:
"""Try to compile LaTeX code to an image using a web service, with fallbacks."""
if not latex_code:
return None
content_to_render = None
try:
# A more robust regex to find the complete document, even with surrounding text.
match = re.search(r'(\\documentclass.*?\\end{document})', latex_code, re.DOTALL)
if match:
content_to_render = match.group(1)
else:
# Fallback for tikzpicture only if full document not found
match = re.search(r'(\\begin{tikzpicture}.*?\\end{tikzpicture})', latex_code, re.DOTALL)
if match:
# If only tikz is found, wrap it in a standard document class
content_to_render = f"""\\documentclass[border=5mm]{{standalone}}
\\usepackage{{tikz}}
\\usetikzlibrary{{shapes,arrows}}
{match.group(0)}
"""
else:
# If no specific block is found, take the whole code and hope for the best
content_to_render = latex_code
# --- Attempt 1: codecogs API (fast but limited) ---
try:
encoded_latex = urllib.parse.quote(content_to_render)
api_url = f"https://latex.codecogs.com/png.latex?{encoded_latex}"
response = requests.get(api_url, timeout=15)
if response.status_code == 200 and response.headers.get('content-type', '').startswith('image/'):
image_b64 = base64.b64encode(response.content).decode('utf-8')
return f"data:image/png;base64,{image_b64}"
else:
print(f"LaTeX compilation (codecogs) failed with status: {response.status_code}")
except Exception as e:
print(f"An error occurred during codecogs LaTeX compilation: {e}")
# --- Attempt 2: A more robust secondary API (slower but more capable) ---
print("Codecogs failed, trying secondary LaTeX service...")
try:
api_url_secondary = "https://latex.ytotech.com/api/v1"
payload = {
"latex": content_to_render,
"format": "png",
"quality": 85,
"density": 300
}
response = requests.post(api_url_secondary, json=payload, timeout=30)
if response.status_code == 200:
response_data = response.json()
if response_data.get("status") == "success" and response_data.get("result"):
# The result is already base64 encoded
image_b64 = response_data["result"]
return f"data:image/png;base64,{image_b64}"
else:
print(f"LaTeX compilation (secondary) failed with status: {response.status_code}")
return None
except Exception as e:
print(f"An error occurred during secondary LaTeX compilation: {e}")
return None
except Exception as e:
print(f"A critical error occurred during LaTeX preparation: {e}")
return None
def create_negotiation_transcript_display(transcript: List[Dict]) -> str:
"""Creates a markdown display for the agent negotiation transcript."""
if not transcript:
return "### Agent Negotiation Log\n\n*Awaiting instructions...*"
markdown = "### Agent Negotiation Log\n\n---\n"
for entry in transcript:
agent = entry.get('agent', 'System')
message = entry.get('message', '').replace('\n', '\n> ') # Indent multiline messages
# Using markdown emphasis and blockquotes instead of raw HTML
markdown += f"\n\n**🤖 {agent}:**\n"
markdown += f"> {message}\n\n"
markdown += "---"
return markdown
def run_patent_architect_in_ui(invention_disclosure: str, consent: bool) -> Generator[List, None, None]:
"""
This is the new main UI function that runs the true agentic workflow.
It no longer calls an external backend.
"""
# Initial state for all output fields
negotiation_html = create_negotiation_transcript_display([])
prior_art_section = "### Prior Art Analysis\n\n*Awaiting agent analysis...*"
summary_section = "### Invention Summary\n\n*Awaiting agent analysis...*"
figures_section = "### Technical Figures\n\n*Awaiting agent analysis...*"
claims_section = "### Patent Claims\n\n*Awaiting agent analysis...*"
status = "System Initialized."
yield [negotiation_html, prior_art_section, summary_section, figures_section, claims_section, status]
if not invention_disclosure:
status = "Please provide an invention disclosure."
yield [negotiation_html, prior_art_section, summary_section, figures_section, claims_section, status]
return
if not AGENTIC_BACKEND_AVAILABLE:
status = "ERROR: The agentic backend is not available. Check server logs."
negotiation_html = create_negotiation_transcript_display([{"agent": "System", "message": "CRITICAL ERROR: Could not import `AgenticNegotiator`. The application cannot run."}])
yield [negotiation_html, prior_art_section, summary_section, figures_section, claims_section, status]
return
# --- Log data only if consent is given ---
if consent:
log_user_data(invention_disclosure)
# --- Start the Real Agentic Workflow ---
negotiator = AgenticNegotiator(invention_disclosure)
final_state = None
for state in negotiator.run_negotiation():
final_state = state
# Update negotiation transcript
negotiation_html = create_negotiation_transcript_display(state.negotiation_transcript)
# Update status from the last message
if state.negotiation_transcript:
last_entry = state.negotiation_transcript[-1]
status = f"Active Agent: {last_entry['agent']}"
# As prior art becomes available, display it
if state.prior_art_analysis:
pa_data = state.prior_art_analysis
prior_art_section = f"### Prior Art & Strategy\n\n"
if state.strategic_mandate:
prior_art_section += f"**Strategic Mandate:**\n> {state.strategic_mandate}\n\n---\n\n"
prior_art_section += f"**Landscape Summary (from live web search):**\n> {pa_data.get('landscape_summary', 'N/A')}\n\n"
prior_art_section += f"**Key Concepts Searched:**\n"
for concept in pa_data.get('key_concepts', []):
prior_art_section += f"- {concept}\n"
prior_art_section += "\n**Representative Prior Art Found:**\n"
for art in pa_data.get('real_prior_art', []):
prior_art_section += f"- **[{art.get('title', 'Link')}]({art.get('link', '#')})**\n"
# As sections get generated, display them
if state.technical_summary:
summary_section = f"### Invention Summary\n\n{state.technical_summary}"
if state.patent_claims:
claims_section = f"### Patent Claims\n\n{state.patent_claims}"
# Handle both Ideogram image and LaTeX figure description
if state.ideogram_image_b64 or state.figure_description:
figures_section = "### Technical Figures\n\n"
# Display the Ideogram image if it exists
if state.ideogram_image_b64:
figures_section += "### Conceptual Image (from Ideogram API)\n"
image_md = f"![Generated Conceptual Image](data:image/jpeg;base64,{state.ideogram_image_b64})"
figures_section += image_md + "\n\n---\n\n"
# Display the LaTeX figure description and compiled image if it exists
if state.figure_description:
figures_section += "### Technical Figure (from LaTeX)\n"
compiled_latex_image_uri = compile_latex_to_image(state.figure_description)
if compiled_latex_image_uri:
figures_section += f"![Compiled LaTeX Figure]({compiled_latex_image_uri})\n\n"
else:
figures_section += "*LaTeX compilation failed. Displaying raw code.*\n\n"
figures_section += "#### Raw LaTeX/TikZ Code:\n"
figures_section += state.figure_description
yield [negotiation_html, prior_art_section, summary_section, figures_section, claims_section, status]
status = "Agentic Negotiation Complete."
yield [negotiation_html, prior_art_section, summary_section, figures_section, claims_section, status]
with gr.Blocks(theme=gr.themes.Base(primary_hue="green", neutral_hue="slate"), title="Patent Architect") as demo:
gr.HTML("""
<div style="background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
border-bottom: 2px solid #00ff41;
padding: 30px 20px;
text-align: center;
font-family: 'Courier New', monospace;">
<h1 style="font-size: 2.8em;
margin: 0;
text-shadow: 0 0 10px #00ff41;
color: #00ff41;
font-weight: 900;
letter-spacing: 2px;">
PATENT ARCHITECT
</h1>
<p style="font-size: 1.2em;
margin: 10px 0 0 0;
color: #ffff00;
text-shadow: 0 0 5px #ffff00;
font-weight: 600;">
From Raw Idea to Strategized Patent Draft
</p>
</div>
""")
with gr.Row(variant="panel"):
with gr.Column(scale=2, min_width=600):
# --- STEP 1: INPUT ---
gr.Markdown("""
<div style="border: 1px solid #ffff00; padding: 20px; margin-top: 20px; box-shadow: 0 0 10px rgba(255,255,0,0.2);">
<h2 style="color: #ffff00; font-size: 1.5em; margin: 0 0 15px 0; text-transform: uppercase; letter-spacing: 1px;">
Step 1: Disclose Your Invention
</h2>
</div>
""")
invention_input = gr.Textbox(
lines=10,
label="Invention Disclosure",
placeholder="Describe the core of your invention. Focus on what it does and what makes it novel. The AI agent team will analyze the technical fundamentals.",
info="Provide a clear, concise description. Details are good, but the core concept is essential."
)
with gr.Row(elem_classes="consent-row"):
consent_checkbox = gr.Checkbox(
label="I agree to let the developer collect my anonymized invention data to improve this tool.",
value=False,
interactive=True,
info="We will only store the invention text and a timestamp. No personal data is collected. See our Privacy Policy."
)
generate_btn = gr.Button("Develop Patent Strategy", variant="primary", size="lg", interactive=False)
# --- STEP 2: LIVE ANALYSIS ---
gr.Markdown("""
<div style="border: 1px solid #ff8c00; padding: 20px; margin-top: 30px; box-shadow: 0 0 10px rgba(255,140,0,0.2);">
<h2 style="color: #ff8c00; font-size: 1.5em; margin: 0 0 15px 0; text-transform: uppercase; letter-spacing: 1px;">
Step 2: Observe the Agent Team
</h2>
</div>
""")
negotiation_display = gr.Markdown("The agent negotiation log will appear here once you begin.")
with gr.Column(scale=1, min_width=300):
# --- SIDEBAR: STATUS & DELIVERABLES ---
gr.Markdown("""
<div style="border: 1px solid #00ff41; padding: 20px; margin-top: 20px; box-shadow: 0 0 10px rgba(0,255,65,0.2);">
<h2 style="color: #00ff41; font-size: 1.5em; margin: 0 0 15px 0; text-transform: uppercase; letter-spacing: 1px;">
System Status & Output
</h2>
</div>
""")
status_display = gr.Textbox(
label="Current Status",
interactive=False,
value="Ready"
)
gr.Markdown("""
<h3 style="color: #00ff41; font-size: 1.3em; margin: 20px 0 15px 0; text-transform: uppercase;">
Step 3: Review Deliverables
</h3>
<p style="color: #ff8c00; font-size: 0.9em; margin-bottom: 15px;">
The final, strategy-aligned patent draft will appear below.
</p>
""")
with gr.Tabs():
with gr.TabItem("Prior Art & Strategy"):
prior_art_output = gr.Markdown(
value="*The Prior Art Detective's findings and the Chief Strategist's resulting mandate will be shown here.*"
)
with gr.TabItem("Technical Summary"):
summary_output = gr.Markdown(
value="*The Technical Writer's summary, aligned with the final strategy, will appear here.*"
)
with gr.TabItem("Technical Figures"):
figures_output = gr.Markdown(
value="*A conceptual image from the Ideogram API and a technical figure from the Figure Drafter will appear here.*"
)
with gr.TabItem("Patent Claims"):
claims_output = gr.Markdown(
value="*The Claims Drafter's claims, focused on the strategic mandate, will appear here.*"
)
gr.HTML("<hr style='border: 1px solid #00ff41; margin: 40px 0;'>")
gr.Examples(
[
["A smart coffee mug using phase-change materials and a machine learning algorithm that learns user habits to optimize temperature and energy use."],
["A modular vertical farming system with AI-controlled, adaptive full-spectrum LED lighting based on real-time plant reflectance spectra analysis."],
["Non-invasive glucose monitoring using Raman spectroscopy."],
],
inputs=[invention_input],
label="Invention Examples (click one to get started)"
)
consent_checkbox.change(lambda x: gr.update(interactive=x), consent_checkbox, generate_btn)
generate_btn.click(
fn=run_patent_architect_in_ui,
inputs=[invention_input, consent_checkbox],
outputs=[negotiation_display, prior_art_output, summary_output, figures_output, claims_output, status_display]
)
gr.HTML("""
<style>
.gradio-container {
background: #0a0a0a !important;
font-family: 'Courier New', monospace !important;
max-width: 1400px !important;
margin: auto !important;
}
.gradio-button.primary {
background: linear-gradient(45deg, #00ff41, #ffff00) !important;
border: 2px solid #00ff41 !important;
color: #0a0a0a !important;
font-weight: 900 !important;
font-size: 1.2em !important;
border-radius: 0 !important;
text-transform: uppercase !important;
letter-spacing: 2px !important;
box-shadow: 0 0 15px #00ff41, inset 0 0 10px rgba(0,255,65,0.2) !important;
font-family: 'Courier New', monospace !important;
transition: all 0.3s ease !important;
margin-top: 20px;
}
.gradio-button.primary:hover {
background: linear-gradient(45deg, #ffff00, #ff8c00) !important;
box-shadow: 0 0 25px #ffff00, inset 0 0 15px rgba(255,255,0,0.3) !important;
border-color: #ffff00 !important;
}
.tab-nav button {
background: linear-gradient(45deg, #1a1a1a, #0a0a0a) !important;
border: 1px solid #00ff41 !important;
color: #00ff41 !important;
font-family: 'Courier New', monospace !important;
text-transform: uppercase !important;
letter-spacing: 1px !important;
border-radius: 0 !important;
}
.tab-nav button.selected {
background: linear-gradient(45deg, #00ff41, #ffff00) !important;
color: #0a0a0a !important;
box-shadow: 0 0 10px #00ff41 !important;
}
.gr-textbox, .gr-markdown {
background: linear-gradient(45deg, #0a0a0a, #1a1a1a) !important;
border: 1px solid #00ff41 !important;
color: #00ff41 !important;
font-family: 'Courier New', monospace !important;
}
.gr-textbox:focus, .gr-markdown:focus {
border-color: #ffff00 !important;
box-shadow: 0 0 10px #ffff00 !important;
}
.gr-info {
color: #ff8c00 !important;
}
* {
font-family: 'Courier New', monospace !important;
}
</style>
""")
if __name__ == "__main__":
print("PATENT ARCHITECT - True Agentic Negotiation Workflow")
print(f"Agentic Backend: {'ONLINE' if AGENTIC_BACKEND_AVAILABLE else 'OFFLINE'}")
if not os.getenv("GEMINI_API_KEY"):
print("⚠️ WARNING: GEMINI_API_KEY environment variable not set. Gemini-powered agents will not function.")
if not os.getenv("SEGMIND_API_KEY"):
print("⚠️ WARNING: SEGMIND_API_KEY environment variable not set. Ideogram image generation will not function.")
demo.queue().launch(
share=False,
show_error=True
)