dev-strender's picture
feat: add v24 pipeline (FT + self-consistency × 2 + tool-calling judge)
d518386
"""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],
)