gaeunseo commited on
Commit
bee660a
·
verified ·
1 Parent(s): 55b371e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -66
app.py CHANGED
@@ -1,63 +1,100 @@
1
  import time
2
  import gradio as gr
3
  import random
 
 
 
4
  from datasets import load_dataset
5
 
6
- # 1. 데이터셋 로드 전역 변수로 저장 (메모리 내에서 업데이트)
7
- global_data = list(load_dataset("gaeunseo/Taskmaster_sample_data", split="train"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  def get_random_row_from_dataset():
10
  """
11
- Hugging Face Dataset의 train split에서
12
- conversation_id별로 그룹화한 후,
13
- 모든 행의 used 컬럼이 False이고, 그룹 내에 overlapping 컬럼이 "TT"행이 존재하는 그룹들 중
 
14
  랜덤하게 하나의 그룹을 선택하고, 해당 그룹 내에서 overlapping 컬럼이 "TT"인 행을 선택하여 반환합니다.
15
- 반환 해당 행의 used 값을 True로 업데이트합니다.
16
  """
17
  global global_data
18
- # conversation_id별로 그룹화
 
19
  conversation_groups = {}
20
  for row in global_data:
21
  cid = row["conversation_id"]
22
  conversation_groups.setdefault(cid, []).append(row)
23
-
24
- # 모든 행의 used 컬럼이 False이며, 그룹 내에 overlapping 컬럼이 "TT"인 행이 있는 그룹만 필터링
25
  valid_groups = [
26
  group for group in conversation_groups.values()
27
  if all(not r["used"] for r in group) and any(r["overlapping"] == "TT" for r in group)
28
  ]
29
-
30
  if not valid_groups:
31
- return None # 유효한 대화 그룹이 없으면 None 반환
32
-
33
- # 유효한 그룹 중 랜덤하게 하나 선택
34
  chosen_group = random.choice(valid_groups)
35
- # 선택된 그룹 내에서 overlapping 컬럼이 "TT"인 행을 선택 (여러 개라면 첫 번째)
36
  for row in chosen_group:
37
  if row["overlapping"] == "TT":
38
- row["used"] = True # 해당 행을 사용함으로 업데이트
39
- return row
 
 
 
40
 
41
- # 2. 초기 대화 가져오기 (human_message, ai_message)
42
- # Dataset의 text 컬럼은 "[turn]"을 기준으로 대화가 구분되어 있다고 가정합니다.
43
  row = get_random_row_from_dataset()
44
  if row is None:
45
  human_message = "No valid conversation available."
46
  ai_message = "No valid conversation available."
47
- conversation_id = "none"
48
  else:
49
  raw_text = row['text']
 
50
  human_message = raw_text.split("[turn]")[0].strip()
51
  ai_message = raw_text.split("[turn]")[1].strip()
52
- conversation_id = row['conversation_id']
53
 
54
  #############################################
55
- # 인터페이스 관련 함수 (말풍선, 타이핑 효과, 클릭 편집)
56
  #############################################
57
 
58
  def get_initial_human_html():
59
  """
60
- 페이지 로드 시, 빈 Human 말풍선과 오른쪽 🧑 이모티콘을 포함한 초기 HTML 반환
61
  """
62
  wrapper_start = (
63
  """<div class="human-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-end; gap: 5px; width: 100%;">"""
@@ -82,35 +119,37 @@ def stream_human_message():
82
  emoji_html = "<div class='emoji'>🧑</div>"
83
  wrapper_end = "</div>"
84
 
85
- # 초기 상태 (빈 말풍선과 이모티콘)
86
  yield wrapper_start + bubble_start + bubble_end + emoji_html + wrapper_end
87
 
88
- # human_message를 한 글자씩 추가 (타이핑 효과)
89
  for i, ch in enumerate(human_message):
90
- bubble_content += f"<span data-index='{i}'>{ch}</span>"
91
- current_html = wrapper_start + bubble_start + bubble_content + bubble_end + emoji_html + wrapper_end
92
- yield current_html
93
- time.sleep(0.05)
94
 
95
  def submit_edit(edited_text):
96
  """
97
  Submit 버튼 클릭 시 호출되는 함수.
98
  1. 편집된 human 메시지(✂️ 앞부분)를 새 행으로 global_data에 추가합니다.
99
- 2. get_random_row_from_dataset() 함수를 통해 새로운 대화(row)가져오고,
100
- 전역 변수 human_message와 ai_message를 업데이트합니다.
101
- 3. 초기 상태의 human 말풍선 HTML과 ai 말풍선 HTML을 반환하여 인터페이스를 리셋합니다.
102
  """
103
- global global_data, human_message, ai_message, conversation_id
104
- # 새 행 생성 (새로운 conversation_id는 임의 생성)
105
  new_row = {
106
- "conversation_id": conversation_id,
107
- "overlapping": "I1",
108
- "text": f"{edited_text}[turn]\n{ai_message}",
109
  "used": False,
 
 
 
 
110
  }
 
111
  global_data.append(new_row)
 
112
 
113
- # 새로운 대화 행을 가져옴 (이전에 사용되지 않은 대화)
114
  new_row_data = get_random_row_from_dataset()
115
  if new_row_data is None:
116
  human_message = "No valid conversation available."
@@ -120,7 +159,6 @@ def submit_edit(edited_text):
120
  human_message = raw_text.split("[turn]")[0].strip()
121
  ai_message = raw_text.split("[turn]")[1].strip()
122
 
123
- # 새로 업데이트된 말풍선 HTML 반환 (human은 빈 상태, ai는 새 메시지)
124
  new_human_html = get_initial_human_html()
125
  new_ai_html = f"""
126
  <div class="ai-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-start; gap: 5px; width: 100%;">
@@ -130,34 +168,34 @@ def submit_edit(edited_text):
130
  """
131
  return new_human_html, new_ai_html
132
 
 
 
 
 
 
 
133
  #############################################
134
  # Gradio 인터페이스 구성
135
  #############################################
136
 
137
  with gr.Blocks() as demo:
138
- # (A) 페이지 상단에 로드될 스크립트:
139
- # - Human 말풍선 내의 <span data-index="...">를 클릭하면 ✂️ 이모티콘이 해당 위치에 삽입되고,
140
- # 클릭한 위치 이후의 텍스트 색상이 회색으로 변경됩니다.
141
  gr.HTML(
142
  """
143
  <script>
144
  document.addEventListener("click", function(event) {
145
- // Human 말풍선 내의 <span data-index="..."> 클릭 시 처리
146
  if (event.target && event.target.matches("div.speech-bubble.human span[data-index]")) {
147
  var span = event.target;
148
  var container = span.closest("div.speech-bubble.human");
149
- // 기존의 ✂️ 이모티콘(클래스 "scissor") 제거
150
  var oldScissors = container.querySelectorAll("span.scissor");
151
  oldScissors.forEach(function(s) { s.remove(); });
152
- // 모든 span 색상 초기화
153
  var spans = container.querySelectorAll("span[data-index]");
154
  spans.forEach(function(s) { s.style.color = ''; });
155
- // 클릭한 span 바로 뒤에 ✂️ 아이콘 삽입
156
  var scissor = document.createElement('span');
157
  scissor.textContent = '✂️';
158
  scissor.classList.add("scissor");
159
  container.insertBefore(scissor, span.nextSibling);
160
- // 클릭한 span 이후의 모든 span 텍스트 색상을 회색으로 변경
161
  var cutIndex = parseInt(span.getAttribute("data-index"));
162
  spans.forEach(function(s) {
163
  var idx = parseInt(s.getAttribute("data-index"));
@@ -171,20 +209,16 @@ with gr.Blocks() as demo:
171
  """
172
  )
173
 
174
- # (B) 추가 스크립트:
175
- # - Submit 버튼 클릭 시, human_message div의 innerText에서 "✂️"를 기준으로 편집된 텍스트(앞부분)를
176
- # 숨김 텍스트박스(edited_text_input)에 업데이트합니다.
177
  gr.HTML(
178
  """
179
  <script>
180
- // DOMContentLoaded 이벤트 이후에 버튼 요소가 준비되었을 때 이벤트 리스너 등록
181
  document.addEventListener("DOMContentLoaded", function() {
182
  var submitBtn = document.getElementById("submit_btn");
183
  if(submitBtn){
184
  submitBtn.addEventListener("click", function(){
185
  var humanDiv = document.getElementById("human_message");
186
  if(humanDiv){
187
- // innerText에서 "✂️"를 기준으로 앞부분 추출
188
  var edited_text = humanDiv.innerText.split("✂️")[0];
189
  document.getElementById("edited_text_input").value = edited_text;
190
  }
@@ -199,14 +233,12 @@ with gr.Blocks() as demo:
199
  gr.HTML(
200
  """
201
  <style>
202
- /* 전체 채팅 영역 */
203
  .chat-container {
204
  display: flex;
205
  flex-direction: column;
206
  gap: 10px;
207
  width: 100%;
208
  }
209
- /* 공통 말풍선 스타일 */
210
  .speech-bubble {
211
  position: relative;
212
  padding: 10px 15px;
@@ -216,7 +248,6 @@ with gr.Blocks() as demo:
216
  font-size: 16px;
217
  line-height: 1.4;
218
  }
219
- /* Human 말풍선 (오른쪽 정렬, 초록 배경) */
220
  .human {
221
  background: #d0f0d0;
222
  margin-right: 10px;
@@ -230,7 +261,6 @@ with gr.Blocks() as demo:
230
  border-style: solid;
231
  border-color: transparent transparent transparent #d0f0d0;
232
  }
233
- /* AI 말풍선 (왼쪽 정렬, 회색 배경) */
234
  .ai {
235
  background: #e0e0e0;
236
  margin-left: 10px;
@@ -244,7 +274,6 @@ with gr.Blocks() as demo:
244
  border-style: solid;
245
  border-color: transparent #e0e0e0 transparent transparent;
246
  }
247
- /* 이모티콘 스타일 */
248
  .emoji {
249
  font-size: 24px;
250
  line-height: 1;
@@ -256,9 +285,9 @@ with gr.Blocks() as demo:
256
  gr.Markdown("## Chat Interface")
257
 
258
  with gr.Column(elem_classes="chat-container"):
259
- # Human 말풍선 (초기: 빈 메시지와 🧑 이모티콘)
260
  human_bubble = gr.HTML(get_initial_human_html())
261
- # AI 말풍선 (왼쪽에 🤖 이모티콘과 함께 고정 메시지)
262
  ai_html = f"""
263
  <div class="ai-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-start; gap: 5px; width: 100%;">
264
  <div class="emoji">🤖</div>
@@ -267,20 +296,18 @@ with gr.Blocks() as demo:
267
  """
268
  ai_bubble = gr.HTML(ai_html)
269
 
270
- # (D) 숨김 텍스트박스: 편집된 텍스트(✂️ 앞부분)를 저장 (visible=False)
271
  edited_text_input = gr.Textbox(visible=False, elem_id="edited_text_input")
272
 
273
- # (E) 버튼 영역: Start Typing Submit 버튼을 같은 행에 배치
274
  with gr.Row():
275
  start_button = gr.Button("Start Typing")
276
- # Submit 버튼에 elem_id "submit_btn"을 부여하여 JS에서 참조합니다.
277
  submit_button = gr.Button("Submit", elem_id="submit_btn")
 
278
 
279
- # Start Typing 버튼: human_message 스트리밍 시작 (타이핑 효과)
280
  start_button.click(fn=stream_human_message, outputs=human_bubble)
281
-
282
- # Submit 버튼: hidden edited_text_input 값을 입력받아 submit_edit 함수 호출 → 새로운 대화로 업데이트
283
  submit_button.click(fn=submit_edit, inputs=edited_text_input, outputs=[human_bubble, ai_bubble])
284
-
 
285
  demo.launch()
286
-
 
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
+ # 파일 경로와 동시 접근을 위한 Lock 선언
10
+ DATA_FILE = "global_data.json"
11
+ data_lock = threading.Lock()
12
+
13
+ def initialize_global_data():
14
+ """
15
+ DATA_FILE이 존재하지 않으면, Dataset을 로드하여 파일에 저장합니다.
16
+ 이미 파일이 있으면 파일에서 데이터를 읽어 반환합니다.
17
+ """
18
+ if not os.path.exists(DATA_FILE):
19
+ ds = load_dataset("gaeunseo/Taskmaster_sample_data", split="train")
20
+ data = list(ds)
21
+ with data_lock:
22
+ with open(DATA_FILE, "w", encoding="utf-8") as f:
23
+ json.dump(data, f, ensure_ascii=False, indent=2)
24
+ return data
25
+ else:
26
+ with data_lock:
27
+ with open(DATA_FILE, "r", encoding="utf-8") as f:
28
+ data = json.load(f)
29
+ return data
30
+
31
+ def load_global_data():
32
+ """파일에서 global_data를 읽어옵니다."""
33
+ with data_lock:
34
+ with open(DATA_FILE, "r", encoding="utf-8") as f:
35
+ data = json.load(f)
36
+ return data
37
+
38
+ def save_global_data(data):
39
+ """데이터를 파일에 저장합니다."""
40
+ with data_lock:
41
+ with open(DATA_FILE, "w", encoding="utf-8") as f:
42
+ json.dump(data, f, ensure_ascii=False, indent=2)
43
+
44
+ # 파일에 저장된 global_data 초기화
45
+ global_data = initialize_global_data()
46
 
47
  def get_random_row_from_dataset():
48
  """
49
+ DATA_FILE에 저장된 global_data에서,
50
+ conversation_id별로 그룹화한 후,
51
+ - 모든 행의 used 컬럼이 False인 그룹이고,
52
+ - 그룹 내에 overlapping 컬럼이 "TT"인 행이 존재하는 그룹들 중에서
53
  랜덤하게 하나의 그룹을 선택하고, 해당 그룹 내에서 overlapping 컬럼이 "TT"인 행을 선택하여 반환합니다.
54
+ 반환 전에 해당 행의 used 값을 True로 업데이트하고 파일에 저장합니다.
55
  """
56
  global global_data
57
+ global_data = load_global_data()
58
+ # conversation_id별 그룹화
59
  conversation_groups = {}
60
  for row in global_data:
61
  cid = row["conversation_id"]
62
  conversation_groups.setdefault(cid, []).append(row)
63
+ # 조건에 맞는 그룹 필터링
 
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
+ chosen_row = None
72
  for row in chosen_group:
73
  if row["overlapping"] == "TT":
74
+ row["used"] = True # 업데이트
75
+ chosen_row = row
76
+ break
77
+ save_global_data(global_data)
78
+ return chosen_row
79
 
80
+ # 초기 대화 불러오기
 
81
  row = get_random_row_from_dataset()
82
  if row is None:
83
  human_message = "No valid conversation available."
84
  ai_message = "No valid conversation available."
 
85
  else:
86
  raw_text = row['text']
87
+ # text 컬럼은 "[turn]"을 기준으로 대화가 구분되어 있다고 가정합니다.
88
  human_message = raw_text.split("[turn]")[0].strip()
89
  ai_message = raw_text.split("[turn]")[1].strip()
 
90
 
91
  #############################################
92
+ # 채팅 인터페이스 관련 함수 (말풍선, 타이핑 효과, 편집 기능)
93
  #############################################
94
 
95
  def get_initial_human_html():
96
  """
97
+ 페이지 로드 시, 빈 Human 말풍선과 오른쪽 🧑 이모티콘을 포함한 초기 HTML 반환
98
  """
99
  wrapper_start = (
100
  """<div class="human-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-end; gap: 5px; width: 100%;">"""
 
119
  emoji_html = "<div class='emoji'>🧑</div>"
120
  wrapper_end = "</div>"
121
 
122
+ # 초기 상태: 빈 말풍선과 이모티콘
123
  yield wrapper_start + bubble_start + bubble_end + emoji_html + wrapper_end
124
 
125
+ # 한 글자씩 추가 (타이핑 효과)
126
  for i, ch in enumerate(human_message):
127
+ bubble_content += f"<span data-index='{i}'>{ch}</span>"
128
+ current_html = wrapper_start + bubble_start + bubble_content + bubble_end + emoji_html + wrapper_end
129
+ yield current_html
130
+ time.sleep(0.05)
131
 
132
  def submit_edit(edited_text):
133
  """
134
  Submit 버튼 클릭 시 호출되는 함수.
135
  1. 편집된 human 메시지(✂️ 앞부분)를 새 행으로 global_data에 추가합니다.
136
+ 2. get_random_row_from_dataset() 통해 새로운 대화를 가져오고, 전역 변수 human_message와 ai_message업데이트합니다.
137
+ 3. 초기 상태의 human 말풍선와 ai 말풍선 HTML을 반환하여 인터페이스를 리셋합니다.
 
138
  """
139
+ global global_data, human_message, ai_message
140
+ # 새 행 생성 ( conversation_id는 임의로 생성)
141
  new_row = {
142
+ "conversation_id": "edited_" + str(random.randint(1000,9999)),
 
 
143
  "used": False,
144
+ "overlapping": "",
145
+ "text": edited_text,
146
+ "human_message": edited_text,
147
+ "ai_message": ""
148
  }
149
+ global_data = load_global_data()
150
  global_data.append(new_row)
151
+ save_global_data(global_data)
152
 
 
153
  new_row_data = get_random_row_from_dataset()
154
  if new_row_data is None:
155
  human_message = "No valid conversation available."
 
159
  human_message = raw_text.split("[turn]")[0].strip()
160
  ai_message = raw_text.split("[turn]")[1].strip()
161
 
 
162
  new_human_html = get_initial_human_html()
163
  new_ai_html = f"""
164
  <div class="ai-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-start; gap: 5px; width: 100%;">
 
168
  """
169
  return new_human_html, new_ai_html
170
 
171
+ def download_global_data():
172
+ """
173
+ Download Data 버튼 클릭 시, global_data.json 파일의 경로를 반환하여 다운로드할 수 있도록 합니다.
174
+ """
175
+ return DATA_FILE
176
+
177
  #############################################
178
  # Gradio 인터페이스 구성
179
  #############################################
180
 
181
  with gr.Blocks() as demo:
182
+ # (A) 페이지 상단 스크립트: Human 말풍선 내의 각 <span data-index="...">를 클릭하면,
183
+ # 해당 위치에 ✂️ 아이콘이 삽입되고, 이후 텍스트가 회색으로 변경됩니다.
 
184
  gr.HTML(
185
  """
186
  <script>
187
  document.addEventListener("click", function(event) {
 
188
  if (event.target && event.target.matches("div.speech-bubble.human span[data-index]")) {
189
  var span = event.target;
190
  var container = span.closest("div.speech-bubble.human");
 
191
  var oldScissors = container.querySelectorAll("span.scissor");
192
  oldScissors.forEach(function(s) { s.remove(); });
 
193
  var spans = container.querySelectorAll("span[data-index]");
194
  spans.forEach(function(s) { s.style.color = ''; });
 
195
  var scissor = document.createElement('span');
196
  scissor.textContent = '✂️';
197
  scissor.classList.add("scissor");
198
  container.insertBefore(scissor, span.nextSibling);
 
199
  var cutIndex = parseInt(span.getAttribute("data-index"));
200
  spans.forEach(function(s) {
201
  var idx = parseInt(s.getAttribute("data-index"));
 
209
  """
210
  )
211
 
212
+ # (B) 추가 스크립트: Submit 버튼 클릭 시, human_message div의 innerText에서 "✂️"를 기준으로 편집된 텍스트(앞부분)를 숨김 텍스트박스에 업데이트
 
 
213
  gr.HTML(
214
  """
215
  <script>
 
216
  document.addEventListener("DOMContentLoaded", function() {
217
  var submitBtn = document.getElementById("submit_btn");
218
  if(submitBtn){
219
  submitBtn.addEventListener("click", function(){
220
  var humanDiv = document.getElementById("human_message");
221
  if(humanDiv){
 
222
  var edited_text = humanDiv.innerText.split("✂️")[0];
223
  document.getElementById("edited_text_input").value = edited_text;
224
  }
 
233
  gr.HTML(
234
  """
235
  <style>
 
236
  .chat-container {
237
  display: flex;
238
  flex-direction: column;
239
  gap: 10px;
240
  width: 100%;
241
  }
 
242
  .speech-bubble {
243
  position: relative;
244
  padding: 10px 15px;
 
248
  font-size: 16px;
249
  line-height: 1.4;
250
  }
 
251
  .human {
252
  background: #d0f0d0;
253
  margin-right: 10px;
 
261
  border-style: solid;
262
  border-color: transparent transparent transparent #d0f0d0;
263
  }
 
264
  .ai {
265
  background: #e0e0e0;
266
  margin-left: 10px;
 
274
  border-style: solid;
275
  border-color: transparent #e0e0e0 transparent transparent;
276
  }
 
277
  .emoji {
278
  font-size: 24px;
279
  line-height: 1;
 
285
  gr.Markdown("## Chat Interface")
286
 
287
  with gr.Column(elem_classes="chat-container"):
288
+ # Human 말풍선 (초기: 빈 메시지 + 🧑 이모티콘)
289
  human_bubble = gr.HTML(get_initial_human_html())
290
+ # AI 말풍선 (왼쪽: 🤖 이모티콘 + 메시지)
291
  ai_html = f"""
292
  <div class="ai-wrapper" style="display: flex; align-items: flex-end; justify-content: flex-start; gap: 5px; width: 100%;">
293
  <div class="emoji">🤖</div>
 
296
  """
297
  ai_bubble = gr.HTML(ai_html)
298
 
299
+ # 숨김 텍스트박스 (편집된 텍스트 저장용)
300
  edited_text_input = gr.Textbox(visible=False, elem_id="edited_text_input")
301
 
302
+ # 버튼 영역: Start Typing, Submit, Download Data 버튼을 같은 행에 배치
303
  with gr.Row():
304
  start_button = gr.Button("Start Typing")
 
305
  submit_button = gr.Button("Submit", elem_id="submit_btn")
306
+ download_button = gr.Button("Download Data")
307
 
308
+ # 버튼 이벤트 연결
309
  start_button.click(fn=stream_human_message, outputs=human_bubble)
 
 
310
  submit_button.click(fn=submit_edit, inputs=edited_text_input, outputs=[human_bubble, ai_bubble])
311
+ download_button.click(fn=download_global_data, outputs=[], show_progress=False)
312
+
313
  demo.launch()