Match desktop chat layout with web version
- Change agent icon from Headphones to MessageCircle - Adjust avatar size to size-7 and icons to size-3.5 - Reposition attach button next to send button (textarea -> attach -> send) - Add Online indicator in header with animated green dot - Implement minimized state (collapsed view like web) - Hide web chat widget when running in Tauri context (avoid duplicate) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7e270bcd3b
commit
229c9aa1c7
2 changed files with 65 additions and 24 deletions
|
|
@ -4,7 +4,7 @@ import { listen } from "@tauri-apps/api/event"
|
||||||
import { Store } from "@tauri-apps/plugin-store"
|
import { Store } from "@tauri-apps/plugin-store"
|
||||||
import { appLocalDataDir, join } from "@tauri-apps/api/path"
|
import { appLocalDataDir, join } from "@tauri-apps/api/path"
|
||||||
import { open } from "@tauri-apps/plugin-dialog"
|
import { open } from "@tauri-apps/plugin-dialog"
|
||||||
import { Send, X, Minus, Loader2, Headphones, Paperclip, FileText, Image as ImageIcon, File, User } from "lucide-react"
|
import { Send, X, Loader2, MessageCircle, Paperclip, FileText, Image as ImageIcon, File, User, ChevronUp, Minimize2 } from "lucide-react"
|
||||||
import type { ChatMessage, ChatMessagesResponse, SendMessageResponse } from "./types"
|
import type { ChatMessage, ChatMessagesResponse, SendMessageResponse } from "./types"
|
||||||
|
|
||||||
const STORE_FILENAME = "machine-agent.json"
|
const STORE_FILENAME = "machine-agent.json"
|
||||||
|
|
@ -47,6 +47,7 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
const [ticketInfo, setTicketInfo] = useState<{ ref: number; subject: string; agentName: string } | null>(null)
|
const [ticketInfo, setTicketInfo] = useState<{ ref: number; subject: string; agentName: string } | null>(null)
|
||||||
const [hasSession, setHasSession] = useState(false)
|
const [hasSession, setHasSession] = useState(false)
|
||||||
const [pendingAttachments, setPendingAttachments] = useState<UploadedAttachment[]>([])
|
const [pendingAttachments, setPendingAttachments] = useState<UploadedAttachment[]>([])
|
||||||
|
const [isMinimized, setIsMinimized] = useState(false)
|
||||||
|
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
const lastFetchRef = useRef<number>(0)
|
const lastFetchRef = useRef<number>(0)
|
||||||
|
|
@ -292,7 +293,7 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMinimize = () => {
|
const handleMinimize = () => {
|
||||||
invoke("minimize_chat_window", { ticketId })
|
setIsMinimized(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
|
@ -326,7 +327,34 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
if (!hasSession) {
|
if (!hasSession) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col items-center justify-center bg-white p-4">
|
<div className="flex h-screen flex-col items-center justify-center bg-white p-4">
|
||||||
<p className="text-sm text-slate-500">Nenhuma sessão de chat ativa</p>
|
<p className="text-sm text-slate-500">Nenhuma sessao de chat ativa</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Versao minimizada (recolhida)
|
||||||
|
if (isMinimized) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen flex-col bg-white">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsMinimized(false)}
|
||||||
|
data-tauri-drag-region
|
||||||
|
className="flex w-full items-center gap-3 border-b border-slate-200 bg-slate-50 px-4 py-3 text-left hover:bg-slate-100"
|
||||||
|
>
|
||||||
|
<div className="flex size-10 items-center justify-center rounded-full bg-black text-white">
|
||||||
|
<MessageCircle className="size-5" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<p className="text-sm font-semibold text-slate-900">
|
||||||
|
Chat #{ticketInfo?.ref}
|
||||||
|
</p>
|
||||||
|
<span className="size-2 rounded-full bg-emerald-500" />
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-500">Clique para expandir</p>
|
||||||
|
</div>
|
||||||
|
<ChevronUp className="size-4 text-slate-400" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -340,15 +368,19 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex size-10 items-center justify-center rounded-full bg-black text-white">
|
<div className="flex size-10 items-center justify-center rounded-full bg-black text-white">
|
||||||
<Headphones className="size-5" />
|
<MessageCircle className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-semibold text-slate-900">
|
<div className="flex items-center gap-2">
|
||||||
{ticketInfo?.agentName ?? "Suporte"}
|
<p className="text-sm font-semibold text-slate-900">Chat</p>
|
||||||
</p>
|
<span className="flex items-center gap-1.5 text-xs text-emerald-600">
|
||||||
|
<span className="size-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||||
|
Online
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{ticketInfo && (
|
{ticketInfo && (
|
||||||
<p className="text-xs text-slate-500">
|
<p className="text-xs text-slate-500">
|
||||||
Chamado #{ticketInfo.ref}
|
#{ticketInfo.ref} - {ticketInfo.agentName ?? "Suporte"}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -356,13 +388,15 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={handleMinimize}
|
onClick={handleMinimize}
|
||||||
className="rounded p-1.5 text-slate-400 hover:bg-slate-200 hover:text-slate-600"
|
className="rounded-md p-1.5 text-slate-500 hover:bg-slate-100"
|
||||||
|
title="Minimizar"
|
||||||
>
|
>
|
||||||
<Minus className="size-4" />
|
<Minimize2 className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
className="rounded p-1.5 text-slate-400 hover:bg-slate-200 hover:text-slate-600"
|
className="rounded-md p-1.5 text-slate-500 hover:bg-slate-100"
|
||||||
|
title="Fechar"
|
||||||
>
|
>
|
||||||
<X className="size-4" />
|
<X className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -393,11 +427,11 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
>
|
>
|
||||||
{/* Avatar */}
|
{/* Avatar */}
|
||||||
<div
|
<div
|
||||||
className={`flex size-8 shrink-0 items-center justify-center rounded-full ${
|
className={`flex size-7 shrink-0 items-center justify-center rounded-full ${
|
||||||
isAgent ? "bg-black text-white" : "bg-slate-200 text-slate-600"
|
isAgent ? "bg-black text-white" : "bg-slate-200 text-slate-600"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isAgent ? <Headphones className="size-4" /> : <User className="size-4" />}
|
{isAgent ? <MessageCircle className="size-3.5" /> : <User className="size-3.5" />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bubble */}
|
{/* Bubble */}
|
||||||
|
|
@ -474,10 +508,18 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-end gap-2">
|
<div className="flex items-end gap-2">
|
||||||
|
<textarea
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder="Digite sua mensagem..."
|
||||||
|
className="max-h-24 min-h-[36px] flex-1 resize-none rounded-lg border border-slate-200 px-3 py-2 text-sm focus:border-slate-400 focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||||
|
rows={1}
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleAttach}
|
onClick={handleAttach}
|
||||||
disabled={isUploading || isSending}
|
disabled={isUploading || isSending}
|
||||||
className="flex size-10 items-center justify-center rounded-lg border border-slate-300 text-slate-500 transition hover:bg-slate-100 disabled:opacity-50"
|
className="flex size-9 items-center justify-center rounded-lg text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 disabled:opacity-50"
|
||||||
title="Anexar arquivo"
|
title="Anexar arquivo"
|
||||||
>
|
>
|
||||||
{isUploading ? (
|
{isUploading ? (
|
||||||
|
|
@ -486,18 +528,10 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
|
||||||
<Paperclip className="size-4" />
|
<Paperclip className="size-4" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<textarea
|
|
||||||
value={inputValue}
|
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
placeholder="Digite sua mensagem..."
|
|
||||||
className="max-h-24 min-h-[40px] flex-1 resize-none rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-1 focus:ring-black"
|
|
||||||
rows={1}
|
|
||||||
/>
|
|
||||||
<button
|
<button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={(!inputValue.trim() && pendingAttachments.length === 0) || isSending}
|
disabled={(!inputValue.trim() && pendingAttachments.length === 0) || isSending}
|
||||||
className="flex size-10 items-center justify-center rounded-lg bg-black text-white transition hover:bg-black/90 disabled:opacity-50"
|
className="flex size-9 items-center justify-center rounded-lg bg-black text-white transition hover:bg-black/90 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isSending ? (
|
{isSending ? (
|
||||||
<Loader2 className="size-4 animate-spin" />
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,10 @@ function MessageAttachment({ attachment }: { attachment: ChatAttachment }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatWidget() {
|
export function ChatWidget() {
|
||||||
|
// Detectar se esta rodando no Tauri (desktop) - nesse caso, nao renderizar
|
||||||
|
// pois o chat nativo do Tauri ja esta disponivel
|
||||||
|
const isTauriContext = typeof window !== "undefined" && "__TAURI__" in window
|
||||||
|
|
||||||
const { convexUserId } = useAuth()
|
const { convexUserId } = useAuth()
|
||||||
const viewerId = convexUserId ?? null
|
const viewerId = convexUserId ?? null
|
||||||
|
|
||||||
|
|
@ -460,7 +464,10 @@ export function ChatWidget() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Não mostrar se não logado ou sem sessões
|
// Nao mostrar se esta no Tauri (usa o chat nativo)
|
||||||
|
if (isTauriContext) return null
|
||||||
|
|
||||||
|
// Nao mostrar se nao logado ou sem sessoes
|
||||||
if (!viewerId) return null
|
if (!viewerId) return null
|
||||||
if (!activeSessions || activeSessions.length === 0) return null
|
if (!activeSessions || activeSessions.length === 0) return null
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue