feat(generator): apply md/50 transfer spec

This commit is contained in:
2026-07-31 06:36:37 +07:00
parent 6ce77c4f20
commit bc36e40967
2 changed files with 74 additions and 27 deletions
+67 -26
View File
@@ -237,15 +237,14 @@
</div> </div>
<div> <div>
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">DANH MỤC</label> <label class="block text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-1">DANH MỤC</label>
<input type="text" id="presetCategoryInput" list="categoryDatalist" class="w-full bg-[#1a1c23] border border-slate-800 rounded-lg px-3 py-2 text-white focus:outline-none focus:border-purple-500 font-sans" value="Orchestral / Film Score" placeholder="Chọn hoặc nhập danh mục..."> <select id="presetCategorySelect" class="w-full bg-[#1a1c23] border border-slate-800 rounded-lg px-3 py-2 text-white focus:outline-none focus:border-purple-500 font-sans">
<datalist id="categoryDatalist"> <option value="Orchestral / Film Score">Orchestral / Film Score</option>
<option value="Orchestral / Film Score"></option> <option value="Piano Solo">Piano Solo</option>
<option value="Piano Solo"></option> <option value="EDM / Pop">EDM / Pop</option>
<option value="EDM / Pop"></option> <option value="Cinematic">Cinematic</option>
<option value="Cinematic"></option> <option value="Jazz & Swing">Jazz & Swing</option>
<option value="Jazz & Swing"></option> <option value="Lo-fi Chillhop">Lo-fi Chillhop</option>
<option value="Lo-fi Chillhop"></option> </select>
</datalist>
</div> </div>
</div> </div>
@@ -280,18 +279,25 @@
</div> </div>
<!-- Action Buttons Row --> <!-- Action Buttons Row -->
<div class="flex items-center justify-between pt-2 border-t border-slate-800/80"> <div class="space-y-3 pt-2 border-t border-slate-800/80">
<div class="flex items-center gap-2"> <div class="flex items-center justify-between">
<button id="btnModalBack" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-semibold transition-colors"> <button id="btnCreateStructuredPreset" class="px-4 py-2 bg-emerald-700 hover:bg-emerald-600 text-white rounded-lg text-xs font-bold transition-all shadow-md flex items-center gap-1.5">
Quay lại <i class="fa-solid fa-wand-magic-sparkles"></i> Tạo preset có cấu trúc
</button> </button>
<button id="btnSavePresetSubmit" class="px-5 py-2 bg-purple-600 hover:bg-purple-500 text-white rounded-lg text-xs font-bold transition-all shadow-lg shadow-purple-950/80 flex items-center gap-1.5"> <div class="flex items-center gap-2">
<i class="fa-solid fa-floppy-disk"></i> <span>Lưu Preset</span> <button id="btnModalBack" class="px-4 py-2 bg-[#252836] hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-semibold transition-colors">
Quay lại
</button>
<button id="btnSavePresetSubmit" class="px-5 py-2 bg-purple-600 hover:bg-purple-500 text-white rounded-lg text-xs font-bold transition-all shadow-lg shadow-purple-950/80 flex items-center gap-1.5">
<i class="fa-solid fa-floppy-disk"></i> <span>Lưu Preset</span>
</button>
</div>
</div>
<div class="flex justify-end">
<button id="btnCloseModalBottom" class="px-4 py-1.5 bg-[#1f222e] hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-semibold transition-colors">
Đóng
</button> </button>
</div> </div>
<button id="btnCloseModalBottom" class="px-4 py-1.5 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-semibold transition-colors">
Đóng
</button>
</div> </div>
</div> </div>
@@ -406,8 +412,9 @@
const btnCloseModalBottom = document.getElementById('btnCloseModalBottom'); const btnCloseModalBottom = document.getElementById('btnCloseModalBottom');
const btnModalBack = document.getElementById('btnModalBack'); const btnModalBack = document.getElementById('btnModalBack');
const btnSavePresetSubmit = document.getElementById('btnSavePresetSubmit'); const btnSavePresetSubmit = document.getElementById('btnSavePresetSubmit');
const btnCreateStructuredPreset = document.getElementById('btnCreateStructuredPreset');
const presetNameInput = document.getElementById('presetNameInput'); const presetNameInput = document.getElementById('presetNameInput');
const presetCategoryInput = document.getElementById('presetCategoryInput'); const presetCategorySelect = document.getElementById('presetCategorySelect');
const presetKeywordsInput = document.getElementById('presetKeywordsInput'); const presetKeywordsInput = document.getElementById('presetKeywordsInput');
const presetBarsInput = document.getElementById('presetBarsInput'); const presetBarsInput = document.getElementById('presetBarsInput');
const presetBpmInput = document.getElementById('presetBpmInput'); const presetBpmInput = document.getElementById('presetBpmInput');
@@ -647,18 +654,35 @@
}, 2500); }, 2500);
} }
// Open Save Preset Modal Event Handler // Open Save Preset Modal Event Handler — auto-populate per spec
btnSavePresetModal.addEventListener('click', () => { btnSavePresetModal.addEventListener('click', () => {
const timeData = computeTimeMetrics(); const timeData = computeTimeMetrics();
const currentPromptText = promptOutputText.innerText; const currentPromptText = promptOutputText.innerText.trim();
// Auto-populate Preset Form Fields
const sectionType = inputSectionType.value.trim() || "Building Section"; const sectionType = inputSectionType.value.trim() || "Building Section";
const role = inputRole.value.trim() || "Composer"; const role = inputRole.value.trim() || "Composer";
const actionDesc = inputActionDesc.value.trim();
// Auto-infer Category based on Role
let category = "Orchestral / Film Score";
const roleLower = role.toLowerCase();
if (roleLower.includes("piano")) category = "Piano Solo";
else if (roleLower.includes("edm") || roleLower.includes("electro")) category = "EDM / Pop";
else if (roleLower.includes("jazz")) category = "Jazz & Swing";
else if (roleLower.includes("lo-fi") || roleLower.includes("chillhop")) category = "Lo-fi Chillhop";
// Auto-generate Keyword Tags
const keywordList = [
sectionType.toLowerCase(),
roleLower.split(' ')[0],
`${timeData.barCount} bars`
];
const keywordsStr = keywordList.filter(Boolean).join(', ');
// Populate Form Fields
presetNameInput.value = `${sectionType} (${timeData.barCount} Bars)`; presetNameInput.value = `${sectionType} (${timeData.barCount} Bars)`;
presetCategoryInput.value = role.includes("piano") ? "Piano Solo" : "Orchestral / Film Score"; presetCategorySelect.value = category;
presetKeywordsInput.value = `${sectionType.toLowerCase()}, ${role.toLowerCase().split(' ')[0]}, ${timeData.barCount} bars`; presetKeywordsInput.value = keywordsStr;
presetBarsInput.value = timeData.barCount; presetBarsInput.value = timeData.barCount;
presetBpmInput.value = 120; presetBpmInput.value = 120;
presetScaleInput.value = "C Minor"; presetScaleInput.value = "C Minor";
@@ -677,10 +701,25 @@
} }
}); });
// Save Preset Action Handler (Saves to daw_ai_prompt_presets localStorage) // Create Structured Preset — replace static numbers with template variables
btnCreateStructuredPreset.addEventListener('click', () => {
let promptText = presetTemplateTextarea.value;
const bars = presetBarsInput.value || "8";
promptText = promptText
.replace(new RegExp(`dài ${bars} ô nhịp`, 'g'), 'dài {{bars}} ô nhịp')
.replace(/Từ Ô nhịp \d+ đến \d+/g, 'Từ Ô nhịp {{start_bar}} đến {{end_bar}}')
.replace(/Phách 0\.0 đến \d+\.0/g, 'Phách 0.0 đến {{total_beats}}')
.replace(/phách \d+\.0/g, 'phách {{total_beats}}');
presetTemplateTextarea.value = promptText;
showToast("Đã chuyển đổi Prompt thành mẫu biến cấu trúc!");
});
// Save Preset Action Handler (Saves to daw_ai_prompt_presets localStorage & dispatches event)
btnSavePresetSubmit.addEventListener('click', () => { btnSavePresetSubmit.addEventListener('click', () => {
const name = presetNameInput.value.trim(); const name = presetNameInput.value.trim();
const category = presetCategoryInput.value.trim() || "General"; const category = presetCategorySelect.value;
const keywordsRaw = presetKeywordsInput.value.trim(); const keywordsRaw = presetKeywordsInput.value.trim();
const defaultBars = parseInt(presetBarsInput.value) || 8; const defaultBars = parseInt(presetBarsInput.value) || 8;
const defaultBpm = parseInt(presetBpmInput.value) || 120; const defaultBpm = parseInt(presetBpmInput.value) || 120;
@@ -726,6 +765,8 @@
try { try {
localStorage.setItem('daw_ai_prompt_presets', JSON.stringify(savedPresets)); localStorage.setItem('daw_ai_prompt_presets', JSON.stringify(savedPresets));
// Dispatch Custom Event so parent window updates preset list instantly
window.dispatchEvent(new CustomEvent('AI_PRESET_SAVED', { detail: newPresetObj }));
showToast(`Đã lưu Preset "${name}" thành công!`); showToast(`Đã lưu Preset "${name}" thành công!`);
presetManagerModal.classList.add('hidden'); presetManagerModal.classList.add('hidden');
} catch (e) { } catch (e) {
+6
View File
@@ -913,3 +913,9 @@
- **Tóm tắt thay đổi:** Sửa layout right column của generator page: header thêm `shrink-0` để không bị co lại; container preview đổi thành `flex-1 flex flex-col min-h-0` thay vì `overflow-y-auto`; `#promptOutputText` đổi từ `h-full` sang `flex-1` + `overflow-y-auto` để bottom luôn neo với cạnh modal và nội dung scroll trong element. - **Tóm tắt thay đổi:** Sửa layout right column của generator page: header thêm `shrink-0` để không bị co lại; container preview đổi thành `flex-1 flex flex-col min-h-0` thay vì `overflow-y-auto`; `#promptOutputText` đổi từ `h-full` sang `flex-1` + `overflow-y-auto` để bottom luôn neo với cạnh modal và nội dung scroll trong element.
- **Các file ảnh hưởng:** `md/49_AI_PROMPT_GENERATOR.md` - **Các file ảnh hưởng:** `md/49_AI_PROMPT_GENERATOR.md`
- **Ghi chú/Test (nếu có):** Refresh trang generator → right column fill full height → content scroll bên trong #promptOutputText. - **Ghi chú/Test (nếu có):** Refresh trang generator → right column fill full height → content scroll bên trong #promptOutputText.
---
### [2026-07-31 06:35] Task: Apply generator-to-preset-manager transfer spec
- **Tóm tắt thay đổi:** Áp dụng spec từ md/50_APPLY_GENERATOR.md vào md/49_AI_PROMPT_GENERATOR.md: (1) Category input chuyển thành `<select id="presetCategorySelect">`; (2) Thêm nút "Tạo preset có cấu trúc" (xanh lá) bên trái action bar; (3) autoFillPresetModal cập nhật logic infer category từ role/keywords; (4) Thêm handler `btnCreateStructuredPreset` thay static number bằng template variable; (5) Save handler dùng `presetCategorySelect.value`, dispatch `AI_PRESET_SAVED` CustomEvent.
- **Các file ảnh hưởng:** `md/49_AI_PROMPT_GENERATOR.md`
- **Ghi chú/Test (nếu có):** Refresh generator → Lưu thành Preset → Category tự chọn dropdown → Tạo preset có cấu trúc → số bars/beats biến thành {{bars}}/{{total_beats}}.