@@ -32,7 +32,7 @@ const {
3232const APP_DISPLAY_NAME = "MirrorNote" ;
3333const ELECTRON_SETTINGS_FILE_NAME = "electron-settings.json" ;
3434const NOTES_DATABASE_FILE_NAME = "notes.sqlite" ;
35- const NOTES_DATABASE_SCHEMA_VERSION = 3 ;
35+ const NOTES_DATABASE_SCHEMA_VERSION = 4 ;
3636const MENU_BAR_ICON_SIZE = 18 ;
3737const MENU_BAR_ICON_IDLE_FILE_NAME = "menu-bar-icon-source.png" ;
3838const MENU_BAR_ICON_RECORDING_BRIGHT_FILE_NAME = "menu-bar-icon-recording-bright.png" ;
@@ -1358,6 +1358,77 @@ function listenerSessionFromMetadata(metadata, noteId) {
13581358 } ;
13591359}
13601360
1361+ function normalizeAppSessionStatus ( input = { } ) {
1362+ const listenerSession = input . listenerSession && typeof input . listenerSession === "object"
1363+ ? input . listenerSession
1364+ : null ;
1365+ const sttChunks = Array . isArray ( input . sttChunks ) ? input . sttChunks : [ ] ;
1366+ const latestChunks = sttChunks . some ( ( chunk ) => optionalNonEmptyString ( chunk ?. chunkID ) )
1367+ ? latestSTTChunks ( sttChunks )
1368+ : sttChunks ;
1369+ const runningChunkCount = latestChunks . filter ( ( chunk ) => chunk . status === "queued" || chunk . status === "running" ) . length ;
1370+ const failedChunkCount = latestChunks . filter ( ( chunk ) => chunk . status === "failed" ) . length ;
1371+ const retryableChunkCount = latestChunks . filter ( ( chunk ) => isRetryableSTTChunkStatus ( chunk . status ) ) . length ;
1372+ const base = {
1373+ detail : null ,
1374+ retryableChunkCount,
1375+ failedChunkCount,
1376+ runningChunkCount,
1377+ } ;
1378+ const listenerState = normalizeListenerSessionState ( listenerSession ?. state ) ;
1379+ const listenerError = optionalNonEmptyString ( listenerSession ?. error ) ;
1380+ const listenerDetail = listenerError || optionalNonEmptyString ( listenerSession ?. detail ) ;
1381+
1382+ if ( listenerState === "failed" ) {
1383+ return { ...base , state : "failed" , detail : listenerDetail } ;
1384+ }
1385+ if ( listenerState === "recovering" ) {
1386+ return { ...base , state : "recoverable" , detail : listenerDetail || "Session needs recovery." } ;
1387+ }
1388+ if ( listenerState === "starting" || listenerState === "recording" ) {
1389+ return {
1390+ ...base ,
1391+ state : listenerError ? "degraded" : "recording" ,
1392+ detail : listenerDetail ,
1393+ } ;
1394+ }
1395+ if ( listenerState === "stopping" ) {
1396+ return { ...base , state : "finalizing" , detail : listenerDetail } ;
1397+ }
1398+ if ( runningChunkCount > 0 ) {
1399+ return { ...base , state : "transcribing" , detail : "Transcription is running." } ;
1400+ }
1401+ if ( failedChunkCount > 0 ) {
1402+ return {
1403+ ...base ,
1404+ state : "recoverable" ,
1405+ detail : `${ failedChunkCount } STT ${ failedChunkCount === 1 ? "chunk needs" : "chunks need" } retry.` ,
1406+ } ;
1407+ }
1408+ if ( listenerState === "completed" || latestChunks . some ( ( chunk ) => chunk . status === "completed" ) ) {
1409+ return { ...base , state : "completed" , detail : listenerDetail } ;
1410+ }
1411+ return null ;
1412+ }
1413+
1414+ function normalizeStoredAppSessionStatus ( value ) {
1415+ const raw = typeof value === "string" ? parseMetadataJSON ( value ) : value ;
1416+ if ( ! raw || typeof raw !== "object" ) {
1417+ return null ;
1418+ }
1419+ const state = optionalNonEmptyString ( raw . state ) ;
1420+ if ( ! [ "recording" , "degraded" , "finalizing" , "transcribing" , "failed" , "completed" , "recoverable" ] . includes ( state ) ) {
1421+ return null ;
1422+ }
1423+ return {
1424+ state,
1425+ detail : optionalNonEmptyString ( raw . detail ) ,
1426+ retryableChunkCount : Number . isFinite ( Number ( raw . retryableChunkCount ) ) ? Number ( raw . retryableChunkCount ) : 0 ,
1427+ failedChunkCount : Number . isFinite ( Number ( raw . failedChunkCount ) ) ? Number ( raw . failedChunkCount ) : 0 ,
1428+ runningChunkCount : Number . isFinite ( Number ( raw . runningChunkCount ) ) ? Number ( raw . runningChunkCount ) : 0 ,
1429+ } ;
1430+ }
1431+
13611432async function writeListenerSessionState ( noteId , patch ) {
13621433 if ( ! noteId ) {
13631434 return ;
@@ -1635,6 +1706,14 @@ function applyNotesDatabaseMigrations(database) {
16351706 ` ) ;
16361707 } ,
16371708 } ,
1709+ {
1710+ version : 4 ,
1711+ up : ( db ) => {
1712+ db . exec ( `
1713+ ALTER TABLE notes ADD COLUMN session_status_json TEXT;
1714+ ` ) ;
1715+ } ,
1716+ } ,
16381717 ] ;
16391718
16401719 for ( const migration of migrations ) {
@@ -1798,6 +1877,9 @@ async function loadBundleSnapshot(directoryName) {
17981877 ? metadata . transcriptSegmentCount
17991878 : transcriptSegments . length ;
18001879
1880+ const listenerSession = listenerSessionFromMetadata ( metadata , directoryName ) ;
1881+ const sttChunks = latestSTTChunks ( parseSTTChunkLedgerJSONL ( await readTextIfExists ( path . join ( directoryPath , STT_CHUNK_LEDGER_FILE_NAME ) ) ) ) ;
1882+
18011883 const summary = {
18021884 id : directoryName ,
18031885 title : displayTitleFromBundle ( directoryName , metadata , markdown ) ,
@@ -1814,7 +1896,8 @@ async function loadBundleSnapshot(directoryName) {
18141896 transcriptPath : paths . transcriptPath ,
18151897 metadataPath : paths . metadataPath ,
18161898 recordingPath : paths . recordingPath ,
1817- listenerSession : listenerSessionFromMetadata ( metadata , directoryName ) ,
1899+ listenerSession,
1900+ sessionStatus : normalizeAppSessionStatus ( { listenerSession, sttChunks } ) ,
18181901 } ;
18191902
18201903 return {
@@ -1828,6 +1911,7 @@ async function loadBundleSnapshot(directoryName) {
18281911
18291912function noteSummaryFromRow ( row ) {
18301913 const metadata = parseMetadataJSON ( row . metadata_json ) ;
1914+ const listenerSession = listenerSessionFromMetadata ( metadata , row . id ) ;
18311915 return {
18321916 id : row . id ,
18331917 title : row . title ,
@@ -1844,7 +1928,8 @@ function noteSummaryFromRow(row) {
18441928 transcriptPath : row . transcript_path ,
18451929 metadataPath : row . metadata_path ,
18461930 recordingPath : row . recording_path ,
1847- listenerSession : listenerSessionFromMetadata ( metadata , row . id ) ,
1931+ listenerSession,
1932+ sessionStatus : normalizeStoredAppSessionStatus ( row . session_status_json ) ,
18481933 } ;
18491934}
18501935
@@ -1884,9 +1969,10 @@ function upsertNoteSnapshot(snapshot) {
18841969 recording_path,
18851970 markdown,
18861971 metadata_json,
1972+ session_status_json,
18871973 created_at,
18881974 updated_at
1889- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1975+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
18901976 ON CONFLICT(id) DO UPDATE SET
18911977 title = excluded.title,
18921978 started_at = excluded.started_at,
@@ -1902,6 +1988,7 @@ function upsertNoteSnapshot(snapshot) {
19021988 recording_path = excluded.recording_path,
19031989 markdown = excluded.markdown,
19041990 metadata_json = excluded.metadata_json,
1991+ session_status_json = excluded.session_status_json,
19051992 updated_at = excluded.updated_at
19061993 ` ) . run (
19071994 summary . id ,
@@ -1919,6 +2006,7 @@ function upsertNoteSnapshot(snapshot) {
19192006 summary . recordingPath ,
19202007 markdown ,
19212008 serializeMetadata ( metadata ) ,
2009+ serializeMetadata ( summary . sessionStatus ) ,
19222010 createdAt ,
19232011 now ,
19242012 ) ;
@@ -2431,6 +2519,7 @@ async function readNote(id) {
24312519 } ) ;
24322520 const directoryPath = resolveBundleDirectory ( row . id ) ;
24332521 const sttChunks = latestSTTChunks ( parseSTTChunkLedgerJSONL ( await readTextIfExists ( path . join ( directoryPath , STT_CHUNK_LEDGER_FILE_NAME ) ) ) ) ;
2522+ const summary = noteSummaryFromRow ( row ) ;
24342523 const speakerLabels = database . prepare ( `
24352524 SELECT
24362525 speaker_id,
@@ -2449,7 +2538,8 @@ async function readNote(id) {
24492538 markNoteOpened ( id ) ;
24502539
24512540 return {
2452- ...noteSummaryFromRow ( row ) ,
2541+ ...summary ,
2542+ sessionStatus : normalizeAppSessionStatus ( { listenerSession : summary . listenerSession , sttChunks } ) ,
24532543 directoryPath,
24542544 markdown : row . markdown ,
24552545 metadata : parseMetadataJSON ( row . metadata_json ) ,
@@ -4538,6 +4628,7 @@ if (process.env.MIRROR_NOTE_TEST_EXPORTS === "1") {
45384628 mergeSummarySettingsPatch,
45394629 normalizeCustomSummaryTemplate,
45404630 normalizeCustomSummaryTemplates,
4631+ normalizeAppSessionStatus,
45414632 normalizeListenerSessionState,
45424633 normalizeSummarySettings,
45434634 nativeHelperWorkingDirectory,
0 commit comments