patent2sysml / ui_workflow.py
cmuchancel's picture
Upload folder using huggingface_hub
6dbdc3b verified
Raw History Blame Contribute Delete
12.2 kB
"""Gradio presentation states for upload, explicit method choice, sign-in and results."""
from contextlib import closing
import logging
import os
import gradio as gr
from chatgpt_login import connect_chatgpt
from user_session import connected, delete_session, new_session
def login_needed(visitor):
if not os.getenv('SPACE_ID'):
return False
try:
return not connected(visitor)
except ValueError:
return True
def build_app(process, unused_gpu_slot=None, extract_gliner=None, infer_windows=None):
with gr.Blocks(title='Translate patents to SysML v2') as app:
session = gr.State(new_session if os.getenv('SPACE_ID') else None,
time_to_live=3600, delete_callback=delete_session)
method = gr.State(None)
if unused_gpu_slot:
gr.Button(visible=False).click(unused_gpu_slot, api_name=False)
if infer_windows:
gpu_requests = gr.JSON(visible=False)
gpu_predictions = gr.JSON(visible=False)
gr.Button(visible=False).click(infer_windows, gpu_requests, gpu_predictions, api_name=False)
gr.Markdown('# Translate patents to SysML v2\nUpload a patent. Generate a system model.')
patent = gr.File(label='Whole patent HTML · .html or .htm',
file_types=['.html', '.htm'], type='filepath', elem_id='patent-upload')
with gr.Row(visible=bool(extract_gliner), elem_id='method-options') as methods:
agents = gr.Button('AI agents', variant='secondary', elem_id='method-agents')
nlp = gr.Button('Fine Tuned NLP', interactive=bool(extract_gliner), elem_id='method-nlp')
if extract_gliner:
with gr.Column(visible=False) as nlp_panel:
gr.Markdown('### Fine-tuned GLiNER RelEx\nUpload the whole patent or paste its text. All text is processed in overlapping windows and merged into one knowledge graph and candidate SJS. ChatGPT sign-in is not required. Patent and relationship quality still need review; candidate SJS uses sjs/1.0.')
nlp_text = gr.Textbox(label='Or enter text', info='Used when no patent file is uploaded.', lines=4, placeholder='The controller drives the motor.')
nlp_passes = gr.CheckboxGroup(['Subsystem', 'Part', 'Port', 'Interface', 'ItemFlow', 'Requirement', 'Action', 'StateMachine', 'Value', 'Constraint', 'VerificationCase'], value=['Subsystem', 'Part', 'Action'], label='Extraction passes')
nlp_threshold = gr.Slider(0.05, 0.95, value=0.4, step=0.05, label='Entity threshold')
nlp_run = gr.Button('Process with GLiNER', variant='primary')
nlp_status = gr.Markdown()
nlp_diagram = gr.HTML()
nlp_graph = gr.JSON(label='Knowledge graph')
nlp_sjs = gr.JSON(label='Candidate SJS (sjs/1.0)')
nlp_download = gr.File(label='Download graph and candidate SJS', file_count='multiple')
patent.change(lambda: ('', '', '', None, None, None), outputs=[nlp_text, nlp_status, nlp_diagram, nlp_graph, nlp_sjs, nlp_download], api_name=False, queue=False)
nlp.click(lambda: gr.update(visible=True), outputs=nlp_panel, api_name=False, queue=False)
nlp_run.click(extract_gliner, [patent, nlp_text, nlp_passes, nlp_threshold],
[nlp_status, nlp_diagram, nlp_graph, nlp_sjs, nlp_download],
api_name='extract_gliner', concurrency_limit=1)
disclosure = gr.Markdown('Research mode: uploaded patents, agent conversations, retrieved evidence, and outputs '
'are retained in the project owner’s private research archive. Login credentials are excluded.',
visible=False, elem_id='research-disclosure')
reconnect = gr.Button('Connect ChatGPT', visible=False, elem_id='reconnect-chatgpt')
start = gr.Button('Process', variant='primary', visible=False, interactive=False, elem_id='process-button')
status = gr.Markdown(visible=False, elem_id='process-status')
with gr.Column(visible=False, elem_id='results-section') as results:
with gr.Tab('Diagram'):
preview = gr.HTML(elem_id='diagram')
with gr.Tab('SJS model'):
result = gr.Code(label='SJS model (sjs/1.2)', language='json', interactive=False)
download = gr.DownloadButton('Download SJS JSON')
research_download = gr.DownloadButton('Download complete research record (.zip)')
with gr.Tab('SysML download'):
sysml_download = gr.DownloadButton('Download .sysml')
sysml_text = gr.Code(label='SysML v2', language=None, interactive=False)
# Native Gradio components styled as a dialog; JS supplies focus trapping and Escape.
with gr.Column(visible=False, elem_id='login-modal') as modal:
with gr.Column(elem_id='login-card'):
close = gr.Button('✕', elem_id='close-login', min_width=36, scale=0, size='sm')
gr.HTML('<h2 id="login-title">Connect ChatGPT</h2>'
'<p id="login-description">Sign in with your ChatGPT account to use AI agents. '
'Your connection is private to this session.</p>')
login_status = gr.Markdown('Continue to get a secure OpenAI sign-in link and code.', elem_id='login-status')
connect = gr.Button('Connect ChatGPT', variant='primary', elem_id='connect-chatgpt')
gr.Markdown('This window closes automatically once you’re connected.', elem_id='login-note')
output_components = [status, result, download, preview, sysml_text, sysml_download, research_download]
def on_upload(file):
return {methods: gr.update(visible=bool(file) or bool(extract_gliner)), method: None,
agents: gr.update(variant='secondary'), disclosure: gr.update(visible=bool(file)),
start: gr.update(visible=False, interactive=False, value='Process'),
status: gr.update(visible=False, value=''), modal: gr.update(visible=False),
reconnect: gr.update(visible=False), connect: gr.update(interactive=True, value='Connect ChatGPT'),
results: gr.update(visible=False), result: '', download: None, preview: '',
sysml_text: '', sysml_download: None, research_download: None}
def choose_agents(file, visitor):
if not file:
raise gr.Error('Upload a patent HTML file first.')
needs_login = login_needed(visitor)
return {method: 'agents', agents: gr.update(variant='primary'),
modal: gr.update(visible=needs_login),
start: gr.update(visible=True, interactive=not needs_login),
reconnect: gr.update(visible=needs_login),
login_status: 'Continue to get a secure OpenAI sign-in link and code.',
connect: gr.update(interactive=True, value='Connect ChatGPT'),
status: gr.update(visible=True, value='AI agents selected. Connect ChatGPT to continue.' if needs_login
else 'AI agents selected. Ready to process.')}
def sign_in(visitor):
yield {connect: gr.update(interactive=False, value='Waiting for sign-in…')}
try:
if login_needed(visitor):
# Closing the dialog cancels this generator and the login subprocess.
with closing(connect_chatgpt(visitor)) as messages:
for message in messages:
yield {login_status: message}
if not login_needed(visitor):
yield {modal: gr.update(visible=False), reconnect: gr.update(visible=False),
start: gr.update(interactive=True),
status: gr.update(visible=True, value='ChatGPT connected. Ready to process.')}
else:
yield {start: gr.update(interactive=False)}
except (ValueError, OSError) as error:
yield {login_status: str(error), start: gr.update(interactive=False)}
yield {connect: gr.update(interactive=True, value='Connect ChatGPT')}
def dismiss_login():
return {modal: gr.update(visible=False), connect: gr.update(interactive=True, value='Connect ChatGPT')}
def run_ui(file, selected, visitor):
if not file or selected != 'agents':
yield {status: gr.update(visible=True, value='Upload a patent and select a method first.')}
return
if login_needed(visitor):
yield {modal: gr.update(visible=True), reconnect: gr.update(visible=True),
start: gr.update(interactive=False),
status: gr.update(visible=True, value='Connect ChatGPT to continue.')}
return
yield {start: gr.update(interactive=False, value='Processing…'), patent: gr.update(interactive=False),
agents: gr.update(interactive=False), results: gr.update(visible=False),
reconnect: gr.update(visible=False), status: gr.update(visible=True, value='Processing your patent…'),
result: '', download: None, preview: '', sysml_text: '', sysml_download: None, research_download: None}
finished = None
try:
with closing(process(file, visitor)) as rows:
for row in rows:
finished = row
yield {status: gr.update(visible=True, value=row[0])}
if finished:
updates = dict(zip(output_components, finished))
updates[status] = gr.update(visible=True, value=finished[0])
# Failed runs can still expose their retained research archive.
updates[results] = gr.update(visible=bool(finished[1] or finished[6]))
yield updates
except Exception:
logging.exception('Patent processing failed')
yield {status: gr.update(visible=True, value='Processing could not finish. Please try again.')}
yield {start: gr.update(interactive=True, value='Process'), patent: gr.update(interactive=True),
agents: gr.update(interactive=True)}
selection_outputs = [method, agents, modal, start, reconnect, login_status, connect, status]
agents.click(choose_agents, [patent, session], selection_outputs,
api_name=False, queue=False, show_progress='hidden')
reconnect.click(choose_agents, [patent, session], selection_outputs,
api_name=False, queue=False, show_progress='hidden')
login_event = connect.click(sign_in, session, [connect, login_status, modal, reconnect, start, status],
api_name=False, concurrency_limit=4, show_progress='hidden')
close.click(dismiss_login, outputs=[modal, connect], cancels=[login_event],
api_name=False, queue=False, show_progress='hidden')
patent.change(on_upload, patent, [methods, method, agents, disclosure, start, status, modal, reconnect, connect,
results, result, download, preview, sysml_text, sysml_download, research_download],
cancels=[login_event], api_name=False, queue=False, show_progress='hidden')
start.click(run_ui, [patent, method, session],
[*output_components, start, patent, agents, results, modal, reconnect],
api_name=False, concurrency_limit=1, concurrency_id='patent_processing', show_progress='hidden')
# Preserve the existing seven-output API and backend authentication gate.
gr.Button(visible=False).click(process, [patent, session], output_components,
api_name='process_patent', concurrency_limit=1, concurrency_id='patent_processing')
return app