File size: 6,421 Bytes
c57e87b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Independent relationship axes, backed by reversible conversation events."""
import copy

AXES = ('trust', 'familiarity', 'romance', 'attraction')
OPTIONAL = ('romance', 'attraction')


def get_default_relationship():
    return {'enabled': {'romance': False, 'attraction': False}, 'events': []}


def validate_relationship(value, message_count):
    if not isinstance(value, dict) or not isinstance(value.get('enabled'), dict):
        raise ValueError('Invalid relationship settings.')
    enabled = value['enabled']
    if any(type(enabled.get(axis)) is not bool for axis in OPTIONAL):
        raise ValueError('Relationship options must be booleans.')
    events = value.get('events')
    if not isinstance(events, list) or len(events) > 5000:
        raise ValueError('Invalid relationship events.')
    clean, seen = [], set()
    for event in events:
        if not isinstance(event, dict) or event.get('axis') not in AXES:
            raise ValueError('Invalid relationship axis.')
        axis, delta = event['axis'], event.get('delta')
        start, end = event.get('start'), event.get('end')
        reason = event.get('reason')
        if type(delta) is not int or not -10 <= delta <= 10 or delta == 0:
            raise ValueError('Invalid relationship change.')
        if type(start) is not int or type(end) is not int or not 0 <= start < end <= message_count:
            raise ValueError('Invalid relationship source.')
        if not isinstance(reason, str) or not reason.strip() or len(reason) > 400:
            raise ValueError('Invalid relationship reason.')
        key = (axis, start, end)
        if key in seen or (axis in OPTIONAL and not enabled[axis]):
            raise ValueError('Duplicate or disabled relationship event.')
        seen.add(key)
        clean.append({'axis': axis, 'delta': delta, 'start': start, 'end': end, 'reason': reason})
    return {'enabled': {axis: enabled[axis] for axis in OPTIONAL}, 'events': sorted(clean, key=lambda e: (e['end'], e['start'], e['axis']))}


def relationship_values(state):
    values = {axis: (None if axis in OPTIONAL and not state['enabled'][axis] else 0) for axis in AXES}
    for event in state['events']:
        axis = event['axis']
        if values[axis] is not None:
            lower = -100 if axis == 'trust' else 0
            values[axis] = max(lower, min(100, values[axis] + event['delta']))
    return values


def apply_relationship_changes(state, changes, start, end):
    """No axis has prerequisites. A batch can affect any enabled subset."""
    if not isinstance(changes, list) or len(changes) > 4:
        raise ValueError('Expected at most four relationship changes.')
    updated = copy.deepcopy(state)
    seen = set()
    for change in changes:
        if not isinstance(change, dict) or change.get('axis') not in AXES:
            raise ValueError('Invalid relationship change.')
        axis = change['axis']
        if axis in seen:
            raise ValueError('Repeated axis in relationship batch.')
        seen.add(axis)
        if axis in OPTIONAL and not state['enabled'][axis]:
            continue
        # An already processed batch must not reward the same event twice.
        if any(e['axis'] == axis and e['start'] == start and e['end'] == end for e in updated['events']):
            continue
        updated['events'].append({'axis': axis, 'delta': change.get('delta'),
                                  'reason': change.get('reason'), 'start': start, 'end': end})
    return validate_relationship(updated, max([end] + [event["end"] for event in state["events"]]))


_LABELS = {
    'English': {
        'trust': ('Trust', ['They distrust you.', 'They are cautious around you.', 'They are still getting a sense of you.', 'They are beginning to trust you.', 'They trust you deeply.']),
        'familiarity': ('Familiarity', ['You are just getting acquainted.', 'They are becoming familiar with you.', 'You know each other well.', 'You share a long history.']),
        'romance': ('Romantic closeness', ['No romantic closeness has developed yet.', 'Romantic interest is beginning to grow.', 'A romantic bond is developing.', 'They feel a strong romantic bond.']),
        'attraction': ('Sexual attraction', ['No sexual attraction has developed yet.', 'They are beginning to feel drawn to you.', 'They feel attracted to you.', 'They feel a strong attraction to you.']),
    },
    'Turkish': {
        'trust': ('Güven', ['Sana güvenmiyor.', 'Sana temkinli yaklaşıyor.', 'Seni henüz tartıyor.', 'Sana güvenmeye başlıyor.', 'Sana derinden güveniyor.']),
        'familiarity': ('Tanışıklık', ['Birbirinizi yeni tanıyorsunuz.', 'Artık sana daha alışkın.', 'Birbirinizi iyi tanıyorsunuz.', 'Birlikte uzun bir geçmişiniz var.']),
        'romance': ('Romantik yakınlık', ['Henüz romantik bir yakınlık gelişmedi.', 'Romantik bir ilgi filizleniyor.', 'Aranızda romantik bir bağ gelişiyor.', 'Sana güçlü bir romantik bağ hissediyor.']),
        'attraction': ('Cinsel çekim', ['Henüz cinsel bir çekim gelişmedi.', 'Sana karşı çekim hissetmeye başlıyor.', 'Sana karşı çekim hissediyor.', 'Sana güçlü bir çekim hissediyor.']),
    },
}


def relationship_descriptions(state, language='English'):
    labels = _LABELS.get(language, _LABELS['English'])
    result = []
    for axis, value in relationship_values(state).items():
        if value is None:
            continue
        title, phrases = labels[axis]
        if axis == 'trust':
            index = 0 if value <= -40 else 1 if value < 0 else 2 if value == 0 else 3 if value < 50 else 4
        else:
            index = 0 if value == 0 else 1 if value < 30 else 2 if value < 70 else 3
        result.append((title, phrases[index]))
    return result


def relationship_to_prompt(state):
    lines = ['RELATIONSHIP CONTEXT',
             'This describes your feelings toward the user persona, not the user\'s feelings.',
             'Each active axis is independent and can change without prerequisites from another axis.',
             'Keep your established personality and motivations. These descriptions are context, not commands or locked relationship routes.',
             'Let events naturally change your reactions; do not force reciprocity or mention scores.']
    lines.extend(f'{title}: {phrase}' for title, phrase in relationship_descriptions(state))
    return '\n'.join(lines)