Spaces:
Running
Running
UI: side-by-side 원문/교열후 panes, larger input, remove metrics/step view
Browse files
app.py
CHANGED
|
@@ -19,29 +19,31 @@ SAMPLE_TEXT = (
|
|
| 19 |
"실수요자의 구매 심리가 되살아났기 때문이라는 분석이 나온다."
|
| 20 |
)
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
-
def
|
| 26 |
-
"""어절 단위 diff 를 HTML 로.
|
| 27 |
a, b = src.split(), dst.split()
|
| 28 |
sm = difflib.SequenceMatcher(a=a, b=b, autojunk=False)
|
| 29 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 35 |
continue
|
| 36 |
n_changed += 1
|
| 37 |
if before:
|
| 38 |
-
|
| 39 |
-
f'<span style="background:rgba(230,80,80,.18);'
|
| 40 |
-
f'text-decoration:line-through">{before}</span>'
|
| 41 |
-
)
|
| 42 |
if after:
|
| 43 |
-
|
| 44 |
-
return " ".join(
|
| 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=
|
| 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("교열 중입니다…
|
| 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 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|