cc8b286f6c
1) Folder picker dung WINDOW EXPLORER (khong prompt nhap tay):
- src-tauri/src/lib.rs: IPC bridge — thread watcher ipc/pick_dir.request
-> run_on_main_thread -> dialog().file().blocking_pick_folder()
(tauri-plugin-dialog = IFileDialog/Explorer) -> ghi pick_dir.response;
ghi marker tauri_bridge_ready luc setup.
- app/api/v1/plugins.py: POST /pick-dir (def sync -> threadpool, khong
auth) — uu tien Tauri bridge, fallback PowerShell FolderBrowserDialog
(Win) / osascript (macOS) / zenity-kdialog (Linux).
- app.jsx pickPluginFolder: 1) pickPluginDir (native) -> 2) __TAURI__
invoke -> 3) in-app browser -> 4) prompt (cuoi cung).
2) Nut Synth liet ke VSTi da scan (truoc day rong):
- Root: list_available() chi quet settings.VST_DIR (mac dinh
/opt/daw_engine/vst3) trong khi Plugin Manager scan plugin_dirs user.
- plugins.py list_plugins: gop _scan_vst_in_dirs(plugin_dirs) (file
.vst3/.dll/.so + folder X.vst3 Windows).
- vst_engine.py: PluginManager.extra_vst_dirs + _scan_plugins quet them
(ca folder .vst3) + get_plugin_manager doc plugin_dirs.json -> load_vst
tim thay plugin user scan khi render.
3) VU meter leak: section-tab play -> sang MAIN SESSION -> track MAIN van
animate theo am section.
- Root: VU tick fallback _sub_ (sub-node section co analyser) chay cho
ca canvas MAIN; startSubTabPlayback ghi node o key track MAIN.
- Fix: fallback _sub_ chi ap dung cho canvas SECTION (isSessVu); 2 trigger
piano-roll them prefix _sess_ theo st.parent_tab_id.
Verify: 86 tests pass; engine frozen STARTUP 1.35s, 1 engine, 0 spawn '-c';
pick-dir IPC mock tra dung path; scan VST user dirs OK.
108 lines
7.8 KiB
JavaScript
108 lines
7.8 KiB
JavaScript
// SonicForge Studio API Service
|
|
window.API_BASE_URL = window.API_BASE_URL || window.location.origin;
|
|
|
|
(function() {
|
|
function getAuthToken() {
|
|
return localStorage.getItem('sonic_token') || '';
|
|
}
|
|
|
|
function getAuthHeaders() {
|
|
const token = getAuthToken();
|
|
return {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
|
};
|
|
}
|
|
|
|
async function apiRequest(endpoint, options = {}) {
|
|
const url = `${window.API_BASE_URL}${endpoint}`;
|
|
const headers = { ...getAuthHeaders(), ...options.headers };
|
|
const response = await fetch(url, { ...options, headers });
|
|
|
|
if (response.status === 401) {
|
|
localStorage.removeItem('sonic_token');
|
|
localStorage.removeItem('sonic_user');
|
|
}
|
|
|
|
const data = await response.json().catch(() => ({}));
|
|
if (!response.ok) {
|
|
throw new Error(data.detail || data.message || 'Lỗi kết nối API Server');
|
|
}
|
|
return data;
|
|
}
|
|
|
|
window.SonicAPI = {
|
|
login: (username, password) => apiRequest('/api/v1/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) }),
|
|
register: (username, email, password) => apiRequest('/api/v1/auth/register', { method: 'POST', body: JSON.stringify({ username, email, password }) }),
|
|
changePassword: (old_password, new_password) => apiRequest('/api/v1/auth/change-password', { method: 'POST', body: JSON.stringify({ old_password, new_password }) }),
|
|
getProfile: () => apiRequest('/api/v1/auth/profile', { method: 'GET' }),
|
|
|
|
listUsers: () => apiRequest('/api/v1/admin/users', { method: 'GET' }),
|
|
updateUserQuota: (userId, storageLimitMb, maxTracks = 16) => apiRequest(`/api/v1/admin/quotas/${userId}`, { method: 'PUT', body: JSON.stringify({ storage_limit_mb: storageLimitMb, max_tracks: maxTracks }) }),
|
|
updateUserRole: (userId, role, isActive = true) => apiRequest(`/api/v1/admin/users/${userId}/role`, { method: 'PUT', body: JSON.stringify({ role, is_active: isActive }) }),
|
|
deleteUser: (userId) => apiRequest(`/api/v1/admin/users/${userId}`, { method: 'DELETE' }),
|
|
createUser: (username, email, password, role = 'standard') => apiRequest('/api/v1/admin/users', { method: 'POST', body: JSON.stringify({ username, email, password, role }) }),
|
|
|
|
saveTempProject: (dataJson) => apiRequest('/api/v1/projects/temp', { method: 'POST', body: JSON.stringify({ data_json: dataJson }) }),
|
|
getTempProject: () => apiRequest('/api/v1/projects/temp', { method: 'GET' }),
|
|
saveCloudProject: (name, dataJson) => apiRequest('/api/v1/projects/cloud', { method: 'POST', body: JSON.stringify({ name, data_json: dataJson }) }),
|
|
listCloudProjects: () => apiRequest('/api/v1/projects/cloud', { method: 'GET' }),
|
|
getCloudProject: (projectId) => apiRequest(`/api/v1/projects/cloud/${projectId}`, { method: 'GET' }),
|
|
deleteCloudProject: (projectId) => apiRequest(`/api/v1/projects/cloud/${projectId}`, { method: 'DELETE' }),
|
|
updateCloudProject: (projectId, name, dataJson) => apiRequest(`/api/v1/projects/cloud/${projectId}`, { method: 'PUT', body: JSON.stringify({ name, data_json: dataJson }) }),
|
|
listMyFiles: (activeFileIds) => apiRequest('/api/v1/audio/my-files', { method: 'POST', body: JSON.stringify({ active_file_ids: activeFileIds }) }),
|
|
deleteMyFile: (fileId) => apiRequest(`/api/v1/audio/my-files/${fileId}`, { method: 'DELETE' }),
|
|
|
|
aiScan: (trackId, fileId, minLoopDuration = 2.0, maxLoopDuration = 6.0) => apiRequest('/api/v1/audio/ai-scan', { method: 'POST', body: JSON.stringify({ track_id: trackId, file_id: fileId, min_loop_duration: minLoopDuration, max_loop_duration: maxLoopDuration }) }),
|
|
aiCut: (sourceTrackId, fileId, selectionStart, selectionEnd) => apiRequest('/api/v1/audio/ai-cut', { method: 'POST', body: JSON.stringify({ source_track_id: sourceTrackId, file_id: fileId, selection_start: selectionStart, selection_end: selectionEnd }) }),
|
|
runPythonTool: (toolType, trackId, fileId, timePos = 0.0, freq = 440.0, duration = 2.0, waveType = "sine") => apiRequest('/api/v1/audio/python-tool', { method: 'POST', body: JSON.stringify({ tool_type: toolType, track_id: trackId, file_id: fileId, time_pos: timePos, freq: freq, duration: duration, wave_type: waveType }) }),
|
|
|
|
getAIConfigs: () => apiRequest('/api/v1/user/config/ai', { method: 'GET' }),
|
|
saveAIConfigs: (providers) => apiRequest('/api/v1/user/config/ai', { method: 'POST', body: JSON.stringify({ providers }) }),
|
|
|
|
getPreferences: () => apiRequest('/api/v1/user/preferences', { method: 'GET' }),
|
|
savePreferences: (prefs) => apiRequest('/api/v1/user/preferences', { method: 'POST', body: JSON.stringify({ preferences: prefs }) }),
|
|
|
|
listPlugins: () => apiRequest('/api/v1/plugins/available', { method: 'GET' }),
|
|
getPluginDirs: () => apiRequest('/api/v1/plugins/dirs', { method: 'GET' }),
|
|
savePluginDirs: (dirs) => apiRequest('/api/v1/plugins/dirs', { method: 'POST', body: JSON.stringify(dirs) }),
|
|
scanPluginDirs: () => apiRequest('/api/v1/plugins/scan', { method: 'POST' }),
|
|
// Native folder picker (Explorer qua Tauri bridge / PowerShell) —
|
|
// user yêu cầu dùng window explorer, không nhập tay
|
|
pickPluginDir: () => apiRequest('/api/v1/plugins/pick-dir', { method: 'POST' }),
|
|
// Folder picker cho Plugin Manager: duyet thu muc qua backend (media)
|
|
// — hoat dong moi OS, khong can window.__TAURI__ (UI chay tren localhost:8000)
|
|
browseComputer: () => apiRequest('/api/v1/media/computer', { method: 'GET' }),
|
|
browseDir: (path) => apiRequest(`/api/v1/media/browse?path=${encodeURIComponent(path)}`, { method: 'GET' }),
|
|
getSoundfontCatalog: () => apiRequest('/api/v1/plugins/soundfonts/catalog', { method: 'GET' }),
|
|
listDefaultSoundfonts: () => apiRequest('/api/v1/plugins/default-soundfonts', { method: 'GET' }),
|
|
listSoundfontInstruments: (sfId) => apiRequest(`/api/v1/plugins/soundfont-instruments/${sfId}`, { method: 'GET' }),
|
|
getAIPresets: () => apiRequest('/api/v1/ai/presets', { method: 'GET' }),
|
|
saveAIPreset: (preset) => apiRequest('/api/v1/ai/presets', { method: 'POST', body: JSON.stringify(preset) }),
|
|
deleteAIPreset: (presetId) => apiRequest(`/api/v1/ai/presets/${presetId}`, { method: 'DELETE' }),
|
|
|
|
createBackup: (projectId) => apiRequest(`/api/v1/projects/cloud/${projectId}/backup`, { method: 'POST' }),
|
|
listBackups: (projectId) => apiRequest(`/api/v1/projects/cloud/${projectId}/backups`, { method: 'GET' }),
|
|
deleteBackup: (backupId) => apiRequest(`/api/v1/projects/cloud/backups/${backupId}`, { method: 'DELETE' }),
|
|
cleanupBackups: (keep) => apiRequest('/api/v1/projects/cloud/backups/cleanup', { method: 'POST', body: JSON.stringify({ keep }) }),
|
|
|
|
renderProject: (projectJson, outputFilename) => apiRequest('/api/v1/plugins/render', { method: 'POST', body: JSON.stringify({ project_json: projectJson, output_filename: outputFilename }) }),
|
|
deleteSoundFont: (sfId) => apiRequest(`/api/v1/plugins/soundfont/${sfId}`, { method: 'DELETE' }),
|
|
uploadSoundFont: async (file) => {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
const token = localStorage.getItem('sonic_token') || '';
|
|
const resp = await fetch(`${window.API_BASE_URL}/api/v1/plugins/upload-soundfont`, {
|
|
method: 'POST',
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
body: formData
|
|
});
|
|
if (!resp.ok) {
|
|
const err = await resp.json().catch(() => ({}));
|
|
throw new Error(err.detail || 'Upload failed');
|
|
}
|
|
return resp.json();
|
|
}
|
|
};
|
|
})();
|