|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Shy Guy Simulator - AI Edition</title> |
|
<style> |
|
body { |
|
font-family: Arial, sans-serif; |
|
max-width: 800px; |
|
margin: 20px auto; |
|
padding: 20px; |
|
background-color: #1a1a1a; |
|
color: #fff; |
|
} |
|
|
|
#game-container { |
|
background-color: #2a2a2a; |
|
padding: 20px; |
|
border-radius: 8px; |
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); |
|
} |
|
|
|
.chat-container { |
|
height: 400px; |
|
overflow-y: auto; |
|
margin: 20px 0; |
|
padding: 10px; |
|
background-color: #333; |
|
border-radius: 4px; |
|
} |
|
|
|
.message { |
|
margin: 10px 0; |
|
padding: 10px; |
|
border-radius: 4px; |
|
word-wrap: break-word; |
|
} |
|
|
|
.wingman { |
|
background-color: #2c5282; |
|
margin-right: 20%; |
|
} |
|
|
|
.shyguy { |
|
background-color: #4a5568; |
|
margin-left: 20%; |
|
} |
|
|
|
.error { |
|
background-color: #c53030; |
|
text-align: center; |
|
} |
|
|
|
#input-container { |
|
display: flex; |
|
gap: 10px; |
|
margin-top: 20px; |
|
} |
|
|
|
#user-input { |
|
flex-grow: 1; |
|
padding: 10px; |
|
border: none; |
|
border-radius: 4px; |
|
background-color: #4a4a4a; |
|
color: white; |
|
} |
|
|
|
button { |
|
padding: 10px 20px; |
|
background-color: #4299e1; |
|
color: white; |
|
border: none; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
} |
|
|
|
button:hover { |
|
background-color: #3182ce; |
|
} |
|
|
|
#stats { |
|
margin-top: 20px; |
|
padding: 10px; |
|
background-color: #333; |
|
border-radius: 4px; |
|
display: flex; |
|
justify-content: space-between; |
|
} |
|
|
|
.typing { |
|
font-style: italic; |
|
color: #718096; |
|
} |
|
|
|
#api-key-container { |
|
margin-bottom: 20px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="game-container"> |
|
<h1>Shy Guy Simulator - AI Edition</h1> |
|
|
|
<div id="api-key-container"> |
|
<input type="password" id="api-key" placeholder="Enter your Mistral API key" style="width: 100%; padding: 10px; margin-bottom: 10px;"> |
|
<button onclick="initializeGame()" id="start-button">Start Game</button> |
|
</div> |
|
|
|
<div id="game-content" style="display: none;"> |
|
<div id="stats"> |
|
<span>Confidence: <span id="confidence">0</span>%</span> |
|
<span>Anxiety: <span id="anxiety">100</span>%</span> |
|
<span>Time: <span id="time">8:00 PM</span></span> |
|
</div> |
|
<div class="chat-container" id="chat-container"></div> |
|
<div id="input-container"> |
|
<input type="text" id="user-input" placeholder="Type your encouragement as wingman..."> |
|
<button onclick="handleUserInput()">Send</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
let game; |
|
|
|
class ShyGuySimulator { |
|
constructor(apiKey) { |
|
this.apiKey = apiKey; |
|
this.state = { |
|
confidence: 0, |
|
anxiety: 100, |
|
time: new Date(2024, 0, 1, 20, 0), |
|
location: 'entrance', |
|
hasSpokenToGirl: false, |
|
isProcessing: false |
|
}; |
|
|
|
this.context = [ |
|
{ |
|
role: 'system', |
|
content: `You are roleplaying as a shy and anxious guy at a homecoming party. |
|
You're standing near the entrance, and the girl you like is across the room. |
|
Your responses should reflect your social anxiety and reluctance to approach her. |
|
Keep responses concise (max 2-3 sentences) and natural. |
|
Express hesitation, worry, and self-doubt while reacting to the wingman's encouragement.` |
|
} |
|
]; |
|
|
|
this.initialize(); |
|
} |
|
|
|
initialize() { |
|
this.addMessage("Hey! I'll be your wingman tonight. I see that girl you like over there - let's help you talk to her!", 'wingman'); |
|
this.addMessage("I... I don't know about this. Maybe I should just go home...", 'shyguy'); |
|
this.updateStats(); |
|
} |
|
|
|
async handleInput(userInput) { |
|
if (this.state.isProcessing) return; |
|
this.state.isProcessing = true; |
|
|
|
try { |
|
if (!userInput.trim()) throw new Error("Please enter some text"); |
|
|
|
this.addMessage(userInput, 'wingman'); |
|
this.addLoadingMessage(); |
|
|
|
const currentState = `Current confidence: ${this.state.confidence}%, anxiety: ${this.state.anxiety}%, location: ${this.state.location}`; |
|
|
|
this.context.push({ |
|
role: 'user', |
|
content: `${userInput}\n\n${currentState}` |
|
}); |
|
|
|
const response = await this.callMistralAPI(); |
|
|
|
this.removeLoadingMessage(); |
|
this.addMessage(response, 'shyguy'); |
|
|
|
this.updateGameState(response); |
|
|
|
this.context.push({ |
|
role: 'assistant', |
|
content: response |
|
}); |
|
|
|
if (this.context.length > 10) { |
|
this.context = [ |
|
this.context[0], |
|
...this.context.slice(-4) |
|
]; |
|
} |
|
} catch (error) { |
|
this.removeLoadingMessage(); |
|
this.addMessage(`Error: ${error.message}`, 'error'); |
|
console.error('Error:', error); |
|
} finally { |
|
this.state.isProcessing = false; |
|
} |
|
} |
|
|
|
async callMistralAPI() { |
|
try { |
|
const response = await fetch('https://api.mistral.ai/v1/chat/completions', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
'Authorization': `Bearer ${this.apiKey}` |
|
}, |
|
body: JSON.stringify({ |
|
model: 'mistral-large-latest', |
|
messages: this.context, |
|
max_tokens: 150, |
|
temperature: 0.7 |
|
}) |
|
}); |
|
|
|
if (!response.ok) { |
|
const error = await response.json(); |
|
throw new Error(error.error?.message || 'API request failed'); |
|
} |
|
|
|
const data = await response.json(); |
|
return data.choices[0].message.content; |
|
} catch (error) { |
|
if (error.message.includes('API key')) { |
|
throw new Error('Invalid API key. Please check your API key and try again.'); |
|
} |
|
throw new Error('Failed to get response from AI. Please try again.'); |
|
} |
|
} |
|
|
|
updateGameState(response) { |
|
const lowerResponse = response.toLowerCase(); |
|
|
|
|
|
if (lowerResponse.includes('okay') || lowerResponse.includes('maybe') || lowerResponse.includes('right')) { |
|
this.state.confidence = Math.min(100, this.state.confidence + 10); |
|
this.state.anxiety = Math.max(0, this.state.anxiety - 5); |
|
} else if (lowerResponse.includes('no') || lowerResponse.includes('can\'t') || lowerResponse.includes('scared')) { |
|
this.state.confidence = Math.max(0, this.state.confidence - 5); |
|
this.state.anxiety = Math.min(100, this.state.anxiety + 10); |
|
} |
|
|
|
|
|
this.state.time = new Date(this.state.time.getTime() + 5 * 60000); |
|
|
|
this.updateStats(); |
|
} |
|
|
|
updateStats() { |
|
document.getElementById('confidence').textContent = this.state.confidence; |
|
document.getElementById('anxiety').textContent = this.state.anxiety; |
|
document.getElementById('time').textContent = |
|
this.state.time.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }); |
|
} |
|
|
|
addMessage(text, type) { |
|
const chat = document.getElementById('chat-container'); |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = `message ${type}`; |
|
messageDiv.textContent = text; |
|
chat.appendChild(messageDiv); |
|
chat.scrollTop = chat.scrollHeight; |
|
} |
|
|
|
addLoadingMessage() { |
|
const chat = document.getElementById('chat-container'); |
|
const loadingDiv = document.createElement('div'); |
|
loadingDiv.className = 'message shyguy typing'; |
|
loadingDiv.id = 'loading-message'; |
|
loadingDiv.textContent = 'Thinking...'; |
|
chat.appendChild(loadingDiv); |
|
chat.scrollTop = chat.scrollHeight; |
|
} |
|
|
|
removeLoadingMessage() { |
|
const loadingMessage = document.getElementById('loading-message'); |
|
if (loadingMessage) { |
|
loadingMessage.remove(); |
|
} |
|
} |
|
} |
|
|
|
function initializeGame() { |
|
const apiKey = document.getElementById('api-key').value.trim(); |
|
if (!apiKey) { |
|
alert('Please enter your Mistral API key'); |
|
return; |
|
} |
|
|
|
document.getElementById('api-key-container').style.display = 'none'; |
|
document.getElementById('game-content').style.display = 'block'; |
|
|
|
game = new ShyGuySimulator(apiKey); |
|
} |
|
|
|
async function handleUserInput() { |
|
if (!game) return; |
|
|
|
const input = document.getElementById('user-input'); |
|
const text = input.value.trim(); |
|
if (text) { |
|
await game.handleInput(text); |
|
input.value = ''; |
|
} |
|
} |
|
|
|
document.getElementById('user-input')?.addEventListener('keypress', function(e) { |
|
if (e.key === 'Enter') { |
|
handleUserInput(); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |