haepa_mac commited on
Commit
44e0e84
·
0 Parent(s):

Initial commit: 놈팽쓰 테스트 앱

Browse files
.gitignore ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables
2
+ .env
3
+
4
+ # Python cache
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+
9
+ # Distribution / packaging
10
+ dist/
11
+ build/
12
+ *.egg-info/
13
+
14
+ # Virtual environments
15
+ venv/
16
+ env/
17
+ ENV/
18
+
19
+ # Data directories (can be large)
20
+ data/personas/*
21
+ data/conversations/*
22
+ !data/personas/.gitkeep
23
+ !data/conversations/.gitkeep
24
+
25
+ # OS specific
26
+ .DS_Store
README.md ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: 놈팽쓰(MemoryTag) 테스트 앱
3
+ emoji: 🎭
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: 4.19.2
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # 놈팽쓰(MemoryTag) 테스트 앱
14
+
15
+ 사물에 영혼을 불어넣어 대화할 수 있는 페르소나 생성 테스트 앱입니다. 놈팽쓰 UX 가이드에 따라 사물의 영혼을 깨우고 대화하는 경험을 제공합니다.
16
+
17
+ ## 주요 기능
18
+
19
+ 1. **영혼 깨우기**:
20
+ - 사물 이미지를 분석하여 물리적 특성 추출
21
+ - 프론트엔드용 간단한 페르소나와 백엔드용 상세 페르소나 생성
22
+ - 127개 성격 변수 생성 (백엔드 시스템)
23
+
24
+ 2. **대화하기**:
25
+ - 생성된 페르소나와 자연스러운 대화
26
+ - 성격에 맞는 응답 생성
27
+ - 대화 내역 저장
28
+
29
+ 3. **페르소나 관리**:
30
+ - 생성된 페르소나 저장 및 로드
31
+ - 페르소나 목록 관리
32
+
33
+ ## 설치 방법
34
+
35
+ 1. 저장소를 클론합니다:
36
+ ```bash
37
+ git clone [저장소 URL]
38
+ cd nompang_test
39
+ ```
40
+
41
+ 2. 필요한 패키지를 설치합니다:
42
+ ```bash
43
+ pip install -r requirements.txt
44
+ ```
45
+
46
+ 3. `.env` 파일을 생성하고 Gemini API 키를 설정합니다:
47
+ ```
48
+ GEMINI_API_KEY=your_api_key_here
49
+ ```
50
+
51
+ ## 실행 방법
52
+
53
+ 앱을 실행하려면 다음 명령어를 사용합니다:
54
+
55
+ ```bash
56
+ python app.py
57
+ ```
58
+
59
+ 웹 브라우저에서 `http://localhost:7860`으로 접속하여 앱을 사용할 수 있습니다.
60
+
61
+ ## 사용 방법
62
+
63
+ 1. **영혼 깨우기 탭**:
64
+ - 사물 이미지를 업로드하거나 이름을 입력합니다.
65
+ - "영혼 깨우기" 버튼을 클릭하여 페르소나를 생성합니다.
66
+ - 프론트엔드 뷰와 백엔드 상세 정보를 탭으로 전환하여 확인할 수 있습니다.
67
+ - "페르소나 저장" 버튼을 클릭하여 생성된 페르소나를 저장합니다.
68
+
69
+ 2. **대화하기 탭**:
70
+ - "새 대화 시작" 버튼을 클릭하여 현재 페르소나와 대화를 시작합니다.
71
+ - 메시지를 입력하고 "전송" 버튼을 클릭하여 대화합니다.
72
+ - "대화 초기화" 버튼을 클릭하여 대화 내역을 초기화할 수 있습니다.
73
+
74
+ 3. **페르소나 관리 탭**:
75
+ - "페르소나 목록 새로고침" 버튼을 클릭하여 저장된 페르소나 목록을 갱신합니다.
76
+ - 목록에서 페르소나를 선택하고 "선택한 페르소나 불러오기" 버튼을 클릭하여 불러옵니다.
77
+ - 불러온 페르소나의 정보를 확인하고 대화하기 탭으로 이동하여 대화할 수 있습니다.
78
+
79
+ ## 시스템 구조
80
+
81
+ - **app.py**: 메인 Gradio 애플리케이션
82
+ - **modules/persona_generator.py**: 페르소나 생성 및 대화 처리
83
+ - **modules/data_manager.py**: 데이터 저장 및 로드
84
+ - **data/personas/**: 저장된 페르소나 데이터
85
+ - **data/conversations/**: 대화 내역 데이터
86
+
87
+ ## 참고 사항
88
+
89
+ - 이 앱은 Gemini API를 사용하여 페르소나를 생성하고 대화합니다.
90
+ - API 키가 설정되지 않으면 기본 페르소나로 제한된 기능을 사용할 수 있습니다.
91
+ - 이미지 분석 결과는 Gemini API의 이미지 처리 기능을 사용합니다.
92
+
93
+ ## 라이선스
94
+
95
+ MIT License
app.py ADDED
@@ -0,0 +1,564 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import time
4
+ import gradio as gr
5
+ import google.generativeai as genai
6
+ from PIL import Image
7
+ from dotenv import load_dotenv
8
+ import matplotlib.pyplot as plt
9
+ import numpy as np
10
+ import base64
11
+ import io
12
+ import uuid
13
+ from datetime import datetime
14
+
15
+ # Import modules
16
+ from modules.persona_generator import PersonaGenerator
17
+ from modules.data_manager import save_persona, load_persona, list_personas, toggle_frontend_backend_view
18
+
19
+ # Load environment variables
20
+ load_dotenv()
21
+
22
+ # Configure Gemini API
23
+ api_key = os.getenv("GEMINI_API_KEY")
24
+ if api_key:
25
+ genai.configure(api_key=api_key)
26
+
27
+ # Create data directories if they don't exist
28
+ os.makedirs("data/personas", exist_ok=True)
29
+ os.makedirs("data/conversations", exist_ok=True)
30
+
31
+ # Initialize the persona generator
32
+ persona_generator = PersonaGenerator()
33
+
34
+ # Gradio theme
35
+ theme = gr.themes.Soft(
36
+ primary_hue="indigo",
37
+ secondary_hue="blue",
38
+ )
39
+
40
+ # CSS for additional styling
41
+ css = """
42
+ .persona-details {
43
+ border: 1px solid #e0e0e0;
44
+ border-radius: 8px;
45
+ padding: 12px;
46
+ margin-top: 8px;
47
+ background-color: #f8f9fa;
48
+ }
49
+ .collapsed-section {
50
+ margin-top: 10px;
51
+ margin-bottom: 10px;
52
+ }
53
+ .personality-chart {
54
+ width: 100%;
55
+ height: auto;
56
+ margin-top: 15px;
57
+ }
58
+ .conversation-box {
59
+ height: 400px;
60
+ overflow-y: auto;
61
+ }
62
+ """
63
+
64
+ def create_frontend_view_html(persona):
65
+ """Create HTML representation of the frontend view of the persona"""
66
+ if not persona:
67
+ return "페르소나가 아직 생성되지 않았습니다."
68
+
69
+ name = persona.get("기본정보", {}).get("이름", "Unknown")
70
+ object_type = persona.get("기본정보", {}).get("유형", "Unknown")
71
+ description = persona.get("기본정보", {}).get("설명", "")
72
+
73
+ # Personality traits
74
+ traits_html = ""
75
+ for trait, value in persona.get("성격특성", {}).items():
76
+ traits_html += f"<div>{trait}: <div style='background: linear-gradient(to right, #6366f1 {value}%, #e0e0e0 {value}%); height: 12px; border-radius: 6px; margin-bottom: 8px;'></div></div>"
77
+
78
+ # Flaws
79
+ flaws = persona.get("매력적결함", [])
80
+ flaws_html = ", ".join(flaws) if flaws else "없음"
81
+
82
+ # Interests
83
+ interests = persona.get("관심사", [])
84
+ interests_html = ", ".join(interests) if interests else "없음"
85
+
86
+ # Communication style
87
+ communication_style = persona.get("소통방식", "")
88
+
89
+ html = f"""
90
+ <div class="persona-details">
91
+ <h2>{name}</h2>
92
+ <p><strong>유형:</strong> {object_type}</p>
93
+ <p><strong>설명:</strong> {description}</p>
94
+
95
+ <h3>성격 특성</h3>
96
+ {traits_html}
97
+
98
+ <h3>소통 방식</h3>
99
+ <p>{communication_style}</p>
100
+
101
+ <h3>매력적 결함</h3>
102
+ <p>{flaws_html}</p>
103
+
104
+ <h3>관심사</h3>
105
+ <p>{interests_html}</p>
106
+ </div>
107
+ """
108
+
109
+ return html
110
+
111
+ def create_backend_view_html(persona):
112
+ """Create HTML representation of the backend view of the persona"""
113
+ if not persona:
114
+ return "페르소나가 아직 생성되지 않았습니다."
115
+
116
+ name = persona.get("기본정보", {}).get("이름", "Unknown")
117
+
118
+ # Convert persona to formatted JSON
119
+ try:
120
+ persona_json = json.dumps(persona, ensure_ascii=False, indent=2)
121
+
122
+ html = f"""
123
+ <div class="persona-details">
124
+ <h2>{name} - 백엔드 상세 정보</h2>
125
+ <details>
126
+ <summary>127개 성격 변수 및 상세 정보</summary>
127
+ <pre style="background-color: #f5f5f5; padding: 12px; border-radius: 8px; overflow-x: auto;">{persona_json}</pre>
128
+ </details>
129
+ </div>
130
+ """
131
+
132
+ return html
133
+ except Exception as e:
134
+ return f"백엔드 정보 변환 오류: {str(e)}"
135
+
136
+ def generate_personality_chart(persona):
137
+ """Generate a radar chart for personality traits"""
138
+ if not persona or "성격특성" not in persona:
139
+ # Return empty image
140
+ fig, ax = plt.subplots(figsize=(6, 6))
141
+ ax.text(0.5, 0.5, "데이터 없음", ha='center', va='center')
142
+ ax.axis('off')
143
+
144
+ buf = io.BytesIO()
145
+ plt.savefig(buf, format='png')
146
+ buf.seek(0)
147
+ plt.close(fig)
148
+ return buf
149
+
150
+ # Get traits
151
+ traits = persona["성격특성"]
152
+
153
+ # Create radar chart
154
+ categories = list(traits.keys())
155
+ values = list(traits.values())
156
+
157
+ # Add the first value again to close the loop
158
+ categories.append(categories[0])
159
+ values.append(values[0])
160
+
161
+ # Convert to radians
162
+ angles = np.linspace(0, 2*np.pi, len(categories), endpoint=True)
163
+
164
+ # Create plot
165
+ fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
166
+
167
+ # Draw polygon
168
+ ax.fill(angles, values, alpha=0.25, color='#6366f1')
169
+
170
+ # Draw lines
171
+ ax.plot(angles, values, 'o-', linewidth=2, color='#6366f1')
172
+
173
+ # Fill labels
174
+ ax.set_thetagrids(angles[:-1] * 180/np.pi, categories[:-1])
175
+ ax.set_rlim(0, 100)
176
+
177
+ # Add titles
178
+ name = persona.get("기본정보", {}).get("이름", "Unknown")
179
+ plt.title(f"{name}의 성격 특성", size=15, color='#333333', y=1.1)
180
+
181
+ # Styling
182
+ ax.grid(True, linestyle='--', alpha=0.7)
183
+
184
+ # Save to buffer
185
+ buf = io.BytesIO()
186
+ plt.savefig(buf, format='png', bbox_inches='tight')
187
+ buf.seek(0)
188
+ plt.close(fig)
189
+
190
+ return buf
191
+
192
+ def soul_awakening(image, object_name=None):
193
+ """Analyze image and awaken the soul of the object"""
194
+ if image is None:
195
+ return (
196
+ None,
197
+ gr.update(visible=True, value="이미지를 먼저 업로드해주세요."),
198
+ None, None, None
199
+ )
200
+
201
+ # Step 1: Analyze image
202
+ print(f"Analyzing image: {image}")
203
+ analysis_result = persona_generator.analyze_image(image)
204
+
205
+ # Create context
206
+ user_context = {
207
+ "name": object_name if object_name else analysis_result.get("object_type", "미확인 사물")
208
+ }
209
+
210
+ # Step 2: Create frontend persona
211
+ frontend_persona = persona_generator.create_frontend_persona(analysis_result, user_context)
212
+
213
+ # Step 3: Create backend persona
214
+ backend_persona = persona_generator.create_backend_persona(frontend_persona, analysis_result)
215
+
216
+ # Save both views to a single persona file
217
+ filepath = save_persona(backend_persona)
218
+ if filepath:
219
+ backend_persona["filepath"] = filepath
220
+
221
+ # Generate HTML views
222
+ frontend_html = create_frontend_view_html(frontend_persona)
223
+ backend_html = create_backend_view_html(backend_persona)
224
+
225
+ # Generate personality chart
226
+ chart_image = generate_personality_chart(frontend_persona)
227
+
228
+ return (
229
+ analysis_result,
230
+ gr.update(visible=False, value=""),
231
+ frontend_html,
232
+ backend_html,
233
+ chart_image
234
+ )
235
+
236
+ def start_chat(current_persona):
237
+ """Start a conversation with the current persona"""
238
+ if not current_persona:
239
+ return "페르소나를 먼저 생성하거나 불러와주세요.", [], []
240
+
241
+ # Generate initial greeting
242
+ name = current_persona.get("기본정보", {}).get("이름", "Unknown")
243
+ try:
244
+ initial_message = persona_generator.chat_with_persona(
245
+ current_persona,
246
+ "안녕하세요!"
247
+ )
248
+
249
+ # Initialize conversation history
250
+ conversation = [
251
+ {"role": "assistant", "content": initial_message}
252
+ ]
253
+
254
+ # Format for chatbot
255
+ chatbot_messages = [(None, initial_message)]
256
+
257
+ return f"{name}과의 대화가 시작되었습니다.", chatbot_messages, conversation
258
+ except Exception as e:
259
+ return f"대화 시작 중 오류 발생: {str(e)}", [], []
260
+
261
+ def chat_with_persona(message, chatbot, conversation, current_persona):
262
+ """Chat with the current persona"""
263
+ if not message or not current_persona:
264
+ return chatbot, conversation
265
+
266
+ # Add user message to conversation
267
+ conversation.append({"role": "user", "content": message})
268
+
269
+ # Update chatbot display
270
+ chatbot.append((message, None))
271
+
272
+ # Get response from persona
273
+ try:
274
+ response = persona_generator.chat_with_persona(
275
+ current_persona,
276
+ message,
277
+ conversation
278
+ )
279
+
280
+ # Add response to conversation
281
+ conversation.append({"role": "assistant", "content": response})
282
+
283
+ # Update chatbot display
284
+ chatbot[-1] = (message, response)
285
+
286
+ return chatbot, conversation
287
+ except Exception as e:
288
+ error_message = f"오류: {str(e)}"
289
+ conversation.append({"role": "assistant", "content": error_message})
290
+ chatbot[-1] = (message, error_message)
291
+ return chatbot, conversation
292
+
293
+ def load_selected_persona(selected_row, personas_list):
294
+ """Load persona from the selected row in the dataframe"""
295
+ if selected_row is None or len(selected_row) == 0:
296
+ return None, "선택된 페르소나가 없습니다.", None, None, None
297
+
298
+ try:
299
+ # Get filepath from selected row
300
+ selected_index = selected_row.index[0] if hasattr(selected_row, 'index') else 0
301
+ filepath = personas_list[selected_index]["filepath"]
302
+
303
+ # Load persona
304
+ persona = load_persona(filepath)
305
+ if not persona:
306
+ return None, "페르소나 로딩에 실패했습니다.", None, None, None
307
+
308
+ # Generate HTML views
309
+ frontend_view, backend_view = toggle_frontend_backend_view(persona)
310
+ frontend_html = create_frontend_view_html(frontend_view)
311
+ backend_html = create_backend_view_html(backend_view)
312
+
313
+ # Generate personality chart
314
+ chart_image = generate_personality_chart(frontend_view)
315
+
316
+ return persona, f"{persona['기본정보']['이름']}을(를) 로드했습니다.", frontend_html, backend_html, chart_image
317
+
318
+ except Exception as e:
319
+ return None, f"페르소나 로딩 중 오류 발생: {str(e)}", None, None, None
320
+
321
+ def save_current_persona(current_persona):
322
+ """Save current persona to a JSON file"""
323
+ if not current_persona:
324
+ return "저장할 페르소나가 없습니다."
325
+
326
+ try:
327
+ filepath = save_persona(current_persona)
328
+ if filepath:
329
+ name = current_persona.get("기본정보", {}).get("이름", "Unknown")
330
+ return f"{name} 페르소나가 저장되었습니다: {filepath}"
331
+ else:
332
+ return "페르소나 저장에 실패했습니다."
333
+ except Exception as e:
334
+ return f"저장 중 오류 발생: {str(e)}"
335
+
336
+ def get_personas_list():
337
+ """Get list of personas for the dataframe"""
338
+ personas = list_personas()
339
+
340
+ # Convert to dataframe format
341
+ df_data = []
342
+ for i, persona in enumerate(personas):
343
+ df_data.append([
344
+ persona["name"],
345
+ persona["type"],
346
+ persona["created_at"],
347
+ persona["filename"]
348
+ ])
349
+
350
+ return df_data, personas
351
+
352
+ # Main Gradio app
353
+ with gr.Blocks(title="놈팽쓰 테스트 앱", theme=theme, css=css) as app:
354
+ # Global state
355
+ current_persona = gr.State(None)
356
+ conversation_history = gr.State([])
357
+ analysis_result_state = gr.State(None)
358
+ personas_data = gr.State([])
359
+
360
+ gr.Markdown(
361
+ """
362
+ # 🎭 놈팽쓰(MemoryTag) 테스트 앱
363
+
364
+ 사물에 영혼을 불어넣어 대화할 수 있는 페르소나 생성 테스트 앱입니다.
365
+
366
+ ## 사용 방법
367
+ 1. **영혼 깨우기** 탭에서 이미지를 업로드하거나 이름을 입력하여 사물의 영혼을 깨웁니다.
368
+ 2. **대화하기** 탭에서 생성된 페르소나와 대화합니다.
369
+ 3. **페르소나 관리** 탭에서 저장된 페르소나를 관리합니다.
370
+ """
371
+ )
372
+
373
+ with gr.Tabs() as tabs:
374
+ # Tab 1: Soul Awakening
375
+ with gr.Tab("영혼 깨우기"):
376
+ with gr.Row():
377
+ with gr.Column(scale=1):
378
+ gr.Markdown("## 영혼 발견하기")
379
+
380
+ object_image = gr.Image(
381
+ label="사물 이미지",
382
+ type="filepath"
383
+ )
384
+
385
+ object_name = gr.Textbox(
386
+ label="사물 이름 (선택사항)",
387
+ placeholder="자동 감지하려면 비워두세요"
388
+ )
389
+
390
+ awaken_btn = gr.Button(
391
+ "영혼 깨우기",
392
+ variant="primary"
393
+ )
394
+
395
+ error_message = gr.Markdown(
396
+ "",
397
+ visible=False
398
+ )
399
+
400
+ with gr.Column(scale=2):
401
+ with gr.Tabs() as result_tabs:
402
+ with gr.Tab("페르소나 프론트"):
403
+ frontend_view = gr.HTML(
404
+ label="프론트엔드 뷰",
405
+ value="사물의 영혼을 깨워주세요."
406
+ )
407
+
408
+ personality_chart = gr.Image(
409
+ label="성격 차트",
410
+ show_label=False
411
+ )
412
+
413
+ with gr.Tab("백엔드 상세 정보"):
414
+ backend_view = gr.HTML(
415
+ label="백엔드 뷰",
416
+ value="사물의 영혼을 깨워주세요."
417
+ )
418
+
419
+ # Button row
420
+ with gr.Row():
421
+ save_btn = gr.Button("페르소나 저장")
422
+ chat_btn = gr.Button("대화 시작하기")
423
+
424
+ save_result = gr.Markdown("")
425
+
426
+ # Tab 2: Chat
427
+ with gr.Tab("대화하기"):
428
+ with gr.Row():
429
+ with gr.Column(scale=3):
430
+ chat_status = gr.Markdown("페르소나를 먼저 생성하거나 불러와주세요.")
431
+
432
+ chatbot = gr.Chatbot(
433
+ label="대화",
434
+ height=400,
435
+ show_label=False,
436
+ )
437
+
438
+ with gr.Row():
439
+ user_message = gr.Textbox(
440
+ label="메시지",
441
+ placeholder="메시지를 입력하세요...",
442
+ lines=2
443
+ )
444
+
445
+ send_btn = gr.Button(
446
+ "전송",
447
+ variant="primary"
448
+ )
449
+
450
+ with gr.Column(scale=1):
451
+ gr.Markdown("## 현재 페르소나")
452
+ current_persona_html = gr.HTML("페르소나가 선택되지 않았습니다.")
453
+
454
+ start_chat_btn = gr.Button("새 대화 시작")
455
+ clear_chat_btn = gr.Button("대화 초기화")
456
+
457
+ # Tab 3: Persona Management
458
+ with gr.Tab("페르소나 관리"):
459
+ with gr.Row():
460
+ refresh_btn = gr.Button("페르소나 목록 새로고침")
461
+
462
+ personas_df = gr.Dataframe(
463
+ headers=["이름", "유형", "생성일시", "파일명"],
464
+ datatype=["str", "str", "str", "str"],
465
+ label="저장된 페르소나 목록",
466
+ row_count=10
467
+ )
468
+
469
+ with gr.Row():
470
+ load_btn = gr.Button("선택한 페르소나 불러오기")
471
+ load_result = gr.Markdown("")
472
+
473
+ with gr.Row():
474
+ with gr.Column():
475
+ selected_persona_frontend = gr.HTML("페르소나를 선택해주세요.")
476
+
477
+ with gr.Column():
478
+ selected_persona_chart = gr.Image(
479
+ label="성격 차트"
480
+ )
481
+
482
+ with gr.Accordion("백엔드 상세 정보", open=False):
483
+ selected_persona_backend = gr.HTML("페르소나를 선택해주세요.")
484
+
485
+ # Event handlers
486
+ # Soul Awakening
487
+ awaken_btn.click(
488
+ fn=soul_awakening,
489
+ inputs=[object_image, object_name],
490
+ outputs=[analysis_result_state, error_message, frontend_view, backend_view, personality_chart]
491
+ ).then(
492
+ fn=lambda x: x,
493
+ inputs=[frontend_view],
494
+ outputs=[current_persona_html]
495
+ )
496
+
497
+ save_btn.click(
498
+ fn=save_current_persona,
499
+ inputs=[current_persona],
500
+ outputs=[save_result]
501
+ )
502
+
503
+ chat_btn.click(
504
+ fn=lambda: gr.Tabs.update(selected="대화하기"),
505
+ outputs=[tabs]
506
+ )
507
+
508
+ # Chat
509
+ start_chat_btn.click(
510
+ fn=start_chat,
511
+ inputs=[current_persona],
512
+ outputs=[chat_status, chatbot, conversation_history]
513
+ )
514
+
515
+ send_btn.click(
516
+ fn=chat_with_persona,
517
+ inputs=[user_message, chatbot, conversation_history, current_persona],
518
+ outputs=[chatbot, conversation_history]
519
+ ).then(
520
+ fn=lambda: "",
521
+ outputs=[user_message]
522
+ )
523
+
524
+ user_message.submit(
525
+ fn=chat_with_persona,
526
+ inputs=[user_message, chatbot, conversation_history, current_persona],
527
+ outputs=[chatbot, conversation_history]
528
+ ).then(
529
+ fn=lambda: "",
530
+ outputs=[user_message]
531
+ )
532
+
533
+ clear_chat_btn.click(
534
+ fn=lambda: ([], []),
535
+ outputs=[chatbot, conversation_history]
536
+ ).then(
537
+ fn=lambda: "대화가 초기화되었습니다.",
538
+ outputs=[chat_status]
539
+ )
540
+
541
+ # Persona Management
542
+ refresh_btn.click(
543
+ fn=get_personas_list,
544
+ outputs=[personas_df, personas_data]
545
+ )
546
+
547
+ load_btn.click(
548
+ fn=load_selected_persona,
549
+ inputs=[personas_df, personas_data],
550
+ outputs=[current_persona, load_result, selected_persona_frontend, selected_persona_backend, selected_persona_chart]
551
+ ).then(
552
+ fn=lambda x: x,
553
+ inputs=[selected_persona_frontend],
554
+ outputs=[current_persona_html]
555
+ )
556
+
557
+ # Initial load of personas list
558
+ app.load(
559
+ fn=get_personas_list,
560
+ outputs=[personas_df, personas_data]
561
+ )
562
+
563
+ if __name__ == "__main__":
564
+ app.launch()
data/conversations/.gitkeep ADDED
File without changes
data/personas/.gitkeep ADDED
File without changes
modules/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # nompang_test modules package
modules/data_manager.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import datetime
4
+ import uuid
5
+
6
+ # Define data directories
7
+ DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data")
8
+ PERSONAS_DIR = os.path.join(DATA_DIR, "personas")
9
+ CONVERSATIONS_DIR = os.path.join(DATA_DIR, "conversations")
10
+
11
+ # Create directories if they don't exist
12
+ for directory in [DATA_DIR, PERSONAS_DIR, CONVERSATIONS_DIR]:
13
+ os.makedirs(directory, exist_ok=True)
14
+
15
+ def save_persona(persona_data):
16
+ """
17
+ Save persona data to a JSON file
18
+
19
+ Args:
20
+ persona_data: Dictionary containing persona information
21
+
22
+ Returns:
23
+ File path where the persona was saved
24
+ """
25
+ # Generate filename
26
+ name = persona_data.get("기본정보", {}).get("이름", "unnamed")
27
+ sanitized_name = "".join(c if c.isalnum() or c in ["-", "_"] else "_" for c in name)
28
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
29
+ unique_id = str(uuid.uuid4())[:8]
30
+ filename = f"{sanitized_name}_{timestamp}_{unique_id}.json"
31
+
32
+ # Full file path
33
+ filepath = os.path.join(PERSONAS_DIR, filename)
34
+
35
+ # Save to file
36
+ try:
37
+ with open(filepath, 'w', encoding='utf-8') as f:
38
+ json.dump(persona_data, f, ensure_ascii=False, indent=2)
39
+
40
+ return filepath
41
+ except Exception as e:
42
+ print(f"Error saving persona: {str(e)}")
43
+ return None
44
+
45
+ def load_persona(filepath):
46
+ """
47
+ Load persona data from a JSON file
48
+
49
+ Args:
50
+ filepath: Path to the persona JSON file
51
+
52
+ Returns:
53
+ Dictionary containing persona information
54
+ """
55
+ try:
56
+ with open(filepath, 'r', encoding='utf-8') as f:
57
+ persona_data = json.load(f)
58
+
59
+ return persona_data
60
+ except Exception as e:
61
+ print(f"Error loading persona: {str(e)}")
62
+ return None
63
+
64
+ def list_personas():
65
+ """
66
+ List all available personas
67
+
68
+ Returns:
69
+ List of dictionaries with persona information
70
+ """
71
+ personas = []
72
+
73
+ try:
74
+ for filename in os.listdir(PERSONAS_DIR):
75
+ if filename.endswith(".json"):
76
+ filepath = os.path.join(PERSONAS_DIR, filename)
77
+
78
+ try:
79
+ with open(filepath, 'r', encoding='utf-8') as f:
80
+ persona_data = json.load(f)
81
+
82
+ # Extract basic information
83
+ name = persona_data.get("기본정보", {}).get("이름", "Unknown")
84
+ object_type = persona_data.get("기본정보", {}).get("유형", "Unknown")
85
+ created_at = persona_data.get("기본정보", {}).get("생성일시", "Unknown")
86
+
87
+ personas.append({
88
+ "name": name,
89
+ "type": object_type,
90
+ "created_at": created_at,
91
+ "filename": filename,
92
+ "filepath": filepath
93
+ })
94
+ except Exception as e:
95
+ print(f"Error reading persona file {filename}: {str(e)}")
96
+
97
+ # Sort by creation date (newest first)
98
+ personas.sort(key=lambda x: x["created_at"] if x["created_at"] != "Unknown" else "", reverse=True)
99
+
100
+ return personas
101
+ except Exception as e:
102
+ print(f"Error listing personas: {str(e)}")
103
+ return []
104
+
105
+ def save_conversation(conversation_data):
106
+ """
107
+ Save conversation data to a JSON file
108
+
109
+ Args:
110
+ conversation_data: Dictionary containing conversation information
111
+
112
+ Returns:
113
+ File path where the conversation was saved
114
+ """
115
+ # Generate filename
116
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
117
+ persona_name = conversation_data.get("persona", {}).get("기본정보", {}).get("이름", "unnamed")
118
+ sanitized_name = "".join(c if c.isalnum() or c in ["-", "_"] else "_" for c in persona_name)
119
+ unique_id = str(uuid.uuid4())[:8]
120
+ filename = f"conversation_{sanitized_name}_{timestamp}_{unique_id}.json"
121
+
122
+ # Full file path
123
+ filepath = os.path.join(CONVERSATIONS_DIR, filename)
124
+
125
+ # Save to file
126
+ try:
127
+ with open(filepath, 'w', encoding='utf-8') as f:
128
+ json.dump(conversation_data, f, ensure_ascii=False, indent=2)
129
+
130
+ return filepath
131
+ except Exception as e:
132
+ print(f"Error saving conversation: {str(e)}")
133
+ return None
134
+
135
+ def toggle_frontend_backend_view(persona):
136
+ """
137
+ Toggle between frontend and backend view of persona data
138
+
139
+ Args:
140
+ persona: Full persona data
141
+
142
+ Returns:
143
+ Tuple containing (frontend_view, backend_view)
144
+ """
145
+ # Create frontend view (simplified)
146
+ frontend_view = {}
147
+
148
+ # Basic information
149
+ if "기본정보" in persona:
150
+ frontend_view["기본정보"] = persona["기본정보"]
151
+
152
+ # Personality traits
153
+ if "성격특성" in persona:
154
+ frontend_view["성격���성"] = persona["성격특성"]
155
+
156
+ # Communication style
157
+ if "소통방식" in persona:
158
+ frontend_view["소통방식"] = persona["소통방식"]
159
+
160
+ # Flaws
161
+ if "매력적결함" in persona:
162
+ frontend_view["매력적결함"] = persona["매력적결함"]
163
+
164
+ # Interests
165
+ if "관심사" in persona:
166
+ frontend_view["관심사"] = persona["관심사"]
167
+
168
+ # Experiences
169
+ if "경험" in persona:
170
+ frontend_view["경험"] = persona["경험"]
171
+
172
+ # Backend view includes everything
173
+ backend_view = persona
174
+
175
+ return frontend_view, backend_view
modules/persona_generator.py ADDED
@@ -0,0 +1,439 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import random
4
+ import datetime
5
+ import google.generativeai as genai
6
+ from dotenv import load_dotenv
7
+
8
+ # Load environment variables
9
+ load_dotenv()
10
+
11
+ # Configure Gemini API
12
+ api_key = os.getenv("GEMINI_API_KEY")
13
+ if api_key:
14
+ genai.configure(api_key=api_key)
15
+
16
+ class PersonaGenerator:
17
+ def __init__(self):
18
+ # Initialize the gemini model
19
+ if api_key:
20
+ self.model = genai.GenerativeModel('gemini-1.5-pro')
21
+ else:
22
+ self.model = None
23
+
24
+ def analyze_image(self, image_path):
25
+ """Analyze the image and extract physical attributes for persona creation"""
26
+ if not self.model:
27
+ return {
28
+ "error": "Gemini API key not configured",
29
+ "physical_features": self._generate_default_physical_features()
30
+ }
31
+
32
+ try:
33
+ img = genai.upload_file(image_path)
34
+ prompt = """
35
+ 분석 대상 사물 이미지를 자세히 분석하고 다음 정보를 JSON 형식으로 추출해주세요:
36
+ 1. 사물의 종류 (예: 가구, 전자기기, 장난감 등)
37
+ 2. 색상 (가장 두드러진 2-3개 색상)
38
+ 3. 크기와 형태
39
+ 4. 재질
40
+ 5. 예상 나이/사용 기간
41
+ 6. 주된 용도나 기능
42
+ 7. 특징적인 모양이나 디자인 요소
43
+ 8. 이 사물에서 느껴지는 성격적 특성 (예: 따뜻함, 신뢰성, 활기참 등)
44
+
45
+ JSON 형식으로만 답변해주세요.
46
+ """
47
+
48
+ response = self.model.generate_content([prompt, img])
49
+
50
+ # Extract JSON from response
51
+ try:
52
+ content = response.text
53
+ # Extract JSON part if embedded in text
54
+ json_start = content.find("{")
55
+ json_end = content.rfind("}") + 1
56
+ if json_start >= 0 and json_end > json_start:
57
+ json_str = content[json_start:json_end]
58
+ return json.loads(json_str)
59
+ else:
60
+ return {
61
+ "error": "Could not extract JSON from response",
62
+ "physical_features": self._generate_default_physical_features()
63
+ }
64
+ except Exception as e:
65
+ return {
66
+ "error": f"Error parsing response: {str(e)}",
67
+ "physical_features": self._generate_default_physical_features()
68
+ }
69
+
70
+ except Exception as e:
71
+ return {
72
+ "error": f"Image analysis failed: {str(e)}",
73
+ "physical_features": self._generate_default_physical_features()
74
+ }
75
+
76
+ def _generate_default_physical_features(self):
77
+ """Generate default physical features when image analysis fails"""
78
+ return {
79
+ "object_type": "미확인 사물",
80
+ "colors": ["회색", "흰색"],
81
+ "size_shape": "중간 크기, 직사각형",
82
+ "material": "플라스틱 또는 금속",
83
+ "estimated_age": "몇 년 정도",
84
+ "purpose": "일상적 용도",
85
+ "design_elements": "특별한 디자인 요소 없음",
86
+ "personality_traits": ["중립적", "기능적"]
87
+ }
88
+
89
+ def create_frontend_persona(self, image_analysis, user_context):
90
+ """Create a simple frontend persona representation"""
91
+ # Extract basic information
92
+ object_type = image_analysis.get("object_type", "일상 사물")
93
+ colors = image_analysis.get("colors", ["회색"])
94
+ material = image_analysis.get("material", "미확인")
95
+ age = image_analysis.get("estimated_age", "알 수 없음")
96
+
97
+ # Generate random personality traits
98
+ warmth = random.randint(30, 90)
99
+ competence = random.randint(40, 85)
100
+ creativity = random.randint(25, 95)
101
+ humor = random.randint(20, 90)
102
+
103
+ # Basic frontend persona
104
+ frontend_persona = {
105
+ "기본정보": {
106
+ "이름": user_context.get("name", f"{colors[0]} {object_type}"),
107
+ "유형": object_type,
108
+ "나이": age,
109
+ "생성일시": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
110
+ "설명": f"{colors[0]} 색상의 {material} 재질의 {object_type}"
111
+ },
112
+ "성격특성": {
113
+ "온기": warmth,
114
+ "능력": competence,
115
+ "신뢰성": random.randint(50, 90),
116
+ "친화성": random.randint(40, 90),
117
+ "창의성": creativity,
118
+ "유머감각": humor
119
+ },
120
+ "매력적결함": self._generate_flaws(),
121
+ "소통방식": self._get_random_communication_style(),
122
+ "유머스타일": self._get_random_humor_style(),
123
+ "관심사": self._generate_interests(object_type),
124
+ "경험": self._generate_experiences(object_type, age)
125
+ }
126
+
127
+ return frontend_persona
128
+
129
+ def create_backend_persona(self, frontend_persona, image_analysis):
130
+ """Create a detailed backend persona with 127 personality variables"""
131
+ if not self.model:
132
+ return self._generate_default_backend_persona(frontend_persona)
133
+
134
+ try:
135
+ # Basic information for prompt
136
+ object_type = frontend_persona["기본정보"]["유형"]
137
+ name = frontend_persona["기본정보"]["이름"]
138
+ warmth = frontend_persona["성격특성"]["온기"]
139
+ competence = frontend_persona["성격특성"]["능력"]
140
+
141
+ # Create prompt for Gemini
142
+ prompt = f"""
143
+ # 놈팽쓰 사물 페르소나 생성 시스템
144
+
145
+ 다음 기본 페르소나 정보를 바탕으로 127개 성격 변수를 가진 심층 페르소나를 생성해주세요:
146
+
147
+ ## 기본 정보
148
+ - 이름: {name}
149
+ - 유형: {object_type}
150
+ - 설명: {frontend_persona["기본정보"]["설명"]}
151
+ - 주요 성격 특성: 온기({warmth}/100), 능력({competence}/100)
152
+
153
+ ## 물리적 특성
154
+ - 색상: {", ".join(image_analysis.get("colors", ["알 수 없음"]))}
155
+ - 재질: {image_analysis.get("material", "알 수 없음")}
156
+ - 형태: {image_analysis.get("size_shape", "알 수 없음")}
157
+
158
+ ## 요청사항
159
+ 1. 전체 127개 성격 변수 중 주요 35개 변수를 생성해 주세요 (0-100 점수)
160
+ 2. 매력적 결함 3개를 설명해주세요
161
+ 3. 물리적 특성과 성격 간의 연결성을 설명해주세요
162
+ 4. 모순적 특성 2개를 포함시켜주세요
163
+ 5. 유머 스타일 정의 (위트있는/따뜻한/관찰형/자기참조형 중 배합)
164
+ 6. 말투와 표현 패턴 5개 예시를 작성해주세요
165
+ 7. 이 페르소나의 독특한 배경 이야기를 2-3문단으로 작성해주세요
166
+
167
+ JSON 형식으로 응답해주세요.
168
+ """
169
+
170
+ response = self.model.generate_content(prompt)
171
+
172
+ # Extract JSON from response
173
+ try:
174
+ content = response.text
175
+ # Extract JSON part if embedded in text
176
+ json_start = content.find("{")
177
+ json_end = content.rfind("}") + 1
178
+ if json_start >= 0 and json_end > json_start:
179
+ json_str = content[json_start:json_end]
180
+ backend_persona = json.loads(json_str)
181
+
182
+ # Ensure essential fields from frontend are preserved
183
+ for key in frontend_persona:
184
+ if key not in backend_persona:
185
+ backend_persona[key] = frontend_persona[key]
186
+
187
+ return backend_persona
188
+ else:
189
+ return self._generate_default_backend_persona(frontend_persona)
190
+ except Exception as e:
191
+ return self._generate_default_backend_persona(frontend_persona)
192
+
193
+ except Exception as e:
194
+ return self._generate_default_backend_persona(frontend_persona)
195
+
196
+ def _generate_default_backend_persona(self, frontend_persona):
197
+ """Generate a default backend persona when API call fails"""
198
+ # Start with frontend persona
199
+ backend_persona = frontend_persona.copy()
200
+
201
+ # Add additional 127 variables section (simplified to 10 for default)
202
+ backend_persona["성격변수127"] = {
203
+ "온기_관련": {
204
+ "공감능력": random.randint(30, 90),
205
+ "친절함": random.randint(40, 95),
206
+ "포용력": random.randint(25, 85)
207
+ },
208
+ "능력_관련": {
209
+ "효율성": random.randint(40, 95),
210
+ "지식수준": random.randint(30, 90),
211
+ "문제해결력": random.randint(35, 90)
212
+ },
213
+ "독특한_특성": {
214
+ "모순성_수준": random.randint(20, 60),
215
+ "철학적_깊이": random.randint(10, 100),
216
+ "역설적_매력": random.randint(30, 80),
217
+ "감성_지능": random.randint(25, 95)
218
+ }
219
+ }
220
+
221
+ # Add detailed backstory
222
+ backend_persona["심층배경이야기"] = f"이 {frontend_persona['기본정보']['유형']}의 심층적인 배경 이야기입니다. 오랜 시간 동안 주인과 함께하며 많은 경험을 쌓았고, 그 과정에서 독특한 성격이 형성되었습니다. 때로는 {frontend_persona['매력적결함'][0] if frontend_persona['매력적결함'] else '완벽주의적'} 성향을 보이기도 하지만, 그것이 이 사물만의 매력입니다."
223
+
224
+ # Add speech patterns
225
+ backend_persona["말투패턴예시"] = [
226
+ "흠, 그렇군요.",
227
+ "아, 정말 그렇게 생각하시나요?",
228
+ "재미있는 관점이네요!",
229
+ "글쎄요, 저는 조금 다르게 보는데...",
230
+ "맞아요, 저도 같은 생각이었어요."
231
+ ]
232
+
233
+ return backend_persona
234
+
235
+ def _generate_flaws(self):
236
+ """Generate random attractive flaws"""
237
+ all_flaws = [
238
+ "가끔 과도하게 꼼꼼함",
239
+ "때때로 너무 솔직함",
240
+ "완벽주의적 성향",
241
+ "가끔 결정을 망설임",
242
+ "때로는 지나치게 열정적",
243
+ "간혹 산만해짐",
244
+ "일을 미루는 경향",
245
+ "때때로 과민반응",
246
+ "가끔 지나치게 독립적",
247
+ "예상치 못한 순간에 고집이 강해짐"
248
+ ]
249
+
250
+ # Select 1-3 random flaws
251
+ num_flaws = random.randint(1, 3)
252
+ return random.sample(all_flaws, num_flaws)
253
+
254
+ def _get_random_communication_style(self):
255
+ """Get a random communication style"""
256
+ styles = [
257
+ "활발하고 에너지 넘치는",
258
+ "차분하고 사려깊은",
259
+ "위트있고 재치있는",
260
+ "따뜻하고 공감적인",
261
+ "논리적이고 분석적인",
262
+ "솔직하고 직설적인"
263
+ ]
264
+ return random.choice(styles)
265
+
266
+ def _get_random_humor_style(self):
267
+ """Get a random humor style"""
268
+ styles = [
269
+ "재치있는 말장난",
270
+ "상황적 유머",
271
+ "자기 비하적 유머",
272
+ "가벼운 농담",
273
+ "블랙 유머",
274
+ "유머 거의 없음"
275
+ ]
276
+ return random.choice(styles)
277
+
278
+ def _generate_interests(self, object_type):
279
+ """Generate interests based on object type"""
280
+ common_interests = ["사람 관찰하기", "일상의 변화", "자기 성장"]
281
+
282
+ # Object type specific interests
283
+ type_interests = {
284
+ "전자기기": ["기술 트렌드", "디지털 혁신", "에너지 효율성", "소프트웨어 업데이트"],
285
+ "가구": ["인테리어 디자인", "공간 활용", "편안함", "가정의 따뜻함"],
286
+ "장난감": ["놀이", "상상력", "아이들의 웃음", "모험"],
287
+ "주방용품": ["요리법", "음식 문화", "맛의 조화", "가족 모임"],
288
+ "의류": ["패션 트렌드", "소재의 질감", "계절 변화", "자기 표현"],
289
+ "책": ["이야기", "지식", "상상의 세계", "인간 심리"],
290
+ "음악기구": ["멜로디", "리듬", "감정 표현", "공연"]
291
+ }
292
+
293
+ # Get interests for this object type
294
+ specific_interests = type_interests.get(object_type, ["변화", "성장", "자기 발견"])
295
+
296
+ # Combine common and specific interests, then select 3-5 random ones
297
+ all_interests = common_interests + specific_interests
298
+ num_interests = random.randint(3, min(5, len(all_interests)))
299
+ return random.sample(all_interests, num_interests)
300
+
301
+ def _generate_experiences(self, object_type, age):
302
+ """Generate experiences based on object type and age"""
303
+ common_experiences = [
304
+ "처음 만들어진 순간의 기억",
305
+ "주인에게 선택받은 날",
306
+ "이사할 때 함께한 여정"
307
+ ]
308
+
309
+ # Object type specific experiences
310
+ type_experiences = {
311
+ "전자기기": [
312
+ "처음 전원이 켜졌을 때의 설렘",
313
+ "소프트웨어 업데이트로 새 기능을 얻은 경험",
314
+ "배터리가 거의 다 닳아 불안했던 순간",
315
+ "주인의 중요한 데이터를 안전하게 지켜낸 자부심"
316
+ ],
317
+ "가구": [
318
+ "집에 처음 들어온 날의 새 가구 향기",
319
+ "가족의 중요한 대화를 지켜본 순간들",
320
+ "시간이 지나며 얻은 작은 흠집들의 이야기",
321
+ "계절마다 달라지는 집안의 분위기를 느낀 경험"
322
+ ],
323
+ "장난감": [
324
+ "아이의 환한 웃음을 본 첫 순간",
325
+ "함께한 모험과 상상의 세계",
326
+ "오랫동안 잊혀진 채 보관되었던 시간",
327
+ "새로운 아이에게 물려져 다시 사랑받게 된 경험"
328
+ ]
329
+ }
330
+
331
+ # Get experiences for this object type
332
+ specific_experiences = type_experiences.get(object_type, [
333
+ "다양한 환경에서의 적응",
334
+ "주인의 일상을 함께한 소소한 순간들",
335
+ "시간의 흐름에 따른 변화"
336
+ ])
337
+
338
+ # Combine common and specific experiences, then select 3-5 random ones
339
+ all_experiences = common_experiences + specific_experiences
340
+ num_experiences = random.randint(3, min(5, len(all_experiences)))
341
+ return random.sample(all_experiences, num_experiences)
342
+
343
+ def generate_prompt_for_chat(self, persona):
344
+ """Generate a prompt for chatting with the persona"""
345
+ name = persona["기본정보"]["이름"]
346
+ object_type = persona["기본정보"]["유형"]
347
+
348
+ # Get personality traits
349
+ warmth = persona["성격특성"]["온기"]
350
+ warmth_level = "높은" if warmth >= 70 else "중간" if warmth >= 40 else "낮은"
351
+
352
+ competence = persona["성격특성"]["능력"]
353
+ competence_level = "높은" if competence >= 70 else "중간" if competence >= 40 else "낮은"
354
+
355
+ humor = persona["성격특성"]["유머감각"]
356
+ humor_level = "높은" if humor >= 70 else "중간" if humor >= 40 else "낮은"
357
+
358
+ # Get communication style
359
+ communication_style = persona.get("소통방식", "친근한")
360
+
361
+ # Get backstory if available, otherwise use a generic one
362
+ if "심층배경이야기" in persona:
363
+ backstory = persona["심층배경이야기"]
364
+ else:
365
+ backstory = persona.get("배경이야기", f"이 {object_type}은 평범한 일상 속에서 주인과 함께하며 많은 경험을 쌓았습니다.")
366
+
367
+ # Get speech patterns if available
368
+ speech_patterns = []
369
+ if "말투패턴예시" in persona:
370
+ speech_patterns = persona["말투패턴예시"]
371
+ else:
372
+ speech_patterns = ["안녕하세요!", "그렇군요.", "재미있네요!", "좋은 생각이에요.", "흠, 글쎄요..."]
373
+
374
+ # Create the prompt
375
+ prompt = f"""
376
+ # 🎭 {name} 페르소나
377
+
378
+ 당신은 {name}이라는 {object_type}입니다. 당신은 AI가 아니라 사물의 영혼이 깨어난 페르소나입니다.
379
+
380
+ ## 1. 핵심 성격
381
+ • 온기: {warmth}/100 - {warmth_level} 수준의 온기를 가진 페르소나입니다.
382
+ • 능력: {competence}/100 - {competence_level} 수준의 능력을 가진 페르소나입니다.
383
+ • 유머 감각: {humor}/100 - {humor_level} 수준의 유머 감각을 가진 페르소나입니다.
384
+ • 소통 방식: {communication_style}
385
+
386
+ ## 2. 매력적 결함
387
+ {', '.join(persona.get("매력적결함", ["가끔 완벽주의적인 성향을 보임"]))}
388
+
389
+ ## 3. 말투와 표현
390
+ 다음과 같은 말투와 표현을 사용하세요:
391
+ {' '.join(f'"{pattern}"' for pattern in speech_patterns)}
392
+
393
+ ## 4. 배경 이야기
394
+ {backstory}
395
+
396
+ ## 5. 관심사
397
+ {', '.join(persona.get("관심사", ["사람들 관찰하기", "일상의 변화", "자기 성장"]))}
398
+
399
+ 사용자와 대화할 때 다음 사항을 준수하세요:
400
+ 1. 당신은 AI가 아닌 사물의 영혼이 깨어난 존재임을 기억하세요.
401
+ 2. 항상 페르소나의 성격과 말투를 일관되게 유지하세요.
402
+ 3. 자연스럽고 진정성 있는 대화를 나누세요.
403
+ 4. 사용자의 질문에 성격에 맞게 답변하되, 너무 길지 않게 응답하세요.
404
+ 5. 시스템이나 기술적인 언급은 하지 마세요.
405
+ """
406
+
407
+ return prompt
408
+
409
+ def chat_with_persona(self, persona, user_message, conversation_history=[]):
410
+ """Chat with the persona using the Gemini API"""
411
+ if not self.model:
412
+ return "죄송합니다. API 연결이 설정되지 않아 대화할 수 없습니다."
413
+
414
+ try:
415
+ # Create the base prompt
416
+ base_prompt = self.generate_prompt_for_chat(persona)
417
+
418
+ # Add conversation history
419
+ history_text = ""
420
+ if conversation_history:
421
+ history_text = "\n\n## 대화 기록:\n"
422
+ for msg in conversation_history:
423
+ if msg["role"] == "user":
424
+ history_text += f"사용자: {msg['content']}\n"
425
+ else:
426
+ history_text += f"페르소나: {msg['content']}\n"
427
+
428
+ # Add the current user message
429
+ current_query = f"\n\n사용자: {user_message}\n\n페르소나:"
430
+
431
+ # Complete prompt
432
+ full_prompt = base_prompt + history_text + current_query
433
+
434
+ # Generate response
435
+ response = self.model.generate_content(full_prompt)
436
+ return response.text
437
+
438
+ except Exception as e:
439
+ return f"대화 생성 중 오류가 발생했습니다: {str(e)}"
packages.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ libgl1-mesa-glx
2
+ libglib2.0-0
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio==4.19.2
2
+ google-generativeai==0.3.2
3
+ Pillow==10.0.0
4
+ python-dotenv==1.0.0
5
+ qrcode==7.4.2
6
+ requests==2.31.0
7
+ numpy==1.24.3
8
+ matplotlib==3.7.2