Interface_1 / app.py
gaeunseo's picture
Update app.py
4c05efc verified
raw
history blame
11.9 kB
import time
import gradio as gr
import random
import json
import os
import threading
from datasets import load_dataset
# --- ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ์„ค์ • ---
DATA_FILE = "global_data.json"
data_lock = threading.Lock()
def initialize_global_data():
"""
DATA_FILE์ด ์—†์œผ๋ฉด Dataset์„ ๋ถˆ๋Ÿฌ์™€์„œ ์ €์žฅํ•˜๊ณ , ์žˆ์œผ๋ฉด ํŒŒ์ผ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
"""
if not os.path.exists(DATA_FILE):
ds = load_dataset("gaeunseo/Taskmaster_sample_data", split="train")
data = list(ds)
with data_lock:
with open(DATA_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return data
else:
with data_lock:
with open(DATA_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
return data
def load_global_data():
"""ํŒŒ์ผ์—์„œ global_data๋ฅผ ์ฝ์–ด์˜ต๋‹ˆ๋‹ค."""
with data_lock:
with open(DATA_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
return data
def save_global_data(data):
"""๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์ผ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค."""
with data_lock:
with open(DATA_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# ์ดˆ๊ธฐ global_data ํŒŒ์ผ ์ƒ์„ฑ ๋˜๋Š” ๋กœ๋“œ
global_data = initialize_global_data()
def get_random_row_from_dataset():
"""
DATA_FILE์— ์ €์žฅ๋œ global_data์—์„œ,
- conversation_id๋ณ„๋กœ ๊ทธ๋ฃนํ™”ํ•˜๊ณ ,
- ๊ฐ ๊ทธ๋ฃน์˜ ๋ชจ๋“  ํ–‰์˜ used๊ฐ€ False์ด๋ฉฐ,
- ๊ทธ๋ฃน ๋‚ด์— overlapping ์ปฌ๋Ÿผ์ด "TT"์ธ ํ–‰์ด ์žˆ๋Š” ๊ทธ๋ฃน๋งŒ valid๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค.
validํ•œ ๊ทธ๋ฃน๋“ค ์ค‘ ๋žœ๋คํ•˜๊ฒŒ ํ•˜๋‚˜์˜ ๊ทธ๋ฃน์„ ์„ ํƒํ•œ ํ›„,
- ํ•ด๋‹น ๊ทธ๋ฃน์˜ ๋ชจ๋“  ํ–‰์˜ used ๊ฐ’์„ True๋กœ ์—…๋ฐ์ดํŠธ(์ฆ‰, ์ „์ฒด ๊ทธ๋ฃน์„ ํ• ๋‹น)ํ•˜๊ณ  ํŒŒ์ผ์— ์ €์žฅํ•œ ๋’ค,
- ์„ ํƒ๋œ ๊ทธ๋ฃน ๋‚ด์—์„œ overlapping ์ปฌ๋Ÿผ์ด "TT"์ธ ํ–‰์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
์ด ๋ฐฉ์‹์œผ๋กœ ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ํ˜ธ์ถœํ•˜๋”๋ผ๋„ ์ž ๊ธˆ์„ ํ†ตํ•ด atomicํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธํ•˜๋ฏ€๋กœ,
์„œ๋กœ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์—๊ฒŒ ์„œ๋กœ ๋‹ค๋ฅธ conversation_id ๊ทธ๋ฃน์ด ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค.
"""
with data_lock:
data = load_global_data()
conversation_groups = {}
for row in data:
cid = row["conversation_id"]
conversation_groups.setdefault(cid, []).append(row)
valid_groups = [
group for group in conversation_groups.values()
if all(not r["used"] for r in group) and any(r["overlapping"] == "TT" for r in group)
]
if not valid_groups:
return None
chosen_group = random.choice(valid_groups)
# ๊ทธ๋ฃน ๋‚ด์˜ ๋ชจ๋“  ํ–‰์˜ used๋ฅผ True๋กœ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ์ „์ฒด ๊ทธ๋ฃน์ด ํ• ๋‹น๋˜๋„๋ก ํ•จ
for row in chosen_group:
row["used"] = True
save_global_data(data)
# ์„ ํƒ๋œ ๊ทธ๋ฃน ๋‚ด์—์„œ overlapping ์ปฌ๋Ÿผ์ด "TT"์ธ ํ–‰์„ ๋ฐ˜ํ™˜ (์—ฌ๋Ÿฌ ๊ฐœ๋ผ๋ฉด ์ฒซ ๋ฒˆ์งธ)
for row in chosen_group:
if row["overlapping"] == "TT":
return row
# --- ์ดˆ๊ธฐ ๋Œ€ํ™” ์„ค์ • ---
# Dataset์˜ text ์ปฌ๋Ÿผ์€ "[turn]"์„ ๊ธฐ์ค€์œผ๋กœ ๋Œ€ํ™”๊ฐ€ ๊ตฌ๋ถ„๋˜์–ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.
row = get_random_row_from_dataset()
if row is None:
human_message = "No valid conversation available."
ai_message = "No valid conversation available."
else:
raw_text = row['text']
human_message = raw_text.split("[turn]")[0].strip()
ai_message = raw_text.split("[turn]")[1].strip()
# --- ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค ๊ด€๋ จ ํ•จ์ˆ˜ ---
def get_initial_human_html():
"""
ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ, ๋นˆ Human ๋งํ’์„ ๊ณผ ์˜ค๋ฅธ์ชฝ ๐Ÿง‘ ์ด๋ชจํ‹ฐ์ฝ˜์ด ๋ณด์ด๋„๋ก ์ดˆ๊ธฐ HTML ๋ฐ˜ํ™˜
"""
wrapper_start = (
"""<div class="human-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-end; gap: 5px; width: 100%;">"""
)
bubble_start = """<div id="human_message" class="speech-bubble human">"""
bubble_end = "</div>"
emoji_html = "<div class='emoji'>๐Ÿง‘</div>"
wrapper_end = "</div>"
return wrapper_start + bubble_start + bubble_end + emoji_html + wrapper_end
def stream_human_message():
"""
Start Typing ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ, ์ „์—ญ ๋ณ€์ˆ˜ human_message์˜ ๋‚ด์šฉ์„ ํ•œ ๊ธ€์ž์”ฉ ํƒ€์ดํ•‘ ํšจ๊ณผ๋กœ ์ถœ๋ ฅ.
์ด์ „ ์ƒํƒœ(โœ‚๏ธ ์•„์ด์ฝ˜, ํšŒ์ƒ‰ ์ฒ˜๋ฆฌ ๋“ฑ)๋Š” ๋ฆฌ์…‹๋ฉ๋‹ˆ๋‹ค.
"""
bubble_content = ""
wrapper_start = (
"""<div class="human-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-end; gap: 5px; width: 100%;">"""
)
bubble_start = """<div id="human_message" class="speech-bubble human">"""
bubble_end = "</div>"
emoji_html = "<div class='emoji'>๐Ÿง‘</div>"
wrapper_end = "</div>"
# ์ดˆ๊ธฐ ์ƒํƒœ (๋นˆ ๋งํ’์„ ๊ณผ ์ด๋ชจํ‹ฐ์ฝ˜)
yield wrapper_start + bubble_start + bubble_end + emoji_html + wrapper_end
# human_message๋ฅผ ํ•œ ๊ธ€์ž์”ฉ ์ถ”๊ฐ€ (ํƒ€์ดํ•‘ ํšจ๊ณผ)
for i, ch in enumerate(human_message):
bubble_content += f"<span data-index='{i}'>{ch}</span>"
current_html = wrapper_start + bubble_start + bubble_content + bubble_end + emoji_html + wrapper_end
yield current_html
time.sleep(0.05)
def submit_edit(edited_text):
"""
Submit ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜.
1. ํŽธ์ง‘๋œ human ๋ฉ”์‹œ์ง€(โœ‚๏ธ ์•ž๋ถ€๋ถ„)๋ฅผ ์ƒˆ ํ–‰์œผ๋กœ global_data์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
2. get_random_row_from_dataset()์„ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๋Œ€ํ™”๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ ,
์ „์—ญ ๋ณ€์ˆ˜ human_message์™€ ai_message๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
3. ์ดˆ๊ธฐ ์ƒํƒœ์˜ human ๋งํ’์„ ์™€ ai ๋งํ’์„  HTML์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ฆฌ์…‹ํ•ฉ๋‹ˆ๋‹ค.
"""
global human_message, ai_message
# ์ƒˆ ํ–‰ ์ƒ์„ฑ (์ƒˆ conversation_id๋Š” ์ž„์˜ ์ƒ์„ฑ)
new_row = {
"conversation_id": "edited_" + str(random.randint(1000, 9999)),
"used": False,
"overlapping": "",
"text": edited_text,
"human_message": edited_text,
"ai_message": ""
}
# ํŒŒ์ผ์—์„œ global_data ๋ถˆ๋Ÿฌ์™€์„œ ์ƒˆ ํ–‰ ์ถ”๊ฐ€ ๋ฐ ์ €์žฅ
data = load_global_data()
data.append(new_row)
save_global_data(data)
new_row_data = get_random_row_from_dataset()
if new_row_data is None:
human_message = "No valid conversation available."
ai_message = "No valid conversation available."
else:
raw_text = new_row_data['text']
human_message = raw_text.split("[turn]")[0].strip()
ai_message = raw_text.split("[turn]")[1].strip()
new_human_html = get_initial_human_html()
new_ai_html = f"""
<div class="ai-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-start; gap: 5px; width: 100%;">
<div class="emoji">๐Ÿค–</div>
<div id="ai_message" class="speech-bubble ai">{ai_message}</div>
</div>
"""
return new_human_html, new_ai_html
# --- Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ ---
with gr.Blocks() as demo:
# (A) ํŽ˜์ด์ง€ ์ƒ๋‹จ ์Šคํฌ๋ฆฝํŠธ: Human ๋งํ’์„  ๋‚ด์˜ ๊ฐ <span data-index="...">๋ฅผ ํด๋ฆญํ•˜๋ฉด,
# ํ•ด๋‹น ์œ„์น˜์— โœ‚๏ธ ์•„์ด์ฝ˜์ด ์‚ฝ์ž…๋˜๊ณ , ๊ทธ ์ดํ›„ ํ…์ŠคํŠธ๊ฐ€ ํšŒ์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.
gr.HTML(
"""
<script>
document.addEventListener("click", function(event) {
if (event.target && event.target.matches("div.speech-bubble.human span[data-index]")) {
var span = event.target;
var container = span.closest("div.speech-bubble.human");
var oldScissors = container.querySelectorAll("span.scissor");
oldScissors.forEach(function(s) { s.remove(); });
var spans = container.querySelectorAll("span[data-index]");
spans.forEach(function(s) { s.style.color = ''; });
var scissor = document.createElement('span');
scissor.textContent = 'โœ‚๏ธ';
scissor.classList.add("scissor");
container.insertBefore(scissor, span.nextSibling);
var cutIndex = parseInt(span.getAttribute("data-index"));
spans.forEach(function(s) {
var idx = parseInt(s.getAttribute("data-index"));
if (idx > cutIndex) {
s.style.color = "grey";
}
});
}
});
</script>
"""
)
# (B) ์ถ”๊ฐ€ ์Šคํฌ๋ฆฝํŠธ: Submit ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ, human_message div์˜ innerText์—์„œ "โœ‚๏ธ"๋ฅผ ๊ธฐ์ค€์œผ๋กœ
# ํŽธ์ง‘๋œ ํ…์ŠคํŠธ(์•ž๋ถ€๋ถ„)๋ฅผ ์ˆจ๊น€ ํ…์ŠคํŠธ๋ฐ•์Šค์— ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
gr.HTML(
"""
<script>
document.addEventListener("DOMContentLoaded", function() {
var submitBtn = document.getElementById("submit_btn");
if(submitBtn){
submitBtn.addEventListener("click", function(){
var humanDiv = document.getElementById("human_message");
if(humanDiv){
var edited_text = humanDiv.innerText.split("โœ‚๏ธ")[0];
document.getElementById("edited_text_input").value = edited_text;
}
});
}
});
</script>
"""
)
# (C) CSS ์Šคํƒ€์ผ
gr.HTML(
"""
<style>
.chat-container {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
}
.speech-bubble {
position: relative;
padding: 10px 15px;
border-radius: 15px;
max-width: 70%;
font-family: sans-serif;
font-size: 16px;
line-height: 1.4;
}
.human {
background: #d0f0d0;
margin-right: 10px;
}
.human::after {
content: "";
position: absolute;
right: -10px;
top: 10px;
border-width: 10px 0 10px 10px;
border-style: solid;
border-color: transparent transparent transparent #d0f0d0;
}
.ai {
background: #e0e0e0;
margin-left: 10px;
}
.ai::after {
content: "";
position: absolute;
left: -10px;
top: 10px;
border-width: 10px 10px 10px 0;
border-style: solid;
border-color: transparent #e0e0e0 transparent transparent;
}
.emoji {
font-size: 24px;
line-height: 1;
}
</style>
"""
)
gr.Markdown("## Chat Interface")
with gr.Column(elem_classes="chat-container"):
# Human ๋งํ’์„  (์ดˆ๊ธฐ: ๋นˆ ๋ฉ”์‹œ์ง€์™€ ๐Ÿง‘ ์ด๋ชจํ‹ฐ์ฝ˜)
human_bubble = gr.HTML(get_initial_human_html())
# AI ๋งํ’์„  (์™ผ์ชฝ: ๐Ÿค– ์ด๋ชจํ‹ฐ์ฝ˜๊ณผ ํ•จ๊ป˜ ๋ฉ”์‹œ์ง€)
ai_html = f"""
<div class="ai-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-start; gap: 5px; width: 100%;">
<div class="emoji">๐Ÿค–</div>
<div id="ai_message" class="speech-bubble ai">{ai_message}</div>
</div>
"""
ai_bubble = gr.HTML(ai_html)
# ์ˆจ๊น€ ํ…์ŠคํŠธ๋ฐ•์Šค: ํŽธ์ง‘๋œ ํ…์ŠคํŠธ(โœ‚๏ธ ์•ž๋ถ€๋ถ„)๋ฅผ ์ €์žฅ (visible=False)
edited_text_input = gr.Textbox(visible=False, elem_id="edited_text_input")
# ๋ฒ„ํŠผ ์˜์—ญ: Start Typing๊ณผ Submit ๋ฒ„ํŠผ์„ ๊ฐ™์€ ํ–‰์— ๋ฐฐ์น˜ (Download ๋ฒ„ํŠผ ์ œ๊ฑฐ)
with gr.Row():
start_button = gr.Button("Start Typing")
submit_button = gr.Button("Submit", elem_id="submit_btn")
# ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
start_button.click(fn=stream_human_message, outputs=human_bubble)
submit_button.click(fn=submit_edit, inputs=edited_text_input, outputs=[human_bubble, ai_bubble])
demo.launch()