Skip to content

Commit c4ae5e3

Browse files
authored
Add new window for transcript-editor (#778)
1 parent c47b43c commit c4ae5e3

File tree

17 files changed

+281
-78
lines changed

17 files changed

+281
-78
lines changed

apps/desktop/src/components/right-panel/views/legacy/base.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { useQuery } from "@tanstack/react-query";
22
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
33
import { CheckIcon, ClipboardCopyIcon, FileAudioIcon, PencilIcon } from "lucide-react";
4-
import React, { useState } from "react";
4+
import React from "react";
55

66
import { commands as miscCommands } from "@hypr/plugin-misc";
7+
import { commands as windowsCommands } from "@hypr/plugin-windows";
78
import { Button } from "@hypr/ui/components/ui/button";
89
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@hypr/ui/components/ui/tooltip";
910
import { useOngoingSession, useSessions } from "@hypr/utils/contexts";
@@ -26,8 +27,6 @@ export const TranscriptBase: React.FC<TranscriptBaseProps> = ({
2627
const { showEmptyMessage, isEnhanced, hasTranscript } = useTranscriptWidget(sessionId);
2728
const { timeline } = useTranscript(sessionId);
2829

29-
const [editing, setEditing] = useState(false);
30-
3130
const handleCopyAll = () => {
3231
if (timeline && timeline.items && timeline.items.length > 0) {
3332
const transcriptText = timeline.items.map((item) => item.text).join("\n");
@@ -45,6 +44,23 @@ export const TranscriptBase: React.FC<TranscriptBaseProps> = ({
4544
wrapperProps.queryClient,
4645
);
4746

47+
const editing = useQuery({
48+
queryKey: ["editing", sessionId],
49+
queryFn: () => windowsCommands.windowIsVisible({ type: "main" }).then((v) => !v),
50+
});
51+
52+
const handleClickToggleEditing = () => {
53+
if (editing.data) {
54+
windowsCommands.windowHide({ type: "transcript", value: sessionId! }).then(() => {
55+
windowsCommands.windowShow({ type: "main" });
56+
});
57+
} else {
58+
windowsCommands.windowHide({ type: "main" }).then(() => {
59+
windowsCommands.windowShow({ type: "transcript", value: sessionId! });
60+
});
61+
}
62+
};
63+
4864
const handleOpenSession = () => {
4965
if (sessionId) {
5066
miscCommands.audioOpen(sessionId);
@@ -93,16 +109,16 @@ export const TranscriptBase: React.FC<TranscriptBaseProps> = ({
93109
</Tooltip>
94110
</TooltipProvider>
95111
),
96-
<Button variant="ghost" size="icon" className="p-0" onClick={() => setEditing(!editing)}>
97-
{editing
112+
<Button variant="ghost" size="icon" className="p-0" onClick={handleClickToggleEditing}>
113+
{editing.data
98114
? <CheckIcon size={16} className="text-black" />
99115
: <PencilIcon size={16} className="text-black" />}
100116
</Button>,
101117
].filter(Boolean)}
102118
/>
103119
</div>
104120

105-
{sessionId && <Transcript sessionId={sessionId} editing={editing} />}
121+
{sessionId && <Transcript sessionId={sessionId} />}
106122

107123
{!sessionId && (
108124
<div className="absolute inset-0 backdrop-blur-sm bg-white/50 flex items-center justify-center">

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

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { EarIcon, Loader2Icon } from "lucide-react";
22
import { useEffect, useRef } from "react";
33

4-
import TranscriptEditor from "@hypr/tiptap/transcript";
4+
import { commands as windowsCommands, events as windowsEvents } from "@hypr/plugin-windows";
55
import { Badge } from "@hypr/ui/components/ui/badge";
66
import { useSessions } from "@hypr/utils/contexts";
77
import { useTranscript } from "./useTranscript";
88

9-
export function Transcript({ sessionId, editing }: { sessionId?: string; editing: boolean }) {
9+
export function Transcript({ sessionId }: { sessionId?: string }) {
1010
const currentSessionId = useSessions((s) => s.currentSessionId);
1111
const effectiveSessionId = sessionId || currentSessionId;
1212

@@ -27,19 +27,19 @@ export function Transcript({ sessionId, editing }: { sessionId?: string; editing
2727
}
2828
}, [timeline?.items, isLive, ref]);
2929

30-
const content = {
31-
type: "doc",
32-
content: [
33-
{
34-
type: "speaker",
35-
attrs: { label: "" },
36-
content: (timeline?.items || []).flatMap((item) => item.text.split(" ")).filter(Boolean).map((word) => ({
37-
type: "word",
38-
content: [{ type: "text", text: word }],
39-
})),
40-
},
41-
],
42-
};
30+
useEffect(() => {
31+
let unlisten: () => void;
32+
33+
windowsEvents.windowDestroyed.listen(({ payload: { window } }) => {
34+
if (window.type === "transcript") {
35+
windowsCommands.windowShow({ type: "main" });
36+
}
37+
}).then((u) => {
38+
unlisten = u;
39+
});
40+
41+
return () => unlisten?.();
42+
}, []);
4343

4444
return (
4545
<div
@@ -54,14 +54,13 @@ export function Transcript({ sessionId, editing }: { sessionId?: string; editing
5454
)
5555
: (
5656
<>
57-
{(!editing && timeline?.items?.length) && timeline.items.map((item, index) => (
57+
{(timeline?.items ?? []).map((item, index) => (
5858
<div key={index}>
5959
<p>
6060
{item.text}
6161
</p>
6262
</div>
6363
))}
64-
{editing && <TranscriptEditor initialContent={content} />}
6564
{isLive && (
6665
<div className="flex items-center gap-2 justify-center py-2 text-neutral-400">
6766
<EarIcon size={14} /> Listening... (there might be a delay)

apps/desktop/src/main.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,24 @@ import "./styles/globals.css";
33

44
import { i18n } from "@lingui/core";
55
import { I18nProvider } from "@lingui/react";
6-
import { QueryClient, QueryClientProvider, useQuery, useQueryClient } from "@tanstack/react-query";
6+
import { QueryClient, QueryClientProvider, useQueries, useQueryClient } from "@tanstack/react-query";
77
import { CatchBoundary, createRouter, ErrorComponent, RouterProvider } from "@tanstack/react-router";
88
import { useEffect } from "react";
99
import ReactDOM from "react-dom/client";
1010

1111
import type { Context } from "@/types";
1212
import { commands } from "@/types";
1313
import { commands as authCommands } from "@hypr/plugin-auth";
14+
import { commands as dbCommands } from "@hypr/plugin-db";
1415
import { Toaster } from "@hypr/ui/components/ui/toast";
1516
import { TooltipProvider } from "@hypr/ui/components/ui/tooltip";
1617
import { ThemeProvider } from "@hypr/ui/contexts/theme";
18+
import { createOngoingSessionStore, createSessionsStore } from "@hypr/utils/stores";
1719
import { broadcastQueryClient } from "./utils";
1820

1921
import { messages as enMessages } from "./locales/en/messages";
2022
import { messages as koMessages } from "./locales/ko/messages";
2123

22-
import { createOngoingSessionStore, createSessionsStore } from "@hypr/utils/stores";
2324
import { routeTree } from "./routeTree.gen";
2425

2526
import * as Sentry from "@sentry/react";
@@ -86,17 +87,29 @@ function App() {
8687
return broadcastQueryClient(queryClient);
8788
}, [queryClient]);
8889

89-
const userId = useQuery({
90-
queryKey: ["userId"],
91-
queryFn: () => authCommands.getFromStore("auth-user-id"),
92-
staleTime: Infinity,
90+
const [userId, onboardingSessionId] = useQueries({
91+
queries: [
92+
{
93+
queryKey: ["auth-user-id"],
94+
queryFn: () => authCommands.getFromStore("auth-user-id"),
95+
},
96+
{
97+
queryKey: ["onboarding-session-id"],
98+
queryFn: () => dbCommands.onboardingSessionId(),
99+
},
100+
],
93101
});
94102

95-
if (!userId.data) {
103+
if (!userId.data || !onboardingSessionId.data) {
96104
return null;
97105
}
98106

99-
return <RouterProvider router={router} context={{ ...context, userId: userId.data }} />;
107+
return (
108+
<RouterProvider
109+
router={router}
110+
context={{ ...context, userId: userId.data, onboardingSessionId: onboardingSessionId.data }}
111+
/>
112+
);
100113
}
101114

102115
if (!rootElement.innerHTML) {

apps/desktop/src/routeTree.gen.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { Route as AppSettingsImport } from './routes/app.settings'
1818
import { Route as AppPlansImport } from './routes/app.plans'
1919
import { Route as AppNewImport } from './routes/app.new'
2020
import { Route as AppCalendarImport } from './routes/app.calendar'
21+
import { Route as AppTranscriptIdImport } from './routes/app.transcript.$id'
2122
import { Route as AppOrganizationIdImport } from './routes/app.organization.$id'
2223
import { Route as AppNoteIdImport } from './routes/app.note.$id'
2324
import { Route as AppHumanIdImport } from './routes/app.human.$id'
@@ -66,6 +67,12 @@ const AppCalendarRoute = AppCalendarImport.update({
6667
getParentRoute: () => AppRoute,
6768
} as any)
6869

70+
const AppTranscriptIdRoute = AppTranscriptIdImport.update({
71+
id: '/transcript/$id',
72+
path: '/transcript/$id',
73+
getParentRoute: () => AppRoute,
74+
} as any)
75+
6976
const AppOrganizationIdRoute = AppOrganizationIdImport.update({
7077
id: '/organization/$id',
7178
path: '/organization/$id',
@@ -158,6 +165,13 @@ declare module '@tanstack/react-router' {
158165
preLoaderRoute: typeof AppOrganizationIdImport
159166
parentRoute: typeof AppImport
160167
}
168+
'/app/transcript/$id': {
169+
id: '/app/transcript/$id'
170+
path: '/transcript/$id'
171+
fullPath: '/app/transcript/$id'
172+
preLoaderRoute: typeof AppTranscriptIdImport
173+
parentRoute: typeof AppImport
174+
}
161175
}
162176
}
163177

@@ -172,6 +186,7 @@ interface AppRouteChildren {
172186
AppHumanIdRoute: typeof AppHumanIdRoute
173187
AppNoteIdRoute: typeof AppNoteIdRoute
174188
AppOrganizationIdRoute: typeof AppOrganizationIdRoute
189+
AppTranscriptIdRoute: typeof AppTranscriptIdRoute
175190
}
176191

177192
const AppRouteChildren: AppRouteChildren = {
@@ -183,6 +198,7 @@ const AppRouteChildren: AppRouteChildren = {
183198
AppHumanIdRoute: AppHumanIdRoute,
184199
AppNoteIdRoute: AppNoteIdRoute,
185200
AppOrganizationIdRoute: AppOrganizationIdRoute,
201+
AppTranscriptIdRoute: AppTranscriptIdRoute,
186202
}
187203

188204
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
@@ -198,6 +214,7 @@ export interface FileRoutesByFullPath {
198214
'/app/human/$id': typeof AppHumanIdRoute
199215
'/app/note/$id': typeof AppNoteIdRoute
200216
'/app/organization/$id': typeof AppOrganizationIdRoute
217+
'/app/transcript/$id': typeof AppTranscriptIdRoute
201218
}
202219

203220
export interface FileRoutesByTo {
@@ -210,6 +227,7 @@ export interface FileRoutesByTo {
210227
'/app/human/$id': typeof AppHumanIdRoute
211228
'/app/note/$id': typeof AppNoteIdRoute
212229
'/app/organization/$id': typeof AppOrganizationIdRoute
230+
'/app/transcript/$id': typeof AppTranscriptIdRoute
213231
}
214232

215233
export interface FileRoutesById {
@@ -224,6 +242,7 @@ export interface FileRoutesById {
224242
'/app/human/$id': typeof AppHumanIdRoute
225243
'/app/note/$id': typeof AppNoteIdRoute
226244
'/app/organization/$id': typeof AppOrganizationIdRoute
245+
'/app/transcript/$id': typeof AppTranscriptIdRoute
227246
}
228247

229248
export interface FileRouteTypes {
@@ -239,6 +258,7 @@ export interface FileRouteTypes {
239258
| '/app/human/$id'
240259
| '/app/note/$id'
241260
| '/app/organization/$id'
261+
| '/app/transcript/$id'
242262
fileRoutesByTo: FileRoutesByTo
243263
to:
244264
| '/video'
@@ -250,6 +270,7 @@ export interface FileRouteTypes {
250270
| '/app/human/$id'
251271
| '/app/note/$id'
252272
| '/app/organization/$id'
273+
| '/app/transcript/$id'
253274
id:
254275
| '__root__'
255276
| '/app'
@@ -262,6 +283,7 @@ export interface FileRouteTypes {
262283
| '/app/human/$id'
263284
| '/app/note/$id'
264285
| '/app/organization/$id'
286+
| '/app/transcript/$id'
265287
fileRoutesById: FileRoutesById
266288
}
267289

@@ -299,7 +321,8 @@ export const routeTree = rootRoute
299321
"/app/",
300322
"/app/human/$id",
301323
"/app/note/$id",
302-
"/app/organization/$id"
324+
"/app/organization/$id",
325+
"/app/transcript/$id"
303326
]
304327
},
305328
"/video": {
@@ -336,6 +359,10 @@ export const routeTree = rootRoute
336359
"/app/organization/$id": {
337360
"filePath": "app.organization.$id.tsx",
338361
"parent": "/app"
362+
},
363+
"/app/transcript/$id": {
364+
"filePath": "app.transcript.$id.tsx",
365+
"parent": "/app"
339366
}
340367
}
341368
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { createFileRoute } from "@tanstack/react-router";
2+
import { useRef } from "react";
3+
4+
import { commands as dbCommands } from "@hypr/plugin-db";
5+
import TranscriptEditor from "@hypr/tiptap/transcript";
6+
7+
export const Route = createFileRoute("/app/transcript/$id")({
8+
component: Component,
9+
loader: async ({ params: { id }, context: { onboardingSessionId } }) => {
10+
const timeline = onboardingSessionId
11+
? await dbCommands.getTimelineViewOnboarding()
12+
: await dbCommands.getTimelineView(id);
13+
14+
return { timeline };
15+
},
16+
});
17+
18+
function Component() {
19+
const { timeline } = Route.useLoaderData();
20+
const editorRef = useRef(null);
21+
22+
const content = {
23+
type: "doc",
24+
content: [
25+
{
26+
type: "speaker",
27+
attrs: { label: "" },
28+
content: (timeline?.items || []).flatMap((item) => item.text.split(" ")).filter(Boolean).map((word) => ({
29+
type: "word",
30+
content: [{ type: "text", text: word }],
31+
})),
32+
},
33+
],
34+
};
35+
36+
return (
37+
<div className="p-12">
38+
<TranscriptEditor
39+
ref={editorRef}
40+
initialContent={content}
41+
/>
42+
</div>
43+
);
44+
}

apps/desktop/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { NangoIntegration } from "./server.gen";
99

1010
export type Context = {
1111
userId?: string;
12+
onboardingSessionId?: string;
1213
ongoingSessionStore: OngoingSessionStore;
1314
sessionsStore: SessionsStore;
1415
queryClient: QueryClient;

0 commit comments

Comments
 (0)