Skip to content

Commit

Permalink
New Gemini Nano app with actions
Browse files Browse the repository at this point in the history
  • Loading branch information
ToonTalk committed Jun 2, 2024
1 parent 8f3beba commit 8e9ed03
Show file tree
Hide file tree
Showing 3 changed files with 304 additions and 84 deletions.
283 changes: 248 additions & 35 deletions apps/Gemini Nano/chat.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,264 @@
// chat.js
let conversationHistory = []; // Continue using the renamed array to avoid conflicts

document.getElementById('send-button').addEventListener('click', sendMessage);
document.getElementById('user-input').addEventListener('keypress', function(event) {
if (event.key === "Enter") {
event.preventDefault(); // Prevent the default action to avoid form submission
sendMessage();
let personas = [];

async function createPersona() {
const attributes = getRandomAttributes(); // Retrieves random attributes for the persona
const name = await generatePersonaName(attributes.personality, attributes.role); // AI generates a name

if (!name) {
alert('Failed to generate a name. Please try again.');
return;
}

const newPersona = {
name,
attributes,
history: [],
logEvent: function(event) {
this.history.push(event);
}
};
personas.push(newPersona);
updateUI(); // Update the UI with the new persona
}

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;
} catch (error) {
console.error('Error generating persona name:', error);
return null; // Return null if there's an error
}
}

function getRandomAttributes() {
const attributes = [
'curious', 'domineering', 'shy', 'creative', 'witty', 'literal minded',
'optimistic', 'pessimistic', 'analytical', 'compassionate', 'adventurous',
'methodical', 'impulsive', 'pragmatic', 'idealistic', 'introverted', 'extroverted'
];
const roles = [
'mathematician', 'naturalist', 'linguist', 'engineer', 'poet', 'reporter', 'gossip',
'scientist', 'historian', 'philosopher', 'artist', 'musician', 'educator', 'technologist',
'entrepreneur', 'chef', 'politician', 'athlete'
];

// Determine how many attributes to combine (1, 2, or 3)
const numAttributes = Math.floor(Math.random() * 3) + 1; // Generates 1, 2, or 3
let selectedAttributes = [];

for (let i = 0; i < numAttributes; i++) {
let attribute;
do {
attribute = attributes[Math.floor(Math.random() * attributes.length)];
} while (selectedAttributes.includes(attribute)); // Ensure no duplicate attributes
selectedAttributes.push(attribute);
}
});

async function sendMessage() {
const userInput = document.getElementById('user-input').value;
const chatOutput = document.getElementById('chat-output');
const sendButton = document.getElementById('send-button');
const personality = selectedAttributes.join(' and ');

if (!userInput.trim()) return;
const role = roles[Math.floor(Math.random() * roles.length)];

return {
personality: personality,
role: role
};
}

sendButton.disabled = true; // Disable the button to prevent re-entry during processing
chatOutput.innerHTML += `<div>You: ${userInput}</div>`;
conversationHistory.push(`You: ${userInput}<ctrl23>`); // Update conversation history
console.log('Sending prompt:', `You: ${userInput}<ctrl23>`); // Log the prompt

const canCreate = await window.ai.canCreateTextSession();
if (canCreate === "no") {
chatOutput.innerHTML += `<div>Sorry, your device does not support this feature.</div>`;
sendButton.disabled = false;
function updateUI() {
const container = document.getElementById('personas-container');
container.innerHTML = '';
personas.forEach((persona, index) => {
const personaDiv = document.createElement('div');
personaDiv.innerHTML = `<strong>${persona.name}</strong> (${persona.attributes.personality}, ${persona.attributes.role})`;
container.appendChild(personaDiv);
});
}

function handleBroadcast() {
const message = document.getElementById('broadcastMessage').value;
if (message.trim() === '') {
alert('Please enter a message to broadcast.');
return;
}
broadcastEvent(message);
document.getElementById('broadcastMessage').value = ''; // Clear the input field after sending
alert('Message broadcasted to all personas.');
}

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
});
}

function broadcastEvent(event) {
personas.forEach(persona => {
persona.logEvent(event);
});
}

function updateActionUI(persona, action) {
const container = document.getElementById('personas-container');
const actionDiv = document.createElement('div');

// Convert Markdown to HTML before adding to the DOM
const parsedAction = marked.parse(action); // Use marked.parse to convert Markdown to HTML

const session = await window.ai.createTextSession();
let prompt = conversationHistory.join(' ');
if (prompt.length > 950) {
conversationHistory = conversationHistory.slice(-5);
prompt = conversationHistory.join(' ');
actionDiv.innerHTML = `<strong>${persona.name}</strong> action: ${parsedAction}`; // Embed the HTML in the DOM
container.appendChild(actionDiv);
}

async function gatherAndProcessActions() {
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
}

console.log('Full prompt to AI:', prompt); // Log the full prompt being sent to AI
// Shuffle actions to simulate dynamic interactions
shuffleArray(actions);

// Process each shuffled action
for (let action of actions) {
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.`;

try {
const result = await session.prompt(prompt);
chatOutput.innerHTML += `<div>Gemini Nano:</div><div>${marked.parse(result)}</div>`;
conversationHistory.push(`Gemini Nano: ${result}<ctrl23>`);
console.log('Received response:', result); // Log the response from AI
const result = await aiPrompt('GeminiNano', prompt);
persona.history.push(`${result}<ctrl23>`);
return result;
} catch (error) {
console.error('Error prompting Gemini Nano:', error);
chatOutput.innerHTML += `<div>Error communicating with AI.</div>`;
console.error('Error prompting AI for:', error);
return "Error in AI interaction";
}
}

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.`;

try {
const reaction = await aiPrompt('GeminiNano', reactionPrompt); // Prompt for reaction
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
}
}
} catch (error) {
console.error(`Error prompting AI for ${persona.name}:`, error);
updateActionUI(persona, `Error reacting due to AI failure.`);
}
}
}

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
}

async function handleCommunication(initiator, recipient, exchanges) {
let exchangeCount = 0;
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}.`;

// Create the prompt for the initiator with a brevity directive
let initiatorPrompt;
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.`;
}

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.`;

// Recipient responds
const recipientMessage = await aiPrompt('GeminiNano', recipientPrompt);
recipient.logEvent(recipientMessage);
updateActionUI(recipient, recipientMessage);
lastMessage = recipientMessage; // Update lastMessage to the most recent one

exchangeCount += 1;
} catch (error) {
console.error(`Error handling communication between ${initiator.name} and ${recipient.name}:`, error);
break; // Exit the loop if an error occurs
}
}
}

let aiSession = null; // Global variable to hold the session

document.getElementById('user-input').value = '';
sendButton.disabled = false;
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.");
}
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");
}
}

document.getElementById('start-process-actions-button').addEventListener('click', gatherAndProcessActions);
22 changes: 13 additions & 9 deletions apps/Gemini Nano/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gemini Nano Chat</title>
<title>Persona Chat</title>
<link rel="stylesheet" href="styles.css">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<div class="chat-container">
<div id="chat-output"></div>
<div class="input-container"> <!-- Ensures proper alignment -->
<input type="text" id="user-input" placeholder="Type your message here...">
<button id="send-button">Send</button>
</div>
<div id="persona-creation">
<!-- <input type="text" id="persona-name" placeholder="Enter persona name"> -->
<button onclick="createPersona()">Create Persona</button>
</div>
<div>
<label for="broadcastMessage">Send a message to all personas:</label>
<input type="text" id="broadcastMessage" placeholder="Enter your message here">
<button onclick="handleBroadcast()">Broadcast Message</button>
<button id="start-process-actions-button">Start and Process Actions</button>
</div>
<div id="personas-container"></div>
<link rel="stylesheet" href="styles.css">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="chat.js"></script>
</body>
</html>
Loading

0 comments on commit 8e9ed03

Please sign in to comment.