Skip to content

Commit 8da1591

Browse files
committed
chores
1 parent 0da075d commit 8da1591

File tree

25 files changed

+353
-1099
lines changed

25 files changed

+353
-1099
lines changed

apps/desktop/src/components/main/body/sessions/floating/listen.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { Icon } from "@iconify-icon/react";
22
import useMediaQuery from "beautiful-react-hooks/useMediaQuery";
33
import { useCallback, useEffect, useState } from "react";
44

5+
import { type StreamResponse } from "@hypr/plugin-listener";
56
import { DancingSticks } from "@hypr/ui/components/ui/dancing-sticks";
67
import { Spinner } from "@hypr/ui/components/ui/spinner";
78
import { useListener } from "../../../../../contexts/listener";
89
import { useSTTConnection } from "../../../../../hooks/useSTTConnection";
910
import * as persisted from "../../../../../store/tinybase/persisted";
1011
import { type Tab } from "../../../../../store/zustand/tabs";
12+
import { id } from "../../../../../utils";
1113
import { FloatingButton, formatTime } from "./shared";
1214

1315
type RemoteMeeting =
@@ -145,6 +147,7 @@ function useRemoteMeeting(sessionId: string): RemoteMeeting | null {
145147

146148
function useStartSession(sessionId: string) {
147149
const start = useListener((state) => state.start);
150+
const append = useAppendTranscript(sessionId);
148151
const conn = useSTTConnection();
149152

150153
const handleClick = useCallback(() => {
@@ -162,9 +165,31 @@ function useStartSession(sessionId: string) {
162165
base_url: conn.baseUrl,
163166
api_key: conn.apiKey,
164167
}, (response) => {
165-
console.log(response);
168+
append(response);
166169
});
167170
}, [conn, sessionId, start]);
168171

169172
return handleClick;
170173
}
174+
175+
function useAppendTranscript(sessionId: string) {
176+
const store = persisted.UI.useStore(persisted.STORE_ID);
177+
178+
const handler = useCallback((res: StreamResponse) => {
179+
if (store && res.type === "Results") {
180+
res.channel.alternatives[0].words.forEach((w) => {
181+
store.setRow("words", id(), {
182+
session_id: sessionId,
183+
text: w.word,
184+
start_ms: Math.round(w.start * 1000),
185+
end_ms: Math.round(w.end * 1000),
186+
speaker: w.speaker?.toString() ?? undefined,
187+
user_id: "",
188+
created_at: new Date().toISOString(),
189+
});
190+
});
191+
}
192+
}, [store, sessionId]);
193+
194+
return handler;
195+
}
Lines changed: 15 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,25 @@
1-
import { ChevronDownIcon } from "lucide-react";
2-
import { useCallback, useEffect, useRef, useState } from "react";
1+
import { createQueries } from "tinybase/with-schemas";
32
import * as persisted from "../../../../../store/tinybase/persisted";
43

5-
import { Button } from "@hypr/ui/components/ui/button";
6-
import { useSegments } from "../../../../../hooks/useSegments";
7-
8-
export function TranscriptEditorWrapper({
9-
sessionId,
10-
}: {
11-
sessionId: string;
12-
}) {
13-
const value = persisted.UI.useCell("sessions", sessionId, "transcript", persisted.STORE_ID);
14-
15-
return <pre>{JSON.stringify(value, null, 2)}</pre>;
16-
}
17-
184
export function TranscriptView({ sessionId }: { sessionId: string }) {
19-
const segments = useSegments(sessionId);
20-
21-
const [isAtBottom, setIsAtBottom] = useState(true);
22-
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
23-
24-
useEffect(() => {
25-
if (isAtBottom && scrollContainerRef.current) {
26-
scrollContainerRef.current.scrollTo({
27-
top: scrollContainerRef.current.scrollHeight,
28-
behavior: "smooth",
29-
});
30-
}
31-
}, [segments, isAtBottom]);
32-
33-
const handleScroll = useCallback(() => {
34-
const container = scrollContainerRef.current;
35-
if (!container) {
36-
return;
37-
}
38-
39-
const { scrollTop, scrollHeight, clientHeight } = container;
40-
const threshold = 100;
41-
const atBottom = scrollHeight - scrollTop - clientHeight <= threshold;
42-
setIsAtBottom(atBottom);
43-
}, []);
44-
45-
const scrollToBottom = useCallback(() => {
46-
const container = scrollContainerRef.current;
47-
if (!container) {
48-
return;
49-
}
50-
51-
container.scrollTo({
52-
top: container.scrollHeight,
53-
behavior: "smooth",
54-
});
55-
}, []);
56-
57-
const hasContent = segments.length > 0;
5+
const store = persisted.UI.useStore(persisted.STORE_ID);
6+
7+
const QUERY = `${sessionId}_words`;
8+
const QUERIES = persisted.UI.useCreateQueries(
9+
store,
10+
(store) =>
11+
createQueries(store).setQueryDefinition(QUERY, "words", ({ select, where }) => {
12+
select("text");
13+
where("session_id", sessionId);
14+
}),
15+
[sessionId],
16+
);
5817

59-
if (!hasContent) {
60-
return <div className="h-full" />;
61-
}
18+
const words = persisted.UI.useResultTable(QUERY, QUERIES);
6219

6320
return (
6421
<div className="relative h-full flex flex-col">
65-
<div
66-
ref={scrollContainerRef}
67-
className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden pt-8 pb-6"
68-
onScroll={handleScroll}
69-
>
70-
<div className="px-8 text-[15px] leading-relaxed space-y-4">
71-
{segments.map((segment, idx) => (
72-
<div key={idx} className="space-y-1">
73-
<div className="text-xs font-medium text-gray-500">
74-
Speaker {segment.speaker ?? "Unknown"}
75-
</div>
76-
<div className="text-gray-800">
77-
{segment.words.map(word => word.punctuated_word || word.word).join(" ")}
78-
</div>
79-
</div>
80-
))}
81-
</div>
82-
</div>
83-
84-
{!isAtBottom && (
85-
<Button
86-
onClick={scrollToBottom}
87-
size="sm"
88-
className="absolute bottom-4 left-1/2 transform -translate-x-1/2 rounded-full shadow-lg bg-white hover:bg-gray-50 text-gray-700 border border-gray-200 z-10 flex items-center gap-1"
89-
variant="outline"
90-
>
91-
<ChevronDownIcon size={14} />
92-
<span className="text-xs">Go to bottom</span>
93-
</Button>
94-
)}
22+
<pre>{JSON.stringify(words)}</pre>
9523
</div>
9624
);
9725
}

apps/desktop/src/devtool/seed/shared.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
Tag,
1616
TemplateSection,
1717
TemplateStorage,
18+
Word,
1819
} from "../../store/tinybase/persisted";
1920
import { id } from "../../utils";
2021

@@ -136,11 +137,10 @@ export const generateEnhancedMarkdown = () => {
136137

137138
export const generateTranscript = () => {
138139
const wordCount = faker.number.int({ min: 50, max: 200 });
139-
const words: Array<{ speaker: string; text: string; start: string; end: string }> = [];
140+
const words: Array<{ speaker: string; text: string; start_ms: number; end_ms: number }> = [];
140141
const speakers = ["Speaker 1", "Speaker 2"];
141142

142-
const baseTime = faker.date.recent({ days: 30 });
143-
let currentTime = baseTime.getTime();
143+
let currentTimeMs = 0;
144144

145145
for (let i = 0; i < wordCount; i++) {
146146
const word = faker.lorem.word();
@@ -149,11 +149,11 @@ export const generateTranscript = () => {
149149
words.push({
150150
speaker: faker.helpers.arrayElement(speakers),
151151
text: word,
152-
start: new Date(currentTime).toISOString(),
153-
end: new Date(currentTime + durationMs).toISOString(),
152+
start_ms: currentTimeMs,
153+
end_ms: currentTimeMs + durationMs,
154154
});
155155

156-
currentTime += durationMs + faker.number.int({ min: 50, max: 300 });
156+
currentTimeMs += durationMs + faker.number.int({ min: 50, max: 300 });
157157
}
158158

159159
return { words };
@@ -174,7 +174,6 @@ export const createSession = (eventId?: string, folderId?: string): { id: string
174174
created_at: faker.date.recent({ days: 30 }).toISOString(),
175175
event_id: eventId,
176176
folder_id: folderId,
177-
transcript: JSON.stringify(generateTranscript()),
178177
},
179178
};
180179
};
@@ -383,6 +382,7 @@ export const generateMockData = (config: MockConfig) => {
383382
const calendars: Record<string, Calendar> = {};
384383
const folders: Record<string, Folder> = {};
385384
const sessions: Record<string, SessionStorage> = {};
385+
const words: Record<string, Word> = {};
386386
const events: Record<string, Event> = {};
387387
const mapping_session_participant: Record<string, mappingSessionParticipant> = {};
388388
const tags: Record<string, Tag> = {};
@@ -493,6 +493,20 @@ export const generateMockData = (config: MockConfig) => {
493493
const session = createSession(eventId, folderId);
494494
sessions[session.id] = session.data;
495495
sessionIds.push(session.id);
496+
497+
const transcript = generateTranscript();
498+
transcript.words.forEach((word) => {
499+
const wordId = id();
500+
words[wordId] = {
501+
user_id: USER_ID,
502+
session_id: session.id,
503+
text: word.text,
504+
start_ms: word.start_ms,
505+
end_ms: word.end_ms,
506+
speaker: word.speaker || undefined,
507+
created_at: faker.date.recent({ days: 30 }).toISOString(),
508+
};
509+
});
496510
});
497511
});
498512

@@ -539,6 +553,7 @@ export const generateMockData = (config: MockConfig) => {
539553
calendars,
540554
folders,
541555
sessions,
556+
words,
542557
events,
543558
mapping_session_participant,
544559
tags,

apps/desktop/src/hooks/useSegments.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

apps/desktop/src/hooks/useWords.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.

apps/desktop/src/store/tinybase/localPersister.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ export function createLocalPersister<Schemas extends OptionalSchemas>(
1111
return createCustomSqlitePersister(
1212
store,
1313
{ mode: "json", ...config },
14-
async (sql: string, args: any[] = []): Promise<any> => (await db2Commands.executeLocal(sql, args)),
14+
async (sql: string, args: any[] = []): Promise<any> => {
15+
const r = await db2Commands.executeLocal(sql, args);
16+
if (r.status === "error") {
17+
console.error(r.error);
18+
}
19+
},
1520
() => {},
1621
(unsubscribeFunction: any): any => unsubscribeFunction(),
1722
false ? console.log.bind(console, "[LocalPersister]") : () => {},

apps/desktop/src/store/tinybase/persisted.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
TABLE_SESSIONS,
2828
tagSchema as baseTagSchema,
2929
templateSchema as baseTemplateSchema,
30-
transcriptSchema,
30+
wordSchema,
3131
} from "@hypr/db";
3232
import { createBroadcastChannelSynchronizer } from "tinybase/synchronizers/synchronizer-broadcast-channel/with-schemas";
3333
import * as internal from "./internal";
@@ -62,7 +62,6 @@ export const folderSchema = baseFolderSchema.omit({ id: true }).extend({
6262
});
6363

6464
export const sessionSchema = baseSessionSchema.omit({ id: true }).extend({
65-
transcript: jsonObject(transcriptSchema),
6665
created_at: z.string(),
6766
event_id: z.preprocess(val => val ?? undefined, z.string().optional()),
6867
folder_id: z.preprocess(val => val ?? undefined, z.string().optional()),
@@ -97,12 +96,18 @@ export const chatMessageSchema = baseChatMessageSchema.omit({ id: true }).extend
9796
parts: jsonObject(z.any()),
9897
});
9998

99+
export const wordSchemaOverride = wordSchema.omit({ id: true }).extend({
100+
created_at: z.string(),
101+
speaker: z.preprocess(val => val ?? undefined, z.string().optional()),
102+
});
103+
100104
export type Human = z.infer<typeof humanSchema>;
101105
export type Event = z.infer<typeof eventSchema>;
102106
export type Calendar = z.infer<typeof calendarSchema>;
103107
export type Organization = z.infer<typeof organizationSchema>;
104108
export type Folder = z.infer<typeof folderSchema>;
105109
export type Session = z.infer<typeof sessionSchema>;
110+
export type Word = z.infer<typeof wordSchemaOverride>;
106111
export type mappingSessionParticipant = z.infer<typeof mappingSessionParticipantSchema>;
107112
export type Tag = z.infer<typeof tagSchema>;
108113
export type MappingTagSession = z.infer<typeof mappingTagSessionSchema>;
@@ -132,8 +137,16 @@ const SCHEMA = {
132137
title: { type: "string" },
133138
raw_md: { type: "string" },
134139
enhanced_md: { type: "string" },
135-
transcript: { type: "string" },
136140
} satisfies InferTinyBaseSchema<typeof sessionSchema>,
141+
words: {
142+
user_id: { type: "string" },
143+
created_at: { type: "string" },
144+
text: { type: "string" },
145+
session_id: { type: "string" },
146+
start_ms: { type: "number" },
147+
end_ms: { type: "number" },
148+
speaker: { type: "string" },
149+
} satisfies InferTinyBaseSchema<typeof wordSchema>,
137150
humans: {
138151
user_id: { type: "string" },
139152
created_at: { type: "string" },

0 commit comments

Comments
 (0)