Fix lint errors and improve chat UI

Lint fixes:
- Move HIDDEN_EVENT_TYPES constant outside component to fix useMemo dependency
- Add eslint-disable comments for img elements using blob URLs

Chat widget improvements:
- Add view and download buttons with loading and success indicators
- Click image to open in new tab, download button to save file
- Show check icon after successful download

Chat history fixes:
- Fix title to "Histórico de chat" with proper accents
- Change agent icon from Headphones to MessageCircle
- Change agent icon background from primary to gray

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-07 03:39:14 -03:00
parent f0882c612f
commit ebeda62cfb
3 changed files with 74 additions and 23 deletions

View file

@ -1,5 +1,6 @@
"use client"
import Image from "next/image"
import { useCallback, useEffect, useRef, useState } from "react"
import { useAction, useMutation, useQuery } from "convex/react"
import type { Id } from "@/convex/_generated/dataModel"
@ -30,6 +31,8 @@ import {
Image as ImageIcon,
Download,
ExternalLink,
Eye,
Check,
} from "lucide-react"
const MAX_MESSAGE_LENGTH = 4000
@ -122,9 +125,17 @@ function MessageAttachment({ attachment }: { attachment: ChatAttachment }) {
}, [attachment.storageId, getFileUrl])
const isImage = attachment.type?.startsWith("image/")
const [downloading, setDownloading] = useState(false)
const [downloaded, setDownloaded] = useState(false)
const handleView = () => {
if (!url) return
window.open(url, "_blank", "noopener,noreferrer")
}
const handleDownload = async () => {
if (!url) return
if (!url || downloading) return
setDownloading(true)
try {
const response = await fetch(url)
const blob = await response.blob()
@ -136,8 +147,12 @@ function MessageAttachment({ attachment }: { attachment: ChatAttachment }) {
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(downloadUrl)
setDownloaded(true)
setTimeout(() => setDownloaded(false), 2000)
} catch (error) {
toast.error("Erro ao baixar arquivo")
} finally {
setDownloading(false)
}
}
@ -151,31 +166,67 @@ function MessageAttachment({ attachment }: { attachment: ChatAttachment }) {
if (isImage && url) {
return (
<button
onClick={handleDownload}
className="group relative overflow-hidden rounded-lg border border-slate-200"
>
<div className="group relative overflow-hidden rounded-lg border border-slate-200">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={url}
alt={attachment.name}
className="size-16 object-cover"
className="size-16 cursor-pointer object-cover"
onClick={handleView}
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 transition-opacity group-hover:opacity-100">
<Download className="size-4 text-white" />
<div className="absolute inset-0 flex items-center justify-center gap-1 bg-black/50 opacity-0 transition-opacity group-hover:opacity-100">
<button
onClick={handleView}
className="flex size-6 items-center justify-center rounded-full bg-white/20 hover:bg-white/30"
title="Visualizar"
>
<Eye className="size-3.5 text-white" />
</button>
<button
onClick={handleDownload}
disabled={downloading}
className="flex size-6 items-center justify-center rounded-full bg-white/20 hover:bg-white/30"
title="Baixar"
>
{downloading ? (
<Spinner className="size-3 text-white" />
) : downloaded ? (
<Check className="size-3.5 text-emerald-400" />
) : (
<Download className="size-3.5 text-white" />
)}
</button>
</div>
</button>
</div>
)
}
return (
<button
onClick={handleDownload}
className="flex items-center gap-2 rounded-lg border border-slate-200 bg-slate-50 px-2 py-1.5 text-xs hover:bg-slate-100"
>
<div className="flex items-center gap-1.5 rounded-lg border border-slate-200 bg-slate-50 px-2 py-1.5 text-xs">
<FileText className="size-4 text-slate-500" />
<span className="max-w-[80px] truncate text-slate-700">{attachment.name}</span>
<Download className="size-3 text-slate-400" />
</button>
<button
onClick={handleView}
className="rounded p-0.5 hover:bg-slate-200"
title="Visualizar"
>
<Eye className="size-3 text-slate-400" />
</button>
<button
onClick={handleDownload}
disabled={downloading}
className="rounded p-0.5 hover:bg-slate-200"
title="Baixar"
>
{downloading ? (
<Spinner className="size-3 text-slate-400" />
) : downloaded ? (
<Check className="size-3 text-emerald-500" />
) : (
<Download className="size-3 text-slate-400" />
)}
</button>
</div>
)
}
@ -598,6 +649,7 @@ export function ChatWidget() {
{attachments.map((file, index) => (
<div key={index} className="group relative">
{file.type?.startsWith("image/") && file.previewUrl ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={file.previewUrl}
alt={file.name}