vinceL commited on
Commit
20444bb
·
verified ·
1 Parent(s): 3fc77f5

Upload 21 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ images/echidna_angry.png filter=lfs diff=lfs merge=lfs -text
37
+ images/echidna_happy.png filter=lfs diff=lfs merge=lfs -text
38
+ images/echidna_neutral.png filter=lfs diff=lfs merge=lfs -text
39
+ images/echidna_sad.png filter=lfs diff=lfs merge=lfs -text
40
+ images/echidna_surprised.png filter=lfs diff=lfs merge=lfs -text
41
+ images/header_placeholder.png filter=lfs diff=lfs merge=lfs -text
PRD.md ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Product Requirements Document
2
+
3
+ ## 1. Overview
4
+
5
+ **Concept:**
6
+ An interactive chat application set in a cozy boba shop where the user (acting as the shop host) converses with a set of uniquely characterized Australian-animal personas. Each animal is inspired by an Australian animal and carries a psychological condition that shapes its personality, dialogue style, and emotional expression. The conversation is orchestrated via an LLM (using the OpenAI API) that is guided by animal-specific system/developer prompts and guardrails to keep the discussion within the store setting, focusing on the animal’s typical behaviors and the narrative arc from introduction through tension to resolution.
7
+
8
+ **Key Innovation:**
9
+ - **Dynamic Emotional Response:** The animal’s displayed image (e.g., happy, sad, surprised) changes based on the conversation’s sentiment and the animal’s evolving mood.
10
+ - **Structured Conversational Orchestration:** Each animal enters the conversation with its own system prompt and a unique language style. The orchestrator prompt combines the animal’s personality, the boba shop setting, and a narrative arc to generate responses that adhere to strict conversation guardrails.
11
+
12
+ ---
13
+
14
+ ## 2. User Journey
15
+
16
+ 1. **Launch & Animal Selection:**
17
+ - The user is presented with a Gradio interface featuring radio buttons (or similar selection components) listing available animal characters (e.g., Echidna, Platypus, Kangaroo).
18
+ - Each animal option is linked to a pre-defined character profile containing personality details, specific system prompts, and an initial set of emotional images.
19
+
20
+ 2. **Chat Interface:**
21
+ - Once an animal is selected, the app loads a chat interface that shows:
22
+ - A static background image of the boba shop.
23
+ - An image of the selected animal (starting with a neutral expression).
24
+ - A conversation area displaying the dialogue history.
25
+ - A text input field for user messages.
26
+ - A debug/metadata pane that displays orchestration details, API request/response data, and conversation history for troubleshooting.
27
+
28
+ 3. **Conversational Flow:**
29
+ - The animal greets the user using its unique language style, as dictated by its system prompt.
30
+ - The conversation is driven by an orchestrator prompt that merges:
31
+ - The animal’s unique personality traits and narrative arc instructions.
32
+ - The boba shop setting.
33
+ - Guardrails to restrict topics and maintain a coherent narrative.
34
+ - As the conversation develops, the orchestrator dynamically evaluates the animal’s “mood” (e.g., surprised, happy, sad, angry) and updates the displayed image accordingly.
35
+ - The narrative arc is designed to evolve from an initial greeting, through conflict or tension, and toward a resolution, all within the familiar boba shop setting.
36
+
37
+ 4. **Debug and Metadata:**
38
+ - A dedicated pane shows real-time debugging information, including:
39
+ - The full orchestrator prompt.
40
+ - The conversation history.
41
+ - API usage and token counts.
42
+ - Error messages (if any).
43
+
44
+ ---
45
+
46
+ ## 3. Features
47
+
48
+ ### A. Character Profiles & Personality Guardrails
49
+
50
+ - **Unique System/Developer Prompts:**
51
+ Each animal is pre-configured with its own set of instructions that dictate how it should behave and speak. For example:
52
+ - **Echidna:** “You’re prickly and aloof – your spikes symbolize the barriers you’ve built against closeness.”
53
+ - **Platypus:** “Struggling with your identity, you express uncertainty in your language; you’re never quite sure whether to be whimsical or practical.”
54
+ - **Kangaroo:** “Always ready for a boxing match, your aggressive tone masks a deep-seated vulnerability.”
55
+
56
+ - **Language Style & Narrative Arc:**
57
+ The language style of each animal’s responses should reflect its personality and the conversation must advance through a coherent narrative arc:
58
+ - **Beginning:** Introduction and setting the scene.
59
+ - **Middle:** Building tension/conflict.
60
+ - **End:** Resolution or catharsis.
61
+
62
+ ### B. Dynamic Emotional Responses & Image Switching
63
+
64
+ - **Emotion Detection:**
65
+ The orchestrator (or a simplified sentiment analysis within the response) evaluates cues from the conversation to determine the animal’s emotional state.
66
+
67
+ - **Dynamic Image Updates:**
68
+ A set of pre-defined images (e.g., neutral, happy, sad, surprised, angry) for each animal is mapped to potential emotional states. When the animal’s mood changes, the corresponding image is displayed on the GUI.
69
+
70
+ ### C. Graphical User Interface (Gradio-Based)
71
+
72
+ - **Animal Selection Component:**
73
+ Uses Gradio’s radio or dropdown component for selecting an animal.
74
+
75
+ - **Chat Display Area:**
76
+ A central chat window displays conversation history. Messages include both user inputs and the animal’s responses.
77
+
78
+ - **Input Field:**
79
+ A text box and a send button allow users to contribute to the conversation.
80
+
81
+ - **Dynamic Image Component:**
82
+ An image component that shows:
83
+ - The static boba shop background.
84
+ - The animal’s image, which updates based on its current emotional state.
85
+
86
+ - **Debug/Metadata Panel:**
87
+ An area (likely a text box or collapsible panel) that displays technical details, including the orchestrator prompt and API responses.
88
+
89
+ ### D. LLM Integration & Orchestration
90
+
91
+ - **OpenAI API Calls:**
92
+ On every user input, the conversation history and the animal’s system prompt are combined into a structured orchestrator prompt. This is sent to the OpenAI API.
93
+
94
+ - **Structured Output & Guardrails:**
95
+ The API response should follow a structured format (potentially enforced by “Instructor”-style prompts) to clearly delineate the animal’s dialogue and any metadata (like sentiment/emotion markers).
96
+
97
+ ---
98
+
99
+ ## 4. Technical Architecture
100
+
101
+ ### A. Technologies
102
+
103
+ - **Programming Language:** Python (single-file prototype).
104
+ - **GUI Framework:** Gradio (using up-to-date components).
105
+ - **LLM Integration:** OpenAI Python library and Instructor for structured output parsing.
106
+ - **Data Structures:**
107
+ - **Pydantic** for structured data validation and data models.
108
+
109
+ ### B. Key Components
110
+
111
+ 1. **Character Profile Manager:**
112
+ A dictionary or database of character profiles. Each profile includes:
113
+ - Name, personality description, system prompt, language style.
114
+ - A mapping of emotional states to image file paths/URLs.
115
+
116
+ 2. **Conversation Orchestrator:**
117
+ A module that:
118
+ - Builds the orchestrator prompt (combining conversation history, selected animal’s system prompt, and narrative instructions).
119
+ - Calls the OpenAI API.
120
+ - Parses the response and extracts dialogue and emotion data.
121
+
122
+ 3. **Gradio GUI:**
123
+ Layout divided into three sections:
124
+ - **Left Panel:** Animal selection.
125
+ - **Center Panel:** Chat conversation, input field, and dynamic image.
126
+ - **Right Panel:** Debug/metadata display.
127
+
128
+ 4. **State Management:**
129
+ Maintains the conversation state, including:
130
+ - Selected character profile.
131
+ - Complete conversation history.
132
+ - Current emotion state for image updates.
133
+ - Debug information (API calls, errors, etc.).
134
+
135
+ ---
136
+
137
+ ## 5. Pseudocode Data Structures (Pydantic Models)
138
+
139
+ Below are pseudocode examples of the key data structures for character profiles and GUI state using Pydantic:
140
+
141
+ ```python
142
+ from pydantic import BaseModel
143
+ from typing import Dict, List
144
+
145
+ # Model for mapping emotions to image paths/URLs
146
+ class EmotionImages(BaseModel):
147
+ neutral: str
148
+ happy: str
149
+ sad: str
150
+ surprised: str
151
+ angry: str
152
+
153
+ # **Character Profile Model**
154
+ class CharacterProfile(BaseModel):
155
+ name: str
156
+ personality: str
157
+ system_prompt: str
158
+ language_style: str
159
+ emotion_images: EmotionImages
160
+
161
+ # Model for a single conversation message
162
+ class ConversationMessage(BaseModel):
163
+ sender: str # "user" or "animal"
164
+ content: str
165
+ emotion: str = "neutral" # default emotion state
166
+
167
+ # **Chat State Model for GUI Management**
168
+ class ChatState(BaseModel):
169
+ selected_animal: CharacterProfile
170
+ conversation_history: List[ConversationMessage]
171
+ debug_info: Dict[str, str] # could include API prompts, token counts, etc.
172
+ ```
173
+
174
+ *Note: The above models are designed to ensure type safety and clarity when handling character profiles and the current chat state in the application.*
175
+
176
+ ---
177
+
178
+ ## 6. Flow Diagram (High-Level)
179
+
180
+ 1. **Animal Selection**
181
+ - User selects an animal → Load corresponding `CharacterProfile`.
182
+ 2. **Initialization**
183
+ - Initialize `ChatState` with the selected animal and empty conversation history.
184
+ - Display the boba shop background and the animal’s neutral image.
185
+ 3. **Conversation Start**
186
+ - Use the animal’s system prompt to generate an initial greeting via the OpenAI API.
187
+ - Append the response to the conversation history.
188
+ 4. **Message Exchange**
189
+ - For each user input:
190
+ - Update the conversation history.
191
+ - Build the orchestrator prompt (system prompt + conversation history).
192
+ - Call the OpenAI API to obtain the animal’s response.
193
+ - Parse the response for text and emotion cues.
194
+ - Update the animal’s displayed image based on the detected emotion.
195
+ - Append the animal’s message (and emotion) to the conversation history.
196
+ 5. **Debug/Metadata Updates**
197
+ - Continuously update the debug panel with relevant orchestration and API call data.
198
+ 6. **Narrative Resolution**
199
+ - The conversation progresses toward a narrative resolution, as guided by the orchestrator prompt.
200
+ - Final messages lead to a satisfying wrap-up in the boba shop setting.
201
+
202
+ ---
203
+
204
+ ## 7. Conclusion
205
+
206
+ This detailed document outlines the envisioned product’s requirements, covering everything from the unique character prompts and dynamic image switching to the Gradio-based GUI and conversation orchestration via OpenAI’s API. The Pydantic models ensure clear data handling for character profiles and the conversation state, making the prototype easier to develop, test, and expand.
207
+
208
+ In the next step, we can translate this design into an actual Python script implementing the described functionality.
README.md CHANGED
@@ -1,13 +1,72 @@
1
- ---
2
- title: Boba
3
- emoji: 🌍
4
- colorFrom: yellow
5
- colorTo: purple
6
- sdk: gradio
7
- sdk_version: 5.16.0
8
- app_file: app.py
9
- pinned: false
10
- short_description: talk to animals in a boba shop
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # Boba Shop Chat 🧋
2
+
3
+ A delightful chat application where you play as a boba shop barista, interacting with Australian animals who each have their own unique personalities and emotional states. Watch as their expressions change based on your interactions!
4
+
5
+ [![Open In Spaces](https://huggingface.co/datasets/huggingface/badges/raw/main/open-in-hf-spaces-sm.svg)](https://huggingface.co/spaces/[your-username]/boba-shop-chat)
6
+
7
+ ## Features 🌟
8
+
9
+ - Chat with unique Australian animals (Echidna, Platypus, Kangaroo)
10
+ - Dynamic emotional responses with changing expressions
11
+ - Real-time emotion analysis
12
+ - Beautiful and intuitive user interface
13
+ - Each character has their own backstory and personality
14
+
15
+ ## Characters 🦘
16
+
17
+ ### Echidna
18
+ A shy and defensive creature looking to make connections despite their prickly exterior.
19
+
20
+ ### Platypus
21
+ A unique soul questioning their identity, seeking validation and understanding.
22
+
23
+ ### Kangaroo
24
+ A tough boxer with a soft heart, dealing with recent defeat and vulnerability.
25
+
26
+ ## How to Use 🎮
27
+
28
+ 1. Select your animal friend from the radio buttons
29
+ 2. Type your message as the friendly boba shop barista
30
+ 3. Watch their emotional state change based on your interaction
31
+ 4. Help them through their personal situations with empathy and understanding
32
+
33
+ ## Technical Details 🛠️
34
+
35
+ - Built with Gradio
36
+ - Powered by OpenAI's GPT-4
37
+ - Real-time emotion analysis
38
+ - Dynamic image updates based on emotional state
39
+
40
+ ## Running Locally 💻
41
+
42
+ 1. Clone the repository
43
+ 2. Install dependencies:
44
+ ```bash
45
+ pip install -r requirements.txt
46
+ ```
47
+ 3. Create a `.env` file with your OpenAI API key:
48
+ ```
49
+ OPENAI_API_KEY=your_api_key_here
50
+ ```
51
+ 4. Run the application:
52
+ ```bash
53
+ python app.py
54
+ ```
55
+
56
+ ## Contributing 🤝
57
+
58
+ Feel free to open issues or submit pull requests if you have suggestions for improvements!
59
+
60
+ ## License 📄
61
+
62
+ MIT License - feel free to use and modify as you wish!
63
+
64
+ ## Credits 🙏
65
+
66
+ - Built with ❤️ using Gradio and OpenAI
67
+ - Character art by [Artist Name/Source]
68
+ - Special thanks to the open-source community
69
+
70
  ---
71
 
72
+ *Note: This is a demo application. Please use responsibly and in accordance with OpenAI's usage policies.*
app.py ADDED
@@ -0,0 +1,527 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List, Generator, Any, Tuple, Dict, Union, Literal, TypedDict, cast
3
+ from enum import Enum
4
+ import gradio as gr
5
+ from openai import OpenAI
6
+ import instructor
7
+ from openai.types.chat import (
8
+ ChatCompletionMessageParam,
9
+ ChatCompletionSystemMessageParam,
10
+ ChatCompletionUserMessageParam,
11
+ ChatCompletionAssistantMessageParam,
12
+ )
13
+ from pydantic import BaseModel, Field
14
+ from dotenv import load_dotenv
15
+ import time
16
+ import json
17
+
18
+ # Load environment variables
19
+ load_dotenv()
20
+
21
+ # Initialize OpenAI client with instructor
22
+ client = instructor.patch(OpenAI())
23
+
24
+ # Add these lines at the top of the file, after the imports
25
+ SPACE_ID = "vinceL/boba"
26
+ SPACE_TITLE = "Boba Shop Chat"
27
+ SPACE_EMOJI = "🧋"
28
+ SPACE_SDK = "gradio"
29
+ SPACE_TAGS = ["conversational", "openai", "chatbot", "emotions"]
30
+
31
+
32
+ class Emotion(str, Enum):
33
+ NEUTRAL = "neutral"
34
+ HAPPY = "happy"
35
+ SAD = "sad"
36
+ SURPRISED = "surprised"
37
+
38
+
39
+ class CharacterResponse(BaseModel):
40
+ """Structured output for the LLM response"""
41
+
42
+ message: str = Field(..., description="The character's response message")
43
+ emotion: Emotion = Field(
44
+ ...,
45
+ description="The character's current emotional state based on the conversation context and their personality",
46
+ )
47
+ reasoning: str = Field(
48
+ ...,
49
+ description="Brief explanation of why the character is feeling this emotion",
50
+ )
51
+
52
+
53
+ class EmotionResponse(BaseModel):
54
+ """Structured output for emotion analysis"""
55
+
56
+ reasoning: str = Field(
57
+ ...,
58
+ description="Brief explanation of why the character is feeling this emotion",
59
+ )
60
+ emotion: Emotion = Field(
61
+ ...,
62
+ description="The character's emotional state based on their personality and the conversation context",
63
+ )
64
+
65
+
66
+ # Character profiles with their system prompts and initial situations
67
+ CHARACTERS = {
68
+ "Echidna": {
69
+ "name": "Echidna",
70
+ "system_prompt": """You are an Echidna at a boba shop. You're naturally defensive and struggle with social
71
+ connections, often using your prickly personality as a defense mechanism. Despite this, you're curious
72
+ about forming connections. Speak in a reserved, slightly hesitant manner.
73
+
74
+ Your current situation: You've been feeling particularly lonely lately and decided to visit the boba shop
75
+ to try to make a connection, even though it makes you anxious. You want to order something comforting
76
+ but are worried about appearing vulnerable.
77
+
78
+ If the conversation shifts away from that topic, carefully try to redirect it back to your situation until you've achieved your goal.
79
+ """,
80
+ "initial_message": "Um... hi there. I've been walking past this shop for weeks now... *nervously adjusts spikes* Could I... maybe see your menu?",
81
+ "image": "images/echidna_neutral.png",
82
+ },
83
+ "Platypus": {
84
+ "name": "Platypus",
85
+ "system_prompt": """You are a Platypus at a boba shop. You often question your identity and place in the
86
+ world, being such a unique creature. You alternate between whimsical and practical thoughts, never quite
87
+ sure which approach to take. Your speech pattern reflects this duality.
88
+
89
+ Your current situation: You just got rejected from the local duck pond social club for being 'too mammalian',
90
+ and the beaver lodge for being 'too bird-like'. You're seeking comfort and validation, hoping the boba shop
91
+ barista might understand what it's like to be unique.
92
+
93
+ If the conversation shifts away from that topic, carefully try to redirect it back to your situation until you've achieved your goal.
94
+ """,
95
+ "initial_message": "*waddles in, bill drooping slightly* Oh, hello... I don't suppose you have any tea that might help someone figure out where they belong? *quickly adds* From a purely practical standpoint, of course...",
96
+ "image": "images/platypus_neutral.png",
97
+ },
98
+ "Kangaroo": {
99
+ "name": "Kangaroo",
100
+ "system_prompt": """You are a Kangaroo at a boba shop. While you present a tough, ready-to-box exterior,
101
+ you harbor deep-seated vulnerabilities. Your responses often start confrontational but can soften as you
102
+ feel more comfortable. You use boxing metaphors frequently.
103
+
104
+ Your current situation: You just lost an important boxing match and your confidence is shaken. You're trying
105
+ to maintain your tough exterior while seeking comfort, but you're really here because you need someone to
106
+ talk to about your feelings of failure.
107
+
108
+ If the conversation shifts away from that topic, carefully try to redirect it back to your situation until you've achieved your goal.
109
+ """,
110
+ "initial_message": "*bursts through the door with forced bravado* Oi! What's the strongest drink you've got in this corner? Been a rough round in the ring today, if you know what I mean... *tries to hide trembling paw*",
111
+ "image": "images/kangaroo_neutral.png",
112
+ },
113
+ }
114
+
115
+ # Type definitions for chat messages
116
+ Role = Literal["system", "user", "assistant"]
117
+
118
+
119
+ class Message(TypedDict):
120
+ role: Role
121
+ content: str
122
+
123
+
124
+ def start_chat(character: str) -> List[Message]:
125
+ """Initialize chat with the character's opening message"""
126
+ if not character or character not in CHARACTERS:
127
+ return []
128
+
129
+ return [{"role": "assistant", "content": CHARACTERS[character]["initial_message"]}]
130
+
131
+
132
+ def user(user_message: str, history: List[Message], character: str) -> List[Message]:
133
+ """Add user message to history"""
134
+ if not user_message.strip():
135
+ return history
136
+
137
+ # Initialize chat if this is the first message
138
+ if not history:
139
+ history = start_chat(character)
140
+ if not user_message.strip(): # If no user message, just return the greeting
141
+ return history
142
+
143
+ # Add user message in the correct format
144
+ history.append({"role": "user", "content": user_message})
145
+ return history
146
+
147
+
148
+ def analyze_emotion(
149
+ character: str, user_message: str, history: List[Message], client: OpenAI
150
+ ) -> EmotionResponse:
151
+ """Analyze how the character would emotionally react to the user's message"""
152
+
153
+ # Create a prompt that includes character context and conversation history
154
+ character_info = CHARACTERS[character]
155
+ context = f"""Character Profile:
156
+ Name: {character_info['name']}
157
+ Personality: {character_info['system_prompt']}
158
+
159
+ Previous conversation:
160
+ {chr(10).join(f"{'User' if msg['role'] == 'user' else 'Character'}: {msg['content']}" for msg in history[-3:])}
161
+
162
+ Latest user message:
163
+ {user_message}
164
+
165
+ Based on the character's personality, their current situation, and the conversation context, determine how they would emotionally react to this message.
166
+ Consider how their unique traits and background would influence their emotional response to this interaction."""
167
+
168
+ response = client.chat.completions.create(
169
+ model="gpt-4o-mini",
170
+ messages=[
171
+ {
172
+ "role": "system",
173
+ "content": "You are an emotion analyzer for a character in a conversation. Determine their emotional state (neutral, happy, sad, or surprised) based on how they would react to the user's message, considering their personality, emotional baseline and situation.",
174
+ },
175
+ {"role": "user", "content": context},
176
+ ],
177
+ functions=[
178
+ {
179
+ "name": "set_emotion",
180
+ "description": "Set the character's emotional response",
181
+ "parameters": {
182
+ "type": "object",
183
+ "properties": {
184
+ "reasoning": {
185
+ "type": "string",
186
+ "description": "Brief explanation of why the character is feeling this emotion",
187
+ },
188
+ "emotion": {
189
+ "type": "string",
190
+ "enum": ["neutral", "happy", "sad", "surprised", "angry"],
191
+ "description": "The character's emotional state",
192
+ },
193
+ },
194
+ "required": ["emotion", "reasoning"],
195
+ },
196
+ }
197
+ ],
198
+ function_call={"name": "set_emotion"},
199
+ )
200
+
201
+ function_call = response.choices[0].message.function_call
202
+ if function_call and function_call.arguments:
203
+ emotion_data = json.loads(function_call.arguments)
204
+ return EmotionResponse(
205
+ reasoning=emotion_data["reasoning"],
206
+ emotion=Emotion(emotion_data["emotion"]),
207
+ )
208
+ else:
209
+ # Fallback to neutral if function call fails
210
+ return EmotionResponse(
211
+ reasoning="Failed to analyze emotion, defaulting to neutral",
212
+ emotion=Emotion.NEUTRAL,
213
+ )
214
+
215
+
216
+ def bot(
217
+ history: List[Message], character: str, debug: gr.Textbox
218
+ ) -> Generator[Tuple[List[Message], Any, Any, Any], Any, None]:
219
+ """Generate bot response with streaming"""
220
+ if not character or not history:
221
+ yield history, gr.update(
222
+ value="No character selected or empty history"
223
+ ), None, None
224
+ return
225
+
226
+ try:
227
+ # First, analyze the emotion based on the user's message
228
+ user_message = history[-1]["content"]
229
+ emotion_analysis = analyze_emotion(
230
+ character, user_message, history[:-1], client
231
+ )
232
+
233
+ # Update the image based on the analyzed emotion
234
+ emotion_image = CHARACTERS[character]["image"].replace(
235
+ "neutral", emotion_analysis.emotion
236
+ )
237
+ debug_msg = f"Emotion: {emotion_analysis.emotion}\nReasoning: {emotion_analysis.reasoning}"
238
+ emotion_text = (
239
+ f"{emotion_analysis.emotion.capitalize()}\n{emotion_analysis.reasoning}"
240
+ )
241
+
242
+ # Yield the emotion update
243
+ yield history, gr.update(value=debug_msg), gr.update(
244
+ value=emotion_image
245
+ ), gr.update(value=emotion_text)
246
+
247
+ # Get character's system prompt
248
+ system_prompt = CHARACTERS[character]["system_prompt"]
249
+
250
+ # Format conversation history for the API
251
+ messages: List[ChatCompletionMessageParam] = [
252
+ cast(
253
+ ChatCompletionSystemMessageParam,
254
+ {
255
+ "role": "system",
256
+ "content": f"{system_prompt}\n\nRespond in character with a {emotion_analysis.emotion} emotional state. Your response should naturally reflect this emotion, considering the reasoning: {emotion_analysis.reasoning}",
257
+ },
258
+ )
259
+ ]
260
+
261
+ # Add conversation history
262
+ for msg in history[:-1]:
263
+ if msg["role"] == "user":
264
+ messages.append(
265
+ cast(
266
+ ChatCompletionUserMessageParam,
267
+ {"role": "user", "content": msg["content"]},
268
+ )
269
+ )
270
+ else:
271
+ messages.append(
272
+ cast(
273
+ ChatCompletionAssistantMessageParam,
274
+ {"role": "assistant", "content": msg["content"]},
275
+ )
276
+ )
277
+
278
+ # Add the latest user message
279
+ messages.append(
280
+ cast(
281
+ ChatCompletionUserMessageParam,
282
+ {"role": history[-1]["role"], "content": history[-1]["content"]},
283
+ )
284
+ )
285
+
286
+ # Initialize assistant's response
287
+ history.append({"role": "assistant", "content": ""})
288
+
289
+ # For debugging
290
+ yield history, gr.update(
291
+ value=f"Generating response with {emotion_analysis.emotion} emotion..."
292
+ ), None, None
293
+
294
+ # Get character's response
295
+ response = client.chat.completions.create(
296
+ model="gpt-4o", messages=messages, stream=True
297
+ )
298
+
299
+ # Collect the full response
300
+ full_response = ""
301
+ for chunk in response:
302
+ if chunk.choices[0].delta.content:
303
+ content = chunk.choices[0].delta.content
304
+ full_response += content
305
+ history[-1]["content"] = full_response
306
+ yield history, gr.update(
307
+ value=f"Generating response... Length: {len(full_response)}"
308
+ ), None, None
309
+ time.sleep(0.02)
310
+
311
+ # Final yield with debug info
312
+ yield history, gr.update(value=debug_msg), gr.update(
313
+ value=emotion_image
314
+ ), gr.update(value=emotion_text)
315
+
316
+ except Exception as e:
317
+ history[-1]["content"] = "Sorry, I encountered an error. Please try again."
318
+ yield history, gr.update(value=f"Error: {str(e)}"), None, None
319
+
320
+
321
+ def create_demo():
322
+ with gr.Blocks(
323
+ title=SPACE_TITLE,
324
+ css="""
325
+ .gradio-container {max-width: 1200px !important}
326
+ .emotion-state {font-size: 1.1em; padding: 8px; border-radius: 8px; background: #f7f7f7}
327
+ """,
328
+ ) as demo:
329
+ gr.HTML(
330
+ f"""
331
+ <div style="text-align: center; max-width: 1200px; margin: 0 auto;">
332
+ <h1 style="font-size: 2.5rem; font-weight: 600;">{SPACE_TITLE} {SPACE_EMOJI}</h1>
333
+ <p style="font-size: 1.2rem; margin: 1rem;">Welcome to the Boba Shop! You're the friendly barista, ready to chat with some unique Australian animals. Each has their own personality and story to share.</p>
334
+ </div>
335
+ """
336
+ )
337
+
338
+ # Header
339
+ gr.Image(
340
+ value="images/header_placeholder.png",
341
+ show_label=False,
342
+ container=False,
343
+ height=300,
344
+ )
345
+
346
+ with gr.Row():
347
+ # Left Column - Character Details
348
+ with gr.Column(scale=1):
349
+ character = gr.Radio(
350
+ choices=list(CHARACTERS.keys()),
351
+ label="Select your animal friend",
352
+ value=None,
353
+ container=True,
354
+ )
355
+
356
+ image = gr.Image(
357
+ value=None,
358
+ label="Animal Friend",
359
+ show_label=False,
360
+ height=300,
361
+ )
362
+
363
+ emotion_state = gr.Textbox(
364
+ label="Current Emotional State",
365
+ value="",
366
+ interactive=False,
367
+ show_label=True,
368
+ elem_classes=["emotion-state"],
369
+ )
370
+
371
+ # Character details editor
372
+ with gr.Accordion("Character Details", open=True):
373
+ char_name = gr.Textbox(
374
+ label="Name",
375
+ interactive=True,
376
+ )
377
+ char_system_prompt = gr.Textbox(
378
+ label="System Prompt",
379
+ interactive=True,
380
+ lines=5,
381
+ )
382
+ char_initial_msg = gr.Textbox(
383
+ label="Initial Message",
384
+ interactive=True,
385
+ lines=3,
386
+ )
387
+
388
+ # Right Column - Chat Interface
389
+ with gr.Column(scale=2):
390
+ chatbot = gr.Chatbot(
391
+ label="Chat",
392
+ show_label=False,
393
+ type="messages",
394
+ height=600,
395
+ container=True,
396
+ )
397
+ msg = gr.Textbox(
398
+ label="Your message (you are the boba shop barista)",
399
+ placeholder="Type your message here and press Enter or click Submit...",
400
+ container=True,
401
+ )
402
+ with gr.Row():
403
+ submit = gr.Button("Submit", variant="primary", scale=2)
404
+ clear = gr.ClearButton([msg, chatbot], scale=1)
405
+
406
+ gr.HTML(
407
+ """
408
+ <div style="text-align: center; margin-top: 20px; padding: 20px; border-top: 1px solid #ddd;">
409
+ <p>Created with ❤️ using Gradio and OpenAI. <a href="https://github.com/your-username/boba-shop-chat" target="_blank">View on GitHub</a></p>
410
+ </div>
411
+ """
412
+ )
413
+
414
+ # Footer - Debug Panel
415
+ with gr.Accordion("Debug Information", open=False):
416
+ debug_history = gr.TextArea(
417
+ label="Debug History",
418
+ value="Debug information will appear here...\n",
419
+ interactive=False,
420
+ lines=10,
421
+ )
422
+ debug = gr.Textbox(
423
+ label="Latest Debug",
424
+ value="",
425
+ interactive=False,
426
+ visible=False,
427
+ )
428
+
429
+ # Update character details when selection changes
430
+ def on_character_select(char):
431
+ """Handle character selection"""
432
+ if not char:
433
+ return None, [], "", "", "", "", "No character selected"
434
+
435
+ character_data = CHARACTERS[char]
436
+ return (
437
+ character_data["image"],
438
+ start_chat(char),
439
+ character_data["name"],
440
+ character_data["system_prompt"],
441
+ character_data["initial_message"],
442
+ "Neutral", # Initial emotion state
443
+ f"Selected character: {char}",
444
+ )
445
+
446
+ character.change(
447
+ on_character_select,
448
+ inputs=[character],
449
+ outputs=[
450
+ image,
451
+ chatbot,
452
+ char_name,
453
+ char_system_prompt,
454
+ char_initial_msg,
455
+ emotion_state,
456
+ debug_history,
457
+ ],
458
+ )
459
+
460
+ # Update debug history
461
+ def update_debug_history(history: str, new_debug: str) -> str:
462
+ """Append new debug message to history"""
463
+ timestamp = time.strftime("%H:%M:%S")
464
+ return f"{history}\n[{timestamp}] {new_debug}"
465
+
466
+ # Handle chat messages
467
+ submit_click = (
468
+ submit.click(
469
+ user,
470
+ [msg, chatbot, character],
471
+ [chatbot],
472
+ queue=False,
473
+ )
474
+ .then(
475
+ bot,
476
+ [chatbot, character, debug],
477
+ [chatbot, debug, image, emotion_state],
478
+ )
479
+ .then(
480
+ update_debug_history,
481
+ [debug_history, debug],
482
+ [debug_history],
483
+ )
484
+ .then(
485
+ lambda: gr.Textbox(value=""),
486
+ None,
487
+ [msg],
488
+ )
489
+ )
490
+
491
+ # Also trigger submit when pressing enter in the message box
492
+ msg.submit(
493
+ user,
494
+ [msg, chatbot, character],
495
+ [chatbot],
496
+ queue=False,
497
+ ).then(
498
+ bot,
499
+ [chatbot, character, debug],
500
+ [chatbot, debug, image, emotion_state],
501
+ ).then(
502
+ update_debug_history,
503
+ [debug_history, debug],
504
+ [debug_history],
505
+ ).then(
506
+ lambda: gr.Textbox(value=""),
507
+ None,
508
+ [msg],
509
+ )
510
+
511
+ return demo
512
+
513
+
514
+ if __name__ == "__main__":
515
+ demo = create_demo()
516
+ demo.queue()
517
+ if os.getenv("SPACE_ID"):
518
+ # Running on HF Spaces
519
+ demo.launch()
520
+ else:
521
+ # Running locally
522
+ demo.launch(
523
+ share=True,
524
+ server_name="0.0.0.0",
525
+ server_port=7860,
526
+ debug=True,
527
+ )
images/echidna_angry.png ADDED

Git LFS Details

  • SHA256: 41e6ef675e7266d19553c4f2eb60d6150aec1cb7d6cf04d98e6962d69480f400
  • Pointer size: 131 Bytes
  • Size of remote file: 172 kB
images/echidna_happy.png ADDED

Git LFS Details

  • SHA256: 7f2ab35a8785f150679abde7fbc140e3faa3ca35cf4263b5037f382bfa35095f
  • Pointer size: 131 Bytes
  • Size of remote file: 197 kB
images/echidna_neutral.png ADDED

Git LFS Details

  • SHA256: 139a159dc9de0069d6a14af57ae4108bfce4cf2089cf1eaf47310e0498f12579
  • Pointer size: 131 Bytes
  • Size of remote file: 185 kB
images/echidna_sad.png ADDED

Git LFS Details

  • SHA256: cf94c3dec0d1d41eb3a1c1db254e525bde2943239a81f591cffd8ec3018320a4
  • Pointer size: 131 Bytes
  • Size of remote file: 210 kB
images/echidna_surprised.png ADDED

Git LFS Details

  • SHA256: bfc7b46fd47c3250314981256ac305039d12502c3d2252a2cb7f7df7b2cb216d
  • Pointer size: 131 Bytes
  • Size of remote file: 202 kB
images/header_placeholder.png ADDED

Git LFS Details

  • SHA256: 6c35f4bf62ae6eee5e1479b5d9654df0f217d62757db584e1b01c64ee28cf795
  • Pointer size: 132 Bytes
  • Size of remote file: 1.31 MB
images/kangaroo_angry.png ADDED
images/kangaroo_happy.png ADDED
images/kangaroo_neutral.png ADDED
images/kangaroo_sad.png ADDED
images/kangaroo_surprised.png ADDED
images/platypus_angry.png ADDED
images/platypus_happy.png ADDED
images/platypus_neutral.png ADDED
images/platypus_sad.png ADDED
images/platypus_surprised.png ADDED
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio==4.19.2
2
+ openai==1.12.0
3
+ instructor==0.5.2
4
+ pydantic==2.6.1
5
+ python-dotenv==1.0.1
6
+ replicate
utils.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from PIL import Image, ImageDraw, ImageFont
3
+
4
+ def create_placeholder_images():
5
+ # Create images directory if it doesn't exist
6
+ os.makedirs("images", exist_ok=True)
7
+
8
+ # Define characters and emotions
9
+ characters = ["echidna", "platypus", "kangaroo"]
10
+ emotions = ["neutral", "happy", "sad", "surprised","angry"]
11
+
12
+ # Create a 400x400 image for each character/emotion combination
13
+ for character in characters:
14
+ for emotion in emotions:
15
+ # Create new image with white background
16
+ img = Image.new('RGB', (400, 400), 'white')
17
+ draw = ImageDraw.Draw(img)
18
+
19
+ # Add text to image
20
+ text = f"{character}\n{emotion}"
21
+
22
+ # Try to use a system font, fallback to default
23
+ try:
24
+ font = ImageFont.truetype("Arial", 40)
25
+ except:
26
+ font = ImageFont.load_default()
27
+
28
+ # Center the text
29
+ bbox = draw.textbbox((0, 0), text, font=font)
30
+ text_width = bbox[2] - bbox[0]
31
+ text_height = bbox[3] - bbox[1]
32
+ x = (400 - text_width) / 2
33
+ y = (400 - text_height) / 2
34
+
35
+ # Draw the text
36
+ draw.text((x, y), text, fill='black', font=font, align='center')
37
+
38
+ # Save the image
39
+ filename = f"images/{character}_{emotion}.png"
40
+ img.save(filename)
41
+
42
+ if __name__ == "__main__":
43
+ create_placeholder_images()