feat: route chatbot replies through ai helper
This commit is contained in:
@@ -116,7 +116,7 @@ function extractJson(text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function planWithAI(prompt, library, sources) {
|
||||
function anthConfig() {
|
||||
const base =
|
||||
(process.env.ANTHROPIC_BASE_URL || '').trim() ||
|
||||
(process.env.ANTHROPIC_FALLBACK_BASE_URL || '').trim();
|
||||
@@ -126,43 +126,35 @@ async function planWithAI(prompt, library, sources) {
|
||||
if (!base || !token) {
|
||||
return null;
|
||||
}
|
||||
const endpoint = `${base.replace(/\/$/, '')}/v1/messages`;
|
||||
const contextEntries = library
|
||||
.slice(0, 3)
|
||||
.map((entry) => ({
|
||||
projectName: entry.projectName,
|
||||
hash: entry.hash,
|
||||
tempo: entry.liveSet?.tempo,
|
||||
tracks: (entry.tracks || []).slice(0, 5).map((t) => t.name)
|
||||
}));
|
||||
return {
|
||||
endpoint: `${base.replace(/\/$/, '')}/v1/messages`,
|
||||
token
|
||||
};
|
||||
}
|
||||
|
||||
async function callAnthropic(systemText, userText, maxTokens = 800) {
|
||||
const cfg = anthConfig();
|
||||
if (!cfg) {
|
||||
return null;
|
||||
}
|
||||
const payload = {
|
||||
model: process.env.ANTHROPIC_MODEL || 'claude-3-5-sonnet-20241022',
|
||||
max_tokens: 800,
|
||||
temperature: 0.2,
|
||||
system:
|
||||
'Eres un generador de sesiones Ableton Live (.als). Devuelves un JSON con projectName, templateHash, tempo, trackNames (array) y notes. Usa solo los hashes ofrecidos.',
|
||||
max_tokens: maxTokens,
|
||||
temperature: 0.3,
|
||||
system: systemText,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text:
|
||||
`Prompt del usuario: ${prompt}\n` +
|
||||
`Plantillas disponibles: ${JSON.stringify(contextEntries, null, 2)}\n` +
|
||||
`Sources disponibles (${sources.length}): ${sources.join(', ') || 'ninguna'}\n` +
|
||||
'Respuesta esperada (JSON): {"projectName":"","templateHash":"","tempo":120,"trackNames":["..."],"notes":"..."}'
|
||||
}
|
||||
]
|
||||
content: [{ type: 'text', text: userText }]
|
||||
}
|
||||
]
|
||||
};
|
||||
try {
|
||||
const res = await fetch(endpoint, {
|
||||
const res = await fetch(cfg.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-api-key': token,
|
||||
'x-api-key': cfg.token,
|
||||
'anthropic-version': '2023-06-01'
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
@@ -172,18 +164,37 @@ async function planWithAI(prompt, library, sources) {
|
||||
throw new Error(await res.text());
|
||||
}
|
||||
const data = await res.json();
|
||||
const text =
|
||||
data?.content?.[0]?.text ||
|
||||
data?.content?.[0]?.text ||
|
||||
data?.content ||
|
||||
'';
|
||||
return extractJson(text);
|
||||
return data?.content?.[0]?.text || '';
|
||||
} catch (err) {
|
||||
console.warn('[alsGenerator] Error llamando a la IA:', err.message);
|
||||
console.warn('[alsGenerator] Anthropic error:', err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function planWithAI(prompt, library, sources) {
|
||||
const cfg = anthConfig();
|
||||
if (!cfg) {
|
||||
return null;
|
||||
}
|
||||
const contextEntries = library
|
||||
.slice(0, 3)
|
||||
.map((entry) => ({
|
||||
projectName: entry.projectName,
|
||||
hash: entry.hash,
|
||||
tempo: entry.liveSet?.tempo,
|
||||
tracks: (entry.tracks || []).slice(0, 5).map((t) => t.name)
|
||||
}));
|
||||
const text = await callAnthropic(
|
||||
'Eres un generador de sesiones Ableton Live (.als). Devuelves un JSON con projectName, templateHash, tempo, trackNames (array) y notes. Usa solo los hashes ofrecidos.',
|
||||
`Prompt del usuario: ${prompt}\nPlantillas disponibles: ${JSON.stringify(
|
||||
contextEntries,
|
||||
null,
|
||||
2
|
||||
)}\nSources disponibles (${sources.length}): ${sources.join(', ') || 'ninguna'}\nRespuesta esperada (JSON): {"projectName":"","templateHash":"","tempo":120,"trackNames":["..."],"notes":"..."}`
|
||||
);
|
||||
return extractJson(text);
|
||||
}
|
||||
|
||||
function setNameNode(nameNode, value) {
|
||||
if (!nameNode) {
|
||||
return;
|
||||
@@ -340,6 +351,34 @@ async function generateFromPrompt(prompt, options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
async function conversationalReply(prompt, library, sources) {
|
||||
const context = {
|
||||
prompt,
|
||||
projects: library.slice(-5).map((entry) => ({
|
||||
projectName: entry.projectName,
|
||||
hash: entry.hash,
|
||||
tempo: entry.liveSet?.tempo
|
||||
})),
|
||||
sources: sources.slice(0, 20)
|
||||
};
|
||||
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)}`
|
||||
);
|
||||
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 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.`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateFromPrompt,
|
||||
listLibrary: () => {
|
||||
@@ -349,5 +388,6 @@ module.exports = {
|
||||
listSources: () => {
|
||||
ensureDirs();
|
||||
return scanSources();
|
||||
}
|
||||
},
|
||||
chatReply: conversationalReply
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user