Los prompts, dentro de la ficha del género y con la forma que dice su modelo
Estaban modelados desde la fase 1 y no existían en el panel: `modelo_ia`,
`animo` y `prompt`, cero filas las tres y ni siquiera una línea en la portada.
Y la primera versión no respetaba el modelo. `modelo_ia.esquema` dice esto:
> JSON Schema con la forma que toman los prompts de este modelo. Se validan
> contra él al guardar, y el panel puede pintar el formulario a partir de él sin
> una pantalla por cada IA.
Había puesto un `textarea` único guardando `{ texto }` y forzado
`estado: 'publicado'` al crear, cuando el modelo dice borrador por defecto. Las
dos cosas van ahora como estaban escritas: el formulario del prompt sale del
esquema de su modelo —un campo por propiedad, con su etiqueta, su ayuda, su
lista cerrada y su rango— y lo que se guarda se comprueba contra él.
`$lib/esquema-prompt` es el lector, y dice en su cabecera qué subconjunto
entiende: objeto en la raíz con `properties` y `required`; por campo string,
number, integer o boolean, más `enum`, `maxLength`, `minimum`, `maximum`,
`title` y `description`. Lo que no esté se ignora; un esquema roto se rechaza al
guardarlo, no al usarlo. Un modelo sin esquema sigue funcionando con un bloque
de texto.
Y `x-order`, que no estaba previsto y hace falta: la base guarda el esquema como
`jsonb` y ahí Postgres reordena las claves por longitud, así que el campo
obligatorio salía el último del formulario. Se vio en la captura.
Los prompts van **dentro de la ficha del género**, no en una pestaña aparte: un
prompt es de ese género y escribir la ficha y sacar la música son la misma
sesión. Fuera de su formulario, porque cada prompt se guarda por su cuenta.
Los modelos se dan de alta a mano, con su versión: Suno v4 y v3.5 son dos
modelos, y una lista sembrada en un script envejecería en un mes. Los nueve
ánimos de la GEMS sí se siembran, con `npm run db:animos`.
Comprobado contra el panel de verdad: esquema aceptado y esquema roto
rechazado, formulario pintado desde el esquema —lista cerrada, número con
rango, letra en bloque—, campo obligatorio reclamado, un solo preferido por
combinación y el preferido el primero de la lista.
Falta `prompt.ejemplo_id`, el audio del resultado: pide los medios, que todavía
no se editan aquí.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 weeks ago
|
|
|
|
/**
|
|
|
|
|
|
* Siembra los ánimos.
|
|
|
|
|
|
*
|
|
|
|
|
|
* npm run db:animos
|
|
|
|
|
|
*
|
|
|
|
|
|
* Son los nueve factores de la **GEMS** (*Geneva Emotional Music Scale*), que es
|
|
|
|
|
|
* una escala hecha para emoción musical y no para emoción en general: por eso
|
|
|
|
|
|
* tiene «trascendencia» y «nostalgia» y no tiene «miedo» ni «asco».
|
|
|
|
|
|
*
|
|
|
|
|
|
* Cada uno lleva además los dos ejes del modelo circumplejo de Russell:
|
|
|
|
|
|
* valencia —si tira a agradable o a desagradable— y energía. Sirven para
|
|
|
|
|
|
* ordenar, para interpolar entre dos ánimos, y sobre todo porque son
|
|
|
|
|
|
* redactables: «alta energía, valencia baja» es una instrucción que una IA
|
|
|
|
|
|
* entiende; «tensión» a secas, no.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Idempotente por código: volver a lanzarlo actualiza y no duplica.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import pg from 'pg';
|
|
|
|
|
|
|
|
|
|
|
|
/** `valencia` va de −1 a 1 y `energia` de 0 a 100, como dice el esquema. */
|
|
|
|
|
|
const ANIMOS = [
|
|
|
|
|
|
{
|
|
|
|
|
|
codigo: 'asombro',
|
|
|
|
|
|
nombre: 'Asombro',
|
|
|
|
|
|
gems: 'wonder',
|
|
|
|
|
|
valencia: 1,
|
|
|
|
|
|
energia: 60,
|
|
|
|
|
|
descripcion: 'Lo que abre los ojos. Deslumbra sin arrollar.'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
codigo: 'trascendencia',
|
|
|
|
|
|
nombre: 'Trascendencia',
|
|
|
|
|
|
gems: 'transcendence',
|
|
|
|
|
|
valencia: 1,
|
|
|
|
|
|
energia: 45,
|
|
|
|
|
|
descripcion: 'Sacar a alguien de sí mismo. Lo solemne sin lo grandilocuente.'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
codigo: 'ternura',
|
|
|
|
|
|
nombre: 'Ternura',
|
|
|
|
|
|
gems: 'tenderness',
|
|
|
|
|
|
valencia: 1,
|
|
|
|
|
|
energia: 30,
|
|
|
|
|
|
descripcion: 'El cariño dicho de cerca y en voz baja.'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
codigo: 'nostalgia',
|
|
|
|
|
|
nombre: 'Nostalgia',
|
|
|
|
|
|
gems: 'nostalgia',
|
|
|
|
|
|
valencia: 0,
|
|
|
|
|
|
energia: 30,
|
|
|
|
|
|
descripcion:
|
|
|
|
|
|
'Lo que se recuerda con gusto y duele un poco. La valencia va en medio a propósito.'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
codigo: 'serenidad',
|
|
|
|
|
|
nombre: 'Serenidad',
|
|
|
|
|
|
gems: 'peacefulness',
|
|
|
|
|
|
valencia: 1,
|
|
|
|
|
|
energia: 20,
|
|
|
|
|
|
descripcion: 'Calma sin sueño. Nada que resolver.'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
codigo: 'fuerza',
|
|
|
|
|
|
nombre: 'Fuerza',
|
|
|
|
|
|
gems: 'power',
|
|
|
|
|
|
valencia: 1,
|
|
|
|
|
|
energia: 85,
|
|
|
|
|
|
descripcion: 'Empuje, triunfo, ganas. Lo que se canta de pie.'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
codigo: 'alegria-vital',
|
|
|
|
|
|
nombre: 'Alegría vital',
|
|
|
|
|
|
gems: 'joyful activation',
|
|
|
|
|
|
valencia: 1,
|
|
|
|
|
|
energia: 80,
|
|
|
|
|
|
descripcion: 'Alegría que mueve el cuerpo. Es la de bailar, no la de sonreír.'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
codigo: 'tension',
|
|
|
|
|
|
nombre: 'Tensión',
|
|
|
|
|
|
gems: 'tension',
|
|
|
|
|
|
valencia: -1,
|
|
|
|
|
|
energia: 75,
|
|
|
|
|
|
descripcion: 'Nervio, inquietud, algo a punto de pasar.'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
codigo: 'tristeza',
|
|
|
|
|
|
nombre: 'Tristeza',
|
|
|
|
|
|
gems: 'sadness',
|
|
|
|
|
|
valencia: -1,
|
|
|
|
|
|
energia: 20,
|
|
|
|
|
|
descripcion: 'La pena quieta. En un bolero, la de siempre.'
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const c = new pg.Client({ connectionString: process.env.DATABASE_URL, query_timeout: 15_000 });
|
|
|
|
|
|
await c.connect();
|
|
|
|
|
|
|
|
|
|
|
|
let nuevos = 0;
|
|
|
|
|
|
for (const a of ANIMOS) {
|
|
|
|
|
|
const { rows } = await c.query(
|
|
|
|
|
|
`insert into animo (id, codigo, nombre, gems, valencia, energia, descripcion)
|
|
|
|
|
|
values (gen_random_uuid(), $1, $2, $3, $4, $5, $6)
|
|
|
|
|
|
on conflict (codigo) do update set
|
|
|
|
|
|
nombre = excluded.nombre,
|
|
|
|
|
|
gems = excluded.gems,
|
|
|
|
|
|
valencia = excluded.valencia,
|
|
|
|
|
|
energia = excluded.energia,
|
|
|
|
|
|
descripcion = excluded.descripcion
|
|
|
|
|
|
returning (xmax = 0) as insertado`,
|
|
|
|
|
|
[a.codigo, a.nombre, a.gems, a.valencia, a.energia, a.descripcion]
|
|
|
|
|
|
);
|
|
|
|
|
|
if (rows[0].insertado) nuevos++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`${ANIMOS.length} ánimos: ${nuevos} nuevos, ${ANIMOS.length - nuevos} actualizados.`);
|
|
|
|
|
|
await c.end();
|