328 lines
9.7 KiB
JavaScript
328 lines
9.7 KiB
JavaScript
/**
|
|
* Recordings composable
|
|
* Handles recordings list, selection, and CRUD operations
|
|
*/
|
|
|
|
import { ref, computed } from 'vue';
|
|
import { apiRequest } from '../utils/apiClient.js';
|
|
|
|
export function useRecordings() {
|
|
// State
|
|
const recordings = ref([]);
|
|
const selectedRecording = ref(null);
|
|
const isLoadingRecordings = ref(true);
|
|
const globalError = ref(null);
|
|
const currentView = ref('upload');
|
|
const availableTags = ref([]);
|
|
const selectedTagIds = ref([]);
|
|
const showTagModal = ref(false);
|
|
const showDeleteModal = ref(false);
|
|
const recordingToDelete = ref(null);
|
|
|
|
// Computed
|
|
const completedRecordings = computed(() => {
|
|
return recordings.value.filter(r => r.status === 'COMPLETED');
|
|
});
|
|
|
|
const processingRecordings = computed(() => {
|
|
return recordings.value.filter(r => ['PENDING', 'PROCESSING', 'SUMMARIZING'].includes(r.status));
|
|
});
|
|
|
|
const hasRecordings = computed(() => recordings.value.length > 0);
|
|
|
|
// Methods
|
|
const loadRecordings = async (page = 1, filters = {}) => {
|
|
globalError.value = null;
|
|
isLoadingRecordings.value = true;
|
|
|
|
try {
|
|
let endpoint = '/api/recordings';
|
|
if (filters.archived) {
|
|
endpoint = '/api/recordings/archived';
|
|
} else if (filters.sharedWithMe) {
|
|
endpoint = '/api/recordings/shared-with-me';
|
|
}
|
|
|
|
const params = new URLSearchParams({
|
|
page: page.toString(),
|
|
per_page: '25'
|
|
});
|
|
|
|
if (filters.query) {
|
|
params.set('q', filters.query.trim());
|
|
}
|
|
|
|
const response = await fetch(`${endpoint}?${params}`);
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.error || 'Failed to load recordings');
|
|
}
|
|
|
|
const recordingsList = filters.archived || filters.sharedWithMe ? data : data.recordings;
|
|
|
|
if (!Array.isArray(recordingsList)) {
|
|
throw new Error('Invalid response format');
|
|
}
|
|
|
|
recordings.value = recordingsList;
|
|
|
|
// Restore last selected recording
|
|
const lastRecordingId = localStorage.getItem('lastSelectedRecordingId');
|
|
if (lastRecordingId && recordingsList.length > 0) {
|
|
const recordingToSelect = recordingsList.find(r => r.id == lastRecordingId);
|
|
if (recordingToSelect) {
|
|
selectRecording(recordingToSelect);
|
|
}
|
|
}
|
|
|
|
return filters.archived || filters.sharedWithMe ? null : data.pagination;
|
|
|
|
} catch (error) {
|
|
globalError.value = error.message;
|
|
throw error;
|
|
} finally {
|
|
isLoadingRecordings.value = false;
|
|
}
|
|
};
|
|
|
|
const selectRecording = async (recording) => {
|
|
if (!recording) return;
|
|
|
|
selectedRecording.value = recording;
|
|
currentView.value = 'recording';
|
|
localStorage.setItem('lastSelectedRecordingId', recording.id);
|
|
|
|
// Load full recording details if needed
|
|
if (!recording.transcription && recording.status === 'COMPLETED') {
|
|
try {
|
|
const data = await apiRequest(`/api/recordings/${recording.id}`);
|
|
Object.assign(selectedRecording.value, data);
|
|
} catch (error) {
|
|
console.error('Error loading recording details:', error);
|
|
}
|
|
}
|
|
};
|
|
|
|
const deselectRecording = () => {
|
|
selectedRecording.value = null;
|
|
currentView.value = 'upload';
|
|
localStorage.removeItem('lastSelectedRecordingId');
|
|
};
|
|
|
|
const deleteRecording = async (recordingId) => {
|
|
try {
|
|
await apiRequest(`/api/recordings/${recordingId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
recordings.value = recordings.value.filter(r => r.id !== recordingId);
|
|
|
|
if (selectedRecording.value && selectedRecording.value.id === recordingId) {
|
|
deselectRecording();
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
globalError.value = error.message;
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const archiveRecording = async (recordingId) => {
|
|
try {
|
|
await apiRequest(`/api/recordings/${recordingId}/archive`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
recordings.value = recordings.value.filter(r => r.id !== recordingId);
|
|
|
|
if (selectedRecording.value && selectedRecording.value.id === recordingId) {
|
|
deselectRecording();
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
globalError.value = error.message;
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const unarchiveRecording = async (recordingId) => {
|
|
try {
|
|
await apiRequest(`/api/recordings/${recordingId}/unarchive`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
recordings.value = recordings.value.filter(r => r.id !== recordingId);
|
|
|
|
return true;
|
|
} catch (error) {
|
|
globalError.value = error.message;
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const updateRecording = async (recordingId, updates) => {
|
|
try {
|
|
const data = await apiRequest(`/api/recordings/${recordingId}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(updates)
|
|
});
|
|
|
|
const index = recordings.value.findIndex(r => r.id === recordingId);
|
|
if (index > -1) {
|
|
Object.assign(recordings.value[index], data.recording || data);
|
|
}
|
|
|
|
if (selectedRecording.value && selectedRecording.value.id === recordingId) {
|
|
Object.assign(selectedRecording.value, data.recording || data);
|
|
}
|
|
|
|
return data.recording || data;
|
|
} catch (error) {
|
|
globalError.value = error.message;
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const regenerateSummary = async (recordingId, customPrompt = null) => {
|
|
try {
|
|
const body = customPrompt ? { custom_prompt: customPrompt } : {};
|
|
const data = await apiRequest(`/api/recordings/${recordingId}/regenerate-summary`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(body)
|
|
});
|
|
|
|
if (selectedRecording.value && selectedRecording.value.id === recordingId) {
|
|
selectedRecording.value.status = 'SUMMARIZING';
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
globalError.value = error.message;
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const loadTags = async () => {
|
|
try {
|
|
const data = await apiRequest('/api/tags');
|
|
availableTags.value = data;
|
|
} catch (error) {
|
|
console.error('Error loading tags:', error);
|
|
}
|
|
};
|
|
|
|
const addTagToRecording = async (recordingId, tagId) => {
|
|
try {
|
|
const data = await apiRequest(`/api/recordings/${recordingId}/tags`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ tag_id: tagId })
|
|
});
|
|
|
|
if (selectedRecording.value && selectedRecording.value.id === recordingId) {
|
|
selectedRecording.value.tags = data.tags || [];
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
globalError.value = error.message;
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const removeTagFromRecording = async (recordingId, tagId) => {
|
|
try {
|
|
await apiRequest(`/api/recordings/${recordingId}/tags/${tagId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (selectedRecording.value && selectedRecording.value.id === recordingId) {
|
|
selectedRecording.value.tags = selectedRecording.value.tags.filter(t => t.id !== tagId);
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
globalError.value = error.message;
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const toggleHighlight = async (recordingId) => {
|
|
const recording = recordings.value.find(r => r.id === recordingId);
|
|
if (!recording) return;
|
|
|
|
const newValue = !recording.is_highlighted;
|
|
|
|
try {
|
|
await updateRecording(recordingId, { is_highlighted: newValue });
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const setGlobalError = (message) => {
|
|
globalError.value = message;
|
|
};
|
|
|
|
const clearGlobalError = () => {
|
|
globalError.value = null;
|
|
};
|
|
|
|
const confirmDelete = (recording) => {
|
|
recordingToDelete.value = recording;
|
|
showDeleteModal.value = true;
|
|
};
|
|
|
|
const cancelDelete = () => {
|
|
recordingToDelete.value = null;
|
|
showDeleteModal.value = false;
|
|
};
|
|
|
|
const executeDelete = async () => {
|
|
if (recordingToDelete.value) {
|
|
await deleteRecording(recordingToDelete.value.id);
|
|
cancelDelete();
|
|
}
|
|
};
|
|
|
|
return {
|
|
// State
|
|
recordings,
|
|
selectedRecording,
|
|
isLoadingRecordings,
|
|
globalError,
|
|
currentView,
|
|
availableTags,
|
|
selectedTagIds,
|
|
showTagModal,
|
|
showDeleteModal,
|
|
recordingToDelete,
|
|
|
|
// Computed
|
|
completedRecordings,
|
|
processingRecordings,
|
|
hasRecordings,
|
|
|
|
// Methods
|
|
loadRecordings,
|
|
selectRecording,
|
|
deselectRecording,
|
|
deleteRecording,
|
|
archiveRecording,
|
|
unarchiveRecording,
|
|
updateRecording,
|
|
regenerateSummary,
|
|
loadTags,
|
|
addTagToRecording,
|
|
removeTagFromRecording,
|
|
toggleHighlight,
|
|
setGlobalError,
|
|
clearGlobalError,
|
|
confirmDelete,
|
|
cancelDelete,
|
|
executeDelete
|
|
};
|
|
}
|