Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/frontend/__tests__/lyrics.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('lyrics API', () => {

await lyrics.clearCache();

expect(mockInvoke).toHaveBeenCalledWith('lyrics_clear_cache');
expect(mockInvoke).toHaveBeenCalledWith('lyrics_clear_cache', {});
});
});
});
Expand Down
69 changes: 16 additions & 53 deletions app/frontend/js/api/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
* Agent API
*
* Conversational playlist generation via local LLM (Ollama + Rig).
* All commands return graceful fallbacks when the agent feature is disabled.
* All operations return graceful fallbacks when not running in Tauri.
*/

import { ApiError, invoke } from './shared.js';
import { tauriInvoke } from './shared.js';

export const agent = {
/**
Expand All @@ -14,30 +14,18 @@ export const agent = {
* @returns {Promise<{status: string, playlist_id?: number, playlist_name?: string, track_count?: number, message: string}>}
*/
async generatePlaylist(prompt) {
if (invoke) {
try {
return await invoke('agent_generate_playlist', { prompt });
} catch (error) {
console.error('[api.agent.generatePlaylist] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
throw new ApiError(501, 'Agent requires Tauri runtime');
const result = await tauriInvoke('agent_generate_playlist', { prompt });
if (result !== null) return result;
throw new Error('Agent requires Tauri runtime');
},

/**
* Check agent availability (Ollama running + model present).
* @returns {Promise<{available: boolean, model: string, message: string}>}
*/
async checkStatus() {
if (invoke) {
try {
return await invoke('agent_check_status');
} catch (error) {
console.error('[api.agent.checkStatus] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
const result = await tauriInvoke('agent_check_status');
if (result !== null) return result;
return { available: false, model: '', message: 'Agent requires Tauri runtime' };
},

Expand All @@ -46,14 +34,8 @@ export const agent = {
* @returns {Promise<{connected: boolean, models: string[]}>}
*/
async checkOllama() {
if (invoke) {
try {
return await invoke('agent_check_ollama');
} catch (error) {
console.error('[api.agent.checkOllama] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
const result = await tauriInvoke('agent_check_ollama');
if (result !== null) return result;
return { connected: false, models: [] };
},

Expand All @@ -63,30 +45,18 @@ export const agent = {
* @returns {Promise<{success: boolean, model: string, message: string}>}
*/
async pullModel(model) {
if (invoke) {
try {
return await invoke('agent_pull_model', { model });
} catch (error) {
console.error('[api.agent.pullModel] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
throw new ApiError(501, 'Agent requires Tauri runtime');
const result = await tauriInvoke('agent_pull_model', { model });
if (result !== null) return result;
throw new Error('Agent requires Tauri runtime');
},

/**
* Get onboarding state from persistent store.
* @returns {Promise<{completed: boolean, model?: string}>}
*/
async getOnboardingState() {
if (invoke) {
try {
return await invoke('agent_get_onboarding_state');
} catch (error) {
console.error('[api.agent.getOnboardingState] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
const result = await tauriInvoke('agent_get_onboarding_state');
if (result !== null) return result;
return { completed: false, model: null };
},

Expand All @@ -95,14 +65,7 @@ export const agent = {
* @param {string|null} model - Model name used
* @returns {Promise<void>}
*/
async setOnboardingComplete(model = null) {
if (invoke) {
try {
return await invoke('agent_set_onboarding_complete', { model });
} catch (error) {
console.error('[api.agent.setOnboardingComplete] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
setOnboardingComplete(model = null) {
return tauriInvoke('agent_set_onboarding_complete', { model });
},
};
24 changes: 5 additions & 19 deletions app/frontend/js/api/audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,16 @@
* Audio output device enumeration and selection via Tauri commands.
*/

import { ApiError, invoke } from './shared.js';
import { tauriInvoke } from './shared.js';

export const audio = {
/**
* List available audio output devices
* @returns {Promise<{devices: string[]}>}
*/
async listDevices() {
if (invoke) {
try {
return await invoke('audio_list_devices');
} catch (error) {
console.error('[api.audio.listDevices] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
// No HTTP fallback — audio device selection requires Tauri runtime
const result = await tauriInvoke('audio_list_devices');
if (result !== null) return result;
return { devices: [] };
},

Expand All @@ -29,14 +22,7 @@ export const audio = {
* @param {string|null} deviceName - Device name, or null for system default
* @returns {Promise<void>}
*/
async setDevice(deviceName) {
if (invoke) {
try {
return await invoke('audio_set_device', { deviceName });
} catch (error) {
console.error('[api.audio.setDevice] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
setDevice(deviceName) {
return tauriInvoke('audio_set_device', { deviceName });
},
};
109 changes: 37 additions & 72 deletions app/frontend/js/api/favorites.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Liked songs, top played, recently played/added operations.
*/

import { ApiError, invoke, request } from './shared.js';
import { ApiError, request, tauriInvoke } from './shared.js';

export const favorites = {
/**
Expand All @@ -15,17 +15,11 @@ export const favorites = {
* @returns {Promise<{tracks: Array, total: number, limit: number, offset: number}>}
*/
async get(params = {}) {
if (invoke) {
try {
return await invoke('favorites_get', {
limit: params.limit ?? null,
offset: params.offset ?? null,
});
} catch (error) {
console.error('[api.favorites.get] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
const result = await tauriInvoke('favorites_get', {
limit: params.limit ?? null,
offset: params.offset ?? null,
});
if (result !== null) return result;
// Fallback to HTTP
const query = new URLSearchParams();
if (params.limit) query.set('limit', params.limit.toString());
Expand All @@ -40,14 +34,8 @@ export const favorites = {
* @returns {Promise<{is_favorite: boolean, favorited_date: string|null}>}
*/
async check(trackId) {
if (invoke) {
try {
return await invoke('favorites_check', { trackId });
} catch (error) {
console.error('[api.favorites.check] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
const result = await tauriInvoke('favorites_check', { trackId });
if (result !== null) return result;
return request(`/favorites/${encodeURIComponent(trackId)}`);
},

Expand All @@ -57,20 +45,17 @@ export const favorites = {
* @returns {Promise<{success: boolean, favorited_date: string}>}
*/
async add(trackId) {
if (invoke) {
try {
return await invoke('favorites_add', { trackId });
} catch (error) {
console.error('[api.favorites.add] Tauri error:', error);
// Check for specific error messages
if (error.toString().includes('already favorited')) {
throw new ApiError(409, 'Track is already favorited');
}
if (error.toString().includes('not found')) {
throw new ApiError(404, error.toString());
}
throw new ApiError(500, error.toString());
try {
const result = await tauriInvoke('favorites_add', { trackId });
if (result !== null) return result;
} catch (error) {
if (error.message.includes('already favorited')) {
throw new ApiError(409, 'Track is already favorited');
}
if (error.message.includes('not found')) {
throw new ApiError(404, error.message);
}
throw error;
}
return request(`/favorites/${encodeURIComponent(trackId)}`, {
method: 'POST',
Expand All @@ -83,16 +68,14 @@ export const favorites = {
* @returns {Promise<void>}
*/
async remove(trackId) {
if (invoke) {
try {
return await invoke('favorites_remove', { trackId });
} catch (error) {
console.error('[api.favorites.remove] Tauri error:', error);
if (error.toString().includes('not in favorites')) {
throw new ApiError(404, error.toString());
}
throw new ApiError(500, error.toString());
try {
const result = await tauriInvoke('favorites_remove', { trackId });
if (result !== null) return result;
} catch (error) {
if (error.message.includes('not in favorites')) {
throw new ApiError(404, error.message);
}
throw error;
}
return request(`/favorites/${encodeURIComponent(trackId)}`, {
method: 'DELETE',
Expand All @@ -104,14 +87,8 @@ export const favorites = {
* @returns {Promise<{tracks: Array}>}
*/
async getTop25() {
if (invoke) {
try {
return await invoke('favorites_get_top25');
} catch (error) {
console.error('[api.favorites.getTop25] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
const result = await tauriInvoke('favorites_get_top25');
if (result !== null) return result;
return request('/favorites/top25');
},

Expand All @@ -123,17 +100,11 @@ export const favorites = {
* @returns {Promise<{tracks: Array, days: number}>}
*/
async getRecentlyPlayed(params = {}) {
if (invoke) {
try {
return await invoke('favorites_get_recently_played', {
days: params.days ?? null,
limit: params.limit ?? null,
});
} catch (error) {
console.error('[api.favorites.getRecentlyPlayed] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
const result = await tauriInvoke('favorites_get_recently_played', {
days: params.days ?? null,
limit: params.limit ?? null,
});
if (result !== null) return result;
// Fallback to HTTP
const query = new URLSearchParams();
if (params.days) query.set('days', params.days.toString());
Expand All @@ -150,17 +121,11 @@ export const favorites = {
* @returns {Promise<{tracks: Array, days: number}>}
*/
async getRecentlyAdded(params = {}) {
if (invoke) {
try {
return await invoke('favorites_get_recently_added', {
days: params.days ?? null,
limit: params.limit ?? null,
});
} catch (error) {
console.error('[api.favorites.getRecentlyAdded] Tauri error:', error);
throw new ApiError(500, error.toString());
}
}
const result = await tauriInvoke('favorites_get_recently_added', {
days: params.days ?? null,
limit: params.limit ?? null,
});
if (result !== null) return result;
// Fallback to HTTP
const query = new URLSearchParams();
if (params.days) query.set('days', params.days.toString());
Expand Down
Loading
Loading