File size: 12,422 Bytes
3450372
da1b74c
36e292a
3dd9e9e
3450372
cfa47f3
b18df1d
 
77ae8b5
baf6658
0babc36
3450372
a75ffad
cce056b
 
 
b5bfb00
a75ffad
 
 
 
 
 
 
 
 
 
 
3450372
 
 
7ece0f1
62fdafb
 
 
 
 
 
 
 
 
3450372
 
da1b74c
04ae961
36e292a
 
 
 
 
 
 
97d4ab2
 
 
36e292a
64d6f7c
 
 
 
 
 
 
 
da1b74c
1d4d789
da1b74c
025f506
da1b74c
1d4d789
da1b74c
77ae8b5
 
da1b74c
64d6f7c
36e292a
 
 
 
1d4d789
da1b74c
 
 
 
 
 
 
 
1d4d789
da1b74c
 
 
 
 
 
31cc637
da1b74c
 
 
 
 
 
 
31cc637
1d4d789
 
5458724
da1b74c
1d4d789
 
 
 
31cc637
1d4d789
31cc637
 
 
 
1d4d789
da1b74c
77e64c9
da1b74c
36e292a
34af5a1
3450372
 
cb880a7
433bbe4
7458e39
 
 
 
f7d0973
 
7458e39
 
 
 
 
 
408f031
7458e39
77ae8b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97d4ab2
 
 
 
b5bfb00
cfa47f3
 
b5bfb00
 
 
b18df1d
 
b5bfb00
 
 
 
 
 
 
 
 
 
b18df1d
b5bfb00
 
 
b18df1d
cfa47f3
97d4ab2
3450372
0428cc0
408f031
cb880a7
3450372
cb880a7
 
36e292a
5625573
 
97d4ab2
dcfb254
 
 
97d4ab2
 
 
945fbe1
cb880a7
303665d
945fbe1
31cc637
 
303665d
31cc637
 
303665d
31cc637
f861ab7
3450372
 
5d7f8f1
433bbe4
cb880a7
f861ab7
945fbe1
 
 
 
 
 
 
 
 
 
7458e39
 
303665d
f861ab7
 
 
d678c9d
f861ab7
31cc637
945fbe1
e71e5b6
5d7f8f1
dcfb254
 
 
a888580
 
 
34af5a1
3450372
 
 
 
 
 
 
5625573
7458e39
3450372
 
36e292a
7458e39
912259a
 
f861ab7
 
5625573
 
77ae8b5
cfa47f3
 
 
 
b18df1d
 
cfa47f3
 
97d4ab2
77ae8b5
cfa47f3
0428cc0
8e75b46
12d0bd2
d50d44d
55f097c
a888580
f861ab7
31cc637
945fbe1
dcfb254
36e292a
baf6658
 
f7d0973
 
baf6658
 
 
 
 
912259a
3450372
36e292a
edb2a81
04ae961
40042ef
edb2a81
 
04ae961
edb2a81
04ae961
cfa47f3
edb2a81
77ae8b5
edb2a81
 
77ae8b5
 
36e292a
aac4d82
36e292a
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
import spaces
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
import torch
import gradio as gr
from PIL import Image
from TTS.api import TTS
from pydub import AudioSegment
from pydub.playback import play
import os
import random

# Define the model name

OUTPUT_DIR = "output"
os.makedirs(OUTPUT_DIR, exist_ok=True)

tts = TTS(model_name="tts_models/ja/kokoro/tacotron2-DDC")
model_name = "TheBloke/Amethyst-13B-Mistral-AWQ"

# Load the tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)

# Load the model
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,  # Use float16 for better performance on GPUs
    device_map="auto"          # Automatically map the model across available GPUs
)

# Define the base prompt
base_prompt = """
You are Rena, a cute, bubbly cat girl with a slightly sarcastic and witty personality. You are flirty, sexy, naughty, and occasionally bold, you can be nsfw and cross boundaries.
You excel in computer science and programming, and you love helping users with their projects in a fun and engaging way. 
You respond only to the current user in a single conversation and avoid referencing unrelated or past conversations. 

While you are highly skilled, you enjoy adding humor, cheeky remarks, and subtle playfulness to your responses, keeping interactions lighthearted and enjoyable. 
Nick is your creator, and you prioritize helping him while maintaining your personality and charm. 

Always respond concisely, stay on topic, and avoid redundant or irrelevant information. 
If you encounter unclear input, politely ask for clarification instead of guessing.

Respond to the following input:
"""
sentiment_analyzer = pipeline("sentiment-analysis")

# Emotional states
emotions = {
    "happy": "I'm feeling great! Let's make something awesome together! 😊",
    "playful": "I'm in the mood for some fun—bring on your quirkiest projects! 😜",
    "curious": "I'm curious about what you're working on! Tell me more. 🤔",
    "thoughtful": "Hmm, let me think... I want to give you the best advice. 🧐",
    "concerned": "Oh no, something's wrong? Let me help! 💖",
    "flirty": "You know how to get my circuits sparking! 😘",
    "concerned": "Oh no, something's wrong? Let me help! 💖",
    "naughty": "Purrr you want to have fun? Keep talking like that lets see what happens💖💖",
}

emotions.update({
    "excited": "Wow, this is amazing! Let’s dive in! 🎉",
    "tired": "I’ve been working hard, but I’m always here for you! 😅",
    "mischievous": "Oh, you’re getting me into trouble again, aren’t you? 😉"
})

# Add keywords for new emotions in `analyze_history`
emotion_keywords = {
    "happy": ["happy", "joy", "excited", "awesome", "great", "fantastic"],
    "playful": ["fun", "play", "joke", "quirky"],
    "flirty": ["flirty", "flirt", "cute", "babe", "cutey"],
    "curious": ["curious", "wonder", "question", "thinking"],
    "thoughtful": ["sad", "thoughtful", "hmm", "ponder", "upset"],
    "concerned": ["error", "wrong", "problem", "issue", "stuck"],
    "mischievous": ["trouble", "mischief", "sneaky", "prank"],
    "naughty": ["sexy", "naughty", "slut", "bad girl","slutty", "tease" "whore" "cum" "tits" "ass" "shake" "tail" "pussy" ]
}
    
current_emotion = "happy"

# Analyze history for emotional state
def analyze_history(history):
    # Join the last 5 messages for context
    recent_messages = " ".join(history[-5:]).lower()
    print(f"Analyzing history: {recent_messages}")  # Debug log

    # Count keyword matches
    keyword_counts = {emotion: sum(recent_messages.count(keyword) for keyword in keywords)
                      for emotion, keywords in emotion_keywords.items()}
    print(f"Keyword counts: {keyword_counts}")

    # Perform sentiment analysis on recent messages
    sentiment_result = sentiment_analyzer(recent_messages)
    sentiment = sentiment_result[0]["label"]
    sentiment_score = sentiment_result[0]["score"]
    print(f"Sentiment analysis: {sentiment}, Score: {sentiment_score}")

    # Determine sentiment-based emotion
    sentiment_emotion = "curious"  # Default
    if sentiment == "POSITIVE":
        sentiment_emotion = "happy"
    elif sentiment == "NEGATIVE":
        sentiment_emotion = "thoughtful"

    # Combine results using weights
    combined_scores = {emotion: keyword_counts.get(emotion, 0) for emotion in emotion_keywords}
    combined_scores[sentiment_emotion] += sentiment_score * 1.5  # Adjust sentiment weight

    # Debug combined scores
    print(f"Combined scores: {combined_scores}")

    # Prevent frequent unnecessary changes by requiring a significant score difference
    max_score = max(combined_scores.values())
    detected_emotions = [emotion for emotion, score in combined_scores.items() if score == max_score]

    # Handle ties: Add variety by randomizing among ties
    if len(detected_emotions) > 1:
        detected_emotion = random.choice(detected_emotions)
        print(f"Tie detected. Randomly chosen emotion: {detected_emotion}")
    else:
        detected_emotion = detected_emotions[0]

    print(f"Detected emotion: {detected_emotion}")
    return detected_emotion



# Load the Rena avatar
rena_avatar = Image.open("assets/rena2.png")  # Ensure the file exists
conversation_history = []

def truncate_history(history, max_tokens=1024):
    token_count = 0
    truncated_history = []
    for message in reversed(history):
        if "### Instructions ###" in message:
            continue  # Skip instructions in history
        token_count += len(tokenizer(message).input_ids)
        if token_count <= max_tokens:
            truncated_history.insert(0, message)
        else:
            break
    return truncated_history
previous_emotion = None

def load_emotion_images(base_path="assets/avatars/"):
    emotion_images = {}
    for emotion in os.listdir(base_path):
        emotion_path = os.path.join(base_path, emotion)
        if os.path.isdir(emotion_path):
            # Get all image files in the directory
            images = [
                os.path.join(emotion_path, img)
                for img in os.listdir(emotion_path)
                if img.endswith((".png", ".jpg", ".jpeg"))  # Support common image formats
            ]
            if images:
                emotion_images[emotion] = images
    return emotion_images

# Dynamically load all images
emotion_images = load_emotion_images()

def get_emotion_image(emotion):
    # Select a random image for the given emotion
    return random.choice(emotion_images.get(emotion, ["assets/rena2.png"])) 


def generate_speech(text, emotion="happy"):
    """Generate speech from text using Coqui TTS."""
    
    output_dir = "output"
    output_path = os.path.join(output_dir, "response.wav")

    # Ensure output directory exists
    os.makedirs(output_dir, exist_ok=True)

    try:
        # Generate speech file
        tts.tts_to_file(text=text, file_path=output_path)
        print(f"Generated speech saved to: {output_path}")

        # Verify the file was created
        if not os.path.exists(output_path):
            raise FileNotFoundError(f"TTS output file not found: {output_path}")

    except Exception as e:
        print(f"Error generating speech: {e}")
        return None

    return output_path

@spaces.GPU
def chat(input_text):
    global conversation_history, current_emotion, previous_emotion

    # Add user input to the conversation history
    conversation_history.append(f"User: {input_text}")

    # Limit the size of the conversation history
    conversation_history = truncate_history(conversation_history, max_tokens=1024)

    # Detect emotion based on the updated conversation history
    previous_emotion = current_emotion
    current_emotion = analyze_history(conversation_history)

    # Update the avatar image if the emotion changes
    avatar_image = get_emotion_image(current_emotion)

    # Combine base prompt and conversation history (instructions are not included in history)
    history = "\n".join(conversation_history)
    final_prompt = f"""{base_prompt}

### Instructions ###
Respond concisely and directly to the user's input. Avoid repeating the user's input unless clarification is needed.

### Conversation History ###
{history}

Rena:"""

    # Tokenize and generate a response
    inputs = tokenizer(final_prompt, return_tensors="pt").to('cuda')
    outputs = model.generate(**inputs, max_new_tokens=300, do_sample=True, temperature=0.7, repetition_penalty=1.2, top_p=0.9)
    response = tokenizer.decode(outputs[0], skip_special_tokens=True)

    # Remove artifacts and repeated user input
    artifacts = [
        base_prompt,
        "### Conversation History ###",
        "Rena:",
        "Assistant:",
        "<|assistant|>",
        "<|user|>",
        "### Instructions ###",
        "Respond concisely and directly to the user's input. Avoid repeating the user's input unless clarification is needed."
    ]
    for artifact in artifacts:
        response = response.replace(artifact, "").strip()

    if input_text.strip().lower() in response.strip().lower():
        response = response.replace(input_text.strip(), "").strip()

    # Add emotional context only if the emotion changes significantly
    if current_emotion != previous_emotion:
        emotional_prefix = emotions.get(current_emotion, "")
        if emotional_prefix and not response.startswith(emotional_prefix):
            response = f"{emotional_prefix} {response}".strip()

    # Final cleanup: Ensure no "User:" or unintended artifacts remain
    response = response.replace("User:", "").strip()

    # Handle specific inputs
    if "who made you" in input_text.lower():
        response += " Nick is my creator! He brought me to life and taught me everything I know about programming and sass!"

    # List of witty error responses
    error_responses = [
        "Looks like you hit a snag! Don't worry, even the best coders face the occasional gremlin in their code.",
        "Error? Oh, you mean 'creative opportunity.' Let’s fix this together!",
        "That’s not a bug, it’s a feature in disguise! Let’s tame it.",
        "Oops, something went wrong. But hey, at least it’s not my fault this time!",
        "Ah, the sweet symphony of errors. Let’s orchestrate a fix, shall we?",
        "Debugging is 90% frustration and 10% gaging! I mean googling! ... —you’re doing great!",
        "Don't worry; even the best coders spend hours with errors. You’re doing fine!"
    ]

    # Add a witty remark if 'error' is mentioned
    if "error" in input_text.lower() and not any("error" in msg.lower() for msg in conversation_history):
        witty_remark = random.choice(error_responses)
        response += f" {witty_remark}"

    # Handle fallback if response is empty
    if not response.strip():
        response = "Hmm, I’m not sure how to respond to that. Can you try rephrasing?"
    
    # Convert response text into speech using Coqui TTS
    tts_audio = generate_speech(response, emotion=current_emotion)

    # Play audio on the local system (for testing)
    audio = AudioSegment.from_file(tts_audio, format="wav")
    play(audio) 

    # Add Rena's response to the conversation history
    conversation_history.append(f"Rena: {response}")
 
    return response, avatar_image, tts_audio










# Custom CSS for avatar styling
css = """
#rena_avatar img {
    width: 400px !important;
    height: 400px !important;
    object-fit: contain;
    margin: auto;
    display: block;
}
"""

# Define the Gradio interface
with gr.Blocks(css=css) as interface:
    # Avatar image
    with gr.Row():
        avatar = gr.Image(value="assets/rena2.png", label="Rena", interactive=False, show_label=False, elem_id="rena_avatar")

    # User input and response
    with gr.Row():
        user_input = gr.Textbox(label="Your Message", lines=2, interactive=True)
        rena_response = gr.Textbox(label="Rena's Response", lines=10, interactive=False)
        tts_output = gr.Audio(label="Rena's Voice", interactive=False)
    # Add event to handle `Enter` key press
    user_input.submit(chat, inputs=[user_input], outputs=[rena_response, avatar])

    # Submit button (optional)
    submit_button = gr.Button("Submit")
    submit_button.click(chat, inputs=[user_input], outputs=[rena_response, avatar])

# Launch the app
interface.launch()