421535ca0e
- promptTemplateManager.js: standalone service with keyword scoring, CRUD, fav toggle - ai_presets.py: backend CRUD router (JSON file, auth isolation) - AIPresetModal: PromptTemplateManager, star/fav column, backend API sync - Piano Roll AI: preset matching support - 7 tests: matching, CRUD, anonymous auth, user isolation
136 lines
5.2 KiB
JavaScript
136 lines
5.2 KiB
JavaScript
const PromptTemplateManager = (function() {
|
|
const DEFAULT_PRESETS = [
|
|
{
|
|
id: "preset_epic_orchestra_intro",
|
|
name: "Epic Orchestra Intro (8 Bars)",
|
|
keywords: ["epic orchestra", "epic orchestral", "hoành tráng", "nhạc phim epic"],
|
|
category: "Orchestral / Film Score",
|
|
default_bars: 8,
|
|
default_bpm: 130,
|
|
default_scale: "C Minor",
|
|
system_instruction_template: "You are a professional film composer. Create a powerful, dramatic 8-bar orchestral intro. Keep the note density low (e.g. use mostly whole notes, half notes, or quarter notes) and do NOT generate dense 16th notes or complex drum rolls. This is critical to avoid output token limit timeouts. The required structure to return via the `generate_multitrack_midi` tool consists of 3 tracks: 1. Strings: plays smooth legato chord changes (one chord per 1 or 2 bars). 2. Brass Theme: plays a swelling simple melodic line in the C3-C5 range. 3. Epic Percussion: hits heavily on beats 1 and 3. Ensure the duration is precisely 8 bars (32 beats).",
|
|
is_user_defined: false,
|
|
is_favorite: false,
|
|
created_at: "2026-07-23T16:00:00Z"
|
|
},
|
|
{
|
|
id: "preset_pop_piano_chords",
|
|
name: "Pop Piano Chords (4 Bars)",
|
|
keywords: ["pop piano", "piano chords", "ballad piano", "hợp âm piano"],
|
|
category: "Pop / Ballad",
|
|
default_bars: 4,
|
|
default_bpm: 90,
|
|
default_scale: "C Major",
|
|
system_instruction_template: "You are a professional Pop Piano player. Generate a beautiful 4-bar piano chord progression (e.g. C - G - Am - F) with pleasant chord voicing and simple accompaniment. Return the MIDI notes via `generate_multitrack_midi` function on a track named 'Pop Piano'. Keep notes simple, using mostly whole/half/quarter notes. Ensure the duration of the track is precisely 4 bars (16 beats).",
|
|
is_user_defined: false,
|
|
is_favorite: false,
|
|
created_at: "2026-07-23T16:00:00Z"
|
|
},
|
|
{
|
|
id: "preset_cyberpunk_synth",
|
|
name: "Cyberpunk Synthwave (8 Bars)",
|
|
keywords: ["cyberpunk synth", "synthwave", "cyberpunk", "futuristic synth"],
|
|
category: "Electronic / Synthwave",
|
|
default_bars: 8,
|
|
default_bpm: 120,
|
|
default_scale: "A Minor",
|
|
system_instruction_template: "You are a Synthwave producer. Generate a driving 8-bar cyberpunk synth theme. Return MIDI notes via `generate_multitrack_midi` containing: 1. Synth Bass: eighth notes on pitch A1, C2, G1. 2. Synth Lead: simple melodic line in high register C4-E5. Keep notes clean and concise to ensure fast generation.",
|
|
is_user_defined: false,
|
|
is_favorite: false,
|
|
created_at: "2026-07-23T16:00:00Z"
|
|
}
|
|
];
|
|
|
|
const STORAGE_KEY = 'daw_ai_prompt_presets';
|
|
|
|
function PromptTemplateManager() {
|
|
this.presets = [];
|
|
this.loadPresets();
|
|
}
|
|
|
|
PromptTemplateManager.prototype.loadPresets = function() {
|
|
try {
|
|
const localData = localStorage.getItem(STORAGE_KEY);
|
|
if (localData) {
|
|
const parsed = JSON.parse(localData);
|
|
const userPresets = parsed.filter(p => p.is_user_defined);
|
|
this.presets = [...DEFAULT_PRESETS, ...userPresets];
|
|
} else {
|
|
this.presets = [...DEFAULT_PRESETS];
|
|
this.savePresets();
|
|
}
|
|
} catch (_) {
|
|
this.presets = [...DEFAULT_PRESETS];
|
|
}
|
|
};
|
|
|
|
PromptTemplateManager.prototype.savePresets = function() {
|
|
const userData = this.presets.filter(p => p.is_user_defined);
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(userData));
|
|
};
|
|
|
|
PromptTemplateManager.prototype.getPresets = function() {
|
|
return this.presets;
|
|
};
|
|
|
|
PromptTemplateManager.prototype.matchPreset = function(userQuery) {
|
|
if (!userQuery) return null;
|
|
const queryLower = userQuery.toLowerCase();
|
|
let bestMatch = null;
|
|
let bestScore = 0;
|
|
|
|
for (const preset of this.presets) {
|
|
for (const kw of preset.keywords) {
|
|
const kwLower = kw.toLowerCase();
|
|
if (queryLower === kwLower) {
|
|
if (3 > bestScore) {
|
|
bestScore = 3;
|
|
bestMatch = { preset, score: 3 };
|
|
}
|
|
} else if (queryLower.includes(kwLower)) {
|
|
if (2 > bestScore) {
|
|
bestScore = 2;
|
|
bestMatch = { preset, score: 2 };
|
|
}
|
|
} else if (kwLower.includes(queryLower)) {
|
|
if (1 > bestScore) {
|
|
bestScore = 1;
|
|
bestMatch = { preset, score: 1 };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bestMatch;
|
|
};
|
|
|
|
PromptTemplateManager.prototype.saveUserPreset = function(presetObject) {
|
|
const index = this.presets.findIndex(p => p.id === presetObject.id);
|
|
if (index >= 0) {
|
|
this.presets[index] = presetObject;
|
|
} else {
|
|
this.presets.push(presetObject);
|
|
}
|
|
this.savePresets();
|
|
};
|
|
|
|
PromptTemplateManager.prototype.deletePreset = function(id) {
|
|
this.presets = this.presets.filter(p => p.id !== id);
|
|
this.savePresets();
|
|
};
|
|
|
|
PromptTemplateManager.prototype.toggleFavorite = function(id) {
|
|
const preset = this.presets.find(p => p.id === id);
|
|
if (preset) {
|
|
preset.is_favorite = !preset.is_favorite;
|
|
if (preset.is_user_defined) {
|
|
this.savePresets();
|
|
}
|
|
}
|
|
};
|
|
|
|
return PromptTemplateManager;
|
|
})();
|
|
|
|
window.PromptTemplateManager = PromptTemplateManager;
|
|
window.DEFAULT_PRESETS = (new PromptTemplateManager()).getPresets();
|