| """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 |
|
|
| |
| 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, |
| ], |
| ) |
|
|