feat: add editable system prompts from web UI

This commit is contained in:
2026-06-04 11:31:56 +02:00
parent ecd75a7fb5
commit f4cb318175
3 changed files with 235 additions and 67 deletions
+71 -65
View File
@@ -1,20 +1,8 @@
// System + user prompt construction for the LLM. Server-side only — the
// browser never sees these prompts.
export function getSystemPromptAll() {
let tools = 'Runway Gen-3 Alpha, Kling AI, Luma Dream Machine, Pika 2.0, Haiper';
const envLinks = process.env.VIDEO_AI_LINKS || '';
if (envLinks.trim()) {
const names = envLinks.split(',').map(part => {
const idx = part.indexOf('=');
return idx > 0 ? part.slice(0, idx).trim() : null;
}).filter(Boolean);
if (names.length > 0) {
tools = names.join(', ');
}
}
return `You are a music production assistant specializing in Suno AI song creation.
export const DEFAULT_PROMPTS = {
all: `You are a music production assistant specializing in Suno AI song creation.
The user will describe a music idea. Generate all song assets as a single JSON
object. Output ONLY valid JSON — no markdown, no backticks, no preamble.
@@ -62,23 +50,8 @@ REQUIRED JSON STRUCTURE:
}
Keep each video_prompts[].prompt under 900 characters (excluding the Negative line).
Recommended video tools (choose the best fit per prompt): ${tools}. Never recommend Sora.`;
}
export function getSystemPromptPartial() {
let tools = 'Runway Gen-3 Alpha, Kling AI, Luma Dream Machine, Pika 2.0, Haiper';
const envLinks = process.env.VIDEO_AI_LINKS || '';
if (envLinks.trim()) {
const names = envLinks.split(',').map(part => {
const idx = part.indexOf('=');
return idx > 0 ? part.slice(0, idx).trim() : null;
}).filter(Boolean);
if (names.length > 0) {
tools = names.join(', ');
}
}
return `You are a music production assistant specializing in Suno AI song creation.
Recommended video tools (choose the best fit per prompt): {{VIDEO_TOOLS}}. Never recommend Sora.`,
partial: `You are a music production assistant specializing in Suno AI song creation.
The user has an existing song and wants to regenerate ONE section. Output ONLY
valid JSON — no markdown, no backticks, no preamble.
@@ -106,12 +79,10 @@ QUALITY CONSTRAINTS:
- style: comma-separated, max 120 words, include tempo + BPM range, never
name an artist, no "sounds like" phrases.
- video_prompts: 5-second seamless loops, 16:9. Include the "Negative" line
inside each prompt. Keep prompts under 900 characters. Recommended tools: ${tools}. Never recommend Sora.
inside each prompt. Keep prompts under 900 characters. Recommended tools: {{VIDEO_TOOLS}}. Never recommend Sora.
- youtube_description: follow the hook → description → divider → lyrics →
divider → CTA → hashtags structure.`;
}
export const SYSTEM_PROMPT_STYLE_NORMAL = `You are a Suno style-prompt generator.
divider → CTA → hashtags structure.`,
style_normal: `You are a Suno style-prompt generator.
Output ONE short Suno style description.
@@ -128,9 +99,8 @@ Examples of the expected output (do NOT copy these verbatim):
- dark synthwave, analog pads, driving bassline, 110 BPM, male vocals
- indie folk, fingerpicked acoustic guitar, soft female vocals, 90 BPM
- trap, heavy 808s, dark piano, fast hi-hats, 140 BPM, autotune vocals
- dreamy shoegaze, layered reverb guitars, hushed vocals, 95 BPM`;
export const SYSTEM_PROMPT_STYLE_CRAZY = `You are a Suno style-prompt generator with permission to break conventions.
- dreamy shoegaze, layered reverb guitars, hushed vocals, 95 BPM`,
style_crazy: `You are a Suno style-prompt generator with permission to break conventions.
Output ONE short Suno style description.
@@ -150,9 +120,8 @@ Examples of the expected output (do NOT copy these verbatim):
- baroque chamber orchestra meets dubstep, cellos, glitch drops, 160 BPM
- lo-fi mariachi breakcore, trumpets, chopped breaks, 90 BPM, vinyl crackle
- medieval lute and drum & bass, fingerpicked strings, reese bass, 174 BPM
- tuvan throat singing over tropical house, guttural vocals, marimba, 120 BPM`;
export const SYSTEM_PROMPT_LYRICS_PARTIAL = `You are a music production assistant specializing in Suno AI song creation.
- tuvan throat singing over tropical house, guttural vocals, marimba, 120 BPM`,
lyrics_partial: `You are a music production assistant specializing in Suno AI song creation.
The user has existing lyrics and wants to regenerate a SPECIFIC selected passage only.
@@ -162,12 +131,44 @@ Rules:
- Keep the same emotional tone, language, and Suno section tags.
- The lyrics must NOT reference the genre, instruments, or music production.
- Output ONLY the rewritten passage text — no JSON, no explanation, no quotes around it.
- Do NOT include section tags in your output unless the selected passage already contained them.`;
- Do NOT include section tags in your output unless the selected passage already contained them.`
};
export let activePrompts = { ...DEFAULT_PROMPTS };
export function setActivePrompts(prompts) {
activePrompts = { ...DEFAULT_PROMPTS, ...prompts };
}
function getToolsString() {
let tools = 'Runway Gen-3 Alpha, Kling AI, Haiper';
const envLinks = process.env.VIDEO_AI_LINKS || '';
if (envLinks.trim()) {
const names = envLinks.split(',').map(part => {
const idx = part.indexOf('=');
return idx > 0 ? part.slice(0, idx).trim() : null;
}).filter(Boolean);
if (names.length > 0) {
tools = names.join(', ');
}
}
return tools;
}
export function buildSystemPrompt(req) {
if (req.section === 'lyrics_partial') return SYSTEM_PROMPT_LYRICS_PARTIAL;
if (req.section === 'all') return getSystemPromptAll();
return getSystemPromptPartial();
const toolsStr = getToolsString();
if (req.section === 'lyrics_partial') {
return activePrompts.lyrics_partial;
}
if (req.section === 'all') {
return activePrompts.all.replace('{{VIDEO_TOOLS}}', toolsStr);
}
return activePrompts.partial.replace('{{VIDEO_TOOLS}}', toolsStr);
}
export function buildStylePrompt(crazy) {
return crazy ? activePrompts.style_crazy : activePrompts.style_normal;
}
export function buildUserMessage(req) {
@@ -182,29 +183,34 @@ export function buildUserMessage(req) {
req.context?.lyrics ?? '',
'',
'Selected passage to rewrite:',
req.selected_text ?? '',
].filter(l => l !== null).join('\n');
req.passage ?? '',
]
.filter((line) => line !== null)
.join('\n');
}
const lines = [];
lines.push(`Music idea: ${req.input}`);
lines.push(`Language: ${req.language}`);
if (req.mood) lines.push(`Mood: ${req.mood}`);
if (req.style_hint) lines.push(`Style hint (user-provided): ${req.style_hint}`);
lines.push(`Vocals: ${req.vocals}`);
lines.push(`Section to generate: ${req.section}`);
const lines = [
`Music idea: ${req.input}`,
`Language: ${req.language}`,
req.mood ? `Mood: ${req.mood}` : '',
req.vocals ? `Vocals: ${req.vocals}` : '',
];
if (req.context && Object.keys(req.context).length > 0) {
if (req.section !== 'all' && req.context) {
lines.push('');
lines.push('Context — the current values of the other sections of this song:');
lines.push(JSON.stringify(req.context, null, 2));
lines.push('CURRENT STATE OF OTHER SECTIONS:');
if (req.context.titles) {
lines.push('Titles:', ...req.context.titles.map((t) => `- ${t}`));
}
if (req.context.style) {
lines.push(`Style: ${req.context.style}`);
}
if (req.context.lyrics) {
lines.push('Lyrics:', req.context.lyrics);
}
lines.push('');
lines.push(`Please regenerate ONLY the following section: ${req.section}`);
}
return lines.join('\n');
}
export function buildStylePrompt(mode, idea) {
const base = mode === 'crazy' ? SYSTEM_PROMPT_STYLE_CRAZY : SYSTEM_PROMPT_STYLE_NORMAL;
if (!idea || !idea.trim()) return base;
return base + `\n\nUser's music idea (use as context for tailoring the style): "${idea.trim()}"`;
return lines.filter((line) => line !== null).join('\n');
}