import gradio as gr import os import json import time from datetime import datetime # 모듈 임포트 from modules.image_analyzer import analyze_image from modules.question_generator import generate_questions from modules.persona_generator import generate_persona from modules.chat_engine import start_conversation, process_message from modules.data_manager import save_persona, load_persona, list_personas, save_conversation from modules.gemini_handler import get_persona_enhancement # 디렉토리 확인 try: for dir_path in [ "data/user_personas", "data/conversation_logs", "assets/examples" ]: os.makedirs(dir_path, exist_ok=True) except Exception as e: print(f"초기 디렉토리 생성 중 오류: {str(e)}") print("허깅페이스 환경에서는 일부 파일 저장 기능이 제한될 수 있습니다.") # 기본 성격 특성 범위 trait_range = {"minimum": 0, "maximum": 100, "step": 5, "value": 50} # UI 테마 및 스타일 설정 theme = gr.themes.Soft( primary_hue="indigo", secondary_hue="blue", ).set( button_primary_background_fill="*primary_500", button_primary_background_fill_hover="*primary_600", button_primary_text_color="white", block_title_text_color="*primary_800", block_label_text_size="sm" ) # 메인 애플리케이션 with gr.Blocks(title="사물 페르소나 생성기", theme=theme, css="styles/custom.css") as app: gr.Markdown( """ # 🧸 물魂 (물건의 혼) - 사물 페르소나 생성기 일상 사물에 개성과 성격을 부여하여 대화할 수 있는 페르소나 생성 및 테스트 도구입니다. """ ) # 전역 상태 관리 current_persona = gr.State({}) conversation_history = gr.State([]) session_start_time = gr.State(None) with gr.Tabs() as tabs: # 페르소나 생성 탭 with gr.Tab("페르소나 생성"): with gr.Row(): with gr.Column(scale=2): gr.Markdown("## 사물 정보") with gr.Row(): with gr.Column(): gr.Markdown("### 사물 이미지 (선택사항)") object_image = gr.File( file_types=["image"], file_count="single", type="filepath", elem_id="object_file_uploader" ) image_analysis_btn = gr.Button("이미지 분석하기") with gr.Row(): object_name = gr.Textbox( label="사물 이름", placeholder="예: 할아버지의 낡은 안락의자" ) object_type = gr.Dropdown( choices=[ "전자기기", "가구", "주방용품", "의류/액세서리", "책/문구류", "음악 기구", "장난감", "기타" ], label="사물 유형" ) object_age = gr.Textbox( label="사물 나이/사용 기간", placeholder="예: 15년" ) object_description = gr.Textbox( label="사물 설명", placeholder="물체의 외형, 특징, 용도 등을 설명해주세요.", lines=3 ) image_analysis_result = gr.JSON( label="이미지 분석 결과", visible=False ) with gr.Column(scale=3): gr.Markdown("## 성격 특성") with gr.Row(): with gr.Column(): warmth = gr.Slider( label="온기", info="따뜻함, 친절함, 선의의 정도", **trait_range ) competence = gr.Slider( label="능력", info="효율성, 기술, 지능의 정도", **trait_range ) trustworthiness = gr.Slider( label="신뢰성", info="일관성, 정직함, 안정성의 정도", **trait_range ) with gr.Column(): friendliness = gr.Slider( label="친화성", info="사교성, 개방성, 접근성의 정도", **trait_range ) creativity = gr.Slider( label="창의성", info="독창성, 상상력, 혁신성의 정도", **trait_range ) humor = gr.Slider( label="유머감각", info="재치, 위트, 유머 표현 정도", **trait_range ) gr.Markdown("## 매력적 결함") flaws = gr.CheckboxGroup( choices=[ "때때로 너무 조심스러움", "가끔 과하게 열정적", "약간의 완벽주의", "때로는 우울한 생각에 빠짐", "간혹 산만해짐", "약간의 고집", "때로는 지나치게 솔직함", "가끔 불안해함", "종종 과거에 집착함", "가끔 너무 이상적임" ], label="매력적인 결함 (최대 2개 선택)", ) gr.Markdown("## 소통 방식") with gr.Row(): with gr.Column(): communication_style = gr.Radio( choices=[ "활발하고 에너지 넘치는", "차분하고 사려깊은", "위트있고 재치있는", "따뜻하고 공감적인", "논리적이고 분석적인" ], label="대화 스타일", value="따뜻하고 공감적인" ) humor_style = gr.Dropdown( choices=[ "재치있는 말장난", "상황적 유머", "자기 비하적 유머", "가벼운 농담", "블랙 유머", "유머 거의 없음" ], multiselect=True, label="유머 유형" ) with gr.Column(): speech_pattern = gr.Textbox( label="말투 패턴 예시", placeholder="캐릭터의 특징적인 말투나 표현을 입력하세요", lines=2 ) interests = gr.Textbox( label="관심사 (쉼표로 구분)", placeholder="음악, 요리, 여행, 역사..." ) with gr.Row(): with gr.Column(): gr.Markdown("## 관계 성향") with gr.Row(): attachment_style = gr.Radio( choices=["안정형", "불안형", "회피형", "혼란형"], label="애착 스타일", value="안정형" ) relationship_depth = gr.Radio( choices=["얕은", "중간", "깊은"], label="관계 깊이 선호도", value="중간" ) initial_attitude = gr.Radio( choices=["수줍은", "중립적", "친근한", "열정적", "조심스러운"], label="초기 태도", value="친근한" ) with gr.Column(): backstory = gr.Textbox( label="배경 이야기", placeholder="이 사물의 역사, 경험, 소유자와의 관계 등을 간략히 서술하세요", lines=4 ) with gr.Row(): with gr.Column(): template_selector = gr.Dropdown( choices=[ "템플릿 없음", "스마트폰 - 열정적 조력자", "오래된 안락의자 - 지혜로운 관찰자", "커피머신 - 활기찬 에너자이저", "오래된 책 - 사려깊은 학자", "가죽 가방 - 신뢰할 수 있는 동반자" ], label="템플릿 선택", value="템플릿 없음" ) load_template_btn = gr.Button("템플릿 불러오기") with gr.Column(): create_btn = gr.Button("페르소나 생성하기", variant="primary") # 생성된 페르소나 출력 generated_persona = gr.JSON(label="생성된 페르소나") # 이벤트 핸들러 image_analysis_btn.click( fn=analyze_image, inputs=[object_image], outputs=[image_analysis_result, warmth, competence, trustworthiness, friendliness, creativity, humor, object_type, object_description] ) create_btn.click( fn=generate_persona, inputs=[ object_name, object_type, object_age, object_description, warmth, competence, trustworthiness, friendliness, creativity, humor, flaws, communication_style, humor_style, speech_pattern, interests, attachment_style, relationship_depth, initial_attitude, backstory, image_analysis_result ], outputs=[generated_persona, current_persona] ) # 템플릿 로드 기능 추가 예정 # 대화 테스트 탭 with gr.Tab("대화 테스트"): with gr.Row(): with gr.Column(scale=2): chat_display = gr.Chatbot( label="대화 내용", height=500, avatar_images=(None, "assets/icons/persona_avatar.png") ) with gr.Row(): user_message = gr.Textbox( label="메시지 입력", placeholder="메시지를 입력하세요...", lines=2 ) send_btn = gr.Button("전송", variant="primary") with gr.Column(scale=1): gr.Markdown("## 현재 페르소나") persona_info = gr.Markdown("페르소나를 먼저 생성하거나 불러오세요.") start_chat_btn = gr.Button("대화 시작하기") load_persona_btn = gr.Button("저장된 페르소나 불러오기") gr.Markdown("## 대화 통계") chat_stats = gr.Markdown("대화가 시작되지 않았습니다.") export_chat_btn = gr.Button("대화 내용 저장") export_result = gr.Textbox(label="저장 결과", visible=False) # 이벤트 핸들러 start_chat_btn.click( fn=start_conversation, inputs=[current_persona], outputs=[chat_display, conversation_history, session_start_time, chat_stats, persona_info] ) send_btn.click( fn=process_message, inputs=[user_message, conversation_history, current_persona, session_start_time], outputs=[chat_display, conversation_history, user_message, chat_stats] ) # Enter 키로 메시지 전송 user_message.submit( fn=process_message, inputs=[user_message, conversation_history, current_persona, session_start_time], outputs=[chat_display, conversation_history, user_message, chat_stats] ) # 페르소나 라이브러리 탭 with gr.Tab("페르소나 라이브러리"): with gr.Row(): with gr.Column(): refresh_library_btn = gr.Button("라이브러리 새로고침") personas_table = gr.Dataframe( headers=["이름", "유형", "생성일", "파일명"], datatype=["str", "str", "str", "str"], label="저장된 페르소나 목록" ) with gr.Column(): selected_persona_json = gr.JSON(label="선택된 페르소나 정보") load_to_editor_btn = gr.Button("에디터로 불러오기") load_to_chat_btn = gr.Button("대화 테스트로 불러오기") # 이벤트 핸들러 refresh_library_btn.click( fn=list_personas, inputs=[], outputs=[personas_table] ) # 선택한 페르소나 로드 기능 추가 예정 # 앱 실행 if __name__ == "__main__": app.launch()