| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Negotiation Coach - AI Sales Practice</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); |
| |
| body { |
| font-family: 'Inter', sans-serif; |
| background-color: #f8fafc; |
| } |
| |
| .character-avatar { |
| transition: all 0.3s ease; |
| filter: grayscale(100%); |
| } |
| |
| .character-avatar:hover, .character-avatar.selected { |
| filter: grayscale(0%); |
| transform: scale(1.05); |
| } |
| |
| .typing-indicator span { |
| display: inline-block; |
| width: 8px; |
| height: 8px; |
| background-color: #4b5563; |
| border-radius: 50%; |
| margin: 0 2px; |
| animation: bounce 1.4s infinite ease-in-out; |
| } |
| |
| .typing-indicator span:nth-child(2) { |
| animation-delay: 0.2s; |
| } |
| |
| .typing-indicator span:nth-child(3) { |
| animation-delay: 0.4s; |
| } |
| |
| @keyframes bounce { |
| 0%, 60%, 100% { transform: translateY(0); } |
| 30% { transform: translateY(-5px); } |
| } |
| |
| .message-bubble { |
| max-width: 80%; |
| border-radius: 1.125rem; |
| line-height: 1.25; |
| position: relative; |
| word-wrap: break-word; |
| } |
| |
| .user-message { |
| background-color: #3b82f6; |
| color: white; |
| border-bottom-right-radius: 0.25rem; |
| margin-left: auto; |
| } |
| |
| .bot-message { |
| background-color: #e2e8f0; |
| color: #1f2937; |
| border-bottom-left-radius: 0.25rem; |
| margin-right: auto; |
| } |
| |
| .pulse { |
| animation: pulse 2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); } |
| 70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } |
| 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } |
| } |
| |
| .scene-card { |
| transition: all 0.3s ease; |
| } |
| |
| .scene-card:hover { |
| transform: translateY(-5px); |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
| } |
| |
| .scene-card.selected { |
| border: 2px solid #3b82f6; |
| transform: translateY(-5px); |
| } |
| </style> |
| </head> |
| <body class="min-h-screen bg-gray-50"> |
| <div class="container mx-auto px-4 py-8 max-w-6xl"> |
| <header class="text-center mb-12"> |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">Negotiation Coach Pro</h1> |
| <p class="text-lg text-gray-600">Practice your sales pitch with realistic AI characters</p> |
| </header> |
|
|
| <div id="app" class="bg-white rounded-xl shadow-lg overflow-hidden"> |
| |
| <div id="setup-screen" class="p-8"> |
| <div class="mb-10"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Select Your Character</h2> |
| <div class="grid grid-cols-2 md:grid-cols-5 gap-6"> |
| |
| <div class="character-option cursor-pointer text-center" data-character="1"> |
| <div class="character-avatar rounded-full overflow-hidden mx-auto mb-3 w-24 h-24 border-4 border-gray-200"> |
| <img src="https://randomuser.me/api/portraits/women/44.jpg" alt="Sarah - Marketing Director" class="w-full h-full object-cover"> |
| </div> |
| <h3 class="font-medium text-gray-800">Sarah</h3> |
| <p class="text-sm text-gray-500">Marketing Director</p> |
| </div> |
| |
| |
| <div class="character-option cursor-pointer text-center" data-character="2"> |
| <div class="character-avatar rounded-full overflow-hidden mx-auto mb-3 w-24 h-24 border-4 border-gray-200"> |
| <img src="https://randomuser.me/api/portraits/men/32.jpg" alt="James - CFO" class="w-full h-full object-cover"> |
| </div> |
| <h3 class="font-medium text-gray-800">James</h3> |
| <p class="text-sm text-gray-500">CFO</p> |
| </div> |
| |
| |
| <div class="character-option cursor-pointer text-center" data-character="3"> |
| <div class="character-avatar rounded-full overflow-hidden mx-auto mb-3 w-24 h-24 border-4 border-gray-200"> |
| <img src="https://randomuser.me/api/portraits/women/68.jpg" alt="Priya - Startup Founder" class="w-full h-full object-cover"> |
| </div> |
| <h3 class="font-medium text-gray-800">Priya</h3> |
| <p class="text-sm text-gray-500">Startup Founder</p> |
| </div> |
| |
| |
| <div class="character-option cursor-pointer text-center" data-character="4"> |
| <div class="character-avatar rounded-full overflow-hidden mx-auto mb-3 w-24 h-24 border-4 border-gray-200"> |
| <img src="https://randomuser.me/api/portraits/men/75.jpg" alt="Carlos - IT Manager" class="w-full h-full object-cover"> |
| </div> |
| <h3 class="font-medium text-gray-800">Carlos</h3> |
| <p class="text-sm text-gray-500">IT Manager</p> |
| </div> |
| |
| |
| <div class="character-option cursor-pointer text-center" data-character="5"> |
| <div class="character-avatar rounded-full overflow-hidden mx-auto mb-3 w-24 h-24 border-4 border-gray-200"> |
| <img src="https://randomuser.me/api/portraits/women/90.jpg" alt="Linda - Small Business Owner" class="w-full h-full object-cover"> |
| </div> |
| <h3 class="font-medium text-gray-800">Linda</h3> |
| <p class="text-sm text-gray-500">Small Business Owner</p> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="mb-10"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Select a Scenario</h2> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> |
| |
| <div class="scene-card p-6 bg-white rounded-lg border border-gray-200 cursor-pointer" data-scene="1"> |
| <div class="flex items-center mb-4"> |
| <div class="bg-blue-100 p-3 rounded-full mr-4"> |
| <i class="fas fa-briefcase text-blue-600 text-xl"></i> |
| </div> |
| <h3 class="font-medium text-gray-800">Enterprise Software</h3> |
| </div> |
| <p class="text-gray-600 text-sm">Sell your SaaS platform to a corporate decision maker who's skeptical about ROI.</p> |
| </div> |
| |
| |
| <div class="scene-card p-6 bg-white rounded-lg border border-gray-200 cursor-pointer" data-scene="2"> |
| <div class="flex items-center mb-4"> |
| <div class="bg-green-100 p-3 rounded-full mr-4"> |
| <i class="fas fa-home text-green-600 text-xl"></i> |
| </div> |
| <h3 class="font-medium text-gray-800">Home Security</h3> |
| </div> |
| <p class="text-gray-600 text-sm">Convince a homeowner to upgrade to your premium security system package.</p> |
| </div> |
| |
| |
| <div class="scene-card p-6 bg-white rounded-lg border border-gray-200 cursor-pointer" data-scene="3"> |
| <div class="flex items-center mb-4"> |
| <div class="bg-purple-100 p-3 rounded-full mr-4"> |
| <i class="fas fa-heart text-purple-600 text-xl"></i> |
| </div> |
| <h3 class="font-medium text-gray-800">Health Insurance</h3> |
| </div> |
| <p class="text-gray-600 text-sm">Overcome price objections for a comprehensive health insurance plan.</p> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="text-center"> |
| <button id="start-session" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-8 rounded-full text-lg transition duration-300 transform hover:scale-105"> |
| Start Negotiation Session |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="chat-screen" class="hidden h-full flex flex-col"> |
| |
| <div class="bg-gray-800 text-white p-4 flex items-center justify-between"> |
| <div class="flex items-center"> |
| <img id="current-character-avatar" src="" alt="Character" class="w-10 h-10 rounded-full mr-3"> |
| <div> |
| <h3 id="current-character-name" class="font-medium"></h3> |
| <p id="current-scenario" class="text-xs text-gray-300"></p> |
| </div> |
| </div> |
| <div class="flex items-center space-x-3"> |
| <div class="text-sm bg-gray-700 px-3 py-1 rounded-full"> |
| <i class="fas fa-microphone mr-1"></i> |
| <span id="recording-status">Ready</span> |
| </div> |
| <button id="end-session" class="text-xs bg-red-500 hover:bg-red-600 px-3 py-1 rounded-full transition"> |
| <i class="fas fa-times mr-1"></i> End Session |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="chat-messages" class="flex-1 p-6 overflow-y-auto space-y-4" style="max-height: 60vh;"> |
| |
| </div> |
|
|
| |
| <div class="border-t border-gray-200 p-4 bg-gray-50"> |
| <div class="flex items-center"> |
| <button id="voice-toggle" class="bg-blue-600 text-white p-3 rounded-full mr-3 hover:bg-blue-700 transition pulse"> |
| <i class="fas fa-microphone text-xl"></i> |
| </button> |
| <div class="flex-1 relative"> |
| <input id="text-input" type="text" placeholder="Type your message or speak..." class="w-full border border-gray-300 rounded-full py-3 px-5 pr-12 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> |
| <button id="send-text" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-blue-600"> |
| <i class="fas fa-paper-plane text-xl"></i> |
| </button> |
| </div> |
| </div> |
| <div class="mt-3 text-xs text-gray-500 flex justify-between"> |
| <div> |
| <span id="connection-status" class="inline-flex items-center"> |
| <span class="w-2 h-2 rounded-full bg-green-500 mr-1"></span> |
| Connected |
| </span> |
| </div> |
| <div> |
| <button id="hint-button" class="text-blue-600 hover:text-blue-800"> |
| <i class="fas fa-lightbulb mr-1"></i> Get a hint |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="results-screen" class="hidden p-8 text-center"> |
| <div class="max-w-md mx-auto"> |
| <div class="bg-green-100 text-green-800 p-4 rounded-full w-24 h-24 flex items-center justify-center mx-auto mb-6"> |
| <i class="fas fa-trophy text-4xl"></i> |
| </div> |
| <h2 class="text-2xl font-bold text-gray-800 mb-3">Negotiation Complete!</h2> |
| <div class="bg-white rounded-xl shadow-sm p-6 mb-6"> |
| <div class="grid grid-cols-2 gap-4 text-left mb-4"> |
| <div> |
| <p class="text-sm text-gray-500">Character</p> |
| <p id="result-character" class="font-medium"></p> |
| </div> |
| <div> |
| <p class="text-sm text-gray-500">Scenario</p> |
| <p id="result-scenario" class="font-medium"></p> |
| </div> |
| <div> |
| <p class="text-sm text-gray-500">Duration</p> |
| <p id="result-duration" class="font-medium"></p> |
| </div> |
| <div> |
| <p class="text-sm text-gray-500">Outcome</p> |
| <p id="result-outcome" class="font-medium"></p> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bg-white rounded-xl shadow-sm p-6 mb-8 text-left"> |
| <h3 class="font-semibold text-gray-800 mb-3">Feedback</h3> |
| <p id="feedback-text" class="text-gray-600 mb-4"></p> |
| <div class="space-y-3"> |
| <div> |
| <p class="text-sm font-medium text-gray-700 mb-1">Strengths</p> |
| <ul id="strengths-list" class="text-sm text-gray-600 list-disc pl-5 space-y-1"></ul> |
| </div> |
| <div> |
| <p class="text-sm font-medium text-gray-700 mb-1">Areas to Improve</p> |
| <ul id="improvements-list" class="text-sm text-gray-600 list-disc pl-5 space-y-1"></ul> |
| </div> |
| </div> |
| </div> |
| |
| <div class="flex justify-center space-x-4"> |
| <button id="new-session" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-full transition"> |
| <i class="fas fa-redo mr-2"></i> New Session |
| </button> |
| <button id="view-transcript" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-6 rounded-full transition"> |
| <i class="fas fa-file-alt mr-2"></i> View Transcript |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="hint-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| <div class="bg-white rounded-xl p-6 max-w-md w-full mx-4"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-xl font-semibold text-gray-800">Negotiation Hint</h3> |
| <button id="close-hint" class="text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| <div id="hint-content" class="text-gray-600 mb-4"> |
| |
| </div> |
| <div class="text-right"> |
| <button id="next-hint" class="text-blue-600 hover:text-blue-800 text-sm font-medium"> |
| Next Hint <i class="fas fa-arrow-right ml-1"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="transcript-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| <div class="bg-white rounded-xl p-6 max-w-2xl w-full mx-4 max-h-[80vh] flex flex-col"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-xl font-semibold text-gray-800">Session Transcript</h3> |
| <button id="close-transcript" class="text-gray-500 hover:text-gray-700"> |
| <i class="fas fa-times"></i> |
| </button> |
| </div> |
| <div id="transcript-content" class="overflow-y-auto flex-1 mb-4 space-y-3"> |
| |
| </div> |
| <div class="border-t border-gray-200 pt-4 flex justify-end"> |
| <button id="download-transcript" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-full text-sm"> |
| <i class="fas fa-download mr-2"></i> Download |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const setupScreen = document.getElementById('setup-screen'); |
| const chatScreen = document.getElementById('chat-screen'); |
| const resultsScreen = document.getElementById('results-screen'); |
| const chatMessages = document.getElementById('chat-messages'); |
| const textInput = document.getElementById('text-input'); |
| const sendText = document.getElementById('send-text'); |
| const voiceToggle = document.getElementById('voice-toggle'); |
| const startSession = document.getElementById('start-session'); |
| const endSession = document.getElementById('end-session'); |
| const newSession = document.getElementById('new-session'); |
| const viewTranscript = document.getElementById('view-transcript'); |
| const hintButton = document.getElementById('hint-button'); |
| const hintModal = document.getElementById('hint-modal'); |
| const closeHint = document.getElementById('close-hint'); |
| const nextHint = document.getElementById('next-hint'); |
| const hintContent = document.getElementById('hint-content'); |
| const transcriptModal = document.getElementById('transcript-modal'); |
| const closeTranscript = document.getElementById('close-transcript'); |
| const transcriptContent = document.getElementById('transcript-content'); |
| const downloadTranscript = document.getElementById('download-transcript'); |
| const recordingStatus = document.getElementById('recording-status'); |
| |
| |
| let selectedCharacter = null; |
| let selectedScenario = null; |
| let isListening = false; |
| let recognition; |
| let speechSynthesis = window.speechSynthesis; |
| let sessionStartTime; |
| let messages = []; |
| let currentHints = []; |
| let currentHintIndex = 0; |
| |
| |
| const characters = { |
| 1: { |
| name: "Sarah Johnson", |
| role: "Marketing Director", |
| avatar: "https://randomuser.me/api/portraits/women/44.jpg", |
| personality: "Data-driven, focused on ROI and customer acquisition costs. Skeptical of fluffy claims.", |
| voice: "UK English Female", |
| pitch: 1, |
| rate: 1 |
| }, |
| 2: { |
| name: "James Wilson", |
| role: "CFO", |
| avatar: "https://randomuser.me/api/portraits/men/32.jpg", |
| personality: "All about the numbers. Wants clear cost-benefit analysis and payment flexibility.", |
| voice: "US English Male", |
| pitch: 0.9, |
| rate: 0.9 |
| }, |
| 3: { |
| name: "Priya Patel", |
| role: "Startup Founder", |
| avatar: "https://randomuser.me/api/portraits/women/68.jpg", |
| personality: "Visionary but cash-strapped. Needs to see how this fits her long-term strategy.", |
| voice: "Indian English Female", |
| pitch: 1.1, |
| rate: 1.1 |
| }, |
| 4: { |
| name: "Carlos Mendez", |
| role: "IT Manager", |
| avatar: "https://randomuser.me/api/portraits/men/75.jpg", |
| personality: "Technical but overworked. Worried about implementation complexity and support.", |
| voice: "Spanish English Male", |
| pitch: 1, |
| rate: 1 |
| }, |
| 5: { |
| name: "Linda Thompson", |
| role: "Small Business Owner", |
| avatar: "https://randomuser.me/api/portraits/women/90.jpg", |
| personality: "Practical and hands-on. Needs simple solutions that won't disrupt her daily operations.", |
| voice: "Australian English Female", |
| pitch: 1, |
| rate: 1 |
| } |
| }; |
| |
| |
| const scenarios = { |
| 1: { |
| title: "Enterprise Software Sale", |
| description: "Sell your SaaS platform to a corporate decision maker", |
| product: "Enterprise CRM Software", |
| price: "$120,000/year", |
| features: [ |
| "Advanced analytics dashboard", |
| "Custom workflow automation", |
| "Enterprise-grade security", |
| "24/7 dedicated support" |
| ], |
| objections: [ |
| "We already have a system that works", |
| "This is too expensive for what you're offering", |
| "I'm not convinced this will integrate well with our other tools", |
| "Our team won't adopt this easily" |
| ], |
| hints: [ |
| "Focus on ROI - calculate how much time/money they'll save", |
| "Offer a phased implementation plan to reduce risk", |
| "Provide case studies from similar companies", |
| "Highlight unique features competitors don't have" |
| ] |
| }, |
| 2: { |
| title: "Home Security System", |
| description: "Convince a homeowner to upgrade to premium package", |
| product: "UltraSecure Home System", |
| price: "$1,499 + $49/month", |
| features: [ |
| "24/7 professional monitoring", |
| "Smart home integration", |
| "4K cameras with facial recognition", |
| "Lifetime equipment warranty" |
| ], |
| objections: [ |
| "I already have a basic system that's fine", |
| "This seems too expensive for what I need", |
| "I'm worried about privacy with all these cameras", |
| "Installation sounds disruptive" |
| ], |
| hints: [ |
| "Calculate cost per day - less than a cup of coffee for peace of mind", |
| "Share crime statistics for their neighborhood", |
| "Offer a limited-time discount for signing up today", |
| "Highlight insurance premium discounts they may qualify for" |
| ] |
| }, |
| 3: { |
| title: "Health Insurance Plan", |
| description: "Overcome price objections for comprehensive plan", |
| product: "Platinum Health Coverage", |
| price: "$850/month individual, $2,200/month family", |
| features: [ |
| "$0 deductible option", |
| "Access to top-tier hospitals", |
| "Alternative therapy coverage", |
| "24/7 telehealth services" |
| ], |
| objections: [ |
| "I'm healthy and don't need this much coverage", |
| "This is way more than I pay now", |
| "My current doctor might not be in network", |
| "The paperwork looks complicated" |
| ], |
| hints: [ |
| "Calculate worst-case scenario costs without this coverage", |
| "Highlight preventive care benefits that keep them healthy", |
| "Show network directory to prove their doctors are covered", |
| "Offer to handle all paperwork for them" |
| ] |
| } |
| }; |
| |
| |
| function initSpeechRecognition() { |
| recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)(); |
| recognition.continuous = true; |
| recognition.interimResults = true; |
| recognition.lang = 'en-US'; |
| |
| recognition.onstart = function() { |
| isListening = true; |
| recordingStatus.textContent = "Listening..."; |
| voiceToggle.classList.add('bg-red-600', 'pulse'); |
| voiceToggle.classList.remove('bg-blue-600'); |
| voiceToggle.innerHTML = '<i class="fas fa-microphone-slash text-xl"></i>'; |
| }; |
| |
| recognition.onend = function() { |
| if (isListening) { |
| recognition.start(); |
| } |
| }; |
| |
| recognition.onresult = function(event) { |
| let interimTranscript = ''; |
| let finalTranscript = ''; |
| |
| for (let i = event.resultIndex; i < event.results.length; i++) { |
| const transcript = event.results[i][0].transcript; |
| if (event.results[i].isFinal) { |
| finalTranscript += transcript; |
| } else { |
| interimTranscript += transcript; |
| } |
| } |
| |
| textInput.value = finalTranscript || interimTranscript; |
| |
| if (finalTranscript) { |
| sendUserMessage(finalTranscript); |
| } |
| }; |
| |
| recognition.onerror = function(event) { |
| console.error('Speech recognition error', event.error); |
| stopListening(); |
| recordingStatus.textContent = "Error: " + event.error; |
| setTimeout(() => { |
| recordingStatus.textContent = "Ready"; |
| }, 3000); |
| }; |
| } |
| |
| |
| function speak(text, character) { |
| const charData = characters[character]; |
| const utterance = new SpeechSynthesisUtterance(text); |
| |
| |
| utterance.rate = charData.rate; |
| utterance.pitch = charData.pitch; |
| |
| |
| const voices = speechSynthesis.getVoices(); |
| const preferredVoice = voices.find(v => v.name.includes(charData.voice)); |
| if (preferredVoice) { |
| utterance.voice = preferredVoice; |
| } |
| |
| |
| const typingIndicator = document.createElement('div'); |
| typingIndicator.className = 'typing-indicator mb-4'; |
| typingIndicator.innerHTML = '<span></span><span></span><span></span>'; |
| chatMessages.appendChild(typingIndicator); |
| |
| utterance.onend = function() { |
| typingIndicator.remove(); |
| }; |
| |
| speechSynthesis.speak(utterance); |
| } |
| |
| |
| function startListening() { |
| if (!recognition) { |
| initSpeechRecognition(); |
| } |
| |
| try { |
| recognition.start(); |
| } catch (e) { |
| console.error('Speech recognition start error:', e); |
| recordingStatus.textContent = "Error starting mic"; |
| setTimeout(() => { |
| recordingStatus.textContent = "Ready"; |
| }, 3000); |
| } |
| } |
| |
| |
| function stopListening() { |
| if (recognition) { |
| recognition.stop(); |
| } |
| isListening = false; |
| recordingStatus.textContent = "Ready"; |
| voiceToggle.classList.remove('bg-red-600', 'pulse'); |
| voiceToggle.classList.add('bg-blue-600'); |
| voiceToggle.innerHTML = '<i class="fas fa-microphone text-xl"></i>'; |
| } |
| |
| |
| function toggleVoiceInput() { |
| if (isListening) { |
| stopListening(); |
| } else { |
| startListening(); |
| } |
| } |
| |
| |
| function addMessage(sender, text, isUser = false) { |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `message-bubble p-4 ${isUser ? 'user-message' : 'bot-message'}`; |
| messageDiv.textContent = text; |
| chatMessages.appendChild(messageDiv); |
| |
| |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| |
| |
| messages.push({ |
| sender: isUser ? 'You' : characters[selectedCharacter].name, |
| text: text, |
| timestamp: new Date().toISOString() |
| }); |
| } |
| |
| |
| function sendUserMessage(text) { |
| if (!text.trim()) return; |
| |
| addMessage('You', text, true); |
| textInput.value = ''; |
| |
| |
| setTimeout(() => { |
| generateAIResponse(text); |
| }, 1000 + Math.random() * 2000); |
| } |
| |
| |
| function generateAIResponse(userInput) { |
| const scenario = scenarios[selectedScenario]; |
| const character = characters[selectedCharacter]; |
| |
| |
| const input = userInput.toLowerCase(); |
| |
| |
| if (input.includes('price') || input.includes('cost') || input.includes('how much')) { |
| const responses = [ |
| `Our ${scenario.product} is priced at ${scenario.price}. I know that's a significant investment, but let me explain the value.`, |
| `The total cost is ${scenario.price}. Before you decide, let's discuss how this will actually save you money in the long run.`, |
| `It's ${scenario.price}, which I understand is a consideration. But when you factor in the productivity gains, the ROI becomes clear.` |
| ]; |
| const response = responses[Math.floor(Math.random() * responses.length)]; |
| addMessage(character.name, response); |
| speak(response, selectedCharacter); |
| return; |
| } |
| |
| |
| if (input.includes('feature') || input.includes('what does it do') || input.includes('benefit')) { |
| const featuresList = scenario.features.map(f => `• ${f}`).join('\n'); |
| const response = `Here are the key features of our ${scenario.product}:\n${featuresList}\n\nWhich of these would be most valuable for your situation?`; |
| addMessage(character.name, response); |
| speak(response, selectedCharacter); |
| return; |
| } |
| |
| |
| if (input.includes('yes') || input.includes('agree') || input.includes('interesting') || input.includes('like that')) { |
| if (Math.random() > 0.7) { |
| const closingResponses = [ |
| "You know what? You've convinced me. Let's move forward with this. Where do I sign?", |
| "I have to say, your approach has addressed all my concerns. I'm ready to approve this purchase.", |
| "Okay, I'm sold. Let's get the paperwork started." |
| ]; |
| const response = closingResponses[Math.floor(Math.random() * closingResponses.length)]; |
| addMessage(character.name, response); |
| speak(response, selectedCharacter); |
| |
| |
| setTimeout(() => { |
| endSessionSuccessfully(); |
| }, 3000); |
| } else { |
| const positiveResponses = [ |
| "That's a good point. I'm starting to see the value here.", |
| "Interesting perspective. Tell me more about how this would work in our specific case.", |
| "You're making a compelling case. What kind of implementation timeline are we looking at?" |
| ]; |
| const response = positiveResponses[Math.floor(Math.random() * positiveResponses.length)]; |
| addMessage(character.name, response); |
| speak(response, selectedCharacter); |
| } |
| return; |
| } |
| |
| |
| const objections = scenario.objections; |
| const randomObjection = objections[Math.floor(Math.random() * objections.length)]; |
| |
| const response = `Hmm, ${randomObjection.toLowerCase()} Can you address that concern for me?`; |
| addMessage(character.name, response); |
| speak(response, selectedCharacter); |
| } |
| |
| |
| function startNewSession() { |
| |
| const characterOption = document.querySelector('.character-option.selected'); |
| const scenarioOption = document.querySelector('.scene-card.selected'); |
| |
| if (!characterOption || !scenarioOption) { |
| alert('Please select both a character and a scenario'); |
| return; |
| } |
| |
| selectedCharacter = characterOption.dataset.character; |
| selectedScenario = scenarioOption.dataset.scene; |
| |
| |
| document.getElementById('current-character-avatar').src = characters[selectedCharacter].avatar; |
| document.getElementById('current-character-name').textContent = `${characters[selectedCharacter].name} (${characters[selectedCharacter].role})`; |
| document.getElementById('current-scenario').textContent = scenarios[selectedScenario].title; |
| |
| |
| setupScreen.classList.add('hidden'); |
| chatScreen.classList.remove('hidden'); |
| resultsScreen.classList.add('hidden'); |
| |
| |
| chatMessages.innerHTML = ''; |
| messages = []; |
| |
| |
| sessionStartTime = new Date(); |
| |
| |
| setTimeout(() => { |
| const scenario = scenarios[selectedScenario]; |
| const character = characters[selectedCharacter]; |
| |
| const openingLines = [ |
| `Thanks for taking the time to discuss your ${scenario.product}. As ${character.role}, I have some specific concerns about how this would work for us.`, |
| `I've been reviewing information about your ${scenario.product}. At ${scenario.price}, I need to understand exactly what we're getting.`, |
| `Let's talk about your ${scenario.product}. I'm interested but have several questions before we proceed.` |
| ]; |
| |
| const opening = openingLines[Math.floor(Math.random() * openingLines.length)]; |
| addMessage(character.name, opening); |
| speak(opening, selectedCharacter); |
| }, 1500); |
| } |
| |
| |
| function endSessionSuccessfully() { |
| stopListening(); |
| |
| |
| const duration = Math.floor((new Date() - sessionStartTime) / 1000); |
| const minutes = Math.floor(duration / 60); |
| const seconds = duration % 60; |
| |
| |
| document.getElementById('result-character').textContent = `${characters[selectedCharacter].name} (${characters[selectedCharacter].role})`; |
| document.getElementById('result-scenario').textContent = scenarios[selectedScenario].title; |
| document.getElementById('result-duration').textContent = `${minutes}m ${seconds}s`; |
| document.getElementById('result-outcome').textContent = "Success - Sale Closed"; |
| document.getElementById('result-outcome').className = "font-medium text-green-600"; |
| |
| |
| document.getElementById('feedback-text').textContent = "Congratulations! You successfully navigated the objections and closed the sale. Here's what worked well:"; |
| |
| const strengths = [ |
| "You addressed pricing concerns effectively", |
| "You tailored your pitch to the buyer's specific role", |
| "You provided clear value propositions", |
| "You built rapport throughout the conversation" |
| ]; |
| |
| const improvements = [ |
| "Try to uncover objections earlier in the conversation", |
| "Consider using more specific data to support your claims", |
| "Practice transitioning more smoothly between topics", |
| "Work on your closing techniques to speed up the process" |
| ]; |
| |
| const strengthsList = document.getElementById('strengths-list'); |
| const improvementsList = document.getElementById('improvements-list'); |
| |
| strengthsList.innerHTML = strengths.slice(0, 2).map(s => `<li>${s}</li>`).join(''); |
| improvementsList.innerHTML = improvements.slice(0, 2).map(i => `<li>${i}</li>`).join(''); |
| |
| |
| chatScreen.classList.add('hidden'); |
| resultsScreen.classList.remove('hidden'); |
| } |
| |
| |
| function endSessionManually() { |
| stopListening(); |
| |
| |
| const duration = Math.floor((new Date() - sessionStartTime) / 1000); |
| const minutes = Math.floor(duration / 60); |
| const seconds = duration % 60; |
| |
| |
| document.getElementById('result-character').textContent = `${characters[selectedCharacter].name} (${characters[selectedCharacter].role})`; |
| document.getElementById('result-scenario').textContent = scenarios[selectedScenario].title; |
| document.getElementById('result-duration').textContent = `${minutes}m ${seconds}s`; |
| document.getElementById('result-outcome').textContent = "Practice Ended"; |
| document.getElementById('result-outcome').className = "font-medium text-blue-600"; |
| |
| |
| document.getElementById('feedback-text').textContent = "You ended the session before reaching a conclusion. Here are some observations from your practice:"; |
| |
| const strengths = [ |
| "You asked good probing questions", |
| "You demonstrated product knowledge", |
| "You maintained professional tone throughout", |
| "You listened actively to the buyer's concerns" |
| ]; |
| |
| const improvements = [ |
| "Work on handling objections more effectively", |
| "Practice transitioning to the close", |
| "Try to uncover the real decision criteria earlier", |
| "Use more trial closes to gauge interest" |
| ]; |
| |
| const strengthsList = document.getElementById('strengths-list'); |
| const improvementsList = document.getElementById('improvements-list'); |
| |
| strengthsList.innerHTML = strengths.slice(0, 2).map(s => `<li>${s}</li>`).join(''); |
| improvementsList.innerHTML = improvements.slice(0, 2).map(i => `<li>${i}</li>`).join(''); |
| |
| |
| chatScreen.classList.add('hidden'); |
| resultsScreen.classList.remove('hidden'); |
| } |
| |
| |
| function showHint() { |
| if (!selectedScenario) return; |
| |
| currentHints = [...scenarios[selectedScenario].hints]; |
| currentHintIndex = 0; |
| |
| hintContent.textContent = currentHints[currentHintIndex]; |
| hintModal.classList.remove('hidden'); |
| } |
| |
| |
| function showNextHint() { |
| currentHintIndex = (currentHintIndex + 1) % currentHints.length; |
| hintContent.textContent = currentHints[currentHintIndex]; |
| } |
| |
| |
| function showTranscript() { |
| transcriptContent.innerHTML = messages.map(msg => { |
| return `<div class="mb-2"> |
| <div class="flex justify-between items-baseline mb-1"> |
| <span class="font-medium text-gray-800">${msg.sender}</span> |
| <span class="text-xs text-gray-500">${new Date(msg.timestamp).toLocaleTimeString()}</span> |
| </div> |
| <div class="text-gray-700">${msg.text}</div> |
| </div>`; |
| }).join(''); |
| |
| transcriptModal.classList.remove('hidden'); |
| } |
| |
| |
| function downloadSessionTranscript() { |
| const transcriptText = messages.map(msg => { |
| return `${msg.sender} (${new Date(msg.timestamp).toLocaleString()}):\n${msg.text}\n\n`; |
| }).join(''); |
| |
| const blob = new Blob([transcriptText], { type: 'text/plain' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `negotiation-session-${new Date().toISOString().slice(0,10)}.txt`; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| URL.revokeObjectURL(url); |
| } |
| |
| |
| startSession.addEventListener('click', startNewSession); |
| endSession.addEventListener('click', endSessionManually); |
| newSession.addEventListener('click', () => { |
| resultsScreen.classList.add('hidden'); |
| setupScreen.classList.remove('hidden'); |
| }); |
| viewTranscript.addEventListener('click', showTranscript); |
| downloadTranscript.addEventListener('click', downloadSessionTranscript); |
| |
| |
| document.querySelectorAll('.character-option').forEach(option => { |
| option.addEventListener('click', function() { |
| document.querySelectorAll('.character-option').forEach(opt => { |
| opt.querySelector('.character-avatar').classList.remove('selected', 'border-blue-500'); |
| }); |
| this.querySelector('.character-avatar').classList.add('selected', 'border-blue-500'); |
| }); |
| }); |
| |
| |
| document.querySelectorAll('.scene-card').forEach(card => { |
| card.addEventListener('click', function() { |
| document.querySelectorAll('.scene-card').forEach(c => { |
| c.classList.remove('selected', 'border-blue-500'); |
| }); |
| this.classList.add('selected', 'border-blue-500'); |
| }); |
| }); |
| |
| |
| sendText.addEventListener('click', () => { |
| sendUserMessage(textInput.value); |
| }); |
| |
| textInput.addEventListener('keypress', (e) => { |
| if (e.key === 'Enter') { |
| sendUserMessage(textInput.value); |
| } |
| }); |
| |
| |
| voiceToggle.addEventListener('click', toggleVoiceInput); |
| |
| |
| hintButton.addEventListener('click', showHint); |
| closeHint.addEventListener('click', () => { |
| hintModal.classList.add('hidden'); |
| }); |
| nextHint.addEventListener('click', showNextHint); |
| |
| |
| closeTranscript.addEventListener('click', () => { |
| transcriptModal.classList.add('hidden'); |
| }); |
| |
| |
| speechSynthesis.onvoiceschanged = function() { |
| |
| }; |
| |
| |
| if (speechSynthesis.getVoices().length > 0) { |
| speechSynthesis.onvoiceschanged(); |
| } |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=shaike123/moreter" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |