Files
dictia-public/static/js/composables/useRecordings.js

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
};
}