Spaces:
Sleeping
Sleeping
| """Gradio UI for single-output proofreading feedback. | |
| Flow: | |
| 1. User enters source text and clicks "교정 실행". | |
| 2. The configured pipeline runs. | |
| 3. Output + diff are shown. | |
| 4. User picks Good / Not Bad / Critical (+ optional comment). | |
| 5. On submit: rating is saved to Supabase ratings table. | |
| """ | |
| from __future__ import annotations | |
| import time | |
| from typing import Any | |
| import gradio as gr | |
| from diff_utils import highlight_diff | |
| from pipelines import run_pipeline | |
| from . import db | |
| PipelineConfig = tuple[str, str, str] # (pipeline_key, model, prompt_key) | |
| def _format_summary(counts: dict[str, int]) -> str: | |
| total = sum(counts.values()) | |
| if total == 0: | |
| return "아직 피드백이 없습니다." | |
| g = counts.get("good", 0) | |
| n = counts.get("not_bad", 0) | |
| c = counts.get("critical", 0) | |
| return f"**총 피드백**: {total}\n\n- Good: **{g}**\n- Not Bad: **{n}**\n- Critical: **{c}**" | |
| def build_feedback_tab( | |
| client: Any, | |
| vocabulary: list[dict], | |
| pipeline_config: PipelineConfig, | |
| elem_id_prefix: str = "compare", | |
| ) -> None: | |
| """Build the single-pipeline feedback UI. Call inside a gr.Blocks/Tab. | |
| Args: | |
| elem_id_prefix: Unique prefix for component elem_ids. Required | |
| when building this UI multiple times in the same Blocks (e.g. | |
| for two tabs) — Gradio errors on duplicate elem_ids. | |
| """ | |
| pipeline_key, model, prompt_key = pipeline_config | |
| pipeline_run_id_state = gr.State(None) | |
| input_text = gr.Textbox( | |
| label="원문 입력", | |
| lines=8, | |
| placeholder="교정할 텍스트를 입력하세요.", | |
| ) | |
| run_btn = gr.Button( | |
| "교정 실행 (⌘+Enter / Ctrl+Enter)", | |
| variant="primary", | |
| elem_id=f"{elem_id_prefix}-run-btn", | |
| ) | |
| status = gr.Markdown("") | |
| output = gr.Textbox(label="교정 결과", lines=12, interactive=False) | |
| diff_html = gr.HTML(label="원문 대비 diff") | |
| gr.Markdown("### 피드백") | |
| with gr.Row(): | |
| rate_good = gr.Button("👍 Good", variant="primary") | |
| rate_notbad = gr.Button("🆗 Not Bad") | |
| rate_critical = gr.Button("🚨 Critical", variant="stop") | |
| comment = gr.Textbox(label="코멘트 (선택)", lines=2) | |
| rating_status = gr.Markdown("") | |
| summary_md = gr.Markdown("") | |
| def _on_run(text: str): | |
| if not text or not text.strip(): | |
| return ( | |
| gr.update(value="입력 텍스트가 비어있습니다."), | |
| gr.update(value=""), | |
| gr.update(value=""), | |
| None, | |
| gr.update(value=""), | |
| ) | |
| if client is None: | |
| return ( | |
| gr.update(value="UPSTAGE_API_KEY 미설정."), | |
| gr.update(value=""), | |
| gr.update(value=""), | |
| None, | |
| gr.update(value=""), | |
| ) | |
| start = time.time() | |
| try: | |
| result = run_pipeline(text, pipeline_key, model, prompt_key, client, vocabulary) | |
| except Exception as exc: | |
| return ( | |
| gr.update(value=f"에러: {exc}"), | |
| gr.update(value=""), | |
| gr.update(value=""), | |
| None, | |
| gr.update(value=""), | |
| ) | |
| elapsed = time.time() - start | |
| out_text = result.get("output", "") | |
| article_id = db.save_article(text) | |
| run_id = db.save_pipeline_run( | |
| article_id, | |
| pipeline_key=pipeline_key, | |
| prompt_key=prompt_key, | |
| model=model, | |
| output=out_text, | |
| processing_time_s=float(elapsed), | |
| ) | |
| return ( | |
| gr.update(value=f"완료 · {elapsed:.1f}s"), | |
| gr.update(value=out_text), | |
| gr.update(value=highlight_diff(text, out_text)), | |
| run_id, | |
| gr.update(value=""), | |
| ) | |
| run_btn.click( | |
| _on_run, | |
| inputs=[input_text], | |
| outputs=[status, output, diff_html, pipeline_run_id_state, rating_status], | |
| ) | |
| def _make_rating_handler(rating: str): | |
| def handler(run_id, comment_text): | |
| saved = db.save_rating(run_id, rating, comment_text) | |
| note = ( | |
| "✅ 피드백 저장됨" | |
| if saved | |
| else "⚠️ 저장되지 않았습니다 (먼저 교정 실행 후 피드백을 남겨주세요)" | |
| ) | |
| summary = _format_summary(db.fetch_rating_counts()) | |
| return gr.update(value=note), gr.update(value=summary) | |
| return handler | |
| rate_good.click( | |
| _make_rating_handler("good"), | |
| inputs=[pipeline_run_id_state, comment], | |
| outputs=[rating_status, summary_md], | |
| ) | |
| rate_notbad.click( | |
| _make_rating_handler("not_bad"), | |
| inputs=[pipeline_run_id_state, comment], | |
| outputs=[rating_status, summary_md], | |
| ) | |
| rate_critical.click( | |
| _make_rating_handler("critical"), | |
| inputs=[pipeline_run_id_state, comment], | |
| outputs=[rating_status, summary_md], | |
| ) | |
| refresh_btn = gr.Button("집계 새로고침", size="sm") | |
| refresh_btn.click( | |
| lambda: gr.update(value=_format_summary(db.fetch_rating_counts())), | |
| outputs=[summary_md], | |
| ) | |