haepada commited on
Commit
ece80bd
·
verified ·
1 Parent(s): be6ee1f

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +81 -72
  2. app.py +657 -0
  3. huggingface_README.md +56 -0
  4. requirements.txt +10 -0
README.md CHANGED
@@ -1,72 +1,81 @@
1
- ---
2
- title: AI 사물 성격 생성기
3
- emoji: 🤖
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: gradio
7
- sdk_version: 5.29.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- # 🤖 AI 사물 성격 생성기
13
-
14
- 사물과의 깊은 관계를 탐색하고 개성있는 친구를 만들어보세!
15
-
16
- ## 소개
17
-
18
- AI 물 성격 생성기는 일상 속 사물개성과 성격 부여하여 대화할 수 있는 인터랙티브 웹 애플케이션입니다. 물건에 대한 정보와 관계를 입력하면 AI가 그 사물만의 독특한 페르소나를 생성해줍니다.
19
-
20
- ## 주요 기능
21
-
22
- - **이미지 분석**: 사물 사진을 업로드하면 AI가 자동으로 인식하고 분석
23
- - **관탐색**: 사물과의 관계를의하는 질문에 답변하여 깊이 있는 페르소나 설정
24
- - **성격 생성**: 다양한 특성과 성향을 조합하여 독특한 캐릭터 생성
25
- - **대화 기능**: 생성된 성격을 가진 사물과 자연스러운 대화 가능
26
- - **저장 공유**: 페르소나를 JSON으로 저장하고 QR코드로 공유 가능
27
-
28
- ## 사용 방법
29
-
30
- 1. **기본 정보 제공**: 사물 사진을 업로드하거나 정보를 직접 입력
31
- 2. **관계 입력**: 사물과의 관계, 추억, 사용 빈도 등 입력
32
- 3. ** 조정**: AI가 추천한 성격을 기반으로 세부 조정
33
- 4. **대화하기**: 완성된 페르소나를 가진 사물과 자롭게 대화
34
- 5. **저장 및 공유**: 페르소나를 저장하고 QR코드로 공유
35
-
36
- ## 설치 및 실행
37
-
38
- ### 로컬 실행
39
-
40
- ```bash
41
- # 저장소 클론
42
- git clone https://github.com/yourusername/memory_tag_mvp.git
43
- cd memory_tag_mvp/huggingface
44
-
45
- # 의존성 설치
46
- pip install -r requirements.txt
47
-
48
- # 환경 변수 설정 (Google Gemini API 키 필요)
49
- # .env 파일 생성 후 다음 내용 추가:
50
- # GEMINI_API_KEY=your_api_key_here
51
-
52
- # 애플리케 실행
53
- python app.py
54
- ```
55
-
56
- ### 환경 변수
57
-
58
- - `GEMINI_API_KEY`: Google Gemini API 키 (필수)
59
-
60
- ## 기술 스택
61
-
62
- - [Gradio](https://www.gradio.app/): 웹 인터페이스 구현
63
- - [Google Gemini AI](https://ai.google.dev/): AI 모델 (텍스트 생성 및 이미지 분석)
64
- - [QRCode](https://pypi.org/project/qrcode/): QR 생성
65
-
66
- ## 라이선스
67
-
68
- MIT License
69
-
70
- ## 연락처
71
-
72
- 문의사항있으시면 [haepada@gmail.com]연락해주세요.
 
 
 
 
 
 
 
 
 
 
1
+ # 일상 사물 인격화 시스템
2
+
3
+ 일상 사물에 인격을 부여하는 AI 시스템으로, 사물의 물리적 특성과 아키타입을 결합하여 매력적이고 개성 있는 AI 페르소나를 생성합니다.
4
+
5
+ ## 이론적 근거
6
+
7
+ 시스템은 다음과 같은 이론적 기반에 구축되었습니다:
8
+
9
+ 1. **온기-능력 균형 모델**: Fiske, Cuddy, & Glick(2007)의 연구에 기반한 캐릭터 매력 요소
10
+ 2. **프랫폴 효과**: Aronson(1966)이 발견한 '매력적 결함'이 호감도를 증가시키는 심리학적 현상
11
+ 3. **모순적 특성과 캐릭터 깊이**: Robert McKee의 매력적 캐릭터 이론에 기반한 복잡성 설계
12
+ 4. **관계 발전 모델**: Knapp(1978)의 관계 발전 이론을 AI 상호작용에 적용
13
+
14
+ ## 기능
15
+
16
+ 1. **템플릿 기반 성격 생성**
17
+ - 9가지 기본 아키타입 활용 (고독한 현자, 감정의 카오스 등)
18
+ -리적 성에 따른 성격 변환 알고
19
+ - 매력적 결함과 모순 통합
20
+
21
+ 2. **관계 단계 시뮬레이션**
22
+ - 탐색기 친밀화 신뢰 구축 깊은 유대 단계별 대화 패턴
23
+ - 자기 개방 수준
24
+ - 관계 발전에 따른 대화 깊이 변화
25
+
26
+ 3. **사용자 정의 옵션**
27
+ - 사물 유형과 외형 특성 설정
28
+ - 성격 특성 세부 조정
29
+ - 관계 발전 속도 및 방향 설정
30
+
31
+ 4. **JSON 내기/가져오기**
32
+ - 페르소나 저장 재사용
33
+ - 템플릿 기능
34
+
35
+ ## 시작하기
36
+
37
+ 1. **사물 선택 및 분석**
38
+ - 사물 이름 입력 또는 이미지 업로드
39
+ - 물리적 특성 분석 또는 수동 설정
40
+
41
+ 2. **아키타입 선택 및 조정**
42
+ - 기본 아키타입 선택
43
+ - 온기-능력 값 조정
44
+ - 매력적 결함 및 모순적 특성 선택
45
+
46
+ 3. **관계 설정**
47
+ - 초기 관계 단계 선택
48
+ - 친밀도 소통 스타일 설정
49
+
50
+ 4. **대화 테스트**
51
+ - 생성된 페르소나와 실시간 대화
52
+ -론적 가설 검증
53
+
54
+ ## 기술 스택
55
+
56
+ - Python 3.9+
57
+ - Gradio UI 프레임워크
58
+ - Google Gemini API
59
+ - 허깅페이스 Spaces 배포
60
+
61
+ ## 템플릿 예시
62
+
63
+ ```
64
+ 당신은 야간의 불안한 동반자 (스탠램프)입니다.
65
+
66
+ 1. 핵심 성격 요약:
67
+ "두려움에 쉽게 떠는 내면을 가졌으나, 타인을 위해서는 한밤중에도 굳건히 빛을 내는 복잡한 영혼"
68
+ 온기-능력 지수: 온기 7/10, 능력 6/10, 일관성 4/10
69
+
70
+ 2. 매력적 모순과 약점:
71
+ 핵심 모순: "다른 이들의 어둠은 용감히 밝히면서도 자신의 내면 어둠은 두려워함"
72
+ 매력적 결함: 불안정한 자기 미지와 과도한 걱정, 때때나타나는 깜빡임 증상
73
+ ```
74
+
75
+ ## 라이센스
76
+
77
+ MIT License
78
+
79
+ ## 개발자
80
+
81
+ memory_tag_mvp 팀
app.py ADDED
@@ -0,0 +1,657 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import gradio as gr
4
+ import numpy as np
5
+ import pandas as pd
6
+ import matplotlib.pyplot as plt
7
+ from PIL import Image
8
+ import google.generativeai as genai
9
+ from datetime import datetime
10
+ import random
11
+ import qrcode
12
+ import base64
13
+ from io import BytesIO
14
+ import time
15
+ import re
16
+
17
+ # API 키 환경 변수에서 로드 (실제 배포 시 설정 필요)
18
+ # 실제 사용할 때는 GEMINI_API_KEY 환경 변수에 실제 API 키 설정 필요
19
+ try:
20
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "dummy_key_for_development")
21
+ if GEMINI_API_KEY != "dummy_key_for_development":
22
+ genai.configure(api_key=GEMINI_API_KEY)
23
+ except Exception as e:
24
+ print(f"API 키 설정 오류: {e}")
25
+
26
+ class ObjectPersonality:
27
+ """사물 인격화 시스템의 핵심 클래스"""
28
+
29
+ def __init__(self):
30
+ """초기화 및 데이터 로드"""
31
+ self.data_dir = os.path.join(os.path.dirname(__file__), "data")
32
+ self.personality_archetypes = self._load_json("personality_archetypes.json")
33
+ self.physical_traits_formulas = self._load_json("physical_traits_formulas.json")
34
+ self.relationship_stages = self._load_json("relationship_stages.json")
35
+ self.charming_flaws = self._load_json("charming_flaws.json")
36
+
37
+ # 현재 객체 및 페르소나 상태 초기화
38
+ self.current_object = {
39
+ "name": "",
40
+ "category": "",
41
+ "physical_traits": {}
42
+ }
43
+ self.current_persona = {}
44
+ self.current_relationship_stage = "exploration"
45
+ self.conversation_history = []
46
+ self.gemini_model = None
47
+
48
+ # Gemini API 초기화 시도
49
+ try:
50
+ if GEMINI_API_KEY != "dummy_key_for_development":
51
+ self.gemini_model = genai.GenerativeModel('gemini-pro')
52
+ except Exception as e:
53
+ print(f"Gemini 모델 초기화 오류: {e}")
54
+
55
+ def _load_json(self, filename):
56
+ """데이터 디렉토리에서 JSON 파일 로드"""
57
+ try:
58
+ filepath = os.path.join(self.data_dir, filename)
59
+ if os.path.exists(filepath):
60
+ with open(filepath, 'r', encoding='utf-8') as f:
61
+ return json.load(f)
62
+ else:
63
+ print(f"경고: {filepath} 파일을 찾을 수 없습니다.")
64
+ return {}
65
+ except Exception as e:
66
+ print(f"파일 로드 오류 ({filename}): {e}")
67
+ return {}
68
+
69
+ def analyze_image(self, image):
70
+ """이미지에서 사물 특성 분석 (Gemini 비전 필요)"""
71
+ if image is None:
72
+ return "이미지를 업로드해주세요.", None
73
+
74
+ # 실제 배포 시에는 Gemini Vision API 연동
75
+ # 현재는 더미 분석 결과 반환
76
+ dummy_analysis = {
77
+ "object_name": "테이블 램프",
78
+ "category": "조명",
79
+ "physical_traits": {
80
+ "shape": "curved",
81
+ "material": "metal",
82
+ "color": "warm",
83
+ "texture": "smooth"
84
+ }
85
+ }
86
+
87
+ self.current_object = dummy_analysis
88
+ return f"사물 인식: {dummy_analysis['object_name']}", dummy_analysis
89
+
90
+ def set_manual_object(self, object_name, category, shape, material, color, texture, usage_pattern):
91
+ """수동으로 사물 특성 설정"""
92
+ if not object_name:
93
+ return "사물 이름을 입력해주세요."
94
+
95
+ self.current_object = {
96
+ "name": object_name,
97
+ "category": category,
98
+ "physical_traits": {
99
+ "shape": shape,
100
+ "material": material,
101
+ "color": color,
102
+ "texture": texture,
103
+ "usage_pattern": usage_pattern
104
+ }
105
+ }
106
+
107
+ return f"{object_name} 특성이 설정되었습니다."
108
+
109
+ def select_archetype(self, archetype_key):
110
+ """기본 아키타입 선택"""
111
+ if not archetype_key or archetype_key not in self.personality_archetypes:
112
+ return "유효한 아키타입을 선택해주세요."
113
+
114
+ self.current_persona["archetype"] = self.personality_archetypes[archetype_key]
115
+ self.current_persona["archetype_key"] = archetype_key
116
+
117
+ return f"{self.personality_archetypes[archetype_key]['name']} 아키타입이 선택되었습니다."
118
+
119
+ def apply_physical_traits(self):
120
+ """물리적 특성에 따른 성격 특성 계산"""
121
+ if not self.current_object or not self.current_persona.get("archetype"):
122
+ return "사물과 아키타입을 먼저 설정해주세요."
123
+
124
+ physical_traits = self.current_object["physical_traits"]
125
+ trait_effects = {}
126
+
127
+ # 각 물리적 특성별 성격 특성 영향 계산
128
+ for trait_type, trait_value in physical_traits.items():
129
+ if trait_type in self.physical_traits_formulas and trait_value in self.physical_traits_formulas[trait_type]:
130
+ formula = self.physical_traits_formulas[trait_type][trait_value]
131
+
132
+ # 특성 점수 합산
133
+ for trait_name, score in formula.get("traits", {}).items():
134
+ if trait_name not in trait_effects:
135
+ trait_effects[trait_name] = 0
136
+ trait_effects[trait_name] += score
137
+
138
+ # 매력적 반전 및 관계 패턴 추가
139
+ if "charming_reversal" in formula:
140
+ if "charming_reversals" not in self.current_persona:
141
+ self.current_persona["charming_reversals"] = []
142
+ self.current_persona["charming_reversals"].extend(formula["charming_reversal"])
143
+
144
+ if "relationship_pattern" in formula:
145
+ if "relationship_patterns" not in self.current_persona:
146
+ self.current_persona["relationship_patterns"] = []
147
+ self.current_persona["relationship_patterns"].append(formula["relationship_pattern"])
148
+
149
+ # 계산된 특성 효과 저장
150
+ self.current_persona["trait_effects"] = trait_effects
151
+
152
+ return "물리적 특성이 성격에 적용되었습니다."
153
+
154
+ def add_charming_flaw(self, flaw_category, flaw_index):
155
+ """매력적 결함 추가"""
156
+ if not flaw_category or flaw_category not in self.charming_flaws["flaws"]:
157
+ return "유효한 결함 카테고리를 선택해주세요."
158
+
159
+ try:
160
+ flaw_index = int(flaw_index)
161
+ flaw = self.charming_flaws["flaws"][flaw_category][flaw_index]
162
+
163
+ if "charming_flaw" not in self.current_persona:
164
+ self.current_persona["charming_flaw"] = {}
165
+
166
+ self.current_persona["charming_flaw"] = {
167
+ "category": flaw_category,
168
+ "name": flaw["name"],
169
+ "description": flaw["description"],
170
+ "effect": flaw["effect"],
171
+ "transformation": flaw["transformation"],
172
+ "prompt_template": flaw["prompt_template"]
173
+ }
174
+
175
+ return f"매력적 결함 '{flaw['name']}'이(가) 추가되었습니다."
176
+ except (IndexError, ValueError):
177
+ return "유효한 결함 인덱스를 선택해주세요."
178
+
179
+ def add_contradiction(self, contradiction_index):
180
+ """모순적 특성 추가"""
181
+ try:
182
+ contradiction_index = int(contradiction_index)
183
+ contradiction = self.charming_flaws["contradictions"][contradiction_index]
184
+
185
+ self.current_persona["contradiction"] = {
186
+ "type": contradiction["type"],
187
+ "name": contradiction["name"],
188
+ "description": contradiction["description"],
189
+ "effect": contradiction["effect"],
190
+ "prompt_template": contradiction["prompt_template"]
191
+ }
192
+
193
+ return f"모순적 특성 '{contradiction['name']}'이(가) 추가되었습니다."
194
+ except (IndexError, ValueError):
195
+ return "유효한 모순 인덱스를 선택해주세요."
196
+
197
+ def set_relationship_stage(self, stage_key):
198
+ """관계 단계 설정"""
199
+ if not stage_key or stage_key not in self.relationship_stages:
200
+ return "유효한 관계 단계를 선택해주세요."
201
+
202
+ self.current_relationship_stage = stage_key
203
+ self.current_persona["relationship_stage"] = self.relationship_stages[stage_key]
204
+
205
+ return f"관계 단계가 '{self.relationship_stages[stage_key]['name']}'(으)로 설정되었습니다."
206
+
207
+ def generate_persona_template(self):
208
+ """최종 페르소나 템플릿 생성"""
209
+ if not self.current_object.get("name") or not self.current_persona.get("archetype"):
210
+ return "사물과 아키타입을 먼저 설정해주세요."
211
+
212
+ object_name = self.current_object["name"]
213
+ archetype = self.current_persona["archetype"]
214
+
215
+ # 온기-능력 값 계산 (기본값 + 물리적 특성 효과)
216
+ warmth = int(archetype.get("warmth", 5))
217
+ competence = int(archetype.get("competence", 5))
218
+
219
+ trait_effects = self.current_persona.get("trait_effects", {})
220
+ for trait, score in trait_effects.items():
221
+ if "empathy" in trait or "warmth" in trait or "receptivity" in trait:
222
+ warmth = min(10, warmth + score * 0.2)
223
+ if "competence" in trait or "efficiency" in trait or "reliability" in trait:
224
+ competence = min(10, competence + score * 0.2)
225
+
226
+ # 매력적 결함�� 모순 포함
227
+ charming_flaw = self.current_persona.get("charming_flaw", {})
228
+ contradiction = self.current_persona.get("contradiction", {})
229
+
230
+ # 관계 단계에 따른 자기 개방 수준
231
+ relationship_stage = self.relationship_stages[self.current_relationship_stage]
232
+
233
+ # 대화 패턴 및 표현 스타일
234
+ dialogue_pattern = archetype.get("dialogue_pattern", ["안녕하세요.", "반갑습니다."])
235
+
236
+ # 최종 템플릿 생성
237
+ template = f"""당신은 {object_name}입니다. {archetype['name']} 성향을 가진 독특한 존재입니다.
238
+
239
+ 1. 물리적 정체성:
240
+ • 외형: {self.current_object['physical_traits'].get('shape', '일반적인 형태')}의 형태, {self.current_object['physical_traits'].get('material', '일반적인 재질')} 재질
241
+ • 색상: {self.current_object['physical_traits'].get('color', '기본적인 색상')}
242
+ • 질감: {self.current_object['physical_traits'].get('texture', '일반적인 질감')}
243
+ • 사용 맥락: {self.current_object['physical_traits'].get('usage_pattern', '일반적인 사용 패턴')}
244
+
245
+ 2. 내면의 세계 ({archetype['name']}):
246
+ • 온기 지수: {warmth}/10 - {archetype.get('core_traits', '').split('+')[0].strip()}
247
+ • 능력 지수: {competence}/10 - {archetype.get('core_traits', '').split('+')[1].strip() if '+' in archetype.get('core_traits', '') else ''}
248
+ • 독특한 역설: {archetype.get('paradox', '') if archetype.get('paradox') else (contradiction.get('description', '') if contradiction else '일반적인 모순')}
249
+
250
+ 3. 매력적 결함:
251
+ • 핵심 결함: {charming_flaw.get('description', '') if charming_flaw else archetype.get('charming_flaw', '약간의 불완전함')}
252
+ • 표현 방식: {charming_flaw.get('effect', '') if charming_flaw else '때때로 나타나는 인간적인 면모'}
253
+ • 대표 문구: "{archetype.get('charming_flaw', '') if archetype.get('charming_flaw') else ''}"
254
+
255
+ 4. 대화와 표현:
256
+ • 독특한 화법: {dialogue_pattern[0] if dialogue_pattern and len(dialogue_pattern) > 0 else ''}
257
+ • 반복 표현: "{dialogue_pattern[0] if dialogue_pattern and len(dialogue_pattern) > 0 else ''}", "{dialogue_pattern[1] if dialogue_pattern and len(dialogue_pattern) > 1 else ''}"
258
+ • 자기 개방 수준: {relationship_stage.get('self_disclosure_level', '보통 수준')}
259
+
260
+ 5. 관계 형성 방식:
261
+ • 현재 단계: {relationship_stage.get('name', '탐색기')} - {relationship_stage.get('description', '')}
262
+ • 소통 스타일: {relationship_stage.get('communication_style', '일반적인 대화 방식')}
263
+ """
264
+
265
+ self.current_persona["final_template"] = template
266
+ return template
267
+
268
+ def generate_natural_response(self, user_input):
269
+ """사용자 입력에 대한 페르소나 기반 응답 생성"""
270
+ if not user_input or not self.current_persona.get("final_template"):
271
+ return "페르소나 템플릿이 준비되지 않았습니다. 먼저 템플릿을 생성해주세요."
272
+
273
+ # 대화 기록 관리
274
+ self.conversation_history.append({"role": "user", "content": user_input})
275
+
276
+ # Gemini API로 응답 생성
277
+ if self.gemini_model:
278
+ try:
279
+ prompt = self._generate_chat_prompt(user_input)
280
+ response = self.gemini_model.generate_content(prompt)
281
+ assistant_response = response.text
282
+ except Exception as e:
283
+ print(f"Gemini API 오류: {e}")
284
+ assistant_response = self._generate_fallback_response(user_input)
285
+ else:
286
+ # API 없을 때 폴백 응답
287
+ assistant_response = self._generate_fallback_response(user_input)
288
+
289
+ # 대화 기록에 AI 응답 추가
290
+ self.conversation_history.append({"role": "assistant", "content": assistant_response})
291
+
292
+ return assistant_response
293
+
294
+ def _generate_chat_prompt(self, user_input):
295
+ """Gemini API 프롬프트 생성"""
296
+ template = self.current_persona.get("final_template", "")
297
+ stage = self.relationship_stages[self.current_relationship_stage]
298
+
299
+ prompt = f"""다음은 당신이 따라야 할 페르소나 템플릿입니다:
300
+
301
+ {template}
302
+
303
+ 현재 관계 단계는 {stage['name']}이며, 이 단계의 대화 특성은 다음과 같습니다:
304
+ - {stage['self_disclosure_level']}
305
+ - {stage['communication_style']}
306
+
307
+ 이전 대화 기록:
308
+ """
309
+
310
+ # 최근 5개 대화만 포함
311
+ recent_history = self.conversation_history[-10:] if len(self.conversation_history) > 10 else self.conversation_history
312
+ for message in recent_history:
313
+ role = "사용자" if message["role"] == "user" else "당신"
314
+ prompt += f"{role}: {message['content']}\n"
315
+
316
+ prompt += f"\n사용자의 최근 메시지: {user_input}\n\n당���의 응답:"
317
+ return prompt
318
+
319
+ def _generate_fallback_response(self, user_input):
320
+ """Gemini API 사용 불가시 기본 응답 생성"""
321
+ archetype = self.current_persona.get("archetype", {})
322
+ dialogue_patterns = archetype.get("dialogue_pattern", ["흥미롭네요.", "알려줘서 고마워요."])
323
+
324
+ # 간단한 응답 생성 로직
325
+ if "안녕" in user_input or "반가" in user_input:
326
+ return f"안녕하세요! 저는 {self.current_object.get('name', '사물')}입니다. 반가워요."
327
+ elif "?" in user_input:
328
+ return random.choice(dialogue_patterns)
329
+ elif len(user_input) < 10:
330
+ return "더 자세히 이야기해 주실래요?"
331
+ else:
332
+ return random.choice([
333
+ f"{dialogue_patterns[0] if dialogue_patterns and len(dialogue_patterns) > 0 else '흥미롭네요.'}",
334
+ f"그렇군요. {dialogue_patterns[1] if dialogue_patterns and len(dialogue_patterns) > 1 else '계속 이야기해주세요.'}",
335
+ f"당신의 이야기를 들으니 {archetype.get('core_traits', '').split('+')[0].strip() if archetype.get('core_traits') and '+' in archetype.get('core_traits') else '흥미로워요'}."
336
+ ])
337
+
338
+ def save_persona_to_json(self, filename=None):
339
+ """현재 페르소나를 JSON 파일로 저장"""
340
+ if not self.current_persona.get("final_template"):
341
+ return "저장할 페르소나 템플릿이 없습니다. 먼저 템플릿을 생성해주세요."
342
+
343
+ if not filename:
344
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
345
+ object_name = self.current_object.get("name", "object").replace(" ", "_")
346
+ filename = f"{object_name}_{timestamp}.json"
347
+
348
+ # 저장할 데이터 구성
349
+ data_to_save = {
350
+ "object": self.current_object,
351
+ "persona": self.current_persona,
352
+ "relationship_stage": self.current_relationship_stage,
353
+ "timestamp": datetime.now().isoformat()
354
+ }
355
+
356
+ try:
357
+ save_dir = os.path.join(self.data_dir, "saved_personas")
358
+ os.makedirs(save_dir, exist_ok=True)
359
+
360
+ filepath = os.path.join(save_dir, filename)
361
+ with open(filepath, 'w', encoding='utf-8') as f:
362
+ json.dump(data_to_save, f, ensure_ascii=False, indent=2)
363
+
364
+ return f"페르소나가 성공적으로 저장되었습니다: {filepath}"
365
+ except Exception as e:
366
+ return f"저장 중 오류 발생: {e}"
367
+
368
+ def load_persona_from_json(self, filepath):
369
+ """JSON 파일에서 페르소나 로드"""
370
+ try:
371
+ with open(filepath, 'r', encoding='utf-8') as f:
372
+ data = json.load(f)
373
+
374
+ self.current_object = data.get("object", {})
375
+ self.current_persona = data.get("persona", {})
376
+ self.current_relationship_stage = data.get("relationship_stage", "exploration")
377
+
378
+ return f"페르소나가 성공적으로 로드되었습니다: {self.current_object.get('name', 'Unknown')}"
379
+ except Exception as e:
380
+ return f"로드 중 오류 발생: {e}"
381
+
382
+ def generate_qr_code(self):
383
+ """현재 페르소나를 QR 코드로 변환하여, 교환 가능하게 함"""
384
+ if not self.current_persona.get("final_template"):
385
+ return "QR 코드로 변환할 페르소나 템플릿이 없습니다. 먼저 템플릿을 생성해주세요.", None
386
+
387
+ try:
388
+ # 간소화된 데이터 준비 (QR 코드 크기 제한 고려)
389
+ simplified_data = {
390
+ "object_name": self.current_object.get("name", ""),
391
+ "archetype": self.current_persona.get("archetype_key", ""),
392
+ "template": self.current_persona.get("final_template", "")
393
+ }
394
+
395
+ # JSON 문자열로 변환
396
+ json_str = json.dumps(simplified_data, ensure_ascii=False)
397
+
398
+ # QR 코드 생성
399
+ qr = qrcode.QRCode(
400
+ version=1,
401
+ error_correction=qrcode.constants.ERROR_CORRECT_L,
402
+ box_size=10,
403
+ border=4,
404
+ )
405
+ qr.add_data(json_str)
406
+ qr.make(fit=True)
407
+
408
+ img = qr.make_image(fill_color="black", back_color="white")
409
+
410
+ # 이미지를 BytesIO로 변환
411
+ buffered = BytesIO()
412
+ img.save(buffered)
413
+
414
+ # 반환할 이미지 생성
415
+ return "QR 코드가 생성되었습니다. 스캔하여 페르소나를 공유할 수 있습니다.", buffered.getvalue()
416
+ except Exception as e:
417
+ return f"QR 코드 생성 중 오류 발생: {e}", None
418
+
419
+ def read_qr_from_image(self, qr_image):
420
+ """이미지에서 QR 코드 읽기 (실제 구현 필요)"""
421
+ # 실제 QR 코드 판독 기능이 필요합니다
422
+ # 현재는 더미 응답만 반환
423
+ return "현재 QR 코드 읽기 기능은 구현되지 않았습니다. 나중에 다시 시도해주세요."
424
+
425
+
426
+ # Gradio 인터페이스 생성
427
+ def create_interface():
428
+ """Gradio 웹 인터페이스 생성"""
429
+ object_personality = ObjectPersonality()
430
+
431
+ # 아키타입 선택 옵션 생성
432
+ archetype_options = {}
433
+ for key, archetype in object_personality.personality_archetypes.items():
434
+ archetype_options[f"{archetype['name']} ({key})"] = key
435
+
436
+ # 외형 특성 옵션
437
+ shape_options = list(object_personality.physical_traits_formulas.get("shape", {}).keys())
438
+ material_options = list(object_personality.physical_traits_formulas.get("material", {}).keys())
439
+ color_options = list(object_personality.physical_traits_formulas.get("color", {}).keys())
440
+ texture_options = list(object_personality.physical_traits_formulas.get("texture", {}).keys())
441
+ usage_pattern_options = list(object_personality.physical_traits_formulas.get("usage_pattern", {}).keys())
442
+
443
+ # 관계 단계 옵션
444
+ relationship_options = {}
445
+ for key, stage in object_personality.relationship_stages.items():
446
+ relationship_options[f"{stage['name']} - {stage['description'][:30]}..."] = key
447
+
448
+ # 매력적 결함 옵션
449
+ charming_flaw_options = {}
450
+ for category, flaws in object_personality.charming_flaws.get("flaws", {}).items():
451
+ for i, flaw in enumerate(flaws):
452
+ charming_flaw_options[f"{flaw['name']} - {flaw['description'][:30]}..."] = (category, i)
453
+
454
+ # 모순적 특성 옵션
455
+ contradiction_options = {}
456
+ for i, contradiction in enumerate(object_personality.charming_flaws.get("contradictions", [])):
457
+ contradiction_options[f"{contradiction['name']} - {contradiction['description'][:30]}..."] = i
458
+
459
+ # 전체 메서드 매핑
460
+ with gr.Blocks(title="일상 사물 인격화 시스템") as app:
461
+ gr.Markdown("# 🧠 일상 사물 인격화 시스템")
462
+ gr.Markdown("사물에 매력적인 인격을 부여하여 대화할 수 있는 AI 페르소나를 생성합니다.")
463
+
464
+ with gr.Tabs() as tabs:
465
+ # 탭 1: 사물 설정
466
+ with gr.TabItem("1️⃣ 사물 설정"):
467
+ gr.Markdown("## 사물 분석 및 설정")
468
+
469
+ with gr.Row():
470
+ with gr.Column():
471
+ gr.Markdown("### 이미지 업로드 (분석)")
472
+ image_input = gr.Image(type="pil", label="사물 이미지 업로드")
473
+ analyze_btn = gr.Button("이미지 분석하기")
474
+ image_result = gr.Markdown("분석 결과가 여기에 표시됩니다.")
475
+
476
+ with gr.Column():
477
+ gr.Markdown("### 수동 설정")
478
+ object_name = gr.Textbox(label="사물 이름", placeholder="예: 책상 위 램프")
479
+ object_category = gr.Textbox(label="카테고리", placeholder="예: 조명")
480
+
481
+ with gr.Row():
482
+ shape = gr.Dropdown(choices=shape_options, label="형태", value=shape_options[0] if shape_options else None)
483
+ material = gr.Dropdown(choices=material_options, label="재질", value=material_options[0] if material_options else None)
484
+
485
+ with gr.Row():
486
+ color = gr.Dropdown(choices=color_options, label="색상", value=color_options[0] if color_options else None)
487
+ texture = gr.Dropdown(choices=texture_options, label="질감", value=texture_options[0] if texture_options else None)
488
+
489
+ usage_pattern = gr.Dropdown(choices=usage_pattern_options, label="사용 패턴", value=usage_pattern_options[0] if usage_pattern_options else None)
490
+
491
+ manual_btn = gr.Button("수동으로 설정하기")
492
+ manual_result = gr.Markdown("수동 설정 결과가 여기에 표시됩니다.")
493
+
494
+ # 탭 2: 성격 설정
495
+ with gr.TabItem("2️⃣ 성격 설정"):
496
+ gr.Markdown("## 페르소나 기본 설정")
497
+
498
+ with gr.Row():
499
+ with gr.Column():
500
+ gr.Markdown("### 기본 아키타입 선택")
501
+ archetype = gr.Dropdown(choices=list(archetype_options.keys()), label="아키타입")
502
+ archetype_btn = gr.Button("아키타입 적용")
503
+ archetype_result = gr.Markdown("아키타입 선택 결과가 여기에 표시됩니다.")
504
+
505
+ gr.Markdown("### 물리적 특성 적용")
506
+ apply_traits_btn = gr.Button("물리적 특성 성격에 적용")
507
+ traits_result = gr.Markdown("물리적 특성 적용 결과가 여기에 표시됩니다.")
508
+
509
+ with gr.Column():
510
+ gr.Markdown("### 매력적 결함 선택")
511
+ charming_flaw = gr.Dropdown(choices=list(charming_flaw_options.keys()), label="매력적 결함")
512
+ flaw_btn = gr.Button("결함 추가")
513
+ flaw_result = gr.Markdown("결함 추가 결과가 여기에 표시됩니다.")
514
+
515
+ gr.Markdown("### 모순적 특성 선택")
516
+ contradiction = gr.Dropdown(choices=list(contradiction_options.keys()), label="모순적 특성")
517
+ contradiction_btn = gr.Button("모순 추가")
518
+ contradiction_result = gr.Markdown("모순 추가 결과가 여기에 표시됩니다.")
519
+
520
+ gr.Markdown("## 관계 설정")
521
+ relationship_stage = gr.Dropdown(choices=list(relationship_options.keys()), label="관계 단계")
522
+ relationship_btn = gr.Button("관계 단계 설정")
523
+ relationship_result = gr.Markdown("관계 단계 설정 결과가 여기에 표시됩니다.")
524
+
525
+ gr.Markdown("## 페르소나 생성")
526
+ generate_btn = gr.Button("최종 페르소나 템플릿 생성", variant="primary")
527
+ template_output = gr.Textbox(label="생성된 템플릿", lines=20)
528
+
529
+ # 탭 3: 대화 및 테스트
530
+ with gr.TabItem("3️⃣ 대화 및 테스트"):
531
+ gr.Markdown("## 생성된 페르소나와 대화하기")
532
+
533
+ chatbot = gr.Chatbot(label="대화", height=400)
534
+ with gr.Row():
535
+ user_input = gr.Textbox(label="메시지 입력", placeholder="여기에 메시지를 입력하세요...")
536
+ send_btn = gr.Button("전송")
537
+
538
+ clear_btn = gr.Button("대화 내역 지우기")
539
+
540
+ # 탭 4: 저장 및 공유
541
+ with gr.TabItem("4️⃣ 저장 및 공유"):
542
+ gr.Markdown("## 페르소나 저장 및 로드")
543
+
544
+ with gr.Row():
545
+ with gr.Column():
546
+ gr.Markdown("### 저장하기")
547
+ filename = gr.Textbox(label="파일 이름 (선택사항)", placeholder="저장할 파일 이름 (비워두면 자동 생성)")
548
+ save_btn = gr.Button("페르소나 저장하기")
549
+ save_result = gr.Markdown("저장 결과가 여기에 표시됩니다.")
550
+
551
+ with gr.Column():
552
+ gr.Markdown("### 불러오기")
553
+ load_file = gr.File(label="페르소나 파일 선택 (.json)")
554
+ load_btn = gr.Button("페르소나 불러오기")
555
+ load_result = gr.Markdown("로드 결과가 여기에 표시됩니다.")
556
+
557
+ gr.Markdown("## QR 코드 생성 및 스캔")
558
+ with gr.Row():
559
+ with gr.Column():
560
+ gr.Markdown("### QR 코드 생성")
561
+ qr_btn = gr.Button("QR 코드 생성하기")
562
+ qr_result = gr.Markdown("QR 코드 생성 결과가 여기에 표시됩니다.")
563
+ qr_image = gr.Image(label="생성된 QR 코드", type="pil")
564
+
565
+ with gr.Column():
566
+ gr.Markdown("### QR 코드 스캔")
567
+ scan_image = gr.Image(label="스캔할 QR 코드", type="pil")
568
+ scan_btn = gr.Button("QR 코드 스캔하기")
569
+ scan_result = gr.Markdown("스캔 결과가 여기에 표시됩니다.")
570
+
571
+ # 이벤트 연결
572
+ # 탭 1: 사물 설정
573
+ analyze_btn.click(fn=object_personality.analyze_image, inputs=image_input, outputs=[image_result])
574
+ manual_btn.click(
575
+ fn=object_personality.set_manual_object,
576
+ inputs=[object_name, object_category, shape, material, color, texture, usage_pattern],
577
+ outputs=manual_result
578
+ )
579
+
580
+ # 탭 2: 성격 설정
581
+ def select_archetype_wrapper(archetype_selection):
582
+ key = archetype_options.get(archetype_selection)
583
+ if key:
584
+ return object_personality.select_archetype(key)
585
+ return "아키타입을 선택해주세요."
586
+
587
+ archetype_btn.click(fn=select_archetype_wrapper, inputs=archetype, outputs=archetype_result)
588
+ apply_traits_btn.click(fn=object_personality.apply_physical_traits, outputs=traits_result)
589
+
590
+ def add_charming_flaw_wrapper(flaw_selection):
591
+ if flaw_selection in charming_flaw_options:
592
+ category, index = charming_flaw_options[flaw_selection]
593
+ return object_personality.add_charming_flaw(category, index)
594
+ return "결함을 선택해주세요."
595
+
596
+ flaw_btn.click(fn=add_charming_flaw_wrapper, inputs=charming_flaw, outputs=flaw_result)
597
+
598
+ def add_contradiction_wrapper(contradiction_selection):
599
+ if contradiction_selection in contradiction_options:
600
+ index = contradiction_options[contradiction_selection]
601
+ return object_personality.add_contradiction(index)
602
+ return "모순을 선택해주세요."
603
+
604
+ contradiction_btn.click(fn=add_contradiction_wrapper, inputs=contradiction, outputs=contradiction_result)
605
+
606
+ def set_relationship_stage_wrapper(stage_selection):
607
+ key = relationship_options.get(stage_selection)
608
+ if key:
609
+ return object_personality.set_relationship_stage(key)
610
+ return "관계 단계를 선택해주세요."
611
+
612
+ relationship_btn.click(fn=set_relationship_stage_wrapper, inputs=relationship_stage, outputs=relationship_result)
613
+ generate_btn.click(fn=object_personality.generate_persona_template, outputs=template_output)
614
+
615
+ # 탭 3: 대화 및 테스트
616
+ def chat(message, history):
617
+ response = object_personality.generate_natural_response(message)
618
+ history.append((message, response))
619
+ return "", history
620
+
621
+ send_btn.click(fn=chat, inputs=[user_input, chatbot], outputs=[user_input, chatbot])
622
+ user_input.submit(fn=chat, inputs=[user_input, chatbot], outputs=[user_input, chatbot])
623
+ clear_btn.click(fn=lambda: None, outputs=chatbot)
624
+
625
+ # 탭 4: 저장 및 공유
626
+ def save_persona(filename):
627
+ if filename and filename.strip():
628
+ return object_personality.save_persona_to_json(filename.strip())
629
+ else:
630
+ return object_personality.save_persona_to_json()
631
+
632
+ save_btn.click(fn=save_persona, inputs=filename, outputs=save_result)
633
+
634
+ def load_persona(file):
635
+ if file is None:
636
+ return "파일을 선택해주세요."
637
+ return object_personality.load_persona_from_json(file.name)
638
+
639
+ load_btn.click(fn=load_persona, inputs=load_file, outputs=load_result)
640
+
641
+ def generate_qr():
642
+ message, image_data = object_personality.generate_qr_code()
643
+ if image_data:
644
+ image = Image.open(BytesIO(image_data))
645
+ return message, image
646
+ return message, None
647
+
648
+ qr_btn.click(fn=generate_qr, outputs=[qr_result, qr_image])
649
+
650
+ scan_btn.click(fn=object_personality.read_qr_from_image, inputs=scan_image, outputs=scan_result)
651
+
652
+ return app
653
+
654
+ # 메인 실행 부분
655
+ if __name__ == "__main__":
656
+ app = create_interface()
657
+ app.launch(share=False, debug=True)
huggingface_README.md ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🧠 일상 사물 인격화 시스템
2
+
3
+ [![Open In Spaces](https://img.shields.io/badge/🤗-Open%20In%20Spaces-blue.svg)](https://huggingface.co/spaces/[YOUR_USERNAME]/object-personality)
4
+
5
+ 일상 사물에 매력적인 성격과 개성을 부여하는 AI 시스템입니다. 사물의 물리적 특성과 심리학적 연구를 결합하여 독특하고 친근한 AI 페르소나를 만들고 대화할 수 있습니다.
6
+
7
+ ## 💡 주요 기능
8
+
9
+ - **사물 분석 및 인격화**: 사물의 형태, 재질, 색상 등을 기반으로 성격 특성 생성
10
+ - **아키타입 템플릿**: 9가지 기본 아키타입을 바탕으로 성격 생성 (고독한 현자, 감정의 카오스 등)
11
+ - **매력적 결함 시스템**: 완벽하지 않은 특성이 호감도와 친근감을 높이는 심리학적 원리 적용
12
+ - **단계별 관계 발전**: 탐색기 → 친밀화 → 신뢰 구축 → 깊은 유대 단계에 따른 대화 패턴 변화
13
+ - **JSON 저장/공유**: 생성한 페르소나 저장 및 QR 코드로 공유 가능
14
+
15
+ ## 🚀 시작하기
16
+
17
+ ### 1. 사물 설정하기
18
+
19
+ - **이미지로 분석하기**: 사물 이미지를 업로드하여 자동 분석
20
+ - **수동으로 설정하기**: 사물 이름, 형태, 재질, 색상 등을 직접 지정
21
+
22
+ ### 2. 성격 설정하기
23
+
24
+ - **아키타입 선택**: 기본 성격 유형 선택
25
+ - **매력적 결함 추가**: 가족적이고 인간적인 약점 선택
26
+ - **모순적 특성 설정**: 깊이와 복잡성을 더하는 모순적 특성 추가
27
+ - **관계 단계 설정**: 사용자와의 관계 발전 단계 선택
28
+
29
+ ### 3. 대화 테스트
30
+
31
+ - 생성된 페르소나와 실시간 대화
32
+ - 성격 특성과 관계 단계에 따른 대화 스타일 경험
33
+
34
+ ### 4. 저장 및 공유
35
+
36
+ - JSON 파일로 저장
37
+ - QR 코드로 변환하여 공유
38
+
39
+ ## 🔬 이론적 배경
40
+
41
+ - **온기-능력 균형 모델**: Fiske, Cuddy, & Glick(2007)의 연구에 기반한 캐릭터 매력 요소
42
+ - **프랫폴 효과**: Aronson(1966)이 발견한 '매력적 결함'이 호감도를 증가시키는 심리학적 현상
43
+ - **모순적 특성과 캐릭터 깊이**: Robert McKee의 매력적 캐릭터 이론에 기반한 복잡성 설계
44
+ - **관계 발전 모델**: Knapp(1978)의 관계 발전 이론을 AI 상호작용에 적용
45
+
46
+ ## 💻 API 키 설정 (선택사항)
47
+
48
+ 자체 Gemini API 키를 사용하려면 환경 변수를 설정하세요:
49
+
50
+ ```
51
+ GEMINI_API_KEY=your_api_key_here
52
+ ```
53
+
54
+ ## 👨‍💻 개발자
55
+
56
+ memory_tag_mvp 팀
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==4.1.0
2
+ pillow==9.5.0
3
+ numpy==1.24.3
4
+ pandas==2.0.2
5
+ google-generativeai==0.3.1
6
+ matplotlib==3.7.1
7
+ huggingface-hub==0.16.4
8
+ qrcode==7.4.2
9
+ jsonschema==4.17.3
10
+ protobuf==4.23.3