File size: 7,665 Bytes
7b45c3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c28406d
7b45c3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import random
import datetime
import gradio as gr

from utils.filesys_utils import json_load
from utils.common import list_completed_keys, upload_label


ERROR_CATEGORIES = [
    "λŒ€μƒκ΅°(μ½”ν˜ΈνŠΈ) 해석 였λ₯˜",
    "μ§€ν‘œ/계산식 해석 였λ₯˜",
    "SQL ꡬ문/둜직 였λ₯˜",
    "κ²°κ³Όβ†’μ„€λͺ… 뢈일치",
    "기타",
]


MAX_TURNS = 15  # actual max across the dataset is 13; a little headroom

SESSIONS = json_load("data/sessions.json")
SESSION_BY_KEY = {f"{s['pid']}_{s['sid']}": s for s in SESSIONS}

# In-memory only, shared across all concurrent users of this single process.
# Best-effort de-duplication of who's currently working on what; the durable
# "is this session actually done" truth lives in the GitHub labels/ dir.
CLAIMED: set = set()


def render_dialogue_md(sess: dict) -> str:
    role_label = {"user": "πŸ§‘ μ‚¬μš©μž", "assistant": "πŸ€– μ—μ΄μ „νŠΈ"}
    judgable_set = set(sess["judgable_turns"])
    lines = [f"### μ°Έκ°€μž `{sess['pid']}` / μ„Έμ…˜ `{sess['sid']}` (μ „λž΅: {sess['strategy']})", ""]
    for i, t in enumerate(sess["dialogue"]):
        marker = " **β¬… 라벨링 λŒ€μƒ (μ•„λž˜μ—μ„œ νŒλ‹¨)**" if i in judgable_set else ""
        lines.append(f"**#{i} [{t['type']}] {role_label[t['role']]}**{marker}")
        lines.append(t["content"])
        lines.append("")
    return "\n".join(lines)


def render_turn_detail_md(sess: dict, idx: int) -> str:
    t = sess["dialogue"][idx]
    lines = [f"#### νŒλ‹¨ λŒ€μƒ: ν„΄ #{idx} ({t['type']})"]
    for j, s in enumerate(t.get("sql", [])):
        lines.append(f"**쿼리 {j + 1}:**")
        lines.append(f"```sql\n{s['query']}\n```")
        lines.append(f"**μ‹€ν–‰ κ²°κ³Ό:** `{s['result']}`")
    return "\n".join(lines)


def _reset_turn_updates():
    """One (visible=False, empty text, cleared radio, cleared checkboxes,
    cleared textbox) group per pre-allocated turn slot."""
    updates = []
    for _ in range(MAX_TURNS):
        updates += [gr.update(visible=False), "", gr.update(value=None), gr.update(value=[]), gr.update(value="")]
    return updates


def get_new_session():
    completed = list_completed_keys()
    available = [k for k in SESSION_BY_KEY if k not in completed and k not in CLAIMED]

    if not available:
        return (
            [None, ""]
            + _reset_turn_updates()
            + [gr.update(visible=False), "πŸŽ‰ λͺ¨λ“  μ„Έμ…˜μ΄ λΌλ²¨λ§λ˜μ—ˆμŠ΅λ‹ˆλ‹€! κ°μ‚¬ν•©λ‹ˆλ‹€."]
        )

    key = random.choice(available)
    CLAIMED.add(key)
    sess = SESSION_BY_KEY[key]
    judgable = sess["judgable_turns"]

    updates = []
    for i in range(MAX_TURNS):
        if i < len(judgable):
            updates += [
                gr.update(visible=True),
                render_turn_detail_md(sess, judgable[i]),
                gr.update(value=None),
                gr.update(value=[]),
                gr.update(value=""),
            ]
        else:
            updates += [gr.update(visible=False), "", gr.update(value=None), gr.update(value=[]), gr.update(value="")]

    status = f"μ„Έμ…˜ `{key}` 배정됨 -- {len(judgable)}개 턴을 λΌλ²¨λ§ν•΄μ£Όμ„Έμš”."
    return [key, render_dialogue_md(sess)] + updates + [gr.update(visible=True), status]


def submit_labels(key, *flat):
    # session_key_state is a gr.State -- it needs its actual (unchanged) value
    # passed through, not a bare gr.update(), which is only meaningful for
    # ordinary display/interactive components.
    def unchanged(msg):
        return [key, gr.update()] + [gr.update()] * (MAX_TURNS * 5) + [gr.update(), msg]

    if not key:
        return unchanged("λ¨Όμ € 'μƒˆ μ„Έμ…˜ λ°›κΈ°'λ₯Ό λˆŒλŸ¬μ£Όμ„Έμš”.")

    sess = SESSION_BY_KEY.get(key)
    if sess is None:
        return unchanged("μ„Έμ…˜ 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")

    judgable = sess["judgable_turns"]
    n = len(judgable)
    verdicts = list(flat[0::3])[:n]
    cats_list = list(flat[1::3])[:n]
    reasons = list(flat[2::3])[:n]

    missing = [i + 1 for i, v in enumerate(verdicts) if not v]
    if missing:
        msg = f"❌ 아직 νŒλ‹¨ν•˜μ§€ μ•Šμ€ ν•­λͺ©μ΄ μžˆμŠ΅λ‹ˆλ‹€: #{', '.join(map(str, missing))}. λͺ¨λ‘ μ²΄ν¬ν•΄μ£Όμ„Έμš”."
        return unchanged(msg)

    turn_labels = [
        {"turn_index": idx, "verdict": v, "error_categories": c or [], "reason": r or ""}
        for idx, v, c, r in zip(judgable, verdicts, cats_list, reasons)
    ]
    payload = {
        "pid": sess["pid"],
        "sid": sess["sid"],
        "strategy": sess.get("strategy"),
        "source": sess.get("source"),
        "labeled_at": datetime.datetime.now().isoformat(),
        "turns": turn_labels,
    }

    ok, msg = upload_label(key, payload)
    if not ok:
        return unchanged(msg)

    CLAIMED.discard(key)
    # auto-advance to the next session on success
    next_state = get_new_session()
    next_state[-1] = f"{msg} λ‹€μŒ μ„Έμ…˜μ„ λ°°μ •ν–ˆμŠ΅λ‹ˆλ‹€.\n\n{next_state[-1]}"
    return next_state


css = """
.dialog-box {
    max-height: 500px;
    overflow-y: auto;
    border: 1px solid var(--border-color-primary);
    border-radius: 8px;
    padding: 12px;
}
"""

with gr.Blocks(title="SQL Result Evaluation", css=css) as demo:
    gr.Markdown("# 🩺 SQL κ²°κ³Ό 적합성 라벨링")
    gr.Markdown(
        "μ•„λž˜ 전체 λŒ€ν™”λ₯Ό λκΉŒμ§€ 읽고, **'라벨링 λŒ€μƒ'**으둜 ν‘œμ‹œλœ 각 μ—μ΄μ „νŠΈ 응닡이 "
        "μ‹€μ œλ‘œ μ‹€ν–‰λœ SQLκ³Ό κ·Έ κ²°κ³Όλ₯Ό λ°”νƒ•μœΌλ‘œ μ‚¬μš©μžμ˜ μ΅œμ’… μ˜λ„"
        "(쀑간에 λͺ…ν™•ν™”(clarification)κ°€ μžˆμ—ˆλ‹€λ©΄ κ·Έκ²ƒκΉŒμ§€ λ°˜μ˜ν•œ μ˜λ„)에 λ§žλŠ” 닡을 ν–ˆλŠ”μ§€ νŒλ‹¨ν•΄μ£Όμ„Έμš”."
    )
    gr.Markdown(
        "* ν•œ μ„Έμ…˜μ—λŠ” 라벨링 λŒ€μƒμ΄ μ—¬λŸ¬ 개 μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€ -- μ„Έμ…˜μ˜ λͺ¨λ“  λŒ€μƒμ„ μ±„μ›Œμ•Ό μ œμΆœλ©λ‹ˆλ‹€.\n"
        "* μ œμΆœν•˜λ©΄ κ·Έ μ„Έμ…˜μ€ μ™„λ£Œ μ²˜λ¦¬λ˜μ–΄ λ‹€λ₯Έ μ‚¬λžŒμ—κ²Œ λ‹€μ‹œ λ°°μ •λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.\n"
        "* 'ν‹€λ¦Ό'을 μ„ νƒν•œ 경우 원인 μΉ΄ν…Œκ³ λ¦¬λ₯Ό μ²΄ν¬ν•˜κ³ , κ°€λŠ₯ν•˜λ©΄ 자유 μ„œμˆ λ‘œ μ΄μœ λ„ λ‚¨κ²¨μ£Όμ„Έμš”."
    )

    session_key_state = gr.State(None)
    btn_new = gr.Button("🎲 μƒˆ μ„Έμ…˜ λ°›κΈ°")
    status_msg = gr.Markdown("")
    dialogue_md = gr.Markdown(elem_classes="dialog-box")

    turn_groups, detail_mds, verdict_radios, category_checks, reason_boxes = [], [], [], [], []
    for i in range(MAX_TURNS):
        with gr.Group(visible=False) as grp:
            detail = gr.Markdown()
            verdict = gr.Radio(["맞음", "ν‹€λ¦Ό"], label=f"νŒλ‹¨ #{i + 1}: 이 응닡은 μ‚¬μš©μžμ˜ μ΅œμ’… μ˜λ„μ— λ§žλ‚˜μš”?")
            cats = gr.CheckboxGroup(ERROR_CATEGORIES, label="였λ₯˜ μœ ν˜• (틀렸을 λ•Œλ§Œ ν•΄λ‹Ήν•˜λŠ” ν•­λͺ© 체크)")
            reason = gr.Textbox(label="자유 μ„œμˆ  이유", lines=2)
        turn_groups.append(grp)
        detail_mds.append(detail)
        verdict_radios.append(verdict)
        category_checks.append(cats)
        reason_boxes.append(reason)

    with gr.Row(visible=False) as submit_row:
        submit_btn = gr.Button("πŸ“€ μ œμΆœν•˜κΈ°")

    all_outputs = [session_key_state, dialogue_md]
    for grp, detail, verdict, cats, reason in zip(turn_groups, detail_mds, verdict_radios, category_checks, reason_boxes):
        all_outputs += [grp, detail, verdict, cats, reason]
    all_outputs += [submit_row, status_msg]

    btn_new.click(fn=get_new_session, inputs=[], outputs=all_outputs)

    submit_inputs = [session_key_state]
    for verdict, cats, reason in zip(verdict_radios, category_checks, reason_boxes):
        submit_inputs += [verdict, cats, reason]
    submit_btn.click(fn=submit_labels, inputs=submit_inputs, outputs=all_outputs)

if __name__ == "__main__":
    demo.launch()