diff --git a/README.md b/README.md index f26a5ac..8f79282 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Se añadió un flow en `data/flows.json` que usa Node-RED Dashboard para mostrar { "prompt": "generame un als de reggaeton 2001" } ``` Devuelve el resumen del proyecto creado (nombre, hash y ruta del archivo). En el dashboard aparece la tarjeta **Chatbot ALS** para conversar visualmente. -- Si envías mensajes generales (“hola”, “¿qué estilos tenemos?”, “qué hay en sources?”) el bot responde con el estado actual de la biblioteca o de la carpeta `data/sources/` y te guía hasta que le pidas explícitamente “generame un als ...”. +- Si envías mensajes generales (“hola”, “¿qué estilos tenemos?”, “qué hay en sources?”) el bot usa la API de MiniMax para responder de forma amigable, recuerda el contexto reciente y te guía hasta que le pidas explícitamente “generame un als ...”. - Internamente se usa `data/lib/alsGenerator.js`: el bot elige una plantilla (`als-library.json`), edita el XML del `.als` (nombre del proyecto, tempo, anotaciones con los sources) y registra el resultado llamando otra vez a `/als/upload` para mantener la deduplicación. - También puedes usarlo desde terminal: ```bash diff --git a/data/flows.json b/data/flows.json index cacce49..dbad512 100644 --- a/data/flows.json +++ b/data/flows.json @@ -365,7 +365,7 @@ "type": "function", "z": "f68df1c4d2e4e1a9", "name": "Chatbot ALS", - "func": "\nconst raw = typeof msg.payload === 'string' ? msg.payload : JSON.stringify(msg.payload || {});\nlet body = {};\ntry {\n body = typeof msg.payload === 'object' ? msg.payload : JSON.parse(raw);\n} catch (err) {\n body = {};\n}\nconst prompt = (body.prompt || body.message || msg.prompt || '').trim();\nif (!prompt) {\n msg.statusCode = 400;\n msg.payload = { error: 'Falta el prompt del chatbot.' };\n return msg;\n}\nconst generator = global.get('alsGenerator');\nif (!generator || typeof generator.generateFromPrompt !== 'function') {\n msg.statusCode = 500;\n msg.payload = { error: 'Generador no disponible.' };\n return msg;\n}\nconst info = generator.listLibrary ? generator.listLibrary() : [];\nconst sources = generator.listSources ? generator.listSources() : [];\nfunction shouldGenerate(text) {\n return /(genera(me)?|crea(me)?|haz(me)?|arma(me)?|produce(me)?).*(als|ableton|beat|proyecto)/i.test(text);\n}\nfunction shouldDescribeSources(text) {\n return /(source|sources|stems|loops|samples|archivos|carpeta)/i.test(text);\n}\nfunction describeSources(list) {\n if (!list || !list.length) {\n return 'Todav\u00eda no hay archivos en data/sources/. Copia stems o loops ah\u00ed para que puedan usarse como referencia.';\n }\n const preview = list.slice(0, 10).join(', ');\n return `Encontr\u00e9 ${list.length} archivos en data/sources/. Ejemplos: ${preview}${list.length > 10 ? ' ...' : ''}`;\n}\nfunction buildSummary(text, libraryList, sourceList) {\n if (!libraryList || !libraryList.length) {\n return 'A\u00fan no tengo proyectos cargados. Sube un .als primero y luego dime \"generame un als ...\" para empezar.';\n }\n const recent = libraryList.slice(-1)[0];\n const parts = [\n `Tengo ${libraryList.length} proyectos almacenados. El \u00faltimo fue \"${recent.projectName}\" a ${recent.liveSet?.tempo || 'N/D'} BPM.`,\n sourceList && sourceList.length ? `Hay ${sourceList.length} recursos en sources (ej: ${sourceList.slice(0,3).join(', ')})` : 'A\u00fan no hay archivos en data/sources/.',\n 'Cuando est\u00e9s listo dime algo como \"generame un als afrohouse 2025 con 124 bpm\" y preparo la sesi\u00f3n.'\n ];\n return parts.join(' ');\n}\nif (shouldDescribeSources(prompt)) {\n msg.statusCode = 200;\n msg.payload = { reply: describeSources(sources) };\n return msg;\n}\nif (!shouldGenerate(prompt)) {\n return (async () => {\n try {\n const reply = await (generator.chatReply ? generator.chatReply(prompt, info, sources) : buildSummary(prompt, info, sources));\n msg.statusCode = 200;\n msg.payload = { reply };\n return msg;\n } catch (err) {\n node.error(err.message, msg);\n msg.statusCode = 500;\n msg.payload = { error: err.message };\n return msg;\n }\n })();\n}\nreturn (async () => {\n try {\n const result = await generator.generateFromPrompt(prompt);\n msg.statusCode = 200;\n msg.payload = {\n prompt: prompt,\n projectName: result.plan.projectName,\n templateHash: result.plan.templateHash,\n outputPath: result.outputPath,\n registered: result.registered\n };\n return msg;\n } catch (err) {\n node.error(err.message, msg);\n msg.statusCode = 500;\n msg.payload = { error: err.message };\n return msg;\n }\n})();\n", + "func": "\nconst raw = typeof msg.payload === 'string' ? msg.payload : JSON.stringify(msg.payload || {});\nlet body = {};\ntry {\n body = typeof msg.payload === 'object' ? msg.payload : JSON.parse(raw);\n} catch (err) {\n body = {};\n}\nconst prompt = (body.prompt || body.message || msg.prompt || '').trim();\nconst chatHistory = context.get('chatHistory') || [];\nfunction remember(role, text) {\n if (!text) { return; }\n chatHistory.push({ role, text });\n if (chatHistory.length > 40) {\n chatHistory.shift();\n }\n context.set('chatHistory', chatHistory);\n}\nif (!prompt) {\n msg.statusCode = 400;\n msg.payload = { error: 'Falta el prompt del chatbot.' };\n return msg;\n}\nconst generator = global.get('alsGenerator');\nif (!generator || typeof generator.generateFromPrompt !== 'function') {\n msg.statusCode = 500;\n msg.payload = { error: 'Generador no disponible.' };\n return msg;\n}\nremember('user', prompt);\nconst info = generator.listLibrary ? generator.listLibrary() : [];\nconst sources = generator.listSources ? generator.listSources() : [];\nfunction shouldGenerate(text) {\n return /(genera(me)?|crea(me)?|haz(me)?|arma(me)?|produce(me)?).*(als|ableton|beat|proyecto)/i.test(text);\n}\nfunction shouldDescribeSources(text) {\n return /(source|sources|stems|loops|samples|archivos|carpeta)/i.test(text);\n}\nfunction describeSources(list) {\n if (!list || !list.length) {\n return 'Todav\u00eda no hay archivos en data/sources/. Copia stems o loops ah\u00ed para que puedan usarse como referencia.';\n }\n const preview = list.slice(0, 10).join(', ');\n return `Encontr\u00e9 ${list.length} archivos en data/sources/. Ejemplos: ${preview}${list.length > 10 ? ' ...' : ''}`;\n}\nfunction buildSummary(text, libraryList, sourceList) {\n if (!libraryList || !libraryList.length) {\n return 'A\u00fan no tengo proyectos cargados. Sube un .als primero y luego dime \"generame un als ...\" para empezar.';\n }\n const recent = libraryList.slice(-1)[0];\n const parts = [\n `Tengo ${libraryList.length} proyectos almacenados. El \u00faltimo fue \"${recent.projectName}\" a ${recent.liveSet?.tempo || 'N/D'} BPM.`,\n sourceList && sourceList.length ? `Hay ${sourceList.length} recursos en sources (ej: ${sourceList.slice(0,3).join(', ')})` : 'A\u00fan no hay archivos en data/sources/.',\n 'Cuando est\u00e9s listo dime algo como \"generame un als afrohouse 2025 con 124 bpm\" y preparo la sesi\u00f3n.'\n ];\n return parts.join(' ');\n}\nif (shouldDescribeSources(prompt)) {\n const reply = describeSources(sources);\n remember('bot', reply);\n msg.statusCode = 200;\n msg.payload = { reply };\n return msg;\n}\nif (!shouldGenerate(prompt)) {\n return (async () => {\n try {\n const reply = await (generator.chatReply ? generator.chatReply(prompt, info, sources, chatHistory) : buildSummary(prompt, info, sources));\n remember('bot', reply);\n msg.statusCode = 200;\n msg.payload = { reply };\n return msg;\n } catch (err) {\n node.error(err.message, msg);\n msg.statusCode = 500;\n msg.payload = { error: err.message };\n return msg;\n }\n })();\n}\nreturn (async () => {\n try {\n const result = await generator.generateFromPrompt(prompt);\n const summary = `Listo, gener\u00e9 \"${result.plan.projectName}\" usando ${result.plan.templateHash}.`;\n remember('bot', summary);\n msg.statusCode = 200;\n msg.payload = {\n prompt: prompt,\n projectName: result.plan.projectName,\n templateHash: result.plan.templateHash,\n outputPath: result.outputPath,\n registered: result.registered\n };\n return msg;\n } catch (err) {\n node.error(err.message, msg);\n remember('bot', err.message);\n msg.statusCode = 500;\n msg.payload = { error: err.message };\n return msg;\n }\n})();\n", "outputs": 1, "noerr": 0, "initialize": "", diff --git a/data/lib/alsGenerator.js b/data/lib/alsGenerator.js index bcc2187..1b7fb76 100644 --- a/data/lib/alsGenerator.js +++ b/data/lib/alsGenerator.js @@ -351,7 +351,7 @@ async function generateFromPrompt(prompt, options = {}) { }; } -async function conversationalReply(prompt, library, sources) { +async function conversationalReply(prompt, library, sources, history = []) { const context = { prompt, projects: library.slice(-5).map((entry) => ({ @@ -361,22 +361,29 @@ async function conversationalReply(prompt, library, sources) { })), sources: sources.slice(0, 20) }; + const historySummary = (history || []) + .slice(-10) + .map((msg) => `${msg.role === 'user' ? 'Usuario' : 'Bot'}: ${msg.text}`) + .join('\\n'); + const text = await callAnthropic( 'Actúa como un productor musical amigable que ayuda a planear nuevos proyectos de Ableton Live. Responde en español latino, máximo 3 frases, incluye sugerencias creativas y cuándo usar \"generame un als ...\".', - `Usuario: ${prompt}\nContexto: ${JSON.stringify(context, null, 2)}` + `Historial reciente:\n${historySummary || 'Sin historial'}\nUsuario: ${prompt}\nContexto: ${JSON.stringify(context, null, 2)}` ); if (text) { return text.trim(); } if (!library.length) { - return '¡Hola! Aún no tengo proyectos para tomar como referencia. Sube un .als y cuéntame qué estilo buscas; luego pide "generame un als ..." y prepararé una sesión.'; + const vibe = prompt ? `Me encanta esa idea de ${prompt}. ` : ''; + return `${vibe}Aún no tengo proyectos para tomar como referencia. Sube un .als y luego dime "generame un als ..." y prepararé una sesión nueva.`; } const recent = library.slice(-1)[0]; const srcText = sources && sources.length ? `También veo ${sources.length} archivos en data/sources/ (ej: ${sources.slice(0, 3).join(', ')}). ` : ''; - return `Hola, tengo ${library.length} proyectos guardados y el más reciente es "${recent.projectName}" (${recent.liveSet?.tempo || 'N/D'} BPM). ${srcText}Cuéntame la vibra y cuando digas "generame un als ..." lo construyo.`; + const vibe = prompt ? `Esa vibra de "${prompt}" suena genial. ` : ''; + return `${vibe}Tengo ${library.length} proyectos guardados y el más reciente es "${recent.projectName}" (${recent.liveSet?.tempo || 'N/D'} BPM). ${srcText}Cuéntame más y cuando digas "generame un als ..." lo construyo.`; } module.exports = {