Skip to content
Open
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
18 changes: 13 additions & 5 deletions apps/desktop/src/services/enhancer/eligibility.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import type * as main from "~/store/tinybase/store/main";
import { MIN_WORDS_FOR_MEANINGFUL_TRANSCRIPT } from "~/stt/thresholds";

export const MIN_WORDS_FOR_ENHANCEMENT = 5;
export const MIN_WORDS_FOR_ENHANCEMENT = MIN_WORDS_FOR_MEANINGFUL_TRANSCRIPT;

type TranscriptWordStore = {
getCell: (tableId: "transcripts", rowId: string, cellId: "words") => unknown;
};

export function countTranscriptWords(
transcriptIds: string[],
store: main.Store | undefined,
store: TranscriptWordStore | undefined,
): number {
if (!store) return 0;

Expand All @@ -14,7 +18,11 @@ export function countTranscriptWords(
| string
| undefined;
if (wordsJson) {
totalWordCount += (JSON.parse(wordsJson) as unknown[]).length;
try {
totalWordCount += (JSON.parse(wordsJson) as unknown[]).length;
} catch {
continue;
}
}
}
return totalWordCount;
Expand All @@ -27,7 +35,7 @@ type EligibilityResult =
export function getEligibility(
hasTranscript: boolean,
transcriptIds: string[],
store: main.Store | undefined,
store: TranscriptWordStore | undefined,
): EligibilityResult {
if (!hasTranscript) {
return { eligible: false, reason: "No transcript recorded", wordCount: 0 };
Expand Down
37 changes: 36 additions & 1 deletion apps/desktop/src/services/enhancer/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ type Tables = Record<string, Record<string, Record<string, any>>>;

function createTables(data?: {
transcripts?: Record<string, { session_id: string; words: string }>;
enhanced_notes?: Record<string, { session_id: string; template_id?: string }>;
enhanced_notes?: Record<
string,
{ session_id: string; template_id?: string; content?: string }
>;
sessions?: Record<string, { title: string }>;
}): Tables {
return {
Expand All @@ -49,6 +52,10 @@ function createMockStore(tables: Tables) {
if (!tables[table]) tables[table] = {};
tables[table][rowId] = row;
}),
delRow: vi.fn((table: string, rowId: string) => {
delete tables[table]?.[rowId];
}),
transaction: vi.fn((callback: () => void) => callback()),
setPartialRow: vi.fn(),
} as any;
}
Expand Down Expand Up @@ -355,6 +362,34 @@ describe("EnhancerService", () => {
});
});

it("flushes ineligible transcript rows and empty default summaries", () => {
const tables = createTables({
transcripts: {
"t-1": {
session_id: "session-1",
words: JSON.stringify([{ text: "hi" }, { text: "there" }]),
},
},
enhanced_notes: {
"note-1": {
session_id: "session-1",
content: "",
},
},
});
const store = createMockStore(tables);
const deps = createDeps({
mainStore: store,
indexes: createMockIndexes(tables),
});
const service = new EnhancerService(deps);

(service as any).tryAutoEnhance("session-1", 20);

expect(store.delRow).toHaveBeenCalledWith("transcripts", "t-1");
expect(store.delRow).toHaveBeenCalledWith("enhanced_notes", "note-1");
});

it("clears activeAutoEnhance on skipped after max retries", () => {
const tables = createTables();
const deps = createDeps({
Expand Down
36 changes: 36 additions & 0 deletions apps/desktop/src/services/enhancer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export class EnhancerService {
}

this.activeAutoEnhance.delete(sessionId);
this.flushIneligibleSession(sessionId);
this.emit({
type: "auto-enhance-skipped",
sessionId,
Expand Down Expand Up @@ -231,6 +232,41 @@ export class EnhancerService {
);
}

flushIneligibleSession(sessionId: string) {
const eligibility = this.checkEligibility(sessionId);
if (eligibility.eligible) {
return;
}

const store = this.deps.mainStore;
const transcriptIds = this.getTranscriptIds(sessionId);
const emptyDefaultSummaryIds = this.getEnhancedNoteIds(sessionId).filter(
(id) => {
const templateId = store.getCell("enhanced_notes", id, "template_id");
const content = store.getCell("enhanced_notes", id, "content");

return (
(typeof templateId !== "string" || !templateId) &&
(typeof content !== "string" || !content.trim())
);
},
);

if (!transcriptIds.length && !emptyDefaultSummaryIds.length) {
return;
}

store.transaction(() => {
transcriptIds.forEach((id) => {
store.delRow("transcripts", id);
});

emptyDefaultSummaryIds.forEach((id) => {
store.delRow("enhanced_notes", id);
});
});
}

ensureNote(sessionId: string, templateId?: string): string {
const store = this.deps.mainStore;
const normalizedTemplateId = templateId || undefined;
Expand Down
28 changes: 28 additions & 0 deletions apps/desktop/src/session/components/compute-note-tab.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { describe, expect, test } from "vitest";

import { computeCurrentNoteTab } from "./compute-note-tab";

describe("computeCurrentNoteTab", () => {
test("falls back to raw when transcript view is no longer available", () => {
expect(
computeCurrentNoteTab({ type: "transcript" }, false, false, []),
).toEqual({ type: "raw" });
});

test("falls back to the first enhanced note when the selected one is gone", () => {
expect(
computeCurrentNoteTab(
{ type: "enhanced", id: "missing-note" },
false,
true,
["note-1"],
),
).toEqual({ type: "enhanced", id: "note-1" });
});

test("keeps transcript view while the listener is active", () => {
expect(
computeCurrentNoteTab({ type: "transcript" }, true, false, []),
).toEqual({ type: "transcript" });
});
});
15 changes: 13 additions & 2 deletions apps/desktop/src/session/components/compute-note-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import type { EditorView } from "~/store/zustand/tabs/schema";
export function computeCurrentNoteTab(
tabView: EditorView | null,
isListenerActive: boolean,
firstEnhancedNoteId: string | undefined,
hasTranscript: boolean,
enhancedNoteIds: string[],
): EditorView {
if (isListenerActive) {
if (tabView?.type === "raw" || tabView?.type === "transcript") {
Expand All @@ -13,9 +14,19 @@ export function computeCurrentNoteTab(
}

if (tabView) {
return tabView;
if (tabView.type === "transcript" && !hasTranscript) {
tabView = null;
} else if (
tabView.type === "enhanced" &&
!enhancedNoteIds.includes(tabView.id)
) {
tabView = null;
} else {
return tabView;
}
}

const firstEnhancedNoteId = enhancedNoteIds[0];
if (firstEnhancedNoteId) {
return { type: "enhanced", id: firstEnhancedNoteId };
}
Expand Down
39 changes: 27 additions & 12 deletions apps/desktop/src/session/components/shared.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,28 @@ describe("computeCurrentNoteTab", () => {
const result = computeCurrentNoteTab(
{ type: "enhanced", id: "note-1" },
true,
"note-1",
true,
["note-1"],
);
expect(result).toEqual({ type: "raw" });
});

it("preserves raw view", () => {
const result = computeCurrentNoteTab({ type: "raw" }, true, "note-1");
const result = computeCurrentNoteTab({ type: "raw" }, true, true, [
"note-1",
]);
expect(result).toEqual({ type: "raw" });
});

it("preserves transcript view", () => {
const result = computeCurrentNoteTab(
{ type: "transcript" },
true,
const result = computeCurrentNoteTab({ type: "transcript" }, true, true, [
"note-1",
);
]);
expect(result).toEqual({ type: "transcript" });
});

it("returns raw view when no persisted view", () => {
const result = computeCurrentNoteTab(null, true, "note-1");
const result = computeCurrentNoteTab(null, true, true, ["note-1"]);
expect(result).toEqual({ type: "raw" });
});
});
Expand All @@ -38,32 +39,46 @@ describe("computeCurrentNoteTab", () => {
const result = computeCurrentNoteTab(
{ type: "enhanced", id: "note-1" },
false,
"note-1",
true,
["note-1"],
);
expect(result).toEqual({ type: "enhanced", id: "note-1" });
});

it("respects persisted raw view", () => {
const result = computeCurrentNoteTab({ type: "raw" }, false, "note-1");
const result = computeCurrentNoteTab({ type: "raw" }, false, true, [
"note-1",
]);
expect(result).toEqual({ type: "raw" });
});

it("respects persisted transcript view", () => {
const result = computeCurrentNoteTab(
{ type: "transcript" },
false,
"note-1",
true,
["note-1"],
);
expect(result).toEqual({ type: "transcript" });
});

it("defaults to enhanced view when available and no persisted view", () => {
const result = computeCurrentNoteTab(null, false, "note-1");
const result = computeCurrentNoteTab(null, false, true, ["note-1"]);
expect(result).toEqual({ type: "enhanced", id: "note-1" });
});

it("defaults to raw when no enhanced notes and no persisted view", () => {
const result = computeCurrentNoteTab(null, false, undefined);
const result = computeCurrentNoteTab(null, false, false, []);
expect(result).toEqual({ type: "raw" });
});

it("falls back to raw when transcript view is no longer available", () => {
const result = computeCurrentNoteTab(
{ type: "transcript" },
false,
false,
[],
);
expect(result).toEqual({ type: "raw" });
});
});
Expand Down
23 changes: 18 additions & 5 deletions apps/desktop/src/session/components/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { computeCurrentNoteTab } from "./compute-note-tab";

import { useAITaskTask } from "~/ai/hooks";
import { useNetwork } from "~/contexts/network";
import { countTranscriptWords } from "~/services/enhancer/eligibility";
import * as main from "~/store/tinybase/store/main";
import { createTaskId } from "~/store/zustand/ai-task/task-configs";
import type { Tab } from "~/store/zustand/tabs/schema";
import { type EditorView } from "~/store/zustand/tabs/schema";
import { useListener } from "~/stt/contexts";
import { MIN_WORDS_FOR_MEANINGFUL_TRANSCRIPT } from "~/stt/thresholds";
import { useSTTConnection } from "~/stt/useSTTConnection";

export { computeCurrentNoteTab } from "./compute-note-tab";
Expand All @@ -21,8 +23,18 @@ export function useHasTranscript(sessionId: string): boolean {
sessionId,
main.STORE_ID,
);

return !!transcriptIds && transcriptIds.length > 0;
const store = main.UI.useStore(main.STORE_ID);

return useMemo(() => {
if (!store || !transcriptIds?.length) {
return false;
}

return (
countTranscriptWords(transcriptIds, store) >=
MIN_WORDS_FOR_MEANINGFUL_TRANSCRIPT
);
}, [store, transcriptIds]);
}

export function useCurrentNoteTab(
Expand All @@ -39,22 +51,23 @@ export function useCurrentNoteTab(
sessionMode === "active" ||
sessionMode === "finalizing" ||
isListenerStarting;
const hasTranscript = useHasTranscript(tab.id);

const enhancedNoteIds = main.UI.useSliceRowIds(
main.INDEXES.enhancedNotesBySession,
tab.id,
main.STORE_ID,
);
const firstEnhancedNoteId = enhancedNoteIds?.[0];

return useMemo(
() =>
computeCurrentNoteTab(
tab.state.view ?? null,
isListenerActive,
firstEnhancedNoteId,
hasTranscript,
enhancedNoteIds ?? [],
),
[tab.state.view, isListenerActive, firstEnhancedNoteId],
[tab.state.view, isListenerActive, hasTranscript, enhancedNoteIds],
);
}

Expand Down
Loading
Loading