Skip to content

Commit b745627

Browse files
committed
invalid words cache when transcript edited
1 parent 1ebf9b0 commit b745627

File tree

6 files changed

+78
-56
lines changed

6 files changed

+78
-56
lines changed

apps/desktop/src/components/right-panel/hooks/useTranscript.ts

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from "react";
33
import { commands as dbCommands } from "@hypr/plugin-db";
44
import { events as listenerEvents, type Word } from "@hypr/plugin-listener";
55
import { useOngoingSession, useSession } from "@hypr/utils/contexts";
6+
import { useQuery } from "@tanstack/react-query";
67

78
export function useTranscript(sessionId: string | null) {
89
const ongoingSessionState = useOngoingSession((s) => ({
@@ -21,31 +22,23 @@ export function useTranscript(sessionId: string | null) {
2122

2223
const [words, setWords] = useState<Word[]>([]);
2324
const [selectedLanguage, setSelectedLanguage] = useState<string>("en");
24-
const [isLoading, setIsLoading] = useState<boolean>(false);
2525

26-
useEffect(() => {
27-
if (!sessionId) {
28-
setWords([]);
29-
return;
30-
}
26+
const existingWords = useQuery({
27+
enabled: !!sessionId,
28+
queryKey: ["session", "words", sessionId],
29+
queryFn: async () => {
30+
const onboardingSessionId = await dbCommands.onboardingSessionId();
31+
const fn = (sessionId === onboardingSessionId && isEnhanced)
32+
? dbCommands.getWordsOnboarding
33+
: dbCommands.getWords;
3134

32-
setIsLoading(true);
33-
const fn = async () => {
34-
try {
35-
const onboardingSessionId = await dbCommands.onboardingSessionId();
36-
const fn = (sessionId === onboardingSessionId && isEnhanced)
37-
? dbCommands.getWordsOnboarding
38-
: dbCommands.getWords;
35+
return fn(sessionId!);
36+
},
37+
});
3938

40-
const words = await fn(sessionId);
41-
setWords(words as Word[]);
42-
} finally {
43-
setIsLoading(false);
44-
}
45-
};
46-
47-
fn();
48-
}, [sessionId, isEnhanced]);
39+
useEffect(() => {
40+
setWords(existingWords.data ?? []);
41+
}, [existingWords.data]);
4942

5043
useEffect(() => {
5144
if (ongoingSessionState.status !== "running_active" || ongoingSessionState.sessionId !== sessionId) {
@@ -78,6 +71,5 @@ export function useTranscript(sessionId: string | null) {
7871
isLive,
7972
selectedLanguage,
8073
handleLanguageChange,
81-
isLoading,
8274
};
8375
}

apps/desktop/src/components/right-panel/hooks/useTranscriptWidget.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { useSession } from "@hypr/utils/contexts";
22
import { useTranscript } from "./useTranscript";
33

44
export function useTranscriptWidget(sessionId: string | null) {
5-
const { words, isLive, selectedLanguage, handleLanguageChange, isLoading } = useTranscript(sessionId);
5+
const { words, isLive, selectedLanguage, handleLanguageChange } = useTranscript(sessionId);
66
const isEnhanced = sessionId ? useSession(sessionId, (s) => !!s.session.enhanced_memo_html) : false;
77

88
const hasTranscript = words.length > 0;
99
const isSessionActive = sessionId && (hasTranscript || isLive);
1010

11-
const showEmptyMessage = sessionId && !hasTranscript && !isLive && !isLoading;
11+
const showEmptyMessage = sessionId && !hasTranscript && !isLive;
1212

1313
return {
1414
words,
@@ -19,6 +19,5 @@ export function useTranscriptWidget(sessionId: string | null) {
1919
isSessionActive,
2020
showEmptyMessage,
2121
isEnhanced,
22-
isLoading,
2322
};
2423
}

apps/desktop/src/components/right-panel/views/transcript-view.tsx

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function TranscriptView() {
3131
const sessionId = useSessions((s) => s.currentSessionId);
3232
const isInactive = useOngoingSession((s) => s.status === "inactive");
3333
const { showEmptyMessage, isEnhanced, hasTranscript } = useTranscriptWidget(sessionId);
34-
const { isLive, words, isLoading } = useTranscript(sessionId);
34+
const { isLive, words } = useTranscript(sessionId);
3535

3636
const handleCopyAll = () => {
3737
if (words && words.length > 0) {
@@ -157,29 +157,19 @@ export function TranscriptView() {
157157
ref={transcriptContainerRef}
158158
className="flex-1 scrollbar-none px-4 flex flex-col gap-2 overflow-y-auto text-sm py-4"
159159
>
160-
{isLoading
161-
? (
162-
<div className="flex items-center gap-2 justify-center py-2 text-neutral-400">
163-
<Loader2Icon size={14} className="animate-spin" /> Loading transcript...
164-
</div>
165-
)
166-
: (
167-
<>
168-
<p className="whitespace-pre-wrap">
169-
{words.map((word, i) => (
170-
<span key={`${word.text}-${i}`}>
171-
{i > 0 ? " " : ""}
172-
{word.text}
173-
</span>
174-
))}
175-
</p>
176-
{isLive && (
177-
<div className="flex items-center gap-2 justify-center py-2 text-neutral-400">
178-
<EarIcon size={14} /> Listening... (there might be a delay)
179-
</div>
180-
)}
181-
</>
182-
)}
160+
<p className="whitespace-pre-wrap">
161+
{words.map((word, i) => (
162+
<span key={`${word.text}-${i}`}>
163+
{i > 0 ? " " : ""}
164+
{word.text}
165+
</span>
166+
))}
167+
</p>
168+
{isLive && (
169+
<div className="flex items-center gap-2 justify-center py-2 text-neutral-400">
170+
<EarIcon size={14} /> Listening... (there might be a delay)
171+
</div>
172+
)}
183173
</div>
184174
)}
185175

apps/desktop/src/routes/app.transcript.$id.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useQuery } from "@tanstack/react-query";
1+
import { useQuery, useQueryClient } from "@tanstack/react-query";
22
import { createFileRoute, useParams } from "@tanstack/react-router";
33
import { ReplaceAllIcon } from "lucide-react";
44
import { PencilIcon } from "lucide-react";
@@ -37,6 +37,11 @@ type SpeakerContent = {
3737
type WordContent = {
3838
type: "word";
3939
content: { type: "text"; text: string }[];
40+
attrs?: {
41+
start_ms?: number | null;
42+
end_ms?: number | null;
43+
confidence?: number | null;
44+
};
4045
};
4146

4247
function Component() {
@@ -62,6 +67,7 @@ function Component() {
6267

6368
function TranscriptToolbar({ editorRef }: { editorRef: React.RefObject<any> }) {
6469
const { id } = useParams({ from: "/app/transcript/$id" });
70+
const queryClient = useQueryClient();
6571

6672
const title = useQuery({
6773
queryKey: ["session-title", id],
@@ -80,6 +86,7 @@ function TranscriptToolbar({ editorRef }: { editorRef: React.RefObject<any> }) {
8086
});
8187
}
8288
}).then(() => {
89+
queryClient.invalidateQueries({ predicate: (query) => (query.queryKey[0] as string).includes("session") });
8390
windowsCommands.windowDestroy({ type: "transcript", value: id });
8491
});
8592
};
@@ -226,6 +233,11 @@ const fromWordsToEditor = (words: Word[]): EditorContent => {
226233
state.acc[state.acc.length - 1].content.push({
227234
type: "word",
228235
content: [{ type: "text", text: word.text }],
236+
attrs: {
237+
confidence: word.confidence ?? null,
238+
start_ms: word.start_ms ?? null,
239+
end_ms: word.end_ms ?? null,
240+
},
229241
});
230242
}
231243

@@ -268,13 +280,13 @@ const fromEditorToWords = (content: EditorContent): Word[] => {
268280
if (wordBlock.type !== "word" || !wordBlock.content?.[0]?.text) {
269281
continue;
270282
}
271-
283+
const attrs = wordBlock.attrs || {};
272284
words.push({
273285
text: wordBlock.content[0].text,
274286
speaker,
275-
confidence: 1,
276-
start_ms: 0,
277-
end_ms: 0,
287+
confidence: attrs.confidence ?? null,
288+
start_ms: attrs.start_ms ?? null,
289+
end_ms: attrs.end_ms ?? null,
278290
});
279291
}
280292
}

packages/tiptap/src/transcript/nodes.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,34 @@ export const WordNode = Node.create({
7373
inline: true,
7474
atom: false,
7575
content: "text*",
76+
addAttributes() {
77+
return {
78+
start_ms: {
79+
default: null,
80+
parseHTML: element => {
81+
const value = element.getAttribute("data-start-ms");
82+
return value !== null ? Number(value) : null;
83+
},
84+
renderHTML: attributes => attributes.start_ms != null ? { "data-start-ms": attributes.start_ms } : {},
85+
},
86+
end_ms: {
87+
default: null,
88+
parseHTML: element => {
89+
const value = element.getAttribute("data-end-ms");
90+
return value !== null ? Number(value) : null;
91+
},
92+
renderHTML: attributes => attributes.end_ms != null ? { "data-end-ms": attributes.end_ms } : {},
93+
},
94+
confidence: {
95+
default: null,
96+
parseHTML: element => {
97+
const value = element.getAttribute("data-confidence");
98+
return value !== null ? Number(value) : null;
99+
},
100+
renderHTML: attributes => attributes.confidence != null ? { "data-confidence": attributes.confidence } : {},
101+
},
102+
};
103+
},
76104
parseHTML() {
77105
return [{ tag: "span.transcript-word" }];
78106
},

plugins/listener/js/bindings.gen.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ sessionEvent: "plugin:listener:session-event"
7373
/** user-defined types **/
7474

7575
export type SessionEvent = { type: "inactive" } | { type: "running_active" } | { type: "running_paused" } | { type: "words"; words: Word[] } | { type: "audioAmplitude"; mic: number; speaker: number }
76-
export type Word = { text: string; speaker: number | null; confidence: number | null; start_ms: number | null; end_ms: number | null }
76+
export type SpeakerIdentity = { type: "unassigned"; value: { index: number } } | { type: "assigned"; value: { id: string; label: string } }
77+
export type Word = { text: string; speaker: SpeakerIdentity | null; confidence: number | null; start_ms: number | null; end_ms: number | null }
7778

7879
/** tauri-specta globals **/
7980

0 commit comments

Comments
 (0)