1
1
import { useQuery , useQueryClient } from "@tanstack/react-query" ;
2
2
import { writeText } from "@tauri-apps/plugin-clipboard-manager" ;
3
- import { CheckIcon , ClipboardCopyIcon , EarIcon , FileAudioIcon , PencilIcon } from "lucide-react" ;
3
+ import {
4
+ CheckIcon ,
5
+ ClipboardCopyIcon ,
6
+ ClipboardIcon ,
7
+ EarIcon ,
8
+ FileAudioIcon ,
9
+ PencilIcon ,
10
+ UploadIcon ,
11
+ } from "lucide-react" ;
4
12
import { useEffect , useRef } from "react" ;
5
13
6
14
import { commands as dbCommands } from "@hypr/plugin-db" ;
7
15
import { commands as miscCommands } from "@hypr/plugin-misc" ;
8
16
import { commands as windowsCommands , events as windowsEvents } from "@hypr/plugin-windows" ;
9
17
import { Button } from "@hypr/ui/components/ui/button" ;
18
+ import { Spinner } from "@hypr/ui/components/ui/spinner" ;
10
19
import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from "@hypr/ui/components/ui/tooltip" ;
11
20
import { useOngoingSession , useSessions } from "@hypr/utils/contexts" ;
12
21
import { useTranscript } from "../hooks/useTranscript" ;
@@ -17,8 +26,13 @@ export function TranscriptView() {
17
26
const transcriptContainerRef = useRef < HTMLDivElement > ( null ) ;
18
27
19
28
const sessionId = useSessions ( ( s ) => s . currentSessionId ) ;
20
- const isInactive = useOngoingSession ( ( s ) => s . status === "inactive" ) ;
21
- const { showEmptyMessage, isEnhanced, hasTranscript } = useTranscriptWidget ( sessionId ) ;
29
+ const ongoingSession = useOngoingSession ( ( s ) => ( {
30
+ start : s . start ,
31
+ status : s . status ,
32
+ loading : s . loading ,
33
+ isInactive : s . status === "inactive" ,
34
+ } ) ) ;
35
+ const { showEmptyMessage, hasTranscript } = useTranscriptWidget ( sessionId ) ;
22
36
const { isLive, words } = useTranscript ( sessionId ) ;
23
37
24
38
const handleCopyAll = ( ) => {
@@ -66,6 +80,12 @@ export function TranscriptView() {
66
80
}
67
81
} ;
68
82
83
+ const handleStartRecording = ( ) => {
84
+ if ( sessionId && ongoingSession . status === "inactive" ) {
85
+ ongoingSession . start ( sessionId ) ;
86
+ }
87
+ } ;
88
+
69
89
useEffect ( ( ) => {
70
90
const scrollToBottom = ( ) => {
71
91
requestAnimationFrame ( ( ) => {
@@ -95,7 +115,7 @@ export function TranscriptView() {
95
115
} , [ ] ) ;
96
116
97
117
return (
98
- < div className = "relative w-full h-full flex flex-col" >
118
+ < div className = "w-full h-full flex flex-col" >
99
119
< div className = "p-4 pb-0" >
100
120
< header className = "flex items-center gap-2 w-full" >
101
121
< div className = "flex-1 text-md font-medium" >
@@ -111,7 +131,7 @@ export function TranscriptView() {
111
131
</ div >
112
132
</ div >
113
133
< div className = "not-draggable flex items-center gap-2" >
114
- { ( audioExist . data && isInactive && hasTranscript && sessionId ) && (
134
+ { ( audioExist . data && ongoingSession . isInactive && hasTranscript && sessionId ) && (
115
135
< TooltipProvider key = "listen-recording-tooltip" >
116
136
< Tooltip >
117
137
< TooltipTrigger asChild >
@@ -150,40 +170,64 @@ export function TranscriptView() {
150
170
</ header >
151
171
</ div >
152
172
153
- { sessionId && (
154
- < div
155
- ref = { transcriptContainerRef }
156
- className = "flex-1 scrollbar-none px-4 flex flex-col gap-2 overflow-y-auto text-sm py-4"
157
- >
158
- < p className = "whitespace-pre-wrap" >
159
- { words . map ( ( word , i ) => (
160
- < span key = { `${ word . text } -${ i } ` } >
161
- { i > 0 ? " " : "" }
162
- { word . text }
163
- </ span >
164
- ) ) }
165
- </ p >
166
- { isLive && (
167
- < div className = "flex items-center gap-2 justify-center py-2 text-neutral-400" >
168
- < EarIcon size = { 14 } /> Listening... (there might be a delay)
169
- </ div >
170
- ) }
171
- </ div >
172
- ) }
173
+ { ! sessionId ? null : (
174
+ < div className = "flex-1" >
175
+ { showEmptyMessage
176
+ ? (
177
+ < div className = "h-full flex items-center justify-center" >
178
+ < div className = "text-neutral-500 font-medium text-center" >
179
+ < div className = "mb-6 text-neutral-600 flex items-center gap-1.5" >
180
+ < Button size = "sm" onClick = { handleStartRecording } disabled = { ongoingSession . loading } >
181
+ { ongoingSession . loading ? < Spinner color = "black" /> : (
182
+ < div className = "relative h-2 w-2 mr-2" >
183
+ < div className = "absolute inset-0 rounded-full bg-red-500" > </ div >
184
+ < div className = "absolute inset-0 rounded-full bg-red-400 animate-ping" > </ div >
185
+ </ div >
186
+ ) }
187
+ { ongoingSession . loading ? "Starting..." : "Start recording" }
188
+ </ Button >
189
+ < span className = "text-sm" > to see live transcript</ span >
190
+ </ div >
173
191
174
- { ! sessionId && (
175
- < div className = "absolute inset-0 backdrop-blur-sm bg-white/50 flex items-center justify-center" >
176
- < div className = "text-neutral-500 font-medium" > Session not found </ div >
177
- </ div >
178
- ) }
192
+ < div className = "flex items-center justify-center w-full max-w-[240px] mb-4" >
193
+ < div className = "h-px bg-neutral-200 flex-grow" > </ div >
194
+ < span className = "px-3 text-xs text- neutral-400 font-medium" > or </ span >
195
+ < div className = "h-px bg-neutral-200 flex-grow" > </ div >
196
+ </ div >
179
197
180
- { sessionId && showEmptyMessage && (
181
- < div className = "absolute inset-0 backdrop-blur-sm bg-white/50 flex items-center justify-center rounded-2xl" >
182
- < div className = "text-neutral-500 font-medium" >
183
- { isEnhanced
184
- ? "No transcript available"
185
- : "Meeting is not active" }
186
- </ div >
198
+ < div className = "flex flex-col gap-2" >
199
+ < Button variant = "outline" size = "sm" className = "hover:bg-neutral-100" disabled >
200
+ < UploadIcon size = { 14 } /> Upload recording{ " " }
201
+ < span className = "text-xs text-neutral-400 italic" > coming soon</ span >
202
+ </ Button >
203
+ < Button variant = "outline" size = "sm" className = "hover:bg-neutral-100" disabled >
204
+ < ClipboardIcon size = { 14 } /> Paste transcript{ " " }
205
+ < span className = "text-xs text-neutral-400 italic" > coming soon</ span >
206
+ </ Button >
207
+ </ div >
208
+ </ div >
209
+ </ div >
210
+ )
211
+ : (
212
+ < div
213
+ ref = { transcriptContainerRef }
214
+ className = "h-full scrollbar-none px-4 flex flex-col gap-2 overflow-y-auto text-sm py-4"
215
+ >
216
+ < p className = "whitespace-pre-wrap" >
217
+ { words . map ( ( word , i ) => (
218
+ < span key = { `${ word . text } -${ i } ` } >
219
+ { i > 0 ? " " : "" }
220
+ { word . text }
221
+ </ span >
222
+ ) ) }
223
+ </ p >
224
+ { isLive && (
225
+ < div className = "flex items-center gap-2 justify-center py-2 text-neutral-400" >
226
+ < EarIcon size = { 14 } /> Listening... (there might be a delay)
227
+ </ div >
228
+ ) }
229
+ </ div >
230
+ ) }
187
231
</ div >
188
232
) }
189
233
</ div >
0 commit comments