diff --git a/README.md b/README.md index 52d43cb..7abb885 100644 --- a/README.md +++ b/README.md @@ -110,6 +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?”) el bot responde con el estado actual de la biblioteca 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/als-library.json b/data/als-library.json index 51d0922..aecda4b 100644 --- a/data/als-library.json +++ b/data/als-library.json @@ -514,5 +514,349 @@ }, "storedAt": "2025-12-01T03:02:37.033Z", "filePath": "library/38427f49e8ffcfba20969319b9dda2f7.als" + }, + { + "hash": "e6daf889df9686d9e2f99387e6226fd7", + "projectName": "ai-hola-buenas-1764558274015.als", + "meta": { + "creator": "Ableton Live 12.2", + "version": "5.12.0_12203", + "revision": "1c7a2c5dacd710ba28150f2c1534c22b1c158263", + "fileName": "ai-hola-buenas-1764558274015.als", + "sizeBytes": 6665466, + "sizeHuman": "6.4 MB" + }, + "liveSet": { + "tempo": 124, + "loopLengthBeats": 272, + "durationSeconds": 131.61290322580646, + "durationHuman": "2:12", + "loopStart": 0, + "scenes": [] + }, + "tracks": [ + { + "name": "hola 1", + "type": "Audio", + "deviceCount": 3, + "color": "22" + }, + { + "name": "buenas 2", + "type": "Audio", + "deviceCount": 0, + "color": "22" + }, + { + "name": "Track 3", + "type": "Audio", + "deviceCount": 1, + "color": "22" + }, + { + "name": "Track 4", + "type": "Audio", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 5", + "type": "Audio", + "deviceCount": 1, + "color": "22" + }, + { + "name": "Track 6", + "type": "MIDI", + "deviceCount": 1, + "color": "22" + }, + { + "name": "Track 7", + "type": "MIDI", + "deviceCount": 4, + "color": "22" + }, + { + "name": "Track 8", + "type": "MIDI", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 9", + "type": "MIDI", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 10", + "type": "MIDI", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 11", + "type": "MIDI", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 12", + "type": "MIDI", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 13", + "type": "MIDI", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 14", + "type": "MIDI", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 15", + "type": "MIDI", + "deviceCount": 5, + "color": "22" + }, + { + "name": "Track 16", + "type": "MIDI", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 17", + "type": "Grupo", + "deviceCount": 1, + "color": "22" + }, + { + "name": "Track 18", + "type": "Grupo", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 19", + "type": "Grupo", + "deviceCount": 1, + "color": "22" + } + ], + "stats": { + "audio": 5, + "midi": 11, + "group": 3, + "totalTracks": 19, + "devices": 42, + "scenes": 0, + "samples": 8 + }, + "samples": { + "total": 8, + "relative": [ + "Samples/Imported/RUFUS DU SOL - In the Moment (Adriatique Remix) Acapella.mp3", + "Samples/Processed/Bounce/Bounce KICK #1 [2025-08-30 144250]-3.wav", + "Samples/Recorded/12-Audio 0001 [2025-08-30 143952].wav", + "Presets/Audio Effects/Audio Effect Rack/Filter HI and Low.adg", + "Samples/Recorded/12-Audio 0001 [2025-08-30 144108].wav", + "Samples/Imported/Clap Fx.wav", + "Samples/Imported/Clap.wav", + "Swing and Groove/Swing/Swing 16-99.agr" + ], + "absolute": [ + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Imported/RUFUS DU SOL - In the Moment (Adriatique Remix) Acapella.mp3", + "C:/VST2/Proximity-x64.dll", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Processed/Bounce/Bounce KICK #1 [2025-08-30 144250]-3.wav", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Recorded/12-Audio 0001 [2025-08-30 143952].wav", + "C:/Users/novik/OneDrive/Documenten/Ableton/User Library/Presets/Audio Effects/Audio Effect Rack/Filter HI and Low.adg", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Recorded/12-Audio 0001 [2025-08-30 144108].wav", + "C:/VST2/Fabfilter/FabFilter Pro-Q 4.dll", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Imported/Clap Fx.wav", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Imported/Clap.wav", + "C:/VST2/Fabfilter/FabFilter Pro-C 2.dll" + ] + }, + "storedAt": "2025-12-01T03:04:35.766Z", + "filePath": "library/e6daf889df9686d9e2f99387e6226fd7.als" + }, + { + "hash": "4f6a02d3a9826f59ab7c06f6a622005a", + "projectName": "ai-generame-un-als-techno-1764558411120.als", + "meta": { + "creator": "Ableton Live 12.2", + "version": "5.12.0_12203", + "revision": "1c7a2c5dacd710ba28150f2c1534c22b1c158263", + "fileName": "ai-generame-un-als-techno-1764558411120.als", + "sizeBytes": 6666252, + "sizeHuman": "6.4 MB" + }, + "liveSet": { + "tempo": 124, + "loopLengthBeats": 272, + "durationSeconds": 131.61290322580646, + "durationHuman": "2:12", + "loopStart": 0, + "scenes": [] + }, + "tracks": [ + { + "name": "generame 1", + "type": "Audio", + "deviceCount": 3, + "color": "22" + }, + { + "name": "un 2", + "type": "Audio", + "deviceCount": 0, + "color": "22" + }, + { + "name": "als 3", + "type": "Audio", + "deviceCount": 1, + "color": "22" + }, + { + "name": "techno 4", + "type": "Audio", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 5", + "type": "Audio", + "deviceCount": 1, + "color": "22" + }, + { + "name": "Track 6", + "type": "MIDI", + "deviceCount": 1, + "color": "22" + }, + { + "name": "Track 7", + "type": "MIDI", + "deviceCount": 4, + "color": "22" + }, + { + "name": "Track 8", + "type": "MIDI", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 9", + "type": "MIDI", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 10", + "type": "MIDI", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 11", + "type": "MIDI", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 12", + "type": "MIDI", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 13", + "type": "MIDI", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 14", + "type": "MIDI", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 15", + "type": "MIDI", + "deviceCount": 5, + "color": "22" + }, + { + "name": "Track 16", + "type": "MIDI", + "deviceCount": 2, + "color": "22" + }, + { + "name": "Track 17", + "type": "Grupo", + "deviceCount": 1, + "color": "22" + }, + { + "name": "Track 18", + "type": "Grupo", + "deviceCount": 3, + "color": "22" + }, + { + "name": "Track 19", + "type": "Grupo", + "deviceCount": 1, + "color": "22" + } + ], + "stats": { + "audio": 5, + "midi": 11, + "group": 3, + "totalTracks": 19, + "devices": 42, + "scenes": 0, + "samples": 8 + }, + "samples": { + "total": 8, + "relative": [ + "Samples/Imported/RUFUS DU SOL - In the Moment (Adriatique Remix) Acapella.mp3", + "Samples/Processed/Bounce/Bounce KICK #1 [2025-08-30 144250]-3.wav", + "Samples/Recorded/12-Audio 0001 [2025-08-30 143952].wav", + "Presets/Audio Effects/Audio Effect Rack/Filter HI and Low.adg", + "Samples/Recorded/12-Audio 0001 [2025-08-30 144108].wav", + "Samples/Imported/Clap Fx.wav", + "Samples/Imported/Clap.wav", + "Swing and Groove/Swing/Swing 16-99.agr" + ], + "absolute": [ + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Imported/RUFUS DU SOL - In the Moment (Adriatique Remix) Acapella.mp3", + "C:/VST2/Proximity-x64.dll", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Processed/Bounce/Bounce KICK #1 [2025-08-30 144250]-3.wav", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Recorded/12-Audio 0001 [2025-08-30 143952].wav", + "C:/Users/novik/OneDrive/Documenten/Ableton/User Library/Presets/Audio Effects/Audio Effect Rack/Filter HI and Low.adg", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Recorded/12-Audio 0001 [2025-08-30 144108].wav", + "C:/VST2/Fabfilter/FabFilter Pro-Q 4.dll", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Imported/Clap Fx.wav", + "C:/Users/novik/Desktop/YOUTUBE RUFUS DUU SOL & ARTBAT/GHOSTPRODUCTION.PRO (ABLETON LIVE) (Style RUFUS DUU SOL) Project/Samples/Imported/Clap.wav", + "C:/VST2/Fabfilter/FabFilter Pro-C 2.dll" + ] + }, + "storedAt": "2025-12-01T03:06:52.832Z", + "filePath": "library/4f6a02d3a9826f59ab7c06f6a622005a.als" } ] \ No newline at end of file diff --git a/data/flows.json b/data/flows.json index 663fab3..60033bc 100644 --- a/data/flows.json +++ b/data/flows.json @@ -333,7 +333,7 @@ "order": 0, "width": "12", "height": "10", - "format": "\n\n \n Chatbot ALS\n \n \n
\n
\n {{item.role === 'user' ? 'T\u00fa' : (item.role === 'bot' ? 'ALS Bot' : 'Error')}}:\n {{item.text}}\n
\n
\n
\n \n \n \n \n {{busy ? 'Generando...' : 'Enviar'}}\n
\n
\n
\n\n\n", + "format": "\n\n \n Chatbot ALS\n \n \n
\n
\n {{item.role === 'user' ? 'T\u00fa' : (item.role === 'bot' ? 'ALS Bot' : 'Error')}}:\n {{item.text}}\n
\n
\n
\n \n \n \n \n {{busy ? 'Generando...' : 'Enviar'}}\n
\n
\n
\n\n\n", "storeOutMessages": false, "fwdInMessages": false, "resendOnRefresh": true, @@ -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}\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();\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}\nfunction shouldGenerate(text) {\n return /(genera(me)?|crea(me)?|haz(me)?|arma(me)?|produce(me)?).*(als|ableton|beat|proyecto)/i.test(text);\n}\nfunction buildSummary(text, library, sources) {\n if (!library || !library.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 = library.slice(-1)[0];\n const parts = [\n `Tengo ${library.length} proyectos almacenados. El \u00faltimo fue \"${recent.projectName}\" a ${recent.liveSet?.tempo || 'N/D'} BPM.`,\n sources && sources.length ? `Hay ${sources.length} recursos en sources (ej: ${sources.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 (!shouldGenerate(prompt)) {\n const info = generator.listLibrary ? generator.listLibrary() : [];\n const sources = generator.listSources ? generator.listSources() : [];\n msg.statusCode = 200;\n msg.payload = { reply: buildSummary(prompt, info, sources) };\n return msg;\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", "outputs": 1, "noerr": 0, "initialize": "", diff --git a/data/lib/alsGenerator.js b/data/lib/alsGenerator.js index e3b0b34..6bdbb95 100644 --- a/data/lib/alsGenerator.js +++ b/data/lib/alsGenerator.js @@ -46,7 +46,7 @@ function readLibrary() { } } -function listSources() { +function scanSources() { const result = []; if (!fs.existsSync(SOURCES_DIR)) { return result; @@ -282,7 +282,7 @@ async function generateFromPrompt(prompt, options = {}) { if (!library.length) { throw new Error('No hay ALS en la biblioteca. Sube al menos uno antes de generar.'); } - const sources = listSources(); + const sources = scanSources(); const tokens = prompt .toLowerCase() .split(/[^a-z0-9]+/) @@ -341,5 +341,13 @@ async function generateFromPrompt(prompt, options = {}) { } module.exports = { - generateFromPrompt + generateFromPrompt, + listLibrary: () => { + ensureDirs(); + return readLibrary(); + }, + listSources: () => { + ensureDirs(); + return scanSources(); + } };