Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,84 +1,83 @@
|
|
| 1 |
import time
|
| 2 |
import gradio as gr
|
| 3 |
import random
|
| 4 |
-
import json
|
| 5 |
import os
|
| 6 |
import threading
|
|
|
|
| 7 |
from datasets import load_dataset
|
| 8 |
|
| 9 |
-
#
|
| 10 |
-
DATA_FILE = "global_data.
|
| 11 |
data_lock = threading.Lock()
|
| 12 |
|
| 13 |
def initialize_global_data():
|
| 14 |
"""
|
| 15 |
-
DATA_FILE이
|
|
|
|
| 16 |
"""
|
| 17 |
if not os.path.exists(DATA_FILE):
|
| 18 |
ds = load_dataset("gaeunseo/Taskmaster_sample_data", split="train")
|
| 19 |
-
data =
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
return data
|
| 24 |
else:
|
| 25 |
with data_lock:
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
return data
|
| 29 |
|
| 30 |
def load_global_data():
|
| 31 |
-
"""파일에서 global_data
|
| 32 |
with data_lock:
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
return data
|
| 36 |
|
| 37 |
-
def save_global_data(
|
| 38 |
-
"""
|
| 39 |
with data_lock:
|
| 40 |
-
|
| 41 |
-
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 42 |
|
| 43 |
-
#
|
| 44 |
global_data = initialize_global_data()
|
| 45 |
|
| 46 |
def get_random_row_from_dataset():
|
| 47 |
"""
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
- 그룹 내에 overlapping 컬럼이 "TT"인 행이 있는 그룹만 valid로 간주합니다.
|
| 52 |
valid한 그룹들 중 랜덤하게 하나의 그룹을 선택한 후,
|
| 53 |
-
- 해당 그룹의 모든 행의 used 값을 True로 업데이트(즉, 전체 그룹을 할당)하고 파일에
|
| 54 |
-
- 선택된 그룹 내에서 overlapping 컬럼이 "TT"인
|
| 55 |
-
이 방식으로 여러 사용자가 동시에 호출하더라도 잠금을 통해 atomic하게 업데이트하므로,
|
| 56 |
-
서로 다른 사용자에게 서로 다른 conversation_id 그룹이 할당됩니다.
|
| 57 |
"""
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
return row
|
| 79 |
|
| 80 |
-
# --- 초기 대화
|
| 81 |
-
#
|
| 82 |
row = get_random_row_from_dataset()
|
| 83 |
if row is None:
|
| 84 |
human_message = "No valid conversation available."
|
|
@@ -88,10 +87,13 @@ else:
|
|
| 88 |
human_message = raw_text.split("[turn]")[0].strip()
|
| 89 |
ai_message = raw_text.split("[turn]")[1].strip()
|
| 90 |
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
| 92 |
def get_initial_human_html():
|
| 93 |
"""
|
| 94 |
-
페이지 로드 시, 빈 Human 말풍선과 오른쪽 🧑
|
| 95 |
"""
|
| 96 |
wrapper_start = (
|
| 97 |
"""<div class="human-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-end; gap: 5px; width: 100%;">"""
|
|
@@ -104,7 +106,7 @@ def get_initial_human_html():
|
|
| 104 |
|
| 105 |
def stream_human_message():
|
| 106 |
"""
|
| 107 |
-
Start Typing 버튼 클릭 시, 전역 변수 human_message의 내용을 한 글자씩 타이핑 효과로
|
| 108 |
이전 상태(✂️ 아이콘, 회색 처리 등)는 리셋됩니다.
|
| 109 |
"""
|
| 110 |
bubble_content = ""
|
|
@@ -116,26 +118,25 @@ def stream_human_message():
|
|
| 116 |
emoji_html = "<div class='emoji'>🧑</div>"
|
| 117 |
wrapper_end = "</div>"
|
| 118 |
|
| 119 |
-
# 초기
|
| 120 |
yield wrapper_start + bubble_start + bubble_end + emoji_html + wrapper_end
|
| 121 |
|
| 122 |
# human_message를 한 글자씩 추가 (타이핑 효과)
|
| 123 |
for i, ch in enumerate(human_message):
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
|
| 129 |
def submit_edit(edited_text):
|
| 130 |
"""
|
| 131 |
Submit 버튼 클릭 시 호출되는 함수.
|
| 132 |
1. 편집된 human 메시지(✂️ 앞부분)를 새 행으로 global_data에 추가합니다.
|
| 133 |
-
2. get_random_row_from_dataset()을 통해 새로운 대화를 가져오고,
|
| 134 |
-
|
| 135 |
-
3. 초기 상태의 human 말풍선와 ai 말풍선 HTML을 반환하여 인터페이스를 리셋합니다.
|
| 136 |
"""
|
| 137 |
global human_message, ai_message
|
| 138 |
-
|
| 139 |
new_row = {
|
| 140 |
"conversation_id": "edited_" + str(random.randint(1000, 9999)),
|
| 141 |
"used": False,
|
|
@@ -144,9 +145,8 @@ def submit_edit(edited_text):
|
|
| 144 |
"human_message": edited_text,
|
| 145 |
"ai_message": ""
|
| 146 |
}
|
| 147 |
-
|
| 148 |
-
data =
|
| 149 |
-
data.append(new_row)
|
| 150 |
save_global_data(data)
|
| 151 |
|
| 152 |
new_row_data = get_random_row_from_dataset()
|
|
@@ -167,7 +167,10 @@ def submit_edit(edited_text):
|
|
| 167 |
"""
|
| 168 |
return new_human_html, new_ai_html
|
| 169 |
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
| 171 |
with gr.Blocks() as demo:
|
| 172 |
# (A) 페이지 상단 스크립트: Human 말풍선 내의 각 <span data-index="...">를 클릭하면,
|
| 173 |
# 해당 위치에 ✂️ 아이콘이 삽입되고, 그 이후 텍스트가 회색으로 변경됩니다.
|
|
@@ -199,8 +202,7 @@ with gr.Blocks() as demo:
|
|
| 199 |
"""
|
| 200 |
)
|
| 201 |
|
| 202 |
-
# (B) 추가 스크립트: Submit 버튼 클릭 시, human_message div의 innerText에서 "✂️"를 기준으로
|
| 203 |
-
# 편집된 텍스트(앞부분)를 숨김 텍스트박스에 업데이트합니다.
|
| 204 |
gr.HTML(
|
| 205 |
"""
|
| 206 |
<script>
|
|
@@ -276,9 +278,9 @@ with gr.Blocks() as demo:
|
|
| 276 |
gr.Markdown("## Chat Interface")
|
| 277 |
|
| 278 |
with gr.Column(elem_classes="chat-container"):
|
| 279 |
-
# Human 말풍선 (초기: 빈
|
| 280 |
human_bubble = gr.HTML(get_initial_human_html())
|
| 281 |
-
# AI 말풍선 (왼쪽: 🤖
|
| 282 |
ai_html = f"""
|
| 283 |
<div class="ai-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-start; gap: 5px; width: 100%;">
|
| 284 |
<div class="emoji">🤖</div>
|
|
@@ -287,10 +289,10 @@ with gr.Blocks() as demo:
|
|
| 287 |
"""
|
| 288 |
ai_bubble = gr.HTML(ai_html)
|
| 289 |
|
| 290 |
-
# 숨김
|
| 291 |
edited_text_input = gr.Textbox(visible=False, elem_id="edited_text_input")
|
| 292 |
|
| 293 |
-
# 버튼 영역: Start Typing과 Submit 버튼을 같은 행에 배치
|
| 294 |
with gr.Row():
|
| 295 |
start_button = gr.Button("Start Typing")
|
| 296 |
submit_button = gr.Button("Submit", elem_id="submit_btn")
|
|
@@ -299,4 +301,4 @@ with gr.Blocks() as demo:
|
|
| 299 |
start_button.click(fn=stream_human_message, outputs=human_bubble)
|
| 300 |
submit_button.click(fn=submit_edit, inputs=edited_text_input, outputs=[human_bubble, ai_bubble])
|
| 301 |
|
| 302 |
-
demo.launch()
|
|
|
|
| 1 |
import time
|
| 2 |
import gradio as gr
|
| 3 |
import random
|
|
|
|
| 4 |
import os
|
| 5 |
import threading
|
| 6 |
+
import pandas as pd
|
| 7 |
from datasets import load_dataset
|
| 8 |
|
| 9 |
+
# CSV 파일 경로와 동시 접근을 위한 Lock 선언
|
| 10 |
+
DATA_FILE = "global_data.csv"
|
| 11 |
data_lock = threading.Lock()
|
| 12 |
|
| 13 |
def initialize_global_data():
|
| 14 |
"""
|
| 15 |
+
DATA_FILE이 존재하지 않으면, gaeunseo/Taskmaster_sample_data 데이터셋을 로드하여 DataFrame으로 변환한 후 CSV 파일로 저장합니다.
|
| 16 |
+
이미 파일이 있으면 파일에서 데이터를 읽어 DataFrame을 반환합니다.
|
| 17 |
"""
|
| 18 |
if not os.path.exists(DATA_FILE):
|
| 19 |
ds = load_dataset("gaeunseo/Taskmaster_sample_data", split="train")
|
| 20 |
+
data = ds.to_pandas()
|
| 21 |
+
# 필요한 컬럼이 없으면 추가합니다.
|
| 22 |
+
if "used" not in data.columns:
|
| 23 |
+
data["used"] = False
|
| 24 |
+
if "overlapping" not in data.columns:
|
| 25 |
+
data["overlapping"] = ""
|
| 26 |
+
if "text" not in data.columns:
|
| 27 |
+
data["text"] = ""
|
| 28 |
+
data.to_csv(DATA_FILE, index=False)
|
| 29 |
return data
|
| 30 |
else:
|
| 31 |
with data_lock:
|
| 32 |
+
df = pd.read_csv(DATA_FILE)
|
| 33 |
+
return df
|
|
|
|
| 34 |
|
| 35 |
def load_global_data():
|
| 36 |
+
"""CSV 파일에서 global_data DataFrame을 읽어옵니다."""
|
| 37 |
with data_lock:
|
| 38 |
+
df = pd.read_csv(DATA_FILE)
|
| 39 |
+
return df
|
|
|
|
| 40 |
|
| 41 |
+
def save_global_data(df):
|
| 42 |
+
"""DataFrame을 CSV 파일에 저장합니다."""
|
| 43 |
with data_lock:
|
| 44 |
+
df.to_csv(DATA_FILE, index=False)
|
|
|
|
| 45 |
|
| 46 |
+
# CSV 파일에 저장된 global_data 초기화
|
| 47 |
global_data = initialize_global_data()
|
| 48 |
|
| 49 |
def get_random_row_from_dataset():
|
| 50 |
"""
|
| 51 |
+
CSV 파일에 저장된 global_data에서,
|
| 52 |
+
1. conversation_id별로 그룹화하고,
|
| 53 |
+
2. 각 그룹에서 모든 행의 used 컬럼이 False이며, 그룹 내에 overlapping 컬럼이 "TT"인 행이 존재하는 그룹만 valid로 간주합니다.
|
|
|
|
| 54 |
valid한 그룹들 중 랜덤하게 하나의 그룹을 선택한 후,
|
| 55 |
+
- 해당 그룹의 모든 행의 used 값을 True로 업데이트(즉, 전체 그룹을 할당)하고 CSV 파일에 저장합니다.
|
| 56 |
+
- 선택된 그룹 내에서 overlapping 컬럼이 "TT"인 행(여러 개라면 첫 번째)을 반환합니다.
|
|
|
|
|
|
|
| 57 |
"""
|
| 58 |
+
global global_data
|
| 59 |
+
global_data = load_global_data() # 최신 데이터 로드
|
| 60 |
+
groups = global_data.groupby('conversation_id')
|
| 61 |
+
valid_groups = []
|
| 62 |
+
for cid, group in groups:
|
| 63 |
+
# 모든 행의 used 값이 False이고, 그룹 내에 overlapping 값이 "TT"인 행이 ��는 그룹 필터링
|
| 64 |
+
if group['used'].apply(lambda x: bool(x) == False).all() and (group['overlapping'] == "TT").any():
|
| 65 |
+
valid_groups.append((cid, group))
|
| 66 |
+
if not valid_groups:
|
| 67 |
+
return None
|
| 68 |
+
chosen_cid, chosen_group = random.choice(valid_groups)
|
| 69 |
+
# 전체 그룹의 used 값을 True로 업데이트
|
| 70 |
+
global_data.loc[global_data['conversation_id'] == chosen_cid, 'used'] = True
|
| 71 |
+
save_global_data(global_data)
|
| 72 |
+
# 선택된 그룹 내에서 overlapping 컬럼이 "TT"인 행을 반환 (여러 개라면 첫 번째)
|
| 73 |
+
chosen_rows = chosen_group[chosen_group['overlapping'] == "TT"]
|
| 74 |
+
if chosen_rows.empty:
|
| 75 |
+
return None
|
| 76 |
+
chosen_row = chosen_rows.iloc[0]
|
| 77 |
+
return chosen_row.to_dict()
|
|
|
|
| 78 |
|
| 79 |
+
# --- 초기 대화 불러오기 ---
|
| 80 |
+
# 데이터셋의 text 컬럼은 "[turn]"을 기준으로 대화가 구분되어 있다고 가정합니다.
|
| 81 |
row = get_random_row_from_dataset()
|
| 82 |
if row is None:
|
| 83 |
human_message = "No valid conversation available."
|
|
|
|
| 87 |
human_message = raw_text.split("[turn]")[0].strip()
|
| 88 |
ai_message = raw_text.split("[turn]")[1].strip()
|
| 89 |
|
| 90 |
+
#############################################
|
| 91 |
+
# 채팅 인터페이스 관련 함수 (말풍선, 타이핑 효과, 편집 기능)
|
| 92 |
+
#############################################
|
| 93 |
+
|
| 94 |
def get_initial_human_html():
|
| 95 |
"""
|
| 96 |
+
페이지 로드 시, 빈 Human 말풍선과 오른쪽 🧑 이모티콘을 포함한 초기 HTML 반환
|
| 97 |
"""
|
| 98 |
wrapper_start = (
|
| 99 |
"""<div class="human-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-end; gap: 5px; width: 100%;">"""
|
|
|
|
| 106 |
|
| 107 |
def stream_human_message():
|
| 108 |
"""
|
| 109 |
+
Start Typing 버튼 클릭 시, 전역 변수 human_message의 내용을 한 글자씩 타이핑 효과로 출력합니다.
|
| 110 |
이전 상태(✂️ 아이콘, 회색 처리 등)는 리셋됩니다.
|
| 111 |
"""
|
| 112 |
bubble_content = ""
|
|
|
|
| 118 |
emoji_html = "<div class='emoji'>🧑</div>"
|
| 119 |
wrapper_end = "</div>"
|
| 120 |
|
| 121 |
+
# 초기 상태: 빈 말풍선과 이모티콘
|
| 122 |
yield wrapper_start + bubble_start + bubble_end + emoji_html + wrapper_end
|
| 123 |
|
| 124 |
# human_message를 한 글자씩 추가 (타이핑 효과)
|
| 125 |
for i, ch in enumerate(human_message):
|
| 126 |
+
bubble_content += f"<span data-index='{i}'>{ch}</span>"
|
| 127 |
+
current_html = wrapper_start + bubble_start + bubble_content + bubble_end + emoji_html + wrapper_end
|
| 128 |
+
yield current_html
|
| 129 |
+
time.sleep(0.05)
|
| 130 |
|
| 131 |
def submit_edit(edited_text):
|
| 132 |
"""
|
| 133 |
Submit 버튼 클릭 시 호출되는 함수.
|
| 134 |
1. 편집된 human 메시지(✂️ 앞부분)를 새 행으로 global_data에 추가합니다.
|
| 135 |
+
2. get_random_row_from_dataset()을 통해 새로운 대화를 가져오고, 전역 변수 human_message와 ai_message를 업데이트합니다.
|
| 136 |
+
3. 초기 상태의 human 말풍선과 ai 말풍선 HTML을 반환하여 인터페이스를 리셋합니다.
|
|
|
|
| 137 |
"""
|
| 138 |
global human_message, ai_message
|
| 139 |
+
data = load_global_data()
|
| 140 |
new_row = {
|
| 141 |
"conversation_id": "edited_" + str(random.randint(1000, 9999)),
|
| 142 |
"used": False,
|
|
|
|
| 145 |
"human_message": edited_text,
|
| 146 |
"ai_message": ""
|
| 147 |
}
|
| 148 |
+
new_df = pd.DataFrame([new_row])
|
| 149 |
+
data = pd.concat([data, new_df], ignore_index=True)
|
|
|
|
| 150 |
save_global_data(data)
|
| 151 |
|
| 152 |
new_row_data = get_random_row_from_dataset()
|
|
|
|
| 167 |
"""
|
| 168 |
return new_human_html, new_ai_html
|
| 169 |
|
| 170 |
+
#############################################
|
| 171 |
+
# Gradio 인터페이스 구성
|
| 172 |
+
#############################################
|
| 173 |
+
|
| 174 |
with gr.Blocks() as demo:
|
| 175 |
# (A) 페이지 상단 스크립트: Human 말풍선 내의 각 <span data-index="...">를 클릭하면,
|
| 176 |
# 해당 위치에 ✂️ 아이콘이 삽입되고, 그 이후 텍스트가 회색으로 변경됩니다.
|
|
|
|
| 202 |
"""
|
| 203 |
)
|
| 204 |
|
| 205 |
+
# (B) 추가 스크립트: Submit 버튼 클릭 시, human_message div의 innerText에서 "✂️"를 기준으로 편집된 텍스트(앞부분)를 숨김 텍스트박스에 업데이트
|
|
|
|
| 206 |
gr.HTML(
|
| 207 |
"""
|
| 208 |
<script>
|
|
|
|
| 278 |
gr.Markdown("## Chat Interface")
|
| 279 |
|
| 280 |
with gr.Column(elem_classes="chat-container"):
|
| 281 |
+
# Human 말풍선 (초기: 빈 메시지 + 🧑 이모티콘)
|
| 282 |
human_bubble = gr.HTML(get_initial_human_html())
|
| 283 |
+
# AI 말풍선 (왼쪽: 🤖 이모티콘 + 메시지)
|
| 284 |
ai_html = f"""
|
| 285 |
<div class="ai-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-start; gap: 5px; width: 100%;">
|
| 286 |
<div class="emoji">🤖</div>
|
|
|
|
| 289 |
"""
|
| 290 |
ai_bubble = gr.HTML(ai_html)
|
| 291 |
|
| 292 |
+
# 숨김 텍스트박스 (편집된 텍스트 저장용)
|
| 293 |
edited_text_input = gr.Textbox(visible=False, elem_id="edited_text_input")
|
| 294 |
|
| 295 |
+
# 버튼 영역: Start Typing과 Submit 버튼을 같은 행에 배치
|
| 296 |
with gr.Row():
|
| 297 |
start_button = gr.Button("Start Typing")
|
| 298 |
submit_button = gr.Button("Submit", elem_id="submit_btn")
|
|
|
|
| 301 |
start_button.click(fn=stream_human_message, outputs=human_bubble)
|
| 302 |
submit_button.click(fn=submit_edit, inputs=edited_text_input, outputs=[human_bubble, ai_bubble])
|
| 303 |
|
| 304 |
+
demo.launch()
|