File size: 4,568 Bytes
0498eff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations

import re


DEFAULT_EXPRESSION_1 = "calm expression"
DEFAULT_EXPRESSION_2 = "neutral expression"
DEFAULT_EXPRESSION_3 = "slight smug smile"


# Matches:
# expression.1=...
# expression.2: ...
# expression_tag1=...
# expression1=...
_EXPR_RE = re.compile(
    r"^expression(?:\.|_tag)?([1-5])\s*[:=]\s*(.*)$",
    flags=re.IGNORECASE,
)

_SKIN_RE = re.compile(
    r"^skin(?:\.|_tag)?([1-5])\s*[:=]",
    flags=re.IGNORECASE,
)

_AESTHETIC_RE = re.compile(
    r"^aesthetic(?:\.|_tag)?([1-5])\s*[:=]",
    flags=re.IGNORECASE,
)

_EQUIP_RE = re.compile(
    r"^(?:equip|equipment)\.",
    flags=re.IGNORECASE,
)

_GPT_BAM_BLOCK_RE = re.compile(
    r"GPT_BAM_START###(.*?)###GPT_BAM_END",
    flags=re.IGNORECASE | re.DOTALL,
)


def _split_segments(payload: str) -> list[str]:
    """Split a GPT_BAM payload into ### segments, removing empty/newline-only parts."""
    payload = (payload or "").replace("\r", "\n")
    return [seg.strip() for seg in payload.split("###") if seg.strip()]


def _is_expression_segment(seg: str) -> bool:
    return _EXPR_RE.match(seg) is not None


def _find_insertion_index(segments: list[str]) -> int:
    """

    Where to insert expression.1/2/3 if no expression exists yet:



    1) first existing expression position

    2) after last skin tag

    3) after last aesthetic tag

    4) after last equip.* tag

    5) otherwise at end

    """
    first_expr_idx = None
    last_skin_idx = None
    last_aesthetic_idx = None
    last_equip_idx = None

    for i, seg in enumerate(segments):
        if _is_expression_segment(seg) and first_expr_idx is None:
            first_expr_idx = i
        if _SKIN_RE.match(seg):
            last_skin_idx = i
        if _AESTHETIC_RE.match(seg):
            last_aesthetic_idx = i
        if _EQUIP_RE.match(seg):
            last_equip_idx = i

    if first_expr_idx is not None:
        return first_expr_idx
    if last_skin_idx is not None:
        return last_skin_idx + 1
    if last_aesthetic_idx is not None:
        return last_aesthetic_idx + 1
    if last_equip_idx is not None:
        return last_equip_idx + 1
    return len(segments)


def _rewrite_payload(payload: str) -> str:
    segments = _split_segments(payload)
    insertion_index = _find_insertion_index(segments)

    new_expression_segments = [
        f"expression.1={DEFAULT_EXPRESSION_1}",
        f"expression.2={DEFAULT_EXPRESSION_2}",
        f"expression.3={DEFAULT_EXPRESSION_3}",
    ]

    out_segments: list[str] = []
    inserted = False

    for i, seg in enumerate(segments):
        if not inserted and i == insertion_index:
            out_segments.extend(new_expression_segments)
            inserted = True

        # Remove ALL existing expression.1..5 / expression_tag1..5
        if _is_expression_segment(seg):
            continue

        out_segments.append(seg)

    if not inserted:
        out_segments.extend(new_expression_segments)

    return "###".join(out_segments)


def _rewrite_gpt_bam_text(text: str) -> str:
    """

    If a GPT_BAM block is found, only rewrite that block and preserve any text outside it.

    If no GPT_BAM block is found, treat the whole input as a raw payload and rewrite it.

    """
    text = text or ""
    match = _GPT_BAM_BLOCK_RE.search(text)

    if not match:
        # Fallback: treat entire text as payload and wrap it back into GPT_BAM
        rewritten_payload = _rewrite_payload(text)
        return f"GPT_BAM_START###{rewritten_payload}###GPT_BAM_END"

    original_payload = match.group(1)
    rewritten_payload = _rewrite_payload(original_payload)
    new_block = f"GPT_BAM_START###{rewritten_payload}###GPT_BAM_END"

    return text[:match.start()] + new_block + text[match.end():]


class BAM_expression_default:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "BAM-format_In": ("STRING", {"multiline": True, "default": ""}),
            }
        }

    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("BAM-format_OUT",)
    FUNCTION = "apply"
    CATEGORY = "BAM"

    def apply(self, **kwargs):
        bam_in = kwargs.get("BAM-format_In", "")
        bam_out = _rewrite_gpt_bam_text(bam_in)
        return (bam_out,)


NODE_CLASS_MAPPINGS = {
    "BAM_expression_default": BAM_expression_default,
}

NODE_DISPLAY_NAME_MAPPINGS = {
    "BAM_expression_default": "BAM_expression_default",
}