Initial release: DictIA v0.8.14-alpha (fork de Speakr, AGPL-3.0)

This commit is contained in:
InnovA AI
2026-03-16 21:47:37 +00:00
commit 42772a31ed
365 changed files with 103572 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
/**
* Audio recording state management
*/
export function createAudioState(ref, computed) {
// --- Audio Recording State ---
const isRecording = ref(false);
const mediaRecorder = ref(null);
const audioChunks = ref([]);
const audioBlobURL = ref(null);
const recordingTime = ref(0);
const recordingInterval = ref(null);
const canRecordAudio = ref(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
const canRecordSystemAudio = computed(() => navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia);
const systemAudioSupported = ref(false);
const systemAudioError = ref('');
const recordingNotes = ref('');
const showSystemAudioHelp = ref(false);
// ASR options for recording view
const asrLanguage = ref('');
const asrMinSpeakers = ref('');
const asrMaxSpeakers = ref('');
// Audio context and analyzers
const audioContext = ref(null);
const analyser = ref(null);
const micAnalyser = ref(null);
const systemAnalyser = ref(null);
const visualizer = ref(null);
const micVisualizer = ref(null);
const systemVisualizer = ref(null);
const animationFrameId = ref(null);
const recordingMode = ref('microphone');
const activeStreams = ref([]);
// --- Wake Lock and Background Recording ---
const wakeLock = ref(null);
const recordingNotification = ref(null);
const isPageVisible = ref(true);
// --- Recording Size Monitoring ---
const estimatedFileSize = ref(0);
const fileSizeWarningShown = ref(false);
const recordingQuality = ref('optimized');
const actualBitrate = ref(0);
const maxRecordingMB = ref(200);
const sizeCheckInterval = ref(null);
return {
// Recording state
isRecording,
mediaRecorder,
audioChunks,
audioBlobURL,
recordingTime,
recordingInterval,
canRecordAudio,
canRecordSystemAudio,
systemAudioSupported,
systemAudioError,
recordingNotes,
showSystemAudioHelp,
// ASR options
asrLanguage,
asrMinSpeakers,
asrMaxSpeakers,
// Audio context
audioContext,
analyser,
micAnalyser,
systemAnalyser,
visualizer,
micVisualizer,
systemVisualizer,
animationFrameId,
recordingMode,
activeStreams,
// Wake lock
wakeLock,
recordingNotification,
isPageVisible,
// Size monitoring
estimatedFileSize,
fileSizeWarningShown,
recordingQuality,
actualBitrate,
maxRecordingMB,
sizeCheckInterval
};
}

View File

@@ -0,0 +1,23 @@
/**
* Chat state management
*/
export function createChatState(ref) {
const showChat = ref(false);
const isChatMaximized = ref(false);
const chatMessages = ref([]);
const chatInput = ref('');
const isChatLoading = ref(false);
const chatMessagesRef = ref(null);
const chatInputRef = ref(null);
return {
showChat,
isChatMaximized,
chatMessages,
chatInput,
isChatLoading,
chatMessagesRef,
chatInputRef
};
}

View File

@@ -0,0 +1,164 @@
/**
* Core application state
*/
export function createCoreState(ref, computed) {
// --- Core State ---
const currentView = ref('upload');
const dragover = ref(false);
const recordings = ref([]);
const selectedRecording = ref(null);
const selectedTab = ref('summary');
const searchQuery = ref('');
const isLoadingRecordings = ref(true);
const globalError = ref(null);
// --- Pagination State ---
const currentPage = ref(1);
const perPage = ref(25);
const totalRecordings = ref(0);
const totalPages = ref(0);
const hasNextPage = ref(false);
const hasPrevPage = ref(false);
const isLoadingMore = ref(false);
const searchDebounceTimer = ref(null);
// --- Enhanced Search & Organization State ---
const sortBy = ref('created_at');
const selectedTagFilter = ref(null);
// Advanced filter state
const showAdvancedFilters = ref(false);
const filterTags = ref([]);
const filterSpeakers = ref([]); // Array of speaker names for filtering
const filterDateRange = ref({ start: '', end: '' });
const filterDatePreset = ref('');
const filterTextQuery = ref('');
const showArchivedRecordings = ref(false);
const showSharedWithMe = ref(false);
// --- App Configuration ---
const useAsrEndpoint = ref(false);
const connectorSupportsDiarization = ref(false); // Connector capability for diarization UI
const connectorSupportsSpeakerCount = ref(false); // Connector capability for min/max speakers
const currentUserName = ref('');
const canDeleteRecordings = ref(true);
const enableInternalSharing = ref(false);
const enableArchiveToggle = ref(false);
const showUsernamesInUI = ref(false);
// --- Incognito Mode State ---
const enableIncognitoMode = ref(false); // Server config - whether feature is available
const incognitoMode = ref(false); // User toggle - whether to use incognito for current upload
const incognitoRecording = ref(null);
const incognitoProcessing = ref(false);
// Tag Selection
const availableTags = ref([]);
const selectedTagIds = ref([]);
const uploadTagSearchFilter = ref('');
// Folder Selection
const availableFolders = ref([]);
const selectedFolderId = ref(null);
const foldersEnabled = ref(false);
const filterFolder = ref(''); // '' = all, 'none' = no folder, or folder id
// Speaker Selection
const availableSpeakers = ref([]);
const selectedTags = computed(() => {
return selectedTagIds.value.map(tagId =>
availableTags.value.find(tag => tag.id == tagId)
).filter(Boolean);
});
const filteredAvailableTagsForUpload = computed(() => {
const availableForSelection = availableTags.value.filter(tag => !selectedTagIds.value.includes(tag.id));
if (!uploadTagSearchFilter.value) return availableForSelection;
const filter = uploadTagSearchFilter.value.toLowerCase();
return availableForSelection.filter(tag =>
tag.name.toLowerCase().includes(filter)
);
});
const filteredRecordings = computed(() => {
return recordings.value;
});
const setGlobalError = (message, duration = 7000) => {
globalError.value = message;
if (duration > 0) {
setTimeout(() => { if (globalError.value === message) globalError.value = null; }, duration);
}
};
return {
// Core
currentView,
dragover,
recordings,
selectedRecording,
selectedTab,
searchQuery,
isLoadingRecordings,
globalError,
setGlobalError,
// Pagination
currentPage,
perPage,
totalRecordings,
totalPages,
hasNextPage,
hasPrevPage,
isLoadingMore,
searchDebounceTimer,
// Search & Organization
sortBy,
selectedTagFilter,
showAdvancedFilters,
filterTags,
filterSpeakers,
filterDateRange,
filterDatePreset,
filterTextQuery,
showArchivedRecordings,
showSharedWithMe,
// App Configuration
useAsrEndpoint,
connectorSupportsDiarization,
connectorSupportsSpeakerCount,
currentUserName,
canDeleteRecordings,
enableInternalSharing,
enableArchiveToggle,
showUsernamesInUI,
// Tags
availableTags,
selectedTagIds,
uploadTagSearchFilter,
selectedTags,
filteredAvailableTagsForUpload,
filteredRecordings,
// Folders
availableFolders,
selectedFolderId,
foldersEnabled,
filterFolder,
// Speakers
availableSpeakers,
// Incognito Mode
enableIncognitoMode,
incognitoMode,
incognitoRecording,
incognitoProcessing
};
}

View File

@@ -0,0 +1,11 @@
/**
* State module exports
*/
export { createCoreState } from './core.js';
export { createUIState } from './ui.js';
export { createUploadState } from './upload.js';
export { createAudioState } from './audio.js';
export { createModalState } from './modals.js';
export { createChatState } from './chat.js';
export { createSharingState } from './sharing.js';

View File

@@ -0,0 +1,193 @@
/**
* Modal state management
*/
export function createModalState(ref, reactive) {
// --- Modal Visibility State ---
const showEditModal = ref(false);
const showDeleteModal = ref(false);
const showEditTagsModal = ref(false);
const showReprocessModal = ref(false);
const showResetModal = ref(false);
const showSpeakerModal = ref(false);
const speakerModalTab = ref('speakers'); // 'speakers' or 'transcript' for mobile view
const showShareModal = ref(false);
const showSharesListModal = ref(false);
const showTextEditorModal = ref(false);
const showAsrEditorModal = ref(false);
const showEditSpeakersModal = ref(false);
const showEditTextModal = ref(false);
const showAddSpeakerModal = ref(false);
const showShareDeleteModal = ref(false);
const showUnifiedShareModal = ref(false);
const showDateTimePicker = ref(false);
// --- DateTime Picker State ---
const pickerMonth = ref(new Date().getMonth());
const pickerYear = ref(new Date().getFullYear());
const pickerHour = ref(12);
const pickerMinute = ref(0);
const pickerAmPm = ref('PM');
const pickerSelectedDate = ref(null);
const dateTimePickerTarget = ref(null); // 'meeting_date' or other field name
const dateTimePickerCallback = ref(null); // callback function after applying
// --- Modal Data State ---
const selectedNewTagId = ref('');
const tagSearchFilter = ref('');
const editingRecording = ref(null);
const editingTranscriptionContent = ref('');
const editingSegments = ref([]);
const availableSpeakers = ref([]);
const editingSpeakersList = ref([]);
const databaseSpeakers = ref([]);
const editingSpeakerSuggestions = ref({});
const recordingToDelete = ref(null);
const recordingToReset = ref(null);
const reprocessType = ref(null);
const reprocessRecording = ref(null);
const isAutoIdentifying = ref(false);
const asrReprocessOptions = reactive({
language: '',
min_speakers: null,
max_speakers: null
});
const summaryReprocessPromptSource = ref('default');
const summaryReprocessSelectedTagId = ref('');
const summaryReprocessCustomPrompt = ref('');
const speakerMap = ref({});
const regenerateSummaryAfterSpeakerUpdate = ref(true);
const speakerSuggestions = ref({});
const loadingSuggestions = ref({});
const activeSpeakerInput = ref(null);
const voiceSuggestions = ref({});
const loadingVoiceSuggestions = ref(false);
// --- Transcript Editing State ---
const editingSegmentIndex = ref(null);
const editingSpeakerIndex = ref(null);
const editedText = ref('');
const newSpeakerName = ref('');
const newSpeakerIsMe = ref(false);
const editedTranscriptData = ref(null);
// --- Inline Editing State ---
const editingParticipants = ref(false);
const editingMeetingDate = ref(false);
const editingSummary = ref(false);
const editingNotes = ref(false);
const tempNotesContent = ref('');
const tempSummaryContent = ref('');
const autoSaveTimer = ref(null);
const autoSaveDelay = 2000;
// --- Markdown Editor State ---
const notesMarkdownEditor = ref(null);
const markdownEditorInstance = ref(null);
const summaryMarkdownEditor = ref(null);
const summaryMarkdownEditorInstance = ref(null);
const recordingNotesEditor = ref(null);
const recordingMarkdownEditorInstance = ref(null);
// --- Dropdown Positions ---
const dropdownPositions = ref({});
const editSpeakerDropdownPositions = ref({});
// --- Single-ref dropdown tracking (performance optimization) ---
// Instead of each segment having showSuggestions property (O(n) to close all),
// track which dropdown is open with a single ref (O(1) operations)
const openAsrDropdownIndex = ref(null);
return {
// Modal visibility
showEditModal,
showDeleteModal,
showEditTagsModal,
showReprocessModal,
showResetModal,
showSpeakerModal,
speakerModalTab,
showShareModal,
showSharesListModal,
showTextEditorModal,
showAsrEditorModal,
showEditSpeakersModal,
showEditTextModal,
showAddSpeakerModal,
showShareDeleteModal,
showUnifiedShareModal,
showDateTimePicker,
// DateTime picker
pickerMonth,
pickerYear,
pickerHour,
pickerMinute,
pickerAmPm,
pickerSelectedDate,
dateTimePickerTarget,
dateTimePickerCallback,
// Modal data
selectedNewTagId,
tagSearchFilter,
editingRecording,
editingTranscriptionContent,
editingSegments,
availableSpeakers,
editingSpeakersList,
databaseSpeakers,
editingSpeakerSuggestions,
recordingToDelete,
recordingToReset,
reprocessType,
reprocessRecording,
isAutoIdentifying,
asrReprocessOptions,
summaryReprocessPromptSource,
summaryReprocessSelectedTagId,
summaryReprocessCustomPrompt,
speakerMap,
regenerateSummaryAfterSpeakerUpdate,
speakerSuggestions,
loadingSuggestions,
activeSpeakerInput,
voiceSuggestions,
loadingVoiceSuggestions,
// Transcript editing
editingSegmentIndex,
editingSpeakerIndex,
editedText,
newSpeakerName,
newSpeakerIsMe,
editedTranscriptData,
// Inline editing
editingParticipants,
editingMeetingDate,
editingSummary,
editingNotes,
tempNotesContent,
tempSummaryContent,
autoSaveTimer,
autoSaveDelay,
// Markdown editors
notesMarkdownEditor,
markdownEditorInstance,
summaryMarkdownEditor,
summaryMarkdownEditorInstance,
recordingNotesEditor,
recordingMarkdownEditorInstance,
// Dropdown positions
dropdownPositions,
editSpeakerDropdownPositions,
// Single-ref dropdown tracking
openAsrDropdownIndex
};
}

View File

@@ -0,0 +1,39 @@
/**
* PWA state management
*/
export function createPWAState(ref) {
// --- Install Prompt ---
const deferredInstallPrompt = ref(null);
const showInstallButton = ref(false);
const isPWAInstalled = ref(false);
// --- Notifications ---
const notificationPermission = ref('default');
const pushSubscription = ref(null);
// --- Badging ---
const appBadgeCount = ref(0);
// --- Media Session ---
const currentMediaMetadata = ref(null);
const isMediaSessionActive = ref(false);
return {
// Install prompt
deferredInstallPrompt,
showInstallButton,
isPWAInstalled,
// Notifications
notificationPermission,
pushSubscription,
// Badging
appBadgeCount,
// Media session
currentMediaMetadata,
isMediaSessionActive
};
}

View File

@@ -0,0 +1,66 @@
/**
* Sharing state management
*/
export function createSharingState(ref, reactive) {
// --- Public Sharing State ---
const recordingToShare = ref(null);
const shareOptions = reactive({
share_summary: true,
share_notes: true,
});
const generatedShareLink = ref('');
const existingShareDetected = ref(false);
const userShares = ref([]);
const isLoadingShares = ref(false);
const shareToDelete = ref(null);
// --- Internal Sharing State ---
const internalShareUserSearch = ref('');
const internalShareSearchResults = ref([]);
const internalShareRecording = ref(null);
const internalSharePermissions = ref({ can_edit: false, can_reshare: false });
const recordingInternalShares = ref([]);
const isLoadingInternalShares = ref(false);
const isSearchingUsers = ref(false);
const allUsers = ref([]);
const isLoadingAllUsers = ref(false);
// --- Audio Player State ---
const playerVolume = ref(1.0);
const audioIsPlaying = ref(false);
const audioCurrentTime = ref(0);
const audioDuration = ref(0);
const audioIsMuted = ref(false);
const audioIsLoading = ref(false);
return {
// Public sharing
recordingToShare,
shareOptions,
generatedShareLink,
existingShareDetected,
userShares,
isLoadingShares,
shareToDelete,
// Internal sharing
internalShareUserSearch,
internalShareSearchResults,
internalShareRecording,
internalSharePermissions,
recordingInternalShares,
isLoadingInternalShares,
isSearchingUsers,
allUsers,
isLoadingAllUsers,
// Audio player
playerVolume,
audioIsPlaying,
audioCurrentTime,
audioDuration,
audioIsMuted,
audioIsLoading
};
}

View File

@@ -0,0 +1,109 @@
/**
* UI state management
*/
export function createUIState(ref, computed) {
// --- UI State ---
const browser = ref('unknown');
const isSidebarCollapsed = ref(false);
const searchTipsExpanded = ref(false);
const isUserMenuOpen = ref(false);
const isDarkMode = ref(false);
const currentColorScheme = ref('blue');
const showColorSchemeModal = ref(false);
const windowWidth = ref(window.innerWidth);
const mobileTab = ref('transcript');
const isMetadataExpanded = ref(false);
const expandedSection = ref('settings'); // 'notes' or 'settings'
// --- i18n State ---
const currentLanguage = ref('en');
const currentLanguageName = ref('English');
const availableLanguages = ref([]);
const showLanguageMenu = ref(false);
// --- Column Resizing State ---
const leftColumnWidth = ref(60);
const rightColumnWidth = ref(40);
const isResizing = ref(false);
// --- Transcription State ---
const transcriptionViewMode = ref('simple');
const legendExpanded = ref(false);
const highlightedSpeaker = ref(null);
const processingIndicatorMinimized = ref(false);
// --- Virtual Scroll State ---
// For transcript panel virtual scrolling (performance optimization for long transcriptions)
const transcriptScrollTop = ref(0);
const transcriptContainerHeight = ref(0);
const transcriptItemHeight = 48; // Estimated height per segment in pixels
// --- Computed Properties ---
const isMobileScreen = computed(() => {
return windowWidth.value < 1024;
});
// --- Color Scheme Definitions ---
const colorSchemes = {
light: [
{ id: 'blue', name: 'Ocean Blue', description: 'Classic blue theme with professional appeal', class: '' },
{ id: 'emerald', name: 'Forest Emerald', description: 'Fresh green theme for a natural feel', class: 'theme-light-emerald' },
{ id: 'purple', name: 'Royal Purple', description: 'Elegant purple theme with sophistication', class: 'theme-light-purple' },
{ id: 'rose', name: 'Sunset Rose', description: 'Warm pink theme with gentle energy', class: 'theme-light-rose' },
{ id: 'amber', name: 'Golden Amber', description: 'Warm yellow theme for brightness', class: 'theme-light-amber' },
{ id: 'teal', name: 'Ocean Teal', description: 'Cool teal theme for tranquility', class: 'theme-light-teal' }
],
dark: [
{ id: 'blue', name: 'Midnight Blue', description: 'Deep blue theme for focused work', class: '' },
{ id: 'emerald', name: 'Dark Forest', description: 'Rich green theme for comfortable viewing', class: 'theme-dark-emerald' },
{ id: 'purple', name: 'Deep Purple', description: 'Mysterious purple theme for creativity', class: 'theme-dark-purple' },
{ id: 'rose', name: 'Dark Rose', description: 'Muted pink theme with subtle warmth', class: 'theme-dark-rose' },
{ id: 'amber', name: 'Dark Amber', description: 'Warm brown theme for cozy sessions', class: 'theme-dark-amber' },
{ id: 'teal', name: 'Deep Teal', description: 'Dark teal theme for calm focus', class: 'theme-dark-teal' }
]
};
return {
// UI
browser,
isSidebarCollapsed,
searchTipsExpanded,
isUserMenuOpen,
isDarkMode,
currentColorScheme,
showColorSchemeModal,
windowWidth,
mobileTab,
isMetadataExpanded,
expandedSection,
// i18n
currentLanguage,
currentLanguageName,
availableLanguages,
showLanguageMenu,
// Column Resizing
leftColumnWidth,
rightColumnWidth,
isResizing,
// Transcription
transcriptionViewMode,
legendExpanded,
highlightedSpeaker,
processingIndicatorMinimized,
// Virtual Scroll
transcriptScrollTop,
transcriptContainerHeight,
transcriptItemHeight,
// Computed
isMobileScreen,
// Constants
colorSchemes
};
}

View File

@@ -0,0 +1,77 @@
/**
* Upload state management
*/
export function createUploadState(ref, computed) {
// --- Upload State ---
const uploadQueue = ref([]);
const currentlyProcessingFile = ref(null);
const processingProgress = ref(0);
const processingMessage = ref('');
const isProcessingActive = ref(false);
const pollInterval = ref(null);
const progressPopupMinimized = ref(false);
const progressPopupClosed = ref(false);
const maxFileSizeMB = ref(250);
const chunkingEnabled = ref(true);
const chunkingMode = ref('size');
const chunkingLimit = ref(20);
const chunkingLimitDisplay = ref('20MB');
const maxConcurrentUploads = ref(3);
const recordingDisclaimer = ref('');
const showRecordingDisclaimerModal = ref(false);
const pendingRecordingMode = ref(null);
// Advanced Options for ASR
const showAdvancedOptions = ref(false);
const uploadLanguage = ref('');
const uploadMinSpeakers = ref('');
const uploadMaxSpeakers = ref('');
const uploadHotwords = ref('');
const uploadInitialPrompt = ref('');
// --- Computed Properties ---
const totalInQueue = computed(() => uploadQueue.value.length);
const completedInQueue = computed(() => uploadQueue.value.filter(item => item.status === 'completed' || item.status === 'failed').length);
const finishedFilesInQueue = computed(() => uploadQueue.value.filter(item => ['completed', 'failed'].includes(item.status)));
const clearCompletedUploads = () => {
uploadQueue.value = uploadQueue.value.filter(item => !['completed', 'failed'].includes(item.status));
};
return {
uploadQueue,
currentlyProcessingFile,
processingProgress,
processingMessage,
isProcessingActive,
pollInterval,
progressPopupMinimized,
progressPopupClosed,
maxFileSizeMB,
chunkingEnabled,
chunkingMode,
chunkingLimit,
chunkingLimitDisplay,
maxConcurrentUploads,
recordingDisclaimer,
showRecordingDisclaimerModal,
pendingRecordingMode,
// Advanced Options
showAdvancedOptions,
uploadLanguage,
uploadMinSpeakers,
uploadMaxSpeakers,
uploadHotwords,
uploadInitialPrompt,
// Computed
totalInQueue,
completedInQueue,
finishedFilesInQueue,
// Methods
clearCompletedUploads
};
}