Ajusta player de audio e permite seek
All checks were successful
CI/CD Web + Desktop / Detect changes (push) Successful in 5s
CI/CD Web + Desktop / Deploy (VPS Linux) (push) Successful in 4m9s
CI/CD Web + Desktop / Deploy Convex functions (push) Has been skipped
Quality Checks / Lint, Test and Build (push) Successful in 5m16s

This commit is contained in:
rever-tecnologia 2025-12-19 16:08:00 -03:00
parent d125930cf6
commit bc67dc01ef
4 changed files with 80 additions and 33 deletions

View file

@ -133,7 +133,7 @@ function AudioWaveform({
}) {
const playedBars = Math.round(progress * peaks.length)
return (
<div className="flex h-8 items-center gap-[2px]">
<div className="flex h-8 items-center gap-[2px] overflow-hidden">
{peaks.map((value, index) => {
const height = Math.max(4, Math.round(value * 24))
const played = index <= playedBars
@ -191,7 +191,7 @@ function AudioAttachmentPlayer({
const decoded = await audioContext.decodeAudioData(buffer)
await audioContext.close()
if (!cancelled) {
setPeaks(extractPeaks(decoded))
setPeaks(extractPeaks(decoded, 36))
}
} catch (error) {
console.error("Falha ao gerar waveform:", error)
@ -246,10 +246,11 @@ function AudioAttachmentPlayer({
const progress = duration > 0 ? currentTime / duration : 0
const sizeLabel = formatAttachmentSize(size)
const canSeek = duration > 0
return (
<div
className={`flex items-center gap-3 rounded-2xl border px-3 py-2 ${
className={`flex w-full max-w-[280px] items-center gap-3 rounded-2xl border px-3 py-2 ${
isAgent ? "border-white/10 bg-white/10 text-white" : "border-slate-200 bg-white text-slate-900"
}`}
>
@ -264,14 +265,34 @@ function AudioAttachmentPlayer({
{isPlaying ? <Pause className="size-4" /> : <Play className="size-4" />}
</button>
<div className="flex-1">
{isLoadingWaveform ? (
<div className={`h-8 rounded-lg ${isAgent ? "bg-white/10" : "bg-slate-100"}`} />
) : peaks.length > 0 ? (
<AudioWaveform peaks={peaks} progress={progress} isAgent={isAgent} />
) : (
<div className={`h-8 rounded-lg ${isAgent ? "bg-white/10" : "bg-slate-100"}`} />
)}
<div className="flex-1 min-w-0">
<button
type="button"
onPointerDown={(event) => {
if (!audioRef.current || duration <= 0) return
const rect = event.currentTarget.getBoundingClientRect()
if (rect.width <= 0) return
const ratio = (event.clientX - rect.left) / rect.width
const clamped = Math.min(1, Math.max(0, ratio))
const targetTime = clamped * duration
audioRef.current.currentTime = targetTime
setCurrentTime(targetTime)
}}
aria-label="Buscar posição do áudio"
className={[
"w-full rounded-lg bg-transparent p-0 text-left transition-colors focus-visible:outline-none",
canSeek ? "cursor-pointer" : "cursor-default",
canSeek ? (isAgent ? "hover:bg-white/10" : "hover:bg-slate-100") : "",
].join(" ")}
>
{isLoadingWaveform ? (
<div className={`h-8 rounded-lg ${isAgent ? "bg-white/10" : "bg-slate-100"}`} />
) : peaks.length > 0 ? (
<AudioWaveform peaks={peaks} progress={progress} isAgent={isAgent} />
) : (
<div className={`h-8 rounded-lg ${isAgent ? "bg-white/10" : "bg-slate-100"}`} />
)}
</button>
<div className={`mt-1 flex items-center justify-between text-[10px] ${isAgent ? "text-white/60" : "text-slate-400"}`}>
<span>{formatDuration(currentTime)}</span>
<span className="truncate">{sizeLabel ?? formatDuration(duration)}</span>
@ -282,7 +303,9 @@ function AudioAttachmentPlayer({
type="button"
onClick={handleDownload}
className={`flex size-8 items-center justify-center rounded-md ${
isAgent ? "text-white/70 hover:text-white" : "text-slate-500 hover:text-slate-700"
isAgent
? "text-white/70 hover:bg-white/10 hover:text-white"
: "text-slate-500 hover:bg-slate-100 hover:text-slate-700"
}`}
aria-label="Baixar áudio"
>
@ -1147,12 +1170,12 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
return (
<div
key={att.storageId}
className="flex items-center gap-2 rounded-lg bg-slate-100 px-2 py-1 text-xs"
className="flex w-full items-center gap-2 rounded-lg bg-slate-100 px-2 py-1 text-xs"
>
<audio controls src={att.previewUrl} className="h-7 w-40" />
<audio controls src={att.previewUrl} className="h-8 w-full min-w-0" />
<button
onClick={() => handleRemoveAttachment(att.storageId)}
className="flex size-6 items-center justify-center rounded-full text-slate-500 hover:bg-slate-200"
className="flex size-6 shrink-0 items-center justify-center rounded-full text-slate-500 hover:bg-slate-200"
aria-label="Remover áudio"
>
<X className="size-3" />

View file

@ -42,7 +42,7 @@ function AudioWaveform({
}) {
const playedBars = Math.round(progress * peaks.length)
return (
<div className="flex h-9 items-center gap-[2px]">
<div className="flex h-9 items-center gap-[2px] overflow-hidden">
{peaks.map((value, index) => {
const height = Math.max(4, Math.round(value * 28))
const played = index <= playedBars
@ -100,7 +100,7 @@ function AudioAttachmentPlayer({
const decoded = await audioContext.decodeAudioData(buffer)
await audioContext.close()
if (!cancelled) {
setPeaks(extractPeaks(decoded))
setPeaks(extractPeaks(decoded, 36))
}
} catch (error) {
console.error("Falha ao gerar waveform:", error)
@ -155,11 +155,22 @@ function AudioAttachmentPlayer({
const progress = duration > 0 ? currentTime / duration : 0
const sizeLabel = formatAttachmentSize(size)
const canSeek = duration > 0
const handleSeek = (event: { currentTarget: HTMLButtonElement; clientX: number }) => {
if (!audioRef.current || duration <= 0) return
const rect = event.currentTarget.getBoundingClientRect()
if (rect.width <= 0) return
const ratio = (event.clientX - rect.left) / rect.width
const clamped = Math.min(1, Math.max(0, ratio))
const targetTime = clamped * duration
audioRef.current.currentTime = targetTime
setCurrentTime(targetTime)
}
return (
<div
className={cn(
"flex items-center gap-3 rounded-2xl border px-3 py-2",
"flex w-full max-w-[280px] items-center gap-3 rounded-2xl border px-3 py-2",
tone === "dark"
? "border-white/10 bg-white/10 text-white"
: "border-slate-200 bg-white text-slate-900"
@ -179,14 +190,25 @@ function AudioAttachmentPlayer({
{isPlaying ? <Pause className="size-4" /> : <Play className="size-4" />}
</Button>
<div className="flex-1">
{isLoadingWaveform ? (
<div className={cn("h-9 rounded-lg", tone === "dark" ? "bg-white/10" : "bg-slate-100")} />
) : peaks.length > 0 ? (
<AudioWaveform peaks={peaks} progress={progress} tone={tone} />
) : (
<div className={cn("h-9 rounded-lg", tone === "dark" ? "bg-white/10" : "bg-slate-100")} />
)}
<div className="flex-1 min-w-0">
<button
type="button"
onPointerDown={handleSeek}
aria-label="Buscar posição do áudio"
className={cn(
"w-full rounded-lg bg-transparent p-0 text-left transition-colors focus-visible:outline-none",
canSeek ? "cursor-pointer" : "cursor-default",
canSeek && (tone === "dark" ? "hover:bg-white/10" : "hover:bg-slate-100")
)}
>
{isLoadingWaveform ? (
<div className={cn("h-9 rounded-lg", tone === "dark" ? "bg-white/10" : "bg-slate-100")} />
) : peaks.length > 0 ? (
<AudioWaveform peaks={peaks} progress={progress} tone={tone} />
) : (
<div className={cn("h-9 rounded-lg", tone === "dark" ? "bg-white/10" : "bg-slate-100")} />
)}
</button>
<div
className={cn(
"mt-1 flex items-center justify-between text-[10px]",
@ -204,7 +226,9 @@ function AudioAttachmentPlayer({
size="icon"
className={cn(
"size-8",
tone === "dark" ? "text-white/70 hover:text-white" : "text-slate-500 hover:text-slate-700"
tone === "dark"
? "text-white/70 hover:bg-white/10 hover:text-white"
: "text-slate-500 hover:bg-slate-100 hover:text-slate-700"
)}
onClick={handleDownload}
aria-label="Baixar áudio"

View file

@ -826,12 +826,12 @@ export function ChatWidget() {
return (
<div
key={index}
className="flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-2 py-1"
className="flex w-full items-center gap-2 rounded-lg border border-slate-200 bg-white px-2 py-1"
>
<audio controls src={file.previewUrl} className="h-8 w-40" />
<audio controls src={file.previewUrl} className="h-9 w-full min-w-0" />
<button
onClick={() => removeAttachment(index)}
className="flex size-6 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100"
className="flex size-6 shrink-0 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100"
aria-label="Remover áudio"
>
<X className="size-3" />

View file

@ -547,8 +547,8 @@ export function TicketChatPanel({ ticketId }: TicketChatPanelProps) {
)}
{pendingAudio && (
<div className="mb-2 flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-2 py-1">
<audio controls src={pendingAudio.previewUrl} className="h-8 w-44" />
<div className="mb-2 flex w-full items-center gap-2 rounded-lg border border-slate-200 bg-white px-2 py-1">
<audio controls src={pendingAudio.previewUrl} className="h-9 w-full min-w-0" />
<button
type="button"
onClick={() => {
@ -557,7 +557,7 @@ export function TicketChatPanel({ ticketId }: TicketChatPanelProps) {
}
setPendingAudio(null)
}}
className="flex size-7 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100"
className="flex size-7 shrink-0 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100"
aria-label="Remover áudio"
>
<X className="size-3.5" />