"""Gradio UI — 제목 교열 sandbox 탭 (solar-pro2 단일 모델). production extension 과 동일 모델(solar-pro2) 로 고정. 시스템/유저 프롬프트는 내부 default 사용 — UI 에 노출하지 않음. 본문 입력란 (optional): - 비어있으면 → 제목만 보고 교열 (현 production 동작 = prompt_dev_v1) - 있으면 → 본문 컨텍스트 활용 (prompt_dev_v2, 본문 의존 typo catch rate ↑) """ from __future__ import annotations from typing import Any import gradio as gr from diff_utils import highlight_diff from .runner import MODEL, run_title_proofread # UI 단순화를 위해 category 는 입력 받지 않고 내부 고정. DEFAULT_CATEGORY = "일반기사" def build_title_proofread_tab(client: Any) -> None: """제목 교열 탭 빌더. `with gr.Tab(...)` 안에서 호출. Args: client: openai.OpenAI 호환 클라이언트 (Upstage base_url 설정). """ gr.Markdown(f"## 제목 교열 ({MODEL})") gr.Markdown( "본문을 함께 입력하면 동음이의어·이름·숫자 같은 *문맥 의존 오탈자* 를 " "더 정확히 잡습니다. 비워두면 제목만 보고 교열합니다." ) original = gr.Textbox( label="제목 입력", placeholder="예: 강남 3구, 매물 30% 늘었지만 거래량 15% 줄어유", lines=1, ) body = gr.Textbox( label="기사 본문 (선택)", placeholder=( "본문을 paste 하면 본문에 명시된 단어·이름·숫자를 근거로 더 " "정확하게 교정합니다. 비워두면 제목만 보고 교열합니다." ), lines=8, max_lines=20, ) with gr.Accordion("추론 옵션", open=False): with gr.Row(): temperature = gr.Slider( minimum=0.0, maximum=1.0, step=0.1, value=0.0, label="temperature", ) reasoning_effort = gr.Radio( choices=["low", "medium", "high"], value="low", label="reasoning_effort", ) run_btn = gr.Button( "▶ 교열 실행 (⌘+Enter / Ctrl+Enter)", variant="primary", elem_id="title-proofread-run-btn", ) # ─── 결과 — 단일 컬럼 ─── output = gr.Textbox(label="교정 결과", lines=2, interactive=False) diff_html = gr.HTML(label="원본 대비 diff") def _empty(): return gr.update(value=""), gr.update(value="") def _on_run( original_text: str, body_text: str, temperature_val: float, reasoning_effort_val: str, ): if not original_text or not original_text.strip(): gr.Warning("제목이 비어 있습니다.") return _empty() if client is None: gr.Warning("UPSTAGE_API_KEY 미설정.") return _empty() orig_clean = original_text.strip() body_clean = (body_text or "").strip() result = run_title_proofread( client=client, original=orig_clean, category=DEFAULT_CATEGORY, body=body_clean, temperature=temperature_val, reasoning_effort=reasoning_effort_val, ) if result.get("error"): gr.Warning(f"{MODEL} 호출 실패: {result['error']}") return _empty() out_text = result["output"] diff = highlight_diff(orig_clean, out_text) if out_text else "" return gr.update(value=out_text), gr.update(value=diff) run_btn.click( _on_run, inputs=[ original, body, temperature, reasoning_effort, ], outputs=[ output, diff_html, ], )