diff --git a/md/49_AI_PROMPT_GENERATOR.md b/md/49_AI_PROMPT_GENERATOR.md
index 84d85a0..37f22e0 100644
--- a/md/49_AI_PROMPT_GENERATOR.md
+++ b/md/49_AI_PROMPT_GENERATOR.md
@@ -237,15 +237,14 @@
-
-
@@ -406,8 +412,9 @@
const btnCloseModalBottom = document.getElementById('btnCloseModalBottom');
const btnModalBack = document.getElementById('btnModalBack');
const btnSavePresetSubmit = document.getElementById('btnSavePresetSubmit');
+ const btnCreateStructuredPreset = document.getElementById('btnCreateStructuredPreset');
const presetNameInput = document.getElementById('presetNameInput');
- const presetCategoryInput = document.getElementById('presetCategoryInput');
+ const presetCategorySelect = document.getElementById('presetCategorySelect');
const presetKeywordsInput = document.getElementById('presetKeywordsInput');
const presetBarsInput = document.getElementById('presetBarsInput');
const presetBpmInput = document.getElementById('presetBpmInput');
@@ -647,18 +654,35 @@
}, 2500);
}
- // Open Save Preset Modal Event Handler
+ // Open Save Preset Modal Event Handler — auto-populate per spec
btnSavePresetModal.addEventListener('click', () => {
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 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)`;
- presetCategoryInput.value = role.includes("piano") ? "Piano Solo" : "Orchestral / Film Score";
- presetKeywordsInput.value = `${sectionType.toLowerCase()}, ${role.toLowerCase().split(' ')[0]}, ${timeData.barCount} bars`;
+ presetCategorySelect.value = category;
+ presetKeywordsInput.value = keywordsStr;
presetBarsInput.value = timeData.barCount;
presetBpmInput.value = 120;
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', () => {
const name = presetNameInput.value.trim();
- const category = presetCategoryInput.value.trim() || "General";
+ const category = presetCategorySelect.value;
const keywordsRaw = presetKeywordsInput.value.trim();
const defaultBars = parseInt(presetBarsInput.value) || 8;
const defaultBpm = parseInt(presetBpmInput.value) || 120;
@@ -726,6 +765,8 @@
try {
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!`);
presetManagerModal.classList.add('hidden');
} catch (e) {
diff --git a/wiki.md b/wiki.md
index fc5305e..a542250 100644
--- a/wiki.md
+++ b/wiki.md
@@ -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.
- **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.
+
+---
+### [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 `