"""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('

Connect ChatGPT

' '

Sign in with your ChatGPT account to use AI agents. ' 'Your connection is private to this session.

') 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