dev-strender commited on
Commit
baac679
·
verified ·
1 Parent(s): 9c84f9d

UI: side-by-side 원문/교열후 panes, larger input, remove metrics/step view

Browse files
Files changed (1) hide show
  1. app.py +24 -45
app.py CHANGED
@@ -19,29 +19,31 @@ SAMPLE_TEXT = (
19
  "실수요자의 구매 심리가 되살아났기 때문이라는 분석이 나온다."
20
  )
21
 
22
- LLM_STEPS = {"proofread", "basic_correction", "context_correction", "style_correction"}
 
 
 
23
 
24
 
25
- def diff_html(src: str, dst: str) -> tuple[str, int]:
26
- """어절 단위 diff 를 HTML 로. (마크업, 바뀐 세그먼트 수) 반환."""
27
  a, b = src.split(), dst.split()
28
  sm = difflib.SequenceMatcher(a=a, b=b, autojunk=False)
29
- parts: list[str] = []
 
30
  n_changed = 0
31
  for op, i1, i2, j1, j2 in sm.get_opcodes():
32
  before, after = html.escape(" ".join(a[i1:i2])), html.escape(" ".join(b[j1:j2]))
33
  if op == "equal":
34
- parts.append(after)
 
35
  continue
36
  n_changed += 1
37
  if before:
38
- parts.append(
39
- f'<span style="background:rgba(230,80,80,.18);'
40
- f'text-decoration:line-through">{before}</span>'
41
- )
42
  if after:
43
- parts.append(f'<span style="background:rgba(60,170,90,.22)">{after}</span>')
44
- return " ".join(parts), n_changed
45
 
46
 
47
  @st.cache_resource
@@ -68,9 +70,8 @@ with st.sidebar:
68
  if key:
69
  os.environ["UPSTAGE_API_KEY"] = key
70
  model = st.selectbox("모델 (step1–3 주입)", ["solar-pro4", "solar-pro2"], index=0)
71
- st.caption("FT 교열 스텝은 파이프라인에 고정된 ft:solar-news-correction 을 사용합니다.")
72
 
73
- text = st.text_area("기사 본문", value=SAMPLE_TEXT, height=180, max_chars=4000)
74
 
75
  if st.button("교열 실행", type="primary", disabled=not text.strip()):
76
  if not os.environ.get("UPSTAGE_API_KEY"):
@@ -78,41 +79,19 @@ if st.button("교열 실행", type="primary", disabled=not text.strip()):
78
  "UPSTAGE_API_KEY 가 필요합니다 — 사이드바에 입력하거나 Space Secrets 에 설정하세요."
79
  )
80
  st.stop()
81
- with st.spinner("교열 중입니다… (파이프라인 최악 실측 37.5초, 보통 10초 내)"):
82
  try:
83
  result = runner.run_proofread(get_pipeline(), text.strip(), model=model)
84
  except Exception as e: # noqa: BLE001 — 데모 표면에서는 원인 요약만 보여준다
85
  st.error(f"실행 실패: {type(e).__name__}: {e}")
86
  st.stop()
87
 
88
- markup, n_changed = diff_html(text.strip(), result["output"])
89
- usage = result.get("usage") or {}
90
-
91
- c1, c2, c3 = st.columns(3)
92
- c1.metric("소요 시간", f"{result['elapsed_s']:.1f}s")
93
- c2.metric("바뀐 곳", f"{n_changed}곳")
94
- c3.metric("토큰 (입력+출력)", f"{usage.get('total_tokens', 0):,}")
95
-
96
- st.subheader("교정 결과 (원문 대비 변경 표시)")
97
- st.markdown(
98
- f'<div style="line-height:1.9;font-size:1.02rem">{markup}</div>',
99
- unsafe_allow_html=True,
100
- )
101
-
102
- with st.expander("교정 전문 (복사용)"):
103
- st.code(result["output"], language=None)
104
-
105
- with st.expander("파이프라인 스텝별 출력"):
106
- prev = text.strip()
107
- for name, snapshot in result["step_outputs"].items():
108
- kind = "LLM" if name in LLM_STEPS else "rule"
109
- changed = snapshot != prev
110
- st.markdown(f"**{name}** · {kind} · {'변경함' if changed else '변경 없음'}")
111
- if changed:
112
- step_markup, _ = diff_html(prev, snapshot)
113
- st.markdown(
114
- f'<div style="line-height:1.8;font-size:.92rem;opacity:.9">{step_markup}</div>',
115
- unsafe_allow_html=True,
116
- )
117
- prev = snapshot
118
- st.divider()
 
19
  "실수요자의 구매 심리가 되살아났기 때문이라는 분석이 나온다."
20
  )
21
 
22
+ PANE_STYLE = (
23
+ "border:1px solid rgba(128,128,128,.35);border-radius:8px;"
24
+ "padding:14px 16px;line-height:1.9;font-size:1.02rem;min-height:120px"
25
+ )
26
 
27
 
28
+ def diff_panes(src: str, dst: str) -> tuple[str, str, int]:
29
+ """어절 단위 diff 를 좌(원문)/우(교정) 패널 HTML 로. 바뀐 세그먼트 수도 반환."""
30
  a, b = src.split(), dst.split()
31
  sm = difflib.SequenceMatcher(a=a, b=b, autojunk=False)
32
+ left: list[str] = []
33
+ right: list[str] = []
34
  n_changed = 0
35
  for op, i1, i2, j1, j2 in sm.get_opcodes():
36
  before, after = html.escape(" ".join(a[i1:i2])), html.escape(" ".join(b[j1:j2]))
37
  if op == "equal":
38
+ left.append(before)
39
+ right.append(after)
40
  continue
41
  n_changed += 1
42
  if before:
43
+ left.append(f'<span style="background:rgba(230,80,80,.20)">{before}</span>')
 
 
 
44
  if after:
45
+ right.append(f'<span style="background:rgba(60,170,90,.24)">{after}</span>')
46
+ return " ".join(left), " ".join(right), n_changed
47
 
48
 
49
  @st.cache_resource
 
70
  if key:
71
  os.environ["UPSTAGE_API_KEY"] = key
72
  model = st.selectbox("모델 (step1–3 주입)", ["solar-pro4", "solar-pro2"], index=0)
 
73
 
74
+ text = st.text_area("기사 본문", value=SAMPLE_TEXT, height=320, max_chars=4000)
75
 
76
  if st.button("교열 실행", type="primary", disabled=not text.strip()):
77
  if not os.environ.get("UPSTAGE_API_KEY"):
 
79
  "UPSTAGE_API_KEY 가 필요합니다 — 사이드바에 입력하거나 Space Secrets 에 설정하세요."
80
  )
81
  st.stop()
82
+ with st.spinner("교열 중입니다…"):
83
  try:
84
  result = runner.run_proofread(get_pipeline(), text.strip(), model=model)
85
  except Exception as e: # noqa: BLE001 — 데모 표면에서는 원인 요약만 보여준다
86
  st.error(f"실행 실패: {type(e).__name__}: {e}")
87
  st.stop()
88
 
89
+ left, right, _n_changed = diff_panes(text.strip(), result["output"])
90
+
91
+ c1, c2 = st.columns(2, gap="medium")
92
+ with c1:
93
+ st.subheader("원문")
94
+ st.markdown(f'<div style="{PANE_STYLE}">{left}</div>', unsafe_allow_html=True)
95
+ with c2:
96
+ st.subheader("교열 후")
97
+ st.markdown(f'<div style="{PANE_STYLE}">{right}</div>', unsafe_allow_html=True)