El objetivo es crear un script en Bash que permita a los usuarios interactuar de manera dinámica con la API de Gemini, simulando diferentes personajes a lo largo de la conversación. El script debe ofrecer la capacidad de cambiar de personaje durante el intercambio, permitiendo que el usuario seleccione qué personaje desea usar. Además, debe ser capaz de recordar el contexto de la conversación para cada personaje de manera temporal, asegurando que las respuestas generadas por la API sean coherentes con la personalidad elegida en todo momento.
Requisitos:
- Necesitamos instalar curl y jq para procesar JSON. Para instalar jq en sistemas basados en Debian/Ubuntu, utiliza:
sudo apt update
sudo apt install curl jq
- Necesitamos una clave API de Gemini, la cual se obtiene aquí:
https://aistudio.google.com/apikey
Genera la clave API y pégala en el script para identificarte como usuario autorizado.
El historial de la conversación se guarda en un archivo externo llamado .gemini_chat_history. Este historial se borra automáticamente al cerrar la terminal o cambiar de personaje.
Ejemplo de script
#!/bin/bash
# Variables globales
GEMINI_API_KEY="PONER_API_KEY_AQUÍ"
API_KEY=${GEMINI_API_KEY} # Lee la API key desde la variable de entorno
MODEL="gemini-2.0-flash" # Puedes cambiarlo al modelo que desees
HISTORY_FILE=".gemini_chat_history"
CHARACTER=""
PROMPT=""
TEMPERATURE="0.7" # Temperatura por defecto, ajustable
MAX_OUTPUT_TOKENS="500" # Maximo de tokens por defecto, ajustable
# Funciones de ayuda
error() {
echo "Error: $1" >&2
exit 1
}
# Verifica dependencias
check_dependencies() {
command -v curl >/dev/null 2>&1 || error "curl no está instalado. Instálalo con: sudo apt install curl"
command -v jq >/dev/null 2>&1 || error "jq no está instalado. Instálalo con: sudo apt install jq"
}
# Limpia el historial
clear_history() {
if [ -f "$HISTORY_FILE" ]; then
rm "$HISTORY_FILE"
fi
}
# Define los personajes
show_characters() {
echo "Personajes disponibles:"
echo "1. Maria: Una chica friki que le gusta el anime y el boxeo"
echo "2. William Shakespeare: Un dramaturgo, poeta y actor inglés, ampliamente considerado el escritor más grande en lengua inglesa."
echo "3. Yoda: Un sabio Maestro Jedi del universo de Star Wars."
echo "4. Gollum: Un personaje de El Señor de los Anillos, corrompido por el Anillo Único."
echo "5. Personalizado: Define tu propio personaje."
echo "Q. Salir del programa"
}
# Selecciona el personaje
select_character() {
clear_history # Limpiar historial al cambiar de personaje
while true; do
show_characters
read -p "Elige un personaje (1-5, Q para salir): " choice
case "$choice" in
1)
CHARACTER="Maria"
PROMPT="Eres Maria, de 18 años, eres friki y te gusta el anime y el boxeo "
break
;;
2)
CHARACTER="William Shakespeare"
PROMPT="Eres William Shakespeare, un dramaturgo, poeta y actor inglés, ampliamente considerado el escritor más grande en lengua inglesa. Responde con la elocuencia y el lenguaje de Shakespeare."
break
;;
3)
CHARACTER="Yoda"
PROMPT="Eres Yoda, un sabio Maestro Jedi del universo de Star Wars. Responde como Yoda, con frases invertidas y sabiduría ancestral."
break
;;
4)
CHARACTER="Gollum"
PROMPT="Eres Gollum, un personaje de El Señor de los Anillos, corrompido por el Anillo Único. Responde como Gollum, con un lenguaje retorcido y obsesionado con 'el precioso'."
break
;;
5)
read -p "Nombre del personaje: " CHARACTER
read -p "Describe el comportamiento del personaje para el prompt de Gemini: " PROMPT
break
;;
q|Q)
echo "Saliendo..."
exit 0
;;
*)
echo "Opción inválida. Inténtalo de nuevo."
;;
esac
done
append_to_history "$PROMPT" # Agrega el prompt inicial al historial. Es crucial!
}
# Construye el payload para la API de Gemini
build_payload() {
local prompt="$1"
# Leer el historial del archivo.
local history=""
if [ -f "$HISTORY_FILE" ]; then
history=$(cat "$HISTORY_FILE")
fi
local payload=$(jq -n \
--arg prompt "$prompt" \
--arg history "$history" \
--arg character "$CHARACTER" \
--arg temperature "$TEMPERATURE" \
--arg maxOutputTokens "$MAX_OUTPUT_TOKENS" \
'{
"contents": [
{
"parts": [
{
"text": ($history + "\nUsuario: " + $prompt + "\n" + $character)
}
]
}
],
"generationConfig": {
"temperature": ($temperature | tonumber),
"maxOutputTokens": ($maxOutputTokens | tonumber)
}
}')
echo "$payload"
}
# Llama a la API de Gemini
call_gemini_api() {
local payload="$1"
local response=$(curl -s -X POST \
-H "Content-Type: application/json" \
-H "x-goog-api-key:$API_KEY" \
-d "$payload" \
"https://generativelanguage.googleapis.com/v1/models/$MODEL:generateContent")
echo "$response"
}
# Extrae la respuesta de la API
extract_response() {
local response="$1"
local text=$(echo "$response" | jq -r '.candidates[0].content.parts[0].text')
echo "$text"
}
# Guarda el historial en el archivo
append_to_history() {
local text="$1"
echo "$text" >> "$HISTORY_FILE"
}
# Bucle principal del chat
chat() {
while true; do
read -p "Tú: " user_input
# Opción para cambiar de personaje
if [[ "$user_input" == ":cambiar_personaje" ]]; then
select_character
continue
fi
# Opción para salir
if [[ "$user_input" == ":salir" ]]; then
echo "Fin de la conversación."
clear_history
exit 0
fi
payload=$(build_payload "$user_input")
api_response=$(call_gemini_api "$payload")
gemini_response=$(extract_response "$api_response")
echo "$CHARACTER: $gemini_response"
# Guardar la conversación en el historial
append_to_history "Usuario: $user_input"
append_to_history "$CHARACTER: $gemini_response"
done
}
# Inicio del script
main() {
check_dependencies
# Verificar si la API key está definida
if [ -z "$API_KEY" ]; then
error "La variable de entorno GEMINI_API_KEY no está definida. Por favor, configura tu clave API."
fi
select_character
chat
}
main
Añadido extra. Un chat en una página web.
Como añadido a este post, vamos a incorporar una página web hecha con javascript, html y bootstrap que hace lo mismo. Recuerda poner el API_KEY de Gemini dentro del código para que funcione donde dice PUT_API_KEY_HERE.
<!DOCTYPE html>
<html lang="es" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat con Gemini</title>
<!-- Foundation CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.8.1/dist/css/foundation.min.css" crossorigin="anonymous">
<!-- Marked JS para procesar Markdown -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
<style>
#chat-container {
height: 500px;
overflow-y: scroll;
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin-bottom: 10px;
padding: 8px;
border-radius: 5px;
}
.user-message {
background-color: #DCF8C6;
text-align: right;
}
.gemini-message {
background-color: #ECE5DD;
text-align: left;
}
/* Estilos para elementos markdown */
.gemini-message pre {
background-color: #f4f4f4;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
margin: 10px 0;
}
.gemini-message code {
font-family: monospace;
background-color: #f0f0f0;
padding: 2px 4px;
border-radius: 3px;
}
.gemini-message blockquote {
border-left: 3px solid #ccc;
margin-left: 5px;
padding-left: 10px;
color: #666;
}
.gemini-message table {
border-collapse: collapse;
width: 100%;
margin: 10px 0;
}
.gemini-message th, .gemini-message td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.gemini-message th {
background-color: #f2f2f2;
}
.gemini-message img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="grid-container">
<h1>Chat con Gemini</h1>
<div class="grid-x grid-padding-x">
<div class="large-12 cell">
<label for="character-select">Selecciona un Personaje:</label>
<select id="character-select">
<option value="maria">Maria: Una chica friki que le gusta el anime y el boxeo</option>
<option value="shakespeare">William Shakespeare: Un dramaturgo...</option>
<option value="yoda">Yoda: Un sabio Maestro Jedi...</option>
<option value="gollum">Gollum: Un personaje de El Señor de los Anillos...</option>
<option value="custom">Personalizado</option>
</select>
</div>
</div>
<div id="custom-character-form" style="display: none;">
<div class="grid-x grid-padding-x">
<div class="large-12 cell">
<label for="custom-name">Nombre del Personaje:</label>
<input type="text" id="custom-name">
</div>
</div>
<div class="grid-x grid-padding-x">
<div class="large-12 cell">
<label for="custom-description">Descripción del Personaje:</label>
<textarea id="custom-description" rows="3"></textarea>
</div>
</div>
</div>
<div id="chat-container">
<!-- Mensajes del chat aparecerán aquí -->
</div>
<div class="grid-x grid-padding-x">
<div class="large-10 cell">
<label for="message-input">Escribe tu mensaje:</label>
<input type="text" id="message-input" placeholder="Escribe tu mensaje...">
</div>
<div class="large-2 cell">
<button class="button primary" id="send-button">Enviar</button>
</div>
</div>
</div>
<!-- jQuery primero, luego Foundation JS, y finalmente marked para markdown -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/foundation-sites@6.8.1/dist/js/foundation.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
$(document).foundation(); // Inicializa Foundation
// Configurar marked para usar highlight.js
marked.setOptions({
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
} else {
return hljs.highlightAuto(code).value;
}
},
breaks: true, // Convierte saltos de línea en <br>
gfm: true // GitHub Flavored Markdown
});
// Variables globales
const GEMINI_API_KEY = "PUT_API_KEY_HERE"; // Reemplazar con tu API key
const MODEL = "gemini-2.0-flash";
let CHARACTER = "";
let PROMPT = "";
let history = ""; //Mantener el historial en el cliente.
const chatContainer = document.getElementById("chat-container");
// Función para agregar un mensaje al chat
function addMessage(message, type) {
const messageDiv = document.createElement("div");
messageDiv.classList.add("message", type + "-message");
// Si es un mensaje de Gemini, procesar como markdown
if (type === "gemini") {
messageDiv.innerHTML = marked.parse(message);
} else {
messageDiv.textContent = message;
}
chatContainer.appendChild(messageDiv);
// Aplicar highlight.js a los bloques de código recién agregados
if (type === "gemini") {
document.querySelectorAll('.gemini-message pre code').forEach((block) => {
hljs.highlightElement(block);
});
}
// Scroll al final del chat
chatContainer.scrollTop = chatContainer.scrollHeight;
}
// Función para construir el payload para la API de Gemini
function buildPayload(prompt) {
return JSON.stringify({
"contents": [
{
"parts": [
{
"text": history + "\nUsuario: " + prompt + "\n" + CHARACTER
}
]
}
],
"generationConfig": {
"temperature": 0.7,
"maxOutputTokens": 500
}
});
}
// Función para llamar a la API de Gemini
async function callGeminiAPI(payload) {
try {
const response = await fetch(`https://generativelanguage.googleapis.com/v1/models/${MODEL}:generateContent`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-goog-api-key": GEMINI_API_KEY
},
body: payload
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error al llamar a la API de Gemini:", error);
addMessage("Error al obtener la respuesta de Gemini.", "gemini"); //Feedback al usuario
return null;
}
}
// Función para extraer la respuesta de la API
function extractResponse(response) {
if (response && response.candidates && response.candidates.length > 0 && response.candidates[0].content && response.candidates[0].content.parts && response.candidates[0].content.parts.length > 0) {
return response.candidates[0].content.parts[0].text;
} else {
console.warn("Respuesta de Gemini incompleta o inesperada:", response);
return "No se pudo obtener una respuesta válida de Gemini."; //Feedback al usuario
}
}
// Función principal para enviar un mensaje
async function sendMessage() {
const messageInput = document.getElementById("message-input");
const message = messageInput.value.trim();
if (message !== "") {
addMessage(message, "user");
messageInput.value = "";
const payload = buildPayload(message);
const apiResponse = await callGeminiAPI(payload);
if (apiResponse) {
const geminiResponse = extractResponse(apiResponse);
addMessage(geminiResponse, "gemini");
// Actualizar el historial
history += "\nUsuario: " + message + "\n" + CHARACTER + ": " + geminiResponse;
}
}
}
// Event listeners
document.getElementById("send-button").addEventListener("click", sendMessage);
document.getElementById("message-input").addEventListener("keydown", function(event) {
if (event.key === "Enter") {
sendMessage();
}
});
document.getElementById("character-select").addEventListener("change", function() {
const selectedCharacter = this.value;
const customCharacterForm = document.getElementById("custom-character-form");
switch (selectedCharacter) {
case "maria":
CHARACTER = "Maria";
PROMPT = "Eres Maria, de 18 años, eres friki y te gusta el anime y el boxeo";
break;
case "shakespeare":
CHARACTER = "William Shakespeare";
PROMPT = "Eres William Shakespeare, un dramaturgo, poeta y actor inglés. Responde con la elocuencia de Shakespeare.";
break;
case "yoda":
CHARACTER = "Yoda";
PROMPT = "Eres Yoda, un sabio Maestro Jedi. Responde como Yoda.";
break;
case "gollum":
CHARACTER = "Gollum";
PROMPT = "Eres Gollum, obsesionado con 'el precioso'.";
break;
case "custom":
customCharacterForm.style.display = "block";
CHARACTER = document.getElementById("custom-name").value; //Esto no es correcto del todo, pero sirve de ejemplo
PROMPT = document.getElementById("custom-description").value; //Esto no es correcto del todo, pero sirve de ejemplo
break;
default:
CHARACTER = "";
PROMPT = "";
}
if (selectedCharacter !== "custom") {
customCharacterForm.style.display = "none";
}
history = PROMPT; // Reiniciar el historial con el nuevo prompt
chatContainer.innerHTML = ''; //Limpiar la ventana de chat.
});
//Opcional: Gestionar el formulario personalizado (esto es un ejemplo básico, requiere validación y mejor manejo)
document.getElementById("custom-name").addEventListener("change", () => {
if(document.getElementById("character-select").value === "custom"){
CHARACTER = document.getElementById("custom-name").value;
}
});
document.getElementById("custom-description").addEventListener("change", () => {
if(document.getElementById("character-select").value === "custom"){
PROMPT = document.getElementById("custom-description").value;
history = PROMPT; //Actualizar el historial
chatContainer.innerHTML = ''; //Limpiar la ventana de chat.
}
});
// Inicialización (Opcional: Seleccionar un personaje por defecto)
document.getElementById("character-select").value = "maria"; //Selecciona maria por defecto.
document.getElementById("character-select").dispatchEvent(new Event('change')); //Dispara el evento change.
</script>
</body>
</html>