diff --git a/apps/Gemini Nano/chat.js b/apps/Gemini Nano/chat.js
index 921e7f1..8db068b 100644
--- a/apps/Gemini Nano/chat.js
+++ b/apps/Gemini Nano/chat.js
@@ -1,4 +1,3 @@
-
let personas = [];
let isPaused = false; // This will control whether the interactions are paused or active
@@ -38,10 +37,17 @@ async function generatePersonaName(personality, role) {
const prompt = `Suggest a single, suitable name for a persona characterized as '${personality}' and whose profession is '${role}'. The name should be creative and reflect these attributes. Please provide only the name without any explanations or additional text.`;
try {
- const response = await aiPrompt('GeminiNano', prompt);
- const suggestedName = response.split('\n')[0].trim(); // Takes only the first line to ensure it's just the name
- console.log(`AI suggested name: ${suggestedName}`);
- return suggestedName;
+ const response = await aiPrompt(prompt);
+ // Check if the response is defined and has a valid format before splitting
+ if (response && typeof response === 'string') {
+ const suggestedName = response.split('\n')[0].trim(); // Takes only the first line to ensure it's just the name
+ console.log(`AI suggested name: ${suggestedName}`);
+ return suggestedName;
+ } else {
+ // Handle cases where response is undefined or not a string
+ console.error('Invalid response format:', response);
+ return null;
+ }
} catch (error) {
console.error('Error generating persona name:', error);
return null; // Return null if there's an error
@@ -121,70 +127,40 @@ function appendBroadcastToUI(persona, event) {
container.appendChild(personaDiv);
}
-function updateActionUI(persona, action) {
+function updateActionUI(persona, message, isReaction = false) {
+ if (!message) {
+ console.error("Attempted to update UI with undefined or null message.");
+ return; // Prevent further processing
+ }
+
+ const container = document.getElementById('personas-content');
const actionDiv = document.createElement('div');
- const parsedAction = marked.parse(action); // 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');
+ const lines = message.split('\n');
+ const firstLine = lines.shift(); // Remove and return the first line from the array
+ const rest = lines.join('\n'); // Join the remaining lines back into a single string
- // Prepare the content format depending on the target
- if (persona) {
- // For persona updates, display with persona's name
- actionDiv.innerHTML = `${persona.name} action: ${parsedAction}`;
+ if (isReaction) {
+ actionDiv.innerHTML = `Reaction to ${persona.name}'s action: ${firstLine}${rest ? ` ${rest}` : ''}`; // Add the reaction header
} else {
- // For Game Master updates, use list item tags to match the list format
- actionDiv.innerHTML = `
${parsedAction}
`;
+ actionDiv.innerHTML = `${firstLine}${rest ? ` ${rest}` : ''}`; // Add the action header
}
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) {
- 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);
- }
-
- shuffleArray(actions);
-
- 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);
- }
-}
-
-function shuffleArray(array) {
- for (let i = array.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [array[i], array[j]] = [array[j], array[i]]; // ES6 destructuring swap
- }
-}
-
async function promptForAction(persona) {
const historySummary = persona.history.slice(-5).join(' ');
const attributesDescription = `I am ${persona.name}, characterized as ${persona.attributes.personality} and professionally, I am a ${persona.attributes.role}.`;
-
- const prompt = `Pretend you are ${persona.name}, a ${persona.attributes.personality} ${persona.attributes.role}. Based on your attributes and recent history: ${historySummary}, what specific actions are you planning to take next? How will you approach your next challenges or opportunities? Please respond in just 2 or 3 sentences.`;
+ const prompt = `${attributesDescription} Given my attributes and recent history: ${historySummary}, what actions am I planning to take next? How will I approach my next challenges or opportunities? Respond as if you are the persona. Please provide a short response in 2 or 3 sentences.`;
try {
- const result = await aiPrompt('GeminiNano', prompt);
- persona.history.push(`${result}`);
- return result;
+ const result = await aiPrompt(prompt);
+ const trimmedResult = result.split('\n')[0].trim(); // Take only the first line to ensure it's just the name
+ persona.history.push(`${trimmedResult}`);
+ updateActionUI(persona, trimmedResult); // Update UI with the action
+ return trimmedResult;
} catch (error) {
- console.error('Error prompting AI for:', error);
+ console.error('Error prompting AI for action:', error);
return "Error in AI interaction";
}
}
@@ -193,15 +169,25 @@ async function processAction(action) {
for (let persona of personas) {
if (persona.name === action.persona) continue; // Skip the actor
- 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?`;
+ const reactionPrompt = `Given my attributes and recent history: I am ${persona.name}, characterized as ${persona.attributes.personality} and professionally, I am a ${persona.attributes.role}. Considering the action '${action.action}' by ${action.persona}, how should I react? Please provide a short response in 2 or 3 sentences.`;
try {
- 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}`);
-
- if (shouldInformGameMaster(reaction)) {
- await informGameMaster(action, reaction);
+ const reaction = await aiPrompt(reactionPrompt); // Prompt for reaction
+ const trimmedReaction = reaction.split('\n')[0].trim(); // Ensure we use only the first line
+ persona.logEvent(`Reaction to ${action.persona}'s action: ${trimmedReaction}`);
+ updateActionUI(persona, `Reaction to ${action.persona}'s action: ${marked.parse(trimmedReaction)}`, true);
+
+ const communicationPrompt = `Does the following statement involve communication with another persona? Response: "${trimmedReaction}". Please answer 'yes' or 'no'.`;
+ const involvesCommunicationHtml = await aiPrompt(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
+ }
+ } else {
+ await informGameMaster(action, trimmedReaction); // Inform Game Master if it's not communication
}
} catch (error) {
console.error(`Error prompting AI for ${persona.name}:`, error);
@@ -210,56 +196,49 @@ 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 gatherAndProcessActions() {
+ if (isPaused) {
+ console.log("System is paused.");
+ return;
}
-}
-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
+ let actions = [];
+ for (let persona of personas) {
+ let action = await promptForAction(persona);
+ actions.push({ persona: persona.name, action: action });
+ persona.logEvent(`Action decided: ${action}`);
+ }
+
+ shuffleArray(actions);
+
+ for (let action of actions) {
+ await processAction(action);
+ }
}
-async function processAction(action) {
+async function startActions() {
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);
- }
+ // Prompt each persona for an action
+ const action = await promptForAction(persona);
+ persona.logEvent(`Action taken: ${action}`);
+ updateActionUI(persona, action);
}
+ await gatherAndProcessActions(); // Start processing actions after gathering them
}
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
-}
+ const gmPrompt = `Game Master needs to update environment based on: ${action.action} which led to: ${reaction}. Provide context and consequences for this update.`;
+ const gmUpdate = await aiPrompt(gmPrompt);
-function broadcastEvent(event) {
- personas.forEach(persona => {
- persona.logEvent(`Broadcast from Game Master: ${event}`);
- });
- updateUI(); // Update the UI with the new game master's broadcast
+ const gmUpdatesList = document.getElementById('gmUpdatesList');
+ const updateItem = document.createElement('li');
+ updateItem.innerHTML = `Game Master Update: ${marked.parse(gmUpdate)}`;
+ gmUpdatesList.appendChild(updateItem);
+
+ broadcastEvent(`Game Master Update: ${gmUpdate}`);
}
function extractText(html) {
- // Use DOMParser to parse HTML string and extract text
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return doc.body.textContent || ""; // Get plain text content from parsed HTML
@@ -267,38 +246,33 @@ function extractText(html) {
async function handleCommunication(initiator, recipient, exchanges) {
let exchangeCount = 0;
- let lastMessage = ""; // To hold the last message for reference in the next prompt
+ let lastMessage = ""; // To hold the last message for reference in the next prompt
while (exchangeCount < exchanges) {
- // Gathering the necessary context
const initiatorHistory = initiator.history.slice(-5).join(' '); // Last 5 entries from initiator's history
const recipientHistory = recipient.history.slice(-5).join(' '); // Last 5 entries from recipient's history
- const initiatorAttributes = `${initiator.name} is a ${initiator.attributes.personality}, ${initiator.attributes.role}.`;
- const recipientAttributes = `${recipient.name} is a ${recipient.attributes.personality}, ${recipient.attributes.role}.`;
+ const initiatorAttributes = `Attributes of ${initiator.name}: ${initiator.attributes.personality}, ${initiator.attributes.role}.`;
+ const recipientAttributes = `Attributes of ${recipient.name}: ${recipient.attributes.personality}, ${recipient.attributes.role}.`;
- // Create the prompt for the initiator with a brevity directive
- let initiatorPrompt;
+ let initiatorPrompt = `As ${initiator.name}, respond to ${recipient.name} who previously said: "${lastMessage}". Please role-play according to your attributes: ${initiatorAttributes} Recent interactions include: ${initiatorHistory}. Keep it short, 2 or 3 sentences.`;
if (exchangeCount === 0) {
- initiatorPrompt = `As ${initiator.name}, start a conversation with ${recipient.name}. You are a ${initiator.attributes.personality}, ${initiator.attributes.role}. Recent history includes: ${initiatorHistory}. Respond in just 2 or 3 sentences.`;
- } else {
- initiatorPrompt = `As ${initiator.name}, respond to ${recipient.name} who just said: "${lastMessage}". You are a ${initiator.attributes.personality}, ${initiator.attributes.role}. Recent history includes: ${initiatorHistory}. Keep your response to 2 or 3 sentences.`;
+ initiatorPrompt = `As ${initiator.name}, start a conversation with ${recipient.name}. Reflect your personality and role: ${initiatorAttributes} Recent interactions include: ${initiatorHistory}. Keep it short, 2 or 3 sentences.`;
}
try {
- // Initiator speaks
- const initiatorMessage = await aiPrompt('GeminiNano', initiatorPrompt);
- initiator.logEvent(initiatorMessage);
- updateActionUI(initiator, initiatorMessage);
- lastMessage = initiatorMessage; // Update lastMessage to the most recent one
-
- // Prepare the prompt for the recipient with a similar brevity directive
- const recipientPrompt = `As ${recipient.name}, respond to ${initiator.name} who just said: "${lastMessage}". You are a ${recipient.attributes.personality}, ${recipient.attributes.role}. Recent history includes: ${recipientHistory}. Please keep your response to just 2 or 3 sentences.`;
+ const initiatorMessage = await aiPrompt(initiatorPrompt);
+ const parsedInitiatorMessage = marked.parse(initiatorMessage); // Parse Markdown response
+ initiator.logEvent(parsedInitiatorMessage);
+ updateActionUI(initiator, parsedInitiatorMessage);
+ lastMessage = initiatorMessage;
+
+ const recipientPrompt = `As ${recipient.name}, respond to ${initiator.name} who just said: "${lastMessage}". Please role-play according to your attributes: ${recipientAttributes} Recent interactions include: ${recipientHistory}. Keep it short, 2 or 3 sentences.`;
- // Recipient responds
- const recipientMessage = await aiPrompt('GeminiNano', recipientPrompt);
- recipient.logEvent(recipientMessage);
- updateActionUI(recipient, recipientMessage);
- lastMessage = recipientMessage; // Update lastMessage to the most recent one
+ const recipientMessage = await aiPrompt(recipientPrompt);
+ const parsedRecipientMessage = marked.parse(recipientMessage); // Parse Markdown response
+ recipient.logEvent(parsedRecipientMessage);
+ updateActionUI(recipient, parsedRecipientMessage);
+ lastMessage = recipientMessage;
exchangeCount += 1;
} catch (error) {
@@ -308,37 +282,125 @@ async function handleCommunication(initiator, recipient, exchanges) {
}
}
-let aiSession = null; // Global variable to hold the session
+function shuffleArray(array) {
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [array[i], array[j]] = [array[j], array[i]]; // ES6 destructuring swap
+ }
+}
+
+function togglePauseResume() {
+ isPaused = !isPaused;
+ document.getElementById('pauseResumeButton').textContent = isPaused ? 'Resume' : 'Pause';
+}
+
+// Event Listeners
+document.getElementById('createPersonaButton').addEventListener('click', createPersona);
+document.getElementById('broadcastButton').addEventListener('click', handleBroadcast);
+document.getElementById('start-process-actions-button').addEventListener('click', gatherAndProcessActions);
+document.getElementById('pauseResumeButton').addEventListener('click', togglePauseResume);
+
+// AI Interaction function
+let aiSession = null; // Will hold the session object for Gemini
+
+async function aiPrompt(prompt, retries = 3) {
+ const model = document.getElementById('apiChoice').value || 'gemini'; // Default to Gemini Nano
+ const apiKey = document.getElementById('apiKey').value;
-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
+ switch (model) {
+ case 'gemini':
+ if (!aiSession) { // Check if session exists for Gemini
+ if (await window.ai.canCreateTextSession() === "no") {
+ throw new Error("Unable to create an AI session.");
+ }
+ aiSession = await window.ai.createTextSession(); // Create session if none exists
+ }
+ console.log('Sending prompt to GeminiNano:', prompt);
+ const resultGemini = await aiSession.prompt(prompt);
+ if (!resultGemini) throw new Error("Received undefined result from GeminiNano.");
+ console.log('Received response from GeminiNano:', resultGemini);
+ return resultGemini;
+
+ case 'cohere':
+ if (!apiKey) {
+ throw new Error("API key for Cohere is not provided.");
+ }
+ console.log('Sending prompt to Cohere:', prompt);
+ return await callCohere(apiKey, prompt);
+
+ default:
+ throw new Error('Unsupported AI model specified');
}
-
- 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
+ console.error(`Attempt failed in aiPrompt using ${model}:`, error);
+ retries -= 1;
if (retries <= 0) {
- throw new Error("Error during AI interaction after several attempts");
+ throw new Error(`Error during AI interaction after several attempts with ${model}`);
}
console.log(`Retrying... attempts left: ${retries}`);
}
}
}
-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.");
-}
+async function callCohere(apiKey, fullPrompt, retries = 3) {
+ const url = 'https://api.cohere.com/v1/generate'; // Correct API endpoint for generation
+ let delay = 2000; // Start with a 2-second delay
+ const delayIncreaseFactor = 2.5; // Increase the delay more aggressively
+
+ const data = {
+ "model": "command", // Ensure the correct model is specified
+ "prompt": fullPrompt,
+ "max_tokens": 50, // Adjust based on the expected length of the response
+ "temperature": 0.5,
+ "k": 0,
+ "p": 0.75,
+ "frequency_penalty": 0,
+ "presence_penalty": 0,
+ "stop_sequences": ["\n"] // Assuming the response should stop at the first newline
+ };
-document.getElementById('start-process-actions-button').addEventListener('click', gatherAndProcessActions);
+ while (retries > 0) {
+ try {
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'accept': 'application/json',
+ 'content-type': 'application/json',
+ 'Authorization': 'Bearer ' + apiKey
+ },
+ body: JSON.stringify(data)
+ });
+
+ if (!response.ok) {
+ const errorDetails = await response.text(); // Try to get more details from the response
+ console.error(`API returned non-ok status: ${response.status}, Details: ${errorDetails}`);
+
+ if (response.status === 400) {
+ throw new Error(`Bad request to API: ${errorDetails}`);
+ } else if (response.status === 429) {
+ console.error(`Rate limit exceeded, retrying in ${delay}ms...`);
+ await new Promise(resolve => setTimeout(resolve, delay));
+ delay *= delayIncreaseFactor; // Increase the delay exponentially
+ retries--; // Decrement retries counter
+ continue; // Skip the rest of the loop and retry
+ } else {
+ throw new Error(`API returned non-ok status: ${response.status}`);
+ }
+ }
+
+ const result = await response.json();
+ if (result && result.generations && result.generations.length > 0 && result.generations[0].text) {
+ console.log('Received response from Cohere:', result.generations[0].text);
+ return result.generations[0].text.trim(); // Return the trimmed text
+ } else {
+ throw new Error("No valid text found in Cohere response.");
+ }
+ } catch (error) {
+ console.error('Cohere API Request Error:', error);
+ retries--; // Decrement retries counter
+ if (retries <= 0) throw new Error("Failed after multiple retries."); // Fail after all retries are exhausted
+ }
+ }
+}
diff --git a/apps/Gemini Nano/index.html b/apps/Gemini Nano/index.html
index 1163ce7..efcd166 100644
--- a/apps/Gemini Nano/index.html
+++ b/apps/Gemini Nano/index.html
@@ -2,30 +2,43 @@
- Persona Chat
+
+ AI Persona Interaction
-