gaeunseo commited on
Commit
09af6b3
·
verified ·
1 Parent(s): 4c05efc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -74
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.json"
11
  data_lock = threading.Lock()
12
 
13
  def initialize_global_data():
14
  """
15
- DATA_FILE이 없으면 Dataset을 불러와서 저장하고, 있으면 파일의 데이터를 반환합니다.
 
16
  """
17
  if not os.path.exists(DATA_FILE):
18
  ds = load_dataset("gaeunseo/Taskmaster_sample_data", split="train")
19
- data = list(ds)
20
- with data_lock:
21
- with open(DATA_FILE, "w", encoding="utf-8") as f:
22
- json.dump(data, f, ensure_ascii=False, indent=2)
 
 
 
 
 
23
  return data
24
  else:
25
  with data_lock:
26
- with open(DATA_FILE, "r", encoding="utf-8") as f:
27
- data = json.load(f)
28
- return data
29
 
30
  def load_global_data():
31
- """파일에서 global_data 읽어옵니다."""
32
  with data_lock:
33
- with open(DATA_FILE, "r", encoding="utf-8") as f:
34
- data = json.load(f)
35
- return data
36
 
37
- def save_global_data(data):
38
- """데이터를 파일에 저장합니다."""
39
  with data_lock:
40
- with open(DATA_FILE, "w", encoding="utf-8") as f:
41
- json.dump(data, f, ensure_ascii=False, indent=2)
42
 
43
- # 초기 global_data 파일 생성 또는 로드
44
  global_data = initialize_global_data()
45
 
46
  def get_random_row_from_dataset():
47
  """
48
- DATA_FILE에 저장된 global_data에서,
49
- - conversation_id별로 그룹화하고,
50
- -그룹의 모든 행의 used False이며,
51
- - 그룹 내에 overlapping 컬럼이 "TT"인 행이 있는 그룹만 valid로 간주합니다.
52
  valid한 그룹들 중 랜덤하게 하나의 그룹을 선택한 후,
53
- - 해당 그룹의 모든 행의 used 값을 True로 업데이트(즉, 전체 그룹을 할당)하고 파일에 저장한 뒤,
54
- - 선택된 그룹 내에서 overlapping 컬럼이 "TT"인 행을 반환합니다.
55
- 이 방식으로 여러 사용자가 동시에 호출하더라도 잠금을 통해 atomic하게 업데이트하므로,
56
- 서로 다른 사용자에게 서로 다른 conversation_id 그룹이 할당됩니다.
57
  """
58
- with data_lock:
59
- data = load_global_data()
60
- conversation_groups = {}
61
- for row in data:
62
- cid = row["conversation_id"]
63
- conversation_groups.setdefault(cid, []).append(row)
64
- valid_groups = [
65
- group for group in conversation_groups.values()
66
- if all(not r["used"] for r in group) and any(r["overlapping"] == "TT" for r in group)
67
- ]
68
- if not valid_groups:
69
- return None
70
- chosen_group = random.choice(valid_groups)
71
- # 그룹 내의 모든 행의 used를 True로 업데이트하여 전체 그룹이 할당되도록 함
72
- for row in chosen_group:
73
- row["used"] = True
74
- save_global_data(data)
75
- # 선택된 그룹 내에서 overlapping 컬럼이 "TT"인 행을 반환 (여러 개라면 첫 번째)
76
- for row in chosen_group:
77
- if row["overlapping"] == "TT":
78
- return row
79
 
80
- # --- 초기 대화 설정 ---
81
- # Dataset의 text 컬럼은 "[turn]"을 기준으로 대화가 구분되어 있다고 가정합니다.
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 말풍선과 오른쪽 🧑 이모티콘이 보이도록 초기 HTML 반환
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
- bubble_content += f"<span data-index='{i}'>{ch}</span>"
125
- current_html = wrapper_start + bubble_start + bubble_content + bubble_end + emoji_html + wrapper_end
126
- yield current_html
127
- time.sleep(0.05)
128
 
129
  def submit_edit(edited_text):
130
  """
131
  Submit 버튼 클릭 시 호출되는 함수.
132
  1. 편집된 human 메시지(✂️ 앞부분)를 새 행으로 global_data에 추가합니다.
133
- 2. get_random_row_from_dataset()을 통해 새로운 대화를 가져오고,
134
- 전역 변수 human_message와 ai_message를 업데이트합니다.
135
- 3. 초기 상태의 human 말풍선와 ai 말풍선 HTML을 반환하여 인터페이스를 리셋합니다.
136
  """
137
  global human_message, ai_message
138
- # 행 생성 (새 conversation_id는 임의 생성)
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
- # 파일에서 global_data 불러와서 새 행 추가 및 저장
148
- data = load_global_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
- # --- Gradio 인터페이스 구성 ---
 
 
 
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
- # 숨김 텍스트박스: 편집된 텍스트(✂️ 앞부분)를 저장 (visible=False)
291
  edited_text_input = gr.Textbox(visible=False, elem_id="edited_text_input")
292
 
293
- # 버튼 영역: Start Typing과 Submit 버튼을 같은 행에 배치 (Download 버튼 제거)
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()