File size: 8,354 Bytes
0b572d3
 
19c2d2e
 
c08ff6f
19c2d2e
04f338f
0b572d3
 
 
 
 
 
 
 
 
 
 
8603187
1a6fb12
0b572d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8603187
 
c08ff6f
8603187
04f338f
8603187
 
 
04f338f
 
19c2d2e
c08ff6f
04f338f
 
 
 
 
 
 
c08ff6f
04f338f
c08ff6f
 
 
 
19c2d2e
 
 
 
 
 
 
 
04f338f
19c2d2e
 
 
 
 
c08ff6f
19c2d2e
 
 
 
 
 
04f338f
c08ff6f
 
 
19c2d2e
c08ff6f
 
 
 
19c2d2e
c08ff6f
 
 
 
 
b03bfdc
 
 
04f338f
 
 
 
 
0b572d3
 
8603187
c08ff6f
0b572d3
 
 
 
 
c08ff6f
 
0b572d3
8603187
 
0b572d3
 
8603187
c08ff6f
04f338f
8603187
 
 
04f338f
0b572d3
04f338f
 
c08ff6f
8603187
04f338f
 
 
 
 
 
 
0b572d3
 
8603187
0b572d3
 
 
 
04f338f
0b572d3
1a6fb12
 
 
 
 
04f338f
8603187
04f338f
 
8603187
0b572d3
8603187
04f338f
8603187
 
0b572d3
04f338f
8603187
 
 
0b572d3
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# portfolio/npc_social_network/manager/conversation_manager.py
# ๋Œ€ํ™”์˜ ์‹œ์ž‘, ์ง„ํ–‰, ์ข…๋ฃŒ๋ฅผ ์ด๊ด„ํ•˜๋Š” ์—ญํ• 
from ..models.llm_helper import (summarize_text, query_llm_for_emotion
                                , evaluate_goal_achievement)
from ..npc.npc_manager import get_korean_postposition
from ..npc.emotion_config import EMOTION_RELATION_IMPACT, EMOTION_LIST
import threading
from typing import List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
    from ..npc.npc_base import NPC

class Conversation:
    """๋‹จ์ผ ๋Œ€ํ™”์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค"""
    def __init__(self, initiator: "NPC", target: "NPC", topic: str):
        self.participants: List["NPC"] = [initiator, target]
        self.conversation_history: List[str] = []
        self.topic: str = topic
        self.turn_index: int = 0    # 0: initiator, 1: target
        self.is_ending: bool = False
        self.waiting_for_player: bool = False   # ํ”Œ๋ ˆ์ด์–ด์˜ ์ž…๋ ฅ ๋Œ€๊ธฐ

    def get_current_speaker(self) -> "NPC":
        """์ด๋ฒˆ์— ๋งํ•  ์ฐจ๋ก€์ธ NPC๋ฅผ ๋ฐ˜ํ™˜"""
        return self.participants[self.turn_index]
    
    def switch_turn(self):
        """๋Œ€ํ™” ํ„ด์„ ์ƒ๋Œ€๋ฐฉ์—๊ฒŒ ๋„˜๊น€"""
        self.turn_index = 1 - self.turn_index

class ConversationManager:
    """ํ™œ์„ฑํ™”๋œ ๋Œ€ํ™”๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ค‘์•™ ๊ด€๋ฆฌ์ž"""
    def __init__(self):
        self.active_conversation: Optional[Conversation] = None

    def is_conversation_active(self) -> bool:
        """ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ๋Œ€ํ™”๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ"""
        return self.active_conversation is not None
    
    def _add_and_log_utterance(self,speaker: "NPC", utterance: str):
        """๋Œ€์‚ฌ๋ฅผ ๋Œ€ํ™” ๊ธฐ๋ก๊ณผ UI ๋กœ๊ทธ์— ๋ชจ๋‘ ์ถ”๊ฐ€"""
        from .. import simulation_core
        if not self.is_conversation_active():
            return        
        log_message = f"[{speaker.korean_name}]: {utterance}"
        self.active_conversation.conversation_history.append(log_message)
        simulation_core.add_log(log_message)

    def _summarize_and_remember_in_background(self, conv: "Conversation"):
        """๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋Œ€ํ™”๋ฅผ ์š”์•ฝํ•˜๊ณ  ๊ธฐ์–ตํ•˜๋ฉฐ, ๊ด€๊ณ„์™€ ์„ฑ๊ฒฉ์— ๋ฐ˜์˜, ๋ชฉํ‘œ ๋‹ฌ์„ฑ ํ‰๊ฐ€๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜"""
        from .. import simulation_core
        try:
            if not conv.conversation_history:
                print("๊ธฐ์–ตํ•  ๋Œ€ํ™” ๋‚ด์šฉ์ด ์—†์–ด ์ข…๋ฃŒ.")
                return
            initiator, target = conv.participants[0], conv.participants[1]
            full_conversation = "\n".join(conv.conversation_history)

            # 1. ๋Œ€ํ™” ๋‚ด์šฉ ์š”์•ฝ
            summary = summarize_text(full_conversation)

            # 2. ๋Œ€ํ™”์˜ ์ „๋ฐ˜์ ์ธ ๊ฐ์ • ํ†ค ๋ถ„์„
            overall_emotion = query_llm_for_emotion(summary)

            # 3. ๋ชฉํ‘œ ๋‹ฌ์„ฑ ์—ฌ๋ถ€ ํ‰๊ฐ€
            initiator = conv.participants[0]
            evaluation = evaluate_goal_achievement(
                initiator_name=initiator.korean_name,
                goal=conv.topic,
                conversation_transcript=full_conversation
            )

            with simulation_core.simulation_lock:
                # 4. ํ‰๊ฐ€ ๊ฒฐ๊ณผ ๋กœ๊ทธ ์ถ”๊ฐ€
                simulation_core.add_log(f"[{initiator.korean_name}์˜ ๋ชฉํ‘œ ๋‹ฌ์„ฑ ํ‰๊ฐ€]\n"
                                        f"'{conv.topic}' -> ๋‹ฌ์„ฑ: {evaluation.get('goal_achieved')}.\n"
                                        f"์ด์œ : {evaluation.get('reason')}")

                target_postposition = get_korean_postposition(target.korean_name, "๊ณผ", "์™€")
                # 5. ํ‰๊ฐ€ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ๊ฐ์ • ์—…๋ฐ์ดํŠธ
                initiator_emotion = evaluation.get('initiator_emotion')
                if initiator_emotion and initiator_emotion in EMOTION_LIST:
                    initiator.update_emotion(initiator_emotion, strength=5.0)   # ๋ชฉํ‘œ ๋‹ฌ์„ฑ ์—ฌ๋ถ€๋Š” ๊ฐ•ํ•œ ๊ฐ์ • ์œ ๋ฐœ
                
                # 6. ์š”์•ฝ๋œ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๊ธฐ์–ต ์ƒ์„ฑ
                memory_content = f"'{conv.topic}'์— ๋Œ€ํ•ด ๋Œ€ํ™”ํ•˜๋ฉฐ '{summary}'๋ผ๋Š” ๊ฒฐ๋ก ์„ ๋‚ด๋ ธ๋‹ค."
                initiator.remember(content=f"{target.korean_name}{target_postposition} {memory_content}", importance=7, memory_type="Conversation")
                target.remember(content=f"{initiator.korean_name}{target_postposition} {memory_content}", importance=7, memory_type="Conversation")
        
                # 7. ๋Œ€ํ™”์˜ ๊ฐ์ •์„ ๋ฐ”ํƒ•์œผ๋กœ ๊ด€๊ณ„ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
                if overall_emotion and overall_emotion in EMOTION_RELATION_IMPACT:
                    initiator.relationships.update_relationship(target.name, overall_emotion, strength=3.0)
                    target.relationships.update_relationship(initiator.name, overall_emotion, strength=3.0)

                # 8. ๊ด€๊ณ„ ๋ณ€ํ™”๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์–‘์ชฝ ๋ชจ๋‘์˜ ์„ฑ๊ฒฉ ์—…๋ฐ์ดํŠธ
                initiator.update_personality()
                target.update_personality()

                simulation_core.add_log(f"[{initiator.korean_name}, {target.korean_name}] ๋Œ€ํ™” ๊ฒฝํ—˜์ด ๊ธฐ์–ต๊ณผ ๊ด€๊ณ„, ์„ฑ๊ฒฉ์— ๋ฐ˜์˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")

                # 9. ๋ชจ๋“  ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์ข…๋ฃŒ ํ›„, ํ˜„์žฌ๊นŒ์ง€์˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ „์ฒด ์ƒํƒœ๋ฅผ ์ฆ‰์‹œ ์ €์žฅ.
                simulation_core.save_simulation(simulation_core.npc_manager)

        except Exception as e:
            print(f"[์—๋Ÿฌ] ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ธฐ์–ต ์ €์žฅ ์ค‘ ๋ฌธ์ œ ๋ฐœ์ƒ: {e}")
            import traceback
            traceback.print_exc()

    def start_conversation(self, initiator: "NPC", target: "NPC",
                           topic: Optional[str] = None):
        """์ƒˆ๋กœ์šด ๋Œ€ํ™”๋ฅผ ์‹œ์ž‘, ๋ฐ”๋กœ ๋‹ค์Œ ํ„ด์„ ํ˜ธ์ถœ"""
        from .. import simulation_core
        if self.is_conversation_active():
            return # ์ด๋ฏธ ๋‹ค๋ฅธ ๋Œ€ํ™”๊ฐ€ ์ง„ํ–‰์ค‘์ด๋ฉด ์‹œ์ž‘ x
        
        conv_topic = topic or "๊ฐ€๋ฒผ์šด ์ธ์‚ฌ"
        self.active_conversation = Conversation(initiator, target, conv_topic)
        simulation_core.add_log(f"--[๋Œ€ํ™” ์‹œ์ž‘]--")
        simulation_core.add_log(f"์ฃผ์ œ: {conv_topic} / ์ฐธ์—ฌ์ž: {initiator.korean_name}, {target.korean_name}")
        
        # 1. ์ฒซ ๋งˆ๋”” ์ƒ์„ฑ
        self.next_turn()

    def end_conversation(self, reason: str = "์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ"):
        """ํ˜„์žฌ ๋Œ€ํ™”๋ฅผ ์ข…๋ฃŒํ•˜๊ณ , ๋Œ€ํ™” ๋‚ด์šฉ์„ ์š”์•ฝํ•˜์—ฌ ๊ธฐ์–ต์œผ๋กœ ์ €์žฅ"""
        from .. import simulation_core
        if not self.is_conversation_active():
            return
        
        # ๋Œ€ํ™”๊ฐ€ ๋๋‚˜๋ฉด, ์ „์ฒด ๋Œ€ํ™” ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๊ธฐ์–ต์„ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๊ณ„๋ฅผ ์—…๋ฐ์ดํŠธ
        conv_to_process = self.active_conversation

        # 1. ๋Œ€ํ™” ์ƒํƒœ๋ฅผ ์ฆ‰์‹œ '์ข…๋ฃŒ'๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ๋ฉ”์ธ ๋ฃจํ”„๋ฅผ ํ•ด๋ฐฉ
        self.active_conversation = None
        simulation_core.add_log(f"---[๋Œ€ํ™” ์ข…๋ฃŒ: {reason}]---\n")

        # 2. ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋Š” '๊ธฐ์–ต ์ €์žฅ' ์ž‘์—…์€ ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒ˜๋ฆฌ.
        background_thread = threading.Thread(
            target=self._summarize_and_remember_in_background,
            args=(conv_to_process,)
        )
        background_thread.start()


    def next_turn(self):
        """๋Œ€ํ™”์˜ ๋‹ค์Œ ํ„ด์„ ์ง„ํ–‰, ์ƒ์„ฑ๋œ ๋Œ€์‚ฌ ์ฒ˜๋ฆฌ"""
        if not self.is_conversation_active():
            return
        
        conv = self.active_conversation
        speaker = conv.get_current_speaker()

        # ํ”Œ๋ ˆ์ด์–ด ํ„ด ์ฒ˜๋ฆฌ ๋กœ์ง
        if speaker.name == "player":
            conv.waiting_for_player = True
            return  # ํ”Œ๋ ˆ์ด์–ด์˜ ์‘๋‹ต์ด ์˜ฌ ๋•Œ๊นŒ์ง€ ๋Œ€ํ™” ํ„ด ์ง„ํ–‰์„ ๋ฉˆ์ถค

        # ์ƒํƒœ 1: ๋งˆ๋ฌด๋ฆฌ ๋‹จ๊ณ„ (์ž‘๋ณ„ ์ธ์‚ฌ)
        if conv.is_ending:
            utterance, _ = speaker.generate_dialogue_turn(conv, is_final_turn=True)
            self._add_and_log_utterance(speaker, utterance)
            self.end_conversation("์ž‘๋ณ„ ์ธ์‚ฌ ์™„๋ฃŒ")
            return

        # ์ƒํƒœ 2: ์ง„ํ–‰ ๋‹จ๊ณ„ (์ผ๋ฐ˜ ๋Œ€ํ™”)
        utterance, action = speaker.generate_dialogue_turn(conv)
        self._add_and_log_utterance(speaker, utterance)

        # ์ƒํƒœ ์ „ํ™˜ ๊ฒฐ์ •
        if action != "CONTINUE" or len(conv.conversation_history) >= 10:
            conv.is_ending = True
            conv.switch_turn()
        else:
            conv.switch_turn()