diff --git a/apps/Gemini Nano/chat.js b/apps/Gemini Nano/chat.js index d3335a0..921e7f1 100644 --- a/apps/Gemini Nano/chat.js +++ b/apps/Gemini Nano/chat.js @@ -1,15 +1,26 @@ let personas = []; +let isPaused = false; // This will control whether the interactions are paused or active async function createPersona() { const attributes = getRandomAttributes(); // Retrieves random attributes for the persona - const name = await generatePersonaName(attributes.personality, attributes.role); // AI generates a name + let name = await generatePersonaName(attributes.personality, attributes.role); // AI generates a name if (!name) { alert('Failed to generate a name. Please try again.'); return; } + let retryCount = 0; + while (personas.some(persona => persona.name.toLowerCase() === name.toLowerCase())) { + if (retryCount >= 3) { // Limit the number of retries to prevent infinite loops + alert('Failed to generate a unique name after several attempts. Please try again later.'); + return; + } + name = await generatePersonaName(attributes.personality, attributes.role); // Try generating a new name + retryCount++; + } + const newPersona = { name, attributes, @@ -18,6 +29,7 @@ async function createPersona() { this.history.push(event); } }; + personas.push(newPersona); updateUI(); // Update the UI with the new persona } @@ -70,14 +82,17 @@ function getRandomAttributes() { }; } - function updateUI() { - const container = document.getElementById('personas-container'); - container.innerHTML = ''; - personas.forEach((persona, index) => { - const personaDiv = document.createElement('div'); - personaDiv.innerHTML = `${persona.name} (${persona.attributes.personality}, ${persona.attributes.role})`; - container.appendChild(personaDiv); + const container = document.getElementById('personas-content'); + // Iterate through personas and update UI with new entries only + personas.forEach(persona => { + const existingEntry = document.querySelector(`div[data-persona-id="${persona.name}"]`); + if (!existingEntry) { + const personaDiv = document.createElement('div'); + personaDiv.setAttribute('data-persona-id', persona.name); // Helps identify the div later + personaDiv.innerHTML = `${persona.name} (${persona.attributes.personality}, ${persona.attributes.role})`; + container.appendChild(personaDiv); + } }); } @@ -94,44 +109,59 @@ function handleBroadcast() { function broadcastEvent(event) { personas.forEach(persona => { - persona.logEvent(`Broadcast: ${event}`); // Ensure the log is clear it's a broadcast - updateUI(persona); // Optionally update the UI if needed + persona.logEvent(`Broadcast from Game Master: ${event}`); + appendBroadcastToUI(persona, event); // Append new event to each persona's UI without refreshing everything }); } -function broadcastEvent(event) { - personas.forEach(persona => { - persona.logEvent(event); - }); +function appendBroadcastToUI(persona, event) { + const container = document.getElementById('personas-content'); + const personaDiv = document.createElement('div'); + personaDiv.innerHTML = `${persona.name}: Broadcast from Game Master: ${event}`; + container.appendChild(personaDiv); } function updateActionUI(persona, action) { - const container = document.getElementById('personas-container'); const actionDiv = document.createElement('div'); + const parsedAction = marked.parse(action); // Convert Markdown to HTML - // Convert Markdown to HTML before adding to the DOM - const parsedAction = marked.parse(action); // Use marked.parse to convert Markdown to HTML + // Determine the correct container based on whether the update is for a persona or the Game Master + let container = persona ? document.getElementById('personas-content') : document.getElementById('gmUpdatesList'); + + // Prepare the content format depending on the target + if (persona) { + // For persona updates, display with persona's name + actionDiv.innerHTML = `${persona.name} action: ${parsedAction}`; + } else { + // For Game Master updates, use list item tags to match the list format + actionDiv.innerHTML = `
  • ${parsedAction}
  • `; + } - actionDiv.innerHTML = `${persona.name} action: ${parsedAction}`; // Embed the HTML in the DOM container.appendChild(actionDiv); } async function gatherAndProcessActions() { + if (isPaused) { + console.log("Action processing is paused."); + return; // Exit the function if the system is paused + } + let actions = []; for (let persona of personas) { - // Prompt each persona for an action const action = await promptForAction(persona); actions.push({ persona: persona.name, action: action }); persona.logEvent(`Action decided: ${action}`); console.log(`${persona.name} took the action: ${action}`); - updateActionUI(persona, action); // Display action immediately after gathering + updateActionUI(persona, action); } - // Shuffle actions to simulate dynamic interactions shuffleArray(actions); - // Process each shuffled action for (let action of actions) { + if (isPaused) { + console.log("Processing paused during action handling."); + return; // Check again if paused during processing loop + } await processAction(action); } } @@ -163,24 +193,15 @@ async function processAction(action) { for (let persona of personas) { if (persona.name === action.persona) continue; // Skip the actor - // Updated prompt to limit response length - const reactionPrompt = `I am ${persona.name}, a ${persona.attributes.personality} ${persona.attributes.role}. Given the action '${action.action}' by ${action.persona}, how should I react? Please keep your response to just 2 or 3 sentences, focusing on my immediate reaction based on my attributes and past interactions.`; + const reactionPrompt = `I am ${persona.name}, a ${persona.attributes.personality} ${persona.attributes.role}. Given the action '${action.action}' by ${action.persona}, how should I react?`; try { - const reaction = await aiPrompt('GeminiNano', reactionPrompt); // Prompt for reaction + const reaction = await aiPrompt('GeminiNano', reactionPrompt); persona.logEvent(`My reaction to ${action.persona}'s action: ${reaction}`); updateActionUI(persona, `My reaction to ${action.persona}'s action: ${reaction}`); - // Check if the reaction involves communication - const communicationPrompt = `Does my reaction involve communication with ${action.persona}? Please respond only with 'yes' or 'no'.`; - const involvesCommunicationHtml = await aiPrompt('GeminiNano', communicationPrompt); - const involvesCommunication = extractText(involvesCommunicationHtml); - - if (involvesCommunication.toLowerCase().trim() === 'yes') { - const recipientPersona = personas.find(p => p.name === action.persona); // Find the recipient persona object - if (recipientPersona) { - await handleCommunication(persona, recipientPersona, 3); // Pass the full recipient persona object - } + if (shouldInformGameMaster(reaction)) { + await informGameMaster(action, reaction); } } catch (error) { console.error(`Error prompting AI for ${persona.name}:`, error); @@ -189,6 +210,54 @@ async function processAction(action) { } } +async function isCommunication(reaction) { + // Construct a prompt asking the AI if the reaction involves communication + const prompt = `Does the following statement involve communication with another persona? Response: "${reaction}". Please answer 'yes' or 'no'.`; + + try { + const response = await aiPrompt('GeminiNano', prompt); // Using the aiPrompt function to query the AI + console.log('AI determined:', response.trim().toLowerCase()); + return response.trim().toLowerCase() === 'yes'; + } catch (error) { + console.error('Error querying AI to determine if the reaction is communication:', error); + return false; // Assume no communication if there's an error + } +} + +async function shouldInformGameMaster(reaction) { + // Use the AI to determine if the reaction is communicative + const isComm = await isCommunication(reaction); + return !isComm; // Inform the Game Master if it is not a communication +} + +async function processAction(action) { + for (let persona of personas) { + const reaction = await aiPrompt('GeminiNano', `Reaction prompt for ${persona.name}`); + persona.logEvent(`My reaction: ${reaction}`); + updateActionUI(persona, reaction); + + if (await shouldInformGameMaster(reaction)) { + await informGameMaster(action, reaction); + } + } +} + +async function informGameMaster(action, reaction) { + // Assume some mechanism to inform the Game Master + const gameMasterContext = `Game Master needs to update environment based on: ${action.action} which led to: ${reaction}`; + const environmentUpdate = await aiPrompt('GeminiNano', gameMasterContext); + + broadcastEvent(`Game Master has updated the environment: ${environmentUpdate}`); + updateActionUI(null, environmentUpdate); // Assuming updateActionUI can handle null persona for GM updates +} + +function broadcastEvent(event) { + personas.forEach(persona => { + persona.logEvent(`Broadcast from Game Master: ${event}`); + }); + updateUI(); // Update the UI with the new game master's broadcast +} + function extractText(html) { // Use DOMParser to parse HTML string and extract text const parser = new DOMParser(); @@ -241,24 +310,35 @@ async function handleCommunication(initiator, recipient, exchanges) { let aiSession = null; // Global variable to hold the session -async function aiPrompt(model, prompt) { - if (!aiSession) { // Check if session exists - if (await window.ai.canCreateTextSession() === "no") { - throw new Error("Unable to create an AI session."); +async function aiPrompt(model, prompt, retries = 3) { + while (retries > 0) { + try { + if (!aiSession) { // Check if session exists + if (await window.ai.canCreateTextSession() === "no") { + throw new Error("Unable to create an AI session."); + } + aiSession = await window.ai.createTextSession(); // Create a session if none + } + + console.log('Sending prompt to AI:', prompt); // Log the prompt for debugging + const result = await aiSession.prompt(prompt); + console.log('Received response from AI:', result); // Log the response for debugging + return result; // Return the AI's response + } catch (error) { + console.error('Attempt failed in aiPrompt:', error); // Log attempt failure + retries -= 1; // Decrement the retry counter + if (retries <= 0) { + throw new Error("Error during AI interaction after several attempts"); + } + console.log(`Retrying... attempts left: ${retries}`); } - aiSession = await window.ai.createTextSession(); // Create a session if none } +} - console.log('Sending prompt to AI:', prompt); // Log the prompt for debugging - - try { - const result = await aiSession.prompt(prompt); - console.log('Received response from AI:', result); // Log the response for debugging - return result; // Return the AI's response - } catch (error) { - console.error('Error in aiPrompt:', error); // Log errors in aiPrompt - throw new Error("Error during AI interaction"); - } +function togglePauseResume() { + isPaused = !isPaused; // Toggle the pause state + document.getElementById('pauseResumeButton').textContent = isPaused ? 'Resume' : 'Pause'; // Update button text based on state + console.log(isPaused ? "System is paused." : "System is resumed."); } document.getElementById('start-process-actions-button').addEventListener('click', gatherAndProcessActions); diff --git a/apps/Gemini Nano/index.html b/apps/Gemini Nano/index.html index f46412d..1163ce7 100644 --- a/apps/Gemini Nano/index.html +++ b/apps/Gemini Nano/index.html @@ -7,7 +7,6 @@
    -
    @@ -15,8 +14,16 @@ + +
    +
    +

    Game Master Updates

    + +
    +
    +

    Personas Interactions

    +
    -