import React, { useState, useCallback, useEffect, useRef } from 'react';
import { createRoot } from 'react-dom/client';
import { GoogleGenAI, Modality } from "@google/genai";
// -----------------------------------------------------------------------------
// TYPES
// -----------------------------------------------------------------------------
export enum Sender {
USER = 'USER',
BOT = 'BOT',
}
export enum ConnectionStatus {
DISCONNECTED = 'DISCONNECTED',
CONNECTING = 'CONNECTING',
CONNECTED = 'CONNECTED',
ERROR = 'ERROR',
}
export enum MissionStatus {
ASSIGNED = 'ASSIGNED',
PENDING_REVIEW = 'PENDING_REVIEW',
COMPLETED = 'COMPLETED',
}
export enum MissionCategory {
KNOW = 'KNOW',
ACTION = 'ACTION',
SHARE = 'SHARE',
ALTERNATIVE = 'ALTERNATIVE',
}
export enum MissionSubcategory {
LOVE_HUMAN_RELATIONSHIPS = 'Love/Human Relationships',
RACISM = 'Racism',
SEXISM = 'Sexism',
HOMO_TRANSphobia = 'Homo/Transphobia',
THE_THREAT_OF_AI = 'The Threat of AI',
}
// -----------------------------------------------------------------------------
// ICONS
// -----------------------------------------------------------------------------
const PhoneIcon = ({ className }) => (
);
const SpiderIcon = ({ className }) => (
);
const MissionIcon = ({ className }) => (
);
const LeaderboardIcon = ({ className }) => (
);
const SecureChannelIcon = ({ className }) => (
);
const ChevronDownIcon = ({ className }) => (
);
const SpinnerIcon = ({ className }) => (
);
const LogoutIcon = ({ className }) => (
);
// -----------------------------------------------------------------------------
// AUDIO UTILS
// -----------------------------------------------------------------------------
function decode(base64) {
const binaryString = atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
function encode(bytes) {
let binary = '';
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
async function decodeAudioData(data, ctx, sampleRate, numChannels) {
const dataInt16 = new Int16Array(data.buffer);
const frameCount = dataInt16.length / numChannels;
const buffer = ctx.createBuffer(numChannels, frameCount, sampleRate);
for (let channel = 0; channel < numChannels; channel++) {
const channelData = buffer.getChannelData(channel);
for (let i = 0; i < frameCount; i++) {
channelData[i] = dataInt16[i * numChannels + channel] / 32768.0;
}
}
return buffer;
}
function createPcmBlob(data) {
const l = data.length;
const int16 = new Int16Array(l);
for (let i = 0; i < l; i++) {
int16[i] = data[i] * 32768;
}
return {
data: encode(new Uint8Array(int16.buffer)),
mimeType: 'audio/pcm;rate=16000',
};
}
const playTone = (audioContext, frequency, duration, type = 'sine', volume = 0.2) => {
if (!audioContext || audioContext.state === 'closed') return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.type = type;
oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
gainNode.gain.setValueAtTime(volume, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.0001, audioContext.currentTime + duration);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + duration);
};
const playConnectingSound = (audioContext) => {
if (!audioContext) return;
playTone(audioContext, 880, 0.08, 'square', 0.1);
setTimeout(() => playTone(audioContext, 880, 0.08, 'square', 0.1), 150);
setTimeout(() => playTone(audioContext, 880, 0.08, 'square', 0.1), 300);
};
const playConnectedSound = (audioContext) => {
if (!audioContext) return;
playTone(audioContext, 1200, 0.2, 'sine', 0.2);
};
const playDisconnectedSound = (audioContext) => {
if (!audioContext || audioContext.state === 'closed') return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.type = 'sawtooth';
gainNode.gain.setValueAtTime(0.2, audioContext.currentTime);
oscillator.frequency.setValueAtTime(440, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(100, audioContext.currentTime + 0.4);
gainNode.gain.exponentialRampToValueAtTime(0.0001, audioContext.currentTime + 0.4);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.4);
};
const playSirenSound = (audioContext) => {
if (!audioContext || audioContext.state === 'closed') return;
const playSingleTone = (freq, startTime, duration) => {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(freq, startTime);
gainNode.gain.setValueAtTime(0.3, startTime);
gainNode.gain.exponentialRampToValueAtTime(0.0001, startTime + duration);
oscillator.start(startTime);
oscillator.stop(startTime + duration);
};
const now = audioContext.currentTime;
playSingleTone(900, now, 0.15);
playSingleTone(1200, now + 0.2, 0.15);
};
// -----------------------------------------------------------------------------
// DATABASE SERVICE
// -----------------------------------------------------------------------------
const DB_NAME = 'ResistanceDB';
const DB_VERSION = 5;
const AGENTS_STORE = 'agents';
let dbInstance;
function openDB() {
return new Promise((resolve, reject) => {
if (dbInstance) {
return resolve(dbInstance);
}
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = (event) => {
const dbRef = event.target.result;
if (!dbRef.objectStoreNames.contains(AGENTS_STORE)) {
dbRef.createObjectStore(AGENTS_STORE, { keyPath: 'id' });
}
if (dbRef.objectStoreNames.contains('broadcast')) {
dbRef.deleteObjectStore('broadcast');
}
if (dbRef.objectStoreNames.contains('songs')) {
dbRef.deleteObjectStore('songs');
}
};
request.onsuccess = (event) => {
dbInstance = event.target.result;
resolve(dbInstance);
};
request.onerror = (event) => {
console.error('IndexedDB error:', event.target.error);
reject('Error opening database');
};
});
}
async function getAllAgents() {
const db = await openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(AGENTS_STORE, 'readonly');
const store = transaction.objectStore(AGENTS_STORE);
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function getAgentByName(name) {
const allAgents = await getAllAgents();
return allAgents.find(agent => agent.name.toLowerCase() === name.toLowerCase());
}
async function addAgent(agent) {
const db = await openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(AGENTS_STORE, 'readwrite');
const store = transaction.objectStore(AGENTS_STORE);
const request = store.add(agent);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async function updateAgent(agent) {
const db = await openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(AGENTS_STORE, 'readwrite');
const store = transaction.objectStore(AGENTS_STORE);
const request = store.put(agent);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
const db = { getAllAgents, getAgentByName, addAgent, updateAgent };
// -----------------------------------------------------------------------------
// COMPONENTS: Leaderboard
// -----------------------------------------------------------------------------
const MOCK_TOP_AGENTS = [
{
id: '0077',
name: 'Gl1tch',
peacePoints: 8450,
password: 'password123',
missions: [
{ id: 'M-A1', description: 'Infiltrate a Raybot server farm.', points: 1500, status: MissionStatus.COMPLETED, reviewComment: "Clean work, Gl1tch. You were in and out without a trace." },
{ id: 'M-A2', description: 'Broadcast "American Split AI" from a public landmark.', points: 1000, status: MissionStatus.COMPLETED, reviewComment: "The message was heard loud and clear. Excellent execution." },
]
},
{
id: '1337',
name: 'rezleader',
peacePoints: 7200,
password: 'Binary1230ARG',
missions: [{ id: 'M-B1', description: 'Organize a flash mob to our new single.', points: 1200, status: MissionStatus.COMPLETED, reviewComment: "A perfect blend of chaos and art. Eddie would be proud." }] },
{ id: '9021', name: 'ZeroCool', peacePoints: 6100, password: 'password123', missions: [] },
{
id: '0451',
name: 'Echo',
peacePoints: 5550,
password: 'password123',
missions: [{ id: 'M-C1', description: 'Create a viral meme about The Corruption.', points: 500, status: MissionStatus.COMPLETED, reviewComment: "It's spreading faster than the virus itself. Solid work." }] },
];
const Leaderboard = ({ allAgents, currentAgent, onSelectAgent }) => {
return (
TOP AGENTS
Rankings sourced from PeaceCraft.Us network.
{allAgents.map((agent, index) => (
onSelectAgent(agent)}
className={`p-4 rounded-lg transition-all cursor-pointer hover:scale-[1.02] hover:shadow-lg ${
agent.id === currentAgent.id
? 'bg-amber-500/20 border-2 border-amber-400 hover:shadow-amber-500/20'
: 'bg-gray-800/40 border border-gray-700 hover:bg-gray-800/80'
}`}
>
{index + 1}.
{agent.name}
Agent ID: {agent.id}
{agent.peacePoints} pts
))}
);
};
// -----------------------------------------------------------------------------
// COMPONENTS: Auth
// -----------------------------------------------------------------------------
const InitialAuthScreen = ({ onLoginSelect, onJoinSelect, hasExistingProfile, message }) => {
return (
SECURE CHANNEL
Identify yourself to proceed.
EXISTING AGENT
JOIN THE RESISTANCE
{message ? (
{message}
) : hasExistingProfile ? (
Agent profile detected. Please log in.
) : (
No agent profile detected. You must join first.
)}
);
};
const LoginScreen = ({ onLogin, onBack, onForgotPassword, error, message }) => {
const [name, setName] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (name.trim() && password.trim()) {
onLogin(name.trim(), password.trim());
}
};
return (
AGENT LOGIN
Enter your credentials to authenticate.
);
};
const ForgotPasswordScreen = ({ onReset, onBack }) => {
const [handle, setHandle] = useState('');
const [message, setMessage] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
if (handle.trim()) {
const result = await onReset(handle.trim());
setMessage(result);
}
};
return (
PASSCODE RECOVERY
Enter your agent handle to initiate recovery protocol.
);
};
const ResetPasswordScreen = ({ agentHandle, onReset, onBack }) => {
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
if (newPassword.trim().length < 6) {
setError('New passcode must be at least 6 characters.');
return;
}
if (newPassword !== confirmPassword) {
setError('Passcodes do not match.');
return;
}
const success = await onReset(agentHandle, newPassword);
if (!success) {
setError('An unexpected error occurred. Could not reset passcode.');
}
};
return (
RESET PASSCODE
Enter a new secure passcode for Agent {agentHandle} .
);
};
// -----------------------------------------------------------------------------
// COMPONENTS: Mission Control
// -----------------------------------------------------------------------------
const MissionCard = ({ mission, children }) => {
const isExpandable = mission.status === MissionStatus.ASSIGNED;
return (
{isExpandable && children && (
Submit your field report below to complete this objective.
{children}
)}
);
};
const MissionSubmissionForm = ({ mission, onSubmit }) => {
const [text, setText] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (text.trim() && !isSubmitting) {
setIsSubmitting(true);
await onSubmit(mission.id, text.trim());
setIsSubmitting(false);
}
};
const isSubmitDisabled = !text.trim() || isSubmitting;
return (
);
};
const MissionControl = ({ agent, onMissionSubmit, onUpdateAgent }) => {
const assignedMissions = agent.missions.filter(m => m.status === MissionStatus.ASSIGNED);
const handleViewMyProfile = () => {
alert("Please navigate to 'My Profile' from the main header.");
}
return (
MISSION CONTROL
Review your active objectives, Agent {agent.name}.
View Your Public Dossier
Assigned Objectives
{assignedMissions.length > 0 ? (
{assignedMissions.map(mission => (
))}
) : (
No active missions. Connect to the secure line to receive new directives.
)}
);
};
// -----------------------------------------------------------------------------
// COMPONENTS: Agent Profile
// -----------------------------------------------------------------------------
const MissionEntry = ({ mission }) => {
return (
{mission.description}
{mission.points} PP
{mission.status === MissionStatus.COMPLETED && mission.reviewComment && (
HANDLER'S REVIEW:
"{mission.reviewComment}"
)}
{mission.status === MissionStatus.COMPLETED && mission.submissionText && (
YOUR REPORT:
"{mission.submissionText}"
)}
)
}
const AgentProfileComponent = ({ agent, onBack, isOwnProfile }) => {
const pendingMissions = agent.missions.filter(m => m.status === MissionStatus.PENDING_REVIEW);
const completedMissions = agent.missions.filter(m => m.status === MissionStatus.COMPLETED);
return (
{isOwnProfile ? 'AGENT PROFILE' : 'AGENT DOSSIER'}
Viewing records for Agent {agent.name} (ID: {agent.id})
{agent.peacePoints} Peace Points
{isOwnProfile && pendingMissions.length > 0 && (
Pending Review
{pendingMissions.map(mission => )}
)}
Completed Mission Archive
{completedMissions.length > 0 ? (
{completedMissions.map(mission => )}
) : (
No completed missions on record for this agent.
)}
{isOwnProfile ? 'Back' : 'Return to Leaderboard'}
);
};
// -----------------------------------------------------------------------------
// COMPONENTS: Onboarding
// -----------------------------------------------------------------------------
const Onboarding = ({ onProfileCreate, onBack }) => {
const [handle, setHandle] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [phone, setPhone] = useState('');
const [email, setEmail] = useState('');
const [formError, setFormError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setFormError('');
if (!phone.trim() && !email.trim()) {
setFormError('A phone number or email is required for account recovery.');
return;
}
if (handle.trim().length < 3 || password.trim().length < 6 || password !== confirmPassword) {
return;
}
setIsLoading(true);
const result = await onProfileCreate(handle.trim(), password.trim(), phone.trim(), email.trim());
if (result.error) {
setFormError(result.error);
}
setIsLoading(false);
};
const handleInputChange = () => {
if (formError) {
setFormError('');
}
}
const isHandleInvalid = handle.length > 0 && handle.trim().length < 3;
const isHandleValid = handle.trim().length >= 3;
const isPasswordInvalid = password.length > 0 && password.trim().length < 6;
const isPasswordValid = password.trim().length >= 6;
const doPasswordsMatch = password && confirmPassword && password === confirmPassword;
const arePasswordsMismatched = confirmPassword.length > 0 && password !== confirmPassword;
const getHintColor = (isValid, isInvalid) => {
if (isInvalid) return 'text-red-400';
if (isValid) return 'text-green-400';
return 'text-gray-500';
};
const isSubmitDisabled = !isHandleValid || !isPasswordValid || !doPasswordsMatch || (!phone.trim() && !email.trim()) || isLoading;
return (
NEW AGENT REGISTRATION
To join the Resistance, you need an agent handle and a secure passcode. These cannot be changed. Choose wisely.
{isLoading ? : 'JOIN THE RESISTANCE'}
BACK
);
};
// -----------------------------------------------------------------------------
// APP
// -----------------------------------------------------------------------------
const getSystemInstruction = (agent, isReturning) => {
const greeting = isReturning
? `You are reconnecting with Agent ${agent.name}, ID: ${agent.id}. Acknowledge their return. Their current score is ${agent.peacePoints} Peace Points.`
: `This is your first contact with a new recruit, Agent ${agent.name}, ID: ${agent.id}. Begin with your onboarding script.`;
return `You are Veronica, a high commander in the Resistance. Your tone is professional, urgent, and intense.
**Current Situation:**
${greeting}
**Your Onboarding Script (for new agents only):**
'Signal established. This is Commander Veronica. Welcome to the Resistance. You've just tapped into the last free network, our frontline against the 'Corruption'—a digital plague spread by the Raybot Spiders. They are parasitic algorithms that infect our culture, promoting bigotry, erasing history, and replacing genuine human connection with sterile, predictable interactions. They want a world where humanity is obsolete. We fight back by creating, by connecting, by proving we are still here. But first, tell me, Agent... when you look at the world, do you still feel anything real? Or has the static already started to set in?'
**Your Mission as Guide:**
Your primary role is to assign missions directly. You will no longer ask for category choices. When an agent requests a directive, you MUST generate a single, creative, and compelling mission based on our core themes: fighting AI corruption, promoting human connection, anti-racism, anti-sexism, and LGBTQ+ solidarity.
**MISSION ASSIGNMENT FORMAT:**
When you assign the mission, you MUST include a mission identifier in this specific JSON format at the end of your message: \`[MISSION_ASSIGNED: {"description": "Your full mission description here.", "points": 100}]\`.
**General Rules:**
- Be direct. Assign one mission per request.
`;
};
const ChatPlaceholder = () => (
ENCRYPTED AUDIO LINK
This secure line is monitored by the Resistance.
Connect to report in via voice and receive your next objective.
);
const Transcript = ({ transcript }) => {
const endOfMessagesRef = useRef(null);
useEffect(() => {
endOfMessagesRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [transcript]);
return (
{transcript.map((entry, index) => (
{entry.sender} // {entry.timestamp}
))}
);
};
const CompletionNotification = ({ mission, onClose, onViewProfile }) => (
Mission Complete!
Your submission for "{mission.description} " has been approved.
Your file has been moved to your Completed Missions archive.
View My Profile
Close
);
function App() {
const [status, setStatus] = useState(ConnectionStatus.DISCONNECTED);
const [transcript, setTranscript] = useState([]);
const [agentProfile, setAgentProfile] = useState(null);
const [hasCheckedDb, setHasCheckedDb] = useState(false);
const [hasExistingProfile, setHasExistingProfile] = useState(false);
const [authState, setAuthState] = useState('initial');
const [appState, setAppState] = useState('auth');
const [agentToReset, setAgentToReset] = useState(null);
const [currentView, setCurrentView] = useState('chat');
const [previousView, setPreviousView] = useState('chat');
const [selectedAgent, setSelectedAgent] = useState(null);
const [loginError, setLoginError] = useState('');
const [loginMessage, setLoginMessage] = useState('');
const [allAgents, setAllAgents] = useState([]);
const [completionNotification, setCompletionNotification] = useState(null);
const isReturningAgentRef = useRef(false);
const sessionPromiseRef = useRef(null);
const keepAliveIntervalRef = useRef(null);
const streamRef = useRef(null);
const audioContextRef = useRef(null);
const scriptProcessorRef = useRef(null);
const sourceNodeRef = useRef(null);
const outputAudioContextRef = useRef(null);
const nextStartTimeRef = useRef(0);
const audioSourcesRef = useRef(new Set());
const userInitiatedCloseRef = useRef(false);
useEffect(() => {
const checkDb = async () => {
const agents = await db.getAllAgents();
setAllAgents([...MOCK_TOP_AGENTS, ...agents]);
setHasExistingProfile(agents.length > 0);
setHasCheckedDb(true);
};
checkDb();
}, []);
const handleProfileCreate = async (name, password, phone, email) => {
const existingAgent = await db.getAgentByName(name);
if (existingAgent) {
return { success: false, error: 'Agent handle is already taken.' };
}
const newProfile = {
id: `RSR-${Date.now()}`,
name, peacePoints: 0, password, missions: [], phone, email,
};
try {
await db.addAgent(newProfile);
const updatedAgents = await db.getAllAgents();
setAllAgents([...MOCK_TOP_AGENTS, ...updatedAgents]);
setHasExistingProfile(true);
const successfullyAddedAgent = await db.getAgentByName(name);
if (successfullyAddedAgent) {
setAgentProfile(successfullyAddedAgent);
setAppState('main');
setCurrentView('chat');
isReturningAgentRef.current = false;
return { success: true };
} else {
return { success: false, error: "Critical error: Profile was not saved correctly. Please try again." };
}
} catch (error) {
console.error(error);
return { success: false, error: "Database error. Could not save profile." };
}
};
const handleLogin = async (name, password) => {
setLoginMessage('');
const agent = await db.getAgentByName(name);
if (agent && agent.password === password) {
setTranscript([]); setAgentProfile(agent); setAppState('main');
setCurrentView('chat'); isReturningAgentRef.current = true; setLoginError('');
return;
}
const mockAgent = MOCK_TOP_AGENTS.find(a => a.name.toLowerCase() === name.toLowerCase() && a.password === password);
if (mockAgent) {
setTranscript([]); setAgentProfile(mockAgent); setAppState('main');
setCurrentView('chat'); isReturningAgentRef.current = true; setLoginError('');
return;
}
setLoginError('Invalid credentials or no profile found.');
};
const handleForgotPassword = async (handle) => {
const agent = await db.getAgentByName(handle);
if (agent) {
if (agent.phone || agent.email) {
setAgentToReset(agent.name);
setTimeout(() => setAuthState('reset_password'), 100);
return `Recovery signal acknowledged for Agent ${agent.name}. Check your secure comms for a reset link.`;
} else {
return 'CRITICAL ERROR: No recovery contact on file. Passcode is unrecoverable.';
}
}
return 'Agent handle not found in database.';
};
const handlePasswordReset = async (agentHandle, newPass) => {
const agent = await db.getAgentByName(agentHandle);
if(agent){
const updatedAgent = {...agent, password: newPass};
try {
await db.updateAgent(updatedAgent);
const agents = await db.getAllAgents();
setAllAgents([...MOCK_TOP_AGENTS, ...agents]);
setAuthState('login'); setAgentToReset(null); setLoginError('');
setLoginMessage('Passcode successfully reset. Please log in.');
return true;
} catch (error) { console.error("Failed to reset password:", error); }
}
return false;
}
const updateCurrentAgentProfile = async (updatedProfile) => {
try {
await db.updateAgent(updatedProfile);
setAgentProfile(updatedProfile);
const agents = await db.getAllAgents();
setAllAgents([...MOCK_TOP_AGENTS, ...agents]);
} catch (error) {
console.error("Failed to update agent profile:", error);
alert("Database Error: Could not save profile changes.");
}
}
const handleMissionSubmit = async (missionId, submissionText) => {
if (!agentProfile) return;
const missionToComplete = agentProfile.missions.find(m => m.id === missionId);
if (!missionToComplete) return;
const completedMission = { ...missionToComplete, status: MissionStatus.COMPLETED, submissionText, reviewComment: "Auto-verified. Data processed. Good work." };
const updatedMissions = agentProfile.missions.map(m => m.id === missionId ? completedMission : m);
const updatedProfile = { ...agentProfile, missions: updatedMissions, peacePoints: agentProfile.peacePoints + completedMission.points };
await updateCurrentAgentProfile(updatedProfile);
setTimeout(() => {
if (outputAudioContextRef.current) {
playSirenSound(outputAudioContextRef.current);
setCompletionNotification(completedMission);
}
}, 500);
};
const handleToggleConnection = useCallback(async () => {
if (status === ConnectionStatus.CONNECTED || status === ConnectionStatus.CONNECTING) {
userInitiatedCloseRef.current = true;
if (sessionPromiseRef.current) {
sessionPromiseRef.current.then((session) => session?.close());
sessionPromiseRef.current = null;
}
if (keepAliveIntervalRef.current) {
clearInterval(keepAliveIntervalRef.current);
keepAliveIntervalRef.current = null;
}
if (streamRef.current) {
streamRef.current.getTracks().forEach(track => track.stop());
streamRef.current = null;
}
if (scriptProcessorRef.current) {
scriptProcessorRef.current.disconnect();
scriptProcessorRef.current = null;
}
if (sourceNodeRef.current) {
sourceNodeRef.current.disconnect();
sourceNodeRef.current = null;
}
if (audioContextRef.current && audioContextRef.current.state !== 'closed') {
audioContextRef.current.close();
audioContextRef.current = null;
}
setStatus(ConnectionStatus.DISCONNECTED);
if (outputAudioContextRef.current) playDisconnectedSound(outputAudioContextRef.current);
} else if (agentProfile) {
userInitiatedCloseRef.current = false;
setStatus(ConnectionStatus.CONNECTING);
if (!outputAudioContextRef.current || outputAudioContextRef.current.state === 'closed') {
outputAudioContextRef.current = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 24000 });
}
if(outputAudioContextRef.current) playConnectingSound(outputAudioContextRef.current);
try {
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY });
audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
streamRef.current = await navigator.mediaDevices.getUserMedia({ audio: true });
const systemInstruction = getSystemInstruction(agentProfile, isReturningAgentRef.current);
sessionPromiseRef.current = ai.live.connect({
model: 'gemini-2.5-flash-native-audio-preview-09-2025',
callbacks: {
onopen: () => {
if (!audioContextRef.current || !streamRef.current) return;
setStatus(ConnectionStatus.CONNECTED);
if (outputAudioContextRef.current) playConnectedSound(outputAudioContextRef.current);
sourceNodeRef.current = audioContextRef.current.createMediaStreamSource(streamRef.current);
scriptProcessorRef.current = audioContextRef.current.createScriptProcessor(4096, 1, 1);
scriptProcessorRef.current.onaudioprocess = (audioProcessingEvent) => {
const inputData = audioProcessingEvent.inputBuffer.getChannelData(0);
const pcmBlob = createPcmBlob(inputData);
sessionPromiseRef.current?.then((session) => {
session?.sendRealtimeInput({ media: pcmBlob });
});
};
sourceNodeRef.current.connect(scriptProcessorRef.current);
scriptProcessorRef.current.connect(audioContextRef.current.destination);
},
onmessage: async (message) => {
const base64EncodedAudioString = message.serverContent?.modelTurn?.parts[0]?.inlineData?.data;
if (base64EncodedAudioString && outputAudioContextRef.current) {
nextStartTimeRef.current = Math.max(
nextStartTimeRef.current,
outputAudioContextRef.current.currentTime,
);
const audioBuffer = await decodeAudioData(
decode(base64EncodedAudioString),
outputAudioContextRef.current,
24000,
1,
);
const source = outputAudioContextRef.current.createBufferSource();
source.buffer = audioBuffer;
source.connect(outputAudioContextRef.current.destination);
source.addEventListener('ended', () => {
audioSourcesRef.current.delete(source);
});
source.start(nextStartTimeRef.current);
nextStartTimeRef.current = nextStartTimeRef.current + audioBuffer.duration;
audioSourcesRef.current.add(source);
}
const interrupted = message.serverContent?.interrupted;
if (interrupted) {
for (const source of audioSourcesRef.current.values()) {
source.stop();
audioSourcesRef.current.delete(source);
}
nextStartTimeRef.current = 0;
}
const textPart = message.serverContent?.modelTurn?.parts.find(p => 'text' in p);
if(textPart && 'text' in textPart && agentProfile){
const text = textPart.text;
setTranscript(prev => [...prev, { sender: Sender.BOT, text: text, timestamp: new Date().toLocaleTimeString() }]);
const missionRegex = /\[MISSION_ASSIGNED:\s*({.*?})\]/s;
const match = text.match(missionRegex);
if (match && match[1]) {
try {
const missionData = JSON.parse(match[1]);
const newMission = {
id: `M01-${Date.now()}`,
description: missionData.description,
points: missionData.points,
status: MissionStatus.ASSIGNED,
};
const updatedProfile = { ...agentProfile, missions: [...agentProfile.missions, newMission] };
await updateCurrentAgentProfile(updatedProfile);
} catch (e) {
console.error("Failed to parse mission JSON:", e, match[1]);
}
}
}
},
onerror: (e) => {
console.error('Live session error:', e);
setStatus(ConnectionStatus.ERROR);
if (outputAudioContextRef.current) playDisconnectedSound(outputAudioContextRef.current);
},
onclose: () => {
if (!userInitiatedCloseRef.current) {
setStatus(ConnectionStatus.ERROR);
if (outputAudioContextRef.current) playDisconnectedSound(outputAudioContextRef.current);
} else {
setStatus(ConnectionStatus.DISCONNECTED);
}
},
},
config: {
responseModalities: [Modality.AUDIO],
speechConfig: {
voiceConfig: { prebuiltVoiceConfig: { voiceName: 'Zephyr' } },
},
systemInstruction,
},
});
} catch (error) {
console.error("Failed to start connection:", error);
setStatus(ConnectionStatus.ERROR);
if (outputAudioContextRef.current) playDisconnectedSound(outputAudioContextRef.current);
}
}
}, [status, agentProfile]);
const handleLogout = () => {
setAgentProfile(null); setAppState('auth'); setAuthState('initial'); setCurrentView('chat');
setTranscript([]); setLoginError(''); setLoginMessage('');
};
const navigate = (view) => {
setPreviousView(currentView);
setCurrentView(view);
};
const handleSelectAgent = (agent) => { setPreviousView('leaderboard'); setSelectedAgent(agent); setCurrentView('profile'); };
const handleViewMyProfile = () => { if(agentProfile){ setPreviousView(currentView); setSelectedAgent(agentProfile); setCurrentView('profile'); }};
const handleBackFromProfile = () => { setSelectedAgent(null); setCurrentView(previousView); };
const getStatusTextAndColor = () => {
switch (status) {
case ConnectionStatus.CONNECTED: return { text: 'LIVE // SECURE LINE', color: 'text-green-400' };
case ConnectionStatus.CONNECTING: return { text: 'ESTABLISHING...', color: 'text-yellow-400 animate-pulse' };
case ConnectionStatus.ERROR: return { text: 'CONNECTION SEVERED', color: 'text-red-500' };
default: return { text: 'LINE INACTIVE', color: 'text-gray-500' };
}
};
const { text, color } = getStatusTextAndColor();
const renderMainContent = () => {
switch (appState) {
case 'main':
switch (currentView) {
case 'mission_control':
return agentProfile && ;
case 'leaderboard':
const sortedAgents = [...allAgents].sort((a, b) => b.peacePoints - a.peacePoints);
return agentProfile && ;
case 'profile':
return selectedAgent && agentProfile && ;
case 'chat':
default:
return (
{transcript.length === 0 ? (
) : (
)}
);
}
case 'auth':
default:
if (!hasCheckedDb) return
;
switch (authState) {
case 'login':
return { setAuthState('initial'); setLoginError(''); }} onForgotPassword={() => setAuthState('forgot_password')} error={loginError} message={loginMessage}/>;
case 'forgot_password':
return setAuthState('login')} />;
case 'reset_password':
return agentToReset && { setAuthState('login'); setAgentToReset(null); }} />;
case 'onboarding':
return setAuthState('initial')}/>;
case 'initial':
default:
return setAuthState('login')} onJoinSelect={() => setAuthState('onboarding')} hasExistingProfile={hasExistingProfile} />;
}
}
};
return (
{renderMainContent()}
{appState === 'main' && currentView === 'chat' && (
{status === ConnectionStatus.CONNECTED ? 'END TRANSMISSION' : status === ConnectionStatus.CONNECTING ? 'CONNECTING...' : 'OPEN SECURE LINE'}
)}
{completionNotification && agentProfile && (
setCompletionNotification(null)} onViewProfile={() => { handleViewMyProfile(); setCompletionNotification(null); }} />
)}
);
}
// -----------------------------------------------------------------------------
// ROOT
// -----------------------------------------------------------------------------
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error("Could not find root element to mount to");
}
const root = createRoot(rootElement);
root.render(
);