Refactor: Make UI responsive
This commit is contained in:
@@ -50,8 +50,8 @@ export const ChatHeader = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="border-b border-border bg-card/50 backdrop-blur-sm px-6 py-4 flex items-center justify-between">
|
<header className="border-b border-border bg-card/50 backdrop-blur-sm px-3 md:px-6 py-3 md:py-4 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-2 md:gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-2 h-2 rounded-full bg-primary animate-pulse-glow" />
|
<div className="w-2 h-2 rounded-full bg-primary animate-pulse-glow" />
|
||||||
<span className="text-sm text-muted-foreground">Online</span>
|
<span className="text-sm text-muted-foreground">Online</span>
|
||||||
@@ -60,9 +60,9 @@ export const ChatHeader = ({
|
|||||||
{showModelSelector && onModelChange && (
|
{showModelSelector && onModelChange && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" className="gap-2 glass-effect">
|
<Button variant="outline" className="gap-1 md:gap-2 glass-effect text-xs md:text-sm">
|
||||||
<span className="font-medium">{selectedModel}</span>
|
<span className="font-medium truncate max-w-[80px] md:max-w-none">{selectedModel}</span>
|
||||||
<ChevronDown className="w-4 h-4" />
|
<ChevronDown className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="glass-effect bg-popover border-border z-50">
|
<DropdownMenuContent align="start" className="glass-effect bg-popover border-border z-50">
|
||||||
@@ -84,11 +84,11 @@ export const ChatHeader = ({
|
|||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="gap-2 glass-effect"
|
className="gap-1 md:gap-2 glass-effect text-xs md:text-sm"
|
||||||
onClick={() => setTempSystemPrompt(systemPrompt)}
|
onClick={() => setTempSystemPrompt(systemPrompt)}
|
||||||
>
|
>
|
||||||
<UserCog className="w-4 h-4" />
|
<UserCog className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
<span className="font-medium">Personalidade</span>
|
<span className="hidden md:inline font-medium">Personalidade</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -120,7 +120,7 @@ export const ChatHeader = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="hidden md:block text-sm text-muted-foreground">
|
||||||
{showModelSelector ? "Modelo de linguagem avançado" : "Sistema HGTX Codex"}
|
{showModelSelector ? "Modelo de linguagem avançado" : "Sistema HGTX Codex"}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const Layout = ({ children, activeTab, onTabChange }: LayoutProps) => {
|
|||||||
<div className="flex h-screen w-full bg-background overflow-hidden">
|
<div className="flex h-screen w-full bg-background overflow-hidden">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
{!isSidebarCollapsed ? (
|
{!isSidebarCollapsed ? (
|
||||||
<aside className="w-72 bg-sidebar border-r border-sidebar-border flex flex-col">
|
<aside className="hidden md:flex md:w-72 bg-sidebar border-r border-sidebar-border flex-col">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="p-6 border-b border-sidebar-border flex items-center justify-between">
|
<div className="p-6 border-b border-sidebar-border flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -81,7 +81,7 @@ export const Layout = ({ children, activeTab, onTabChange }: LayoutProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
) : (
|
) : (
|
||||||
<aside className="w-16 bg-sidebar border-r border-sidebar-border flex flex-col">
|
<aside className="hidden md:flex md:w-16 bg-sidebar border-r border-sidebar-border flex-col">
|
||||||
{/* Collapsed Header with Expand Button */}
|
{/* Collapsed Header with Expand Button */}
|
||||||
<div className="p-3 border-b border-sidebar-border space-y-2">
|
<div className="p-3 border-b border-sidebar-border space-y-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -135,7 +135,29 @@ export const Layout = ({ children, activeTab, onTabChange }: LayoutProps) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<main className="flex-1 flex flex-col overflow-hidden">{children}</main>
|
<main className="flex-1 flex flex-col overflow-hidden w-full">{children}</main>
|
||||||
|
|
||||||
|
{/* Mobile Navigation */}
|
||||||
|
<nav className="md:hidden fixed bottom-0 left-0 right-0 bg-sidebar border-t border-sidebar-border flex items-center justify-around py-2 z-50">
|
||||||
|
{tabs.map((tab) => {
|
||||||
|
const Icon = tab.icon;
|
||||||
|
const isActive = activeTab === tab.id;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => onTabChange(tab.id)}
|
||||||
|
className={`flex flex-col items-center gap-1 px-3 py-2 rounded-lg transition-all ${
|
||||||
|
isActive
|
||||||
|
? "text-sidebar-accent-foreground"
|
||||||
|
: "text-sidebar-foreground/70"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon className={`w-5 h-5 ${isActive ? "text-primary" : ""}`} />
|
||||||
|
<span className="text-xs font-medium">{tab.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -113,31 +113,33 @@ export const AgentView = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full bg-background">
|
<div className="flex flex-col h-full bg-background pb-16 md:pb-0">
|
||||||
<div className="p-6 border-b border-border">
|
<div className="p-3 md:p-6 border-b border-border">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 mb-4">
|
||||||
<h1 className="text-3xl font-bold text-foreground">Agente de Parecer Jurídico</h1>
|
<h1 className="text-xl md:text-3xl font-bold text-foreground">Agente de Parecer Jurídico</h1>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2 w-full md:w-auto">
|
||||||
<Button onClick={() => setShowSearch(true)} variant="outline" className="gap-2">
|
<Button onClick={() => setShowSearch(true)} variant="outline" className="gap-1 md:gap-2 flex-1 md:flex-none text-xs md:text-sm">
|
||||||
<Search className="w-4 h-4" />
|
<Search className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
Pesquisar Base de Pareceres
|
<span className="hidden sm:inline">Pesquisar Base</span>
|
||||||
|
<span className="sm:hidden">Base</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleNewOpinion} className="gap-2">
|
<Button onClick={handleNewOpinion} className="gap-1 md:gap-2 flex-1 md:flex-none text-xs md:text-sm">
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
Novo Parecer
|
<span className="hidden sm:inline">Novo Parecer</span>
|
||||||
|
<span className="sm:hidden">Novo</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex flex-col md:flex-row items-stretch md:items-center gap-2 md:gap-4">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Buscar pareceres..."
|
placeholder="Buscar pareceres..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="max-w-sm"
|
className="w-full md:max-w-sm text-sm"
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 justify-between md:justify-start">
|
||||||
<span className="text-sm text-muted-foreground">Itens por página:</span>
|
<span className="text-xs md:text-sm text-muted-foreground whitespace-nowrap">Itens:</span>
|
||||||
<Select
|
<Select
|
||||||
value={itemsPerPage.toString()}
|
value={itemsPerPage.toString()}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
@@ -145,7 +147,7 @@ export const AgentView = () => {
|
|||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-20">
|
<SelectTrigger className="w-16 md:w-20">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -159,42 +161,42 @@ export const AgentView = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto p-6">
|
<div className="flex-1 overflow-auto p-3 md:p-6">
|
||||||
<div className="border rounded-lg">
|
<div className="border rounded-lg overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>
|
<TableHead className="min-w-[200px]">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleSort("title")}
|
onClick={() => handleSort("title")}
|
||||||
className="flex items-center gap-1 font-semibold"
|
className="flex items-center gap-1 font-semibold text-xs md:text-sm p-1 md:p-2"
|
||||||
>
|
>
|
||||||
Título
|
Título
|
||||||
<ArrowUpDown className="w-4 h-4" />
|
<ArrowUpDown className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead>
|
<TableHead className="hidden md:table-cell">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleSort("category")}
|
onClick={() => handleSort("category")}
|
||||||
className="flex items-center gap-1 font-semibold"
|
className="flex items-center gap-1 font-semibold text-sm"
|
||||||
>
|
>
|
||||||
Categoria
|
Categoria
|
||||||
<ArrowUpDown className="w-4 h-4" />
|
<ArrowUpDown className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead>
|
<TableHead className="hidden sm:table-cell">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleSort("createdAt")}
|
onClick={() => handleSort("createdAt")}
|
||||||
className="flex items-center gap-1 font-semibold"
|
className="flex items-center gap-1 font-semibold text-xs md:text-sm p-1 md:p-2"
|
||||||
>
|
>
|
||||||
Data de Criação
|
Data
|
||||||
<ArrowUpDown className="w-4 h-4" />
|
<ArrowUpDown className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead className="text-center">Ações</TableHead>
|
<TableHead className="text-center text-xs md:text-sm">Ações</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
@@ -209,29 +211,30 @@ export const AgentView = () => {
|
|||||||
) : (
|
) : (
|
||||||
paginatedOpinions.map((opinion) => (
|
paginatedOpinions.map((opinion) => (
|
||||||
<TableRow key={opinion.id}>
|
<TableRow key={opinion.id}>
|
||||||
<TableCell className="font-medium">{opinion.title}</TableCell>
|
<TableCell className="font-medium text-xs md:text-sm">{opinion.title}</TableCell>
|
||||||
<TableCell>{opinion.category || "-"}</TableCell>
|
<TableCell className="hidden md:table-cell text-sm">{opinion.category || "-"}</TableCell>
|
||||||
<TableCell>
|
<TableCell className="hidden sm:table-cell text-xs md:text-sm">
|
||||||
{new Date(opinion.createdAt).toLocaleDateString("pt-BR")}
|
{new Date(opinion.createdAt).toLocaleDateString("pt-BR")}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-1">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => handleViewOpinion(opinion)}
|
onClick={() => handleViewOpinion(opinion)}
|
||||||
title="Visualizar"
|
title="Visualizar"
|
||||||
|
className="h-7 w-7 md:h-9 md:w-9"
|
||||||
>
|
>
|
||||||
<Eye className="w-4 h-4" />
|
<Eye className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => handleDeleteOpinion(opinion.id)}
|
onClick={() => handleDeleteOpinion(opinion.id)}
|
||||||
className="text-destructive hover:text-destructive"
|
className="text-destructive hover:text-destructive h-7 w-7 md:h-9 md:w-9"
|
||||||
title="Excluir"
|
title="Excluir"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-3 h-3 md:w-4 md:h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -243,40 +246,55 @@ export const AgentView = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{totalPages > 1 && (
|
{totalPages > 1 && (
|
||||||
<div className="flex items-center justify-between mt-4">
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-2 mt-4">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-xs md:text-sm text-muted-foreground text-center sm:text-left">
|
||||||
Mostrando {startIndex + 1} a {Math.min(startIndex + itemsPerPage, sortedOpinions.length)} de{" "}
|
{startIndex + 1}-{Math.min(startIndex + itemsPerPage, sortedOpinions.length)} de {sortedOpinions.length}
|
||||||
{sortedOpinions.length} pareceres
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-1 md:gap-2 justify-center">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
|
className="text-xs md:text-sm px-2 md:px-4"
|
||||||
>
|
>
|
||||||
Anterior
|
<span className="hidden sm:inline">Anterior</span>
|
||||||
|
<span className="sm:hidden">Ant</span>
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
|
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => {
|
||||||
<Button
|
let page;
|
||||||
key={page}
|
if (totalPages <= 5) {
|
||||||
variant={currentPage === page ? "default" : "outline"}
|
page = i + 1;
|
||||||
size="sm"
|
} else if (currentPage <= 3) {
|
||||||
onClick={() => setCurrentPage(page)}
|
page = i + 1;
|
||||||
className="w-10"
|
} else if (currentPage >= totalPages - 2) {
|
||||||
>
|
page = totalPages - 4 + i;
|
||||||
{page}
|
} else {
|
||||||
</Button>
|
page = currentPage - 2 + i;
|
||||||
))}
|
}
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={page}
|
||||||
|
variant={currentPage === page ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCurrentPage(page)}
|
||||||
|
className="w-8 h-8 md:w-10 md:h-9 p-0 text-xs md:text-sm"
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
|
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
|
||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === totalPages}
|
||||||
|
className="text-xs md:text-sm px-2 md:px-4"
|
||||||
>
|
>
|
||||||
Próxima
|
<span className="hidden sm:inline">Próxima</span>
|
||||||
|
<span className="sm:hidden">Prox</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ Profissional responsável: IA Jurídica HGTX Codex`;
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-4xl max-h-[90vh]">
|
<DialogContent className="max-w-[95vw] md:max-w-4xl max-h-[90vh]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{selectedOpinion ? "Gerar Novo Modelo do Parecer" : "Novo Parecer Jurídico"}
|
{selectedOpinion ? "Gerar Novo Modelo do Parecer" : "Novo Parecer Jurídico"}
|
||||||
|
|||||||
@@ -270,22 +270,22 @@ export const AudioView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full pb-16 md:pb-0">
|
||||||
<ChatHeader />
|
<ChatHeader />
|
||||||
|
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="max-w-4xl mx-auto p-3 md:p-6">
|
||||||
<Tabs defaultValue="transcription" className="space-y-6">
|
<Tabs defaultValue="transcription" className="space-y-4 md:space-y-6">
|
||||||
<TabsList className="grid w-full grid-cols-4 glass-effect">
|
<TabsList className="grid w-full grid-cols-2 md:grid-cols-4 glass-effect text-xs md:text-sm">
|
||||||
<TabsTrigger value="transcription">
|
<TabsTrigger value="transcription" className="text-xs md:text-sm">
|
||||||
Transcrição
|
Transcrição
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="generation">Geração de Voz</TabsTrigger>
|
<TabsTrigger value="generation" className="text-xs md:text-sm">Geração</TabsTrigger>
|
||||||
<TabsTrigger value="transcription-history">
|
<TabsTrigger value="transcription-history" className="text-xs md:text-sm">
|
||||||
Histórico Transcrições
|
Hist. Trans.
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="audio-history">
|
<TabsTrigger value="audio-history" className="text-xs md:text-sm">
|
||||||
Histórico Áudios
|
Hist. Áud.
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
|
|||||||
@@ -95,19 +95,19 @@ export const BotView = ({ onStartChat }: BotViewProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col h-full overflow-hidden">
|
<div className="flex-1 flex flex-col h-full overflow-hidden pb-16 md:pb-0">
|
||||||
<div className="border-b border-border p-6">
|
<div className="border-b border-border p-3 md:p-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
<h2 className="text-xl md:text-2xl font-bold flex items-center gap-2">
|
||||||
<Bot className="w-6 h-6" />
|
<Bot className="w-5 h-5 md:w-6 md:h-6" />
|
||||||
Gerenciador de Bots
|
Gerenciador de Bots
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-muted-foreground mt-1">Crie e gerencie seus assistentes de IA</p>
|
<p className="text-sm md:text-base text-muted-foreground mt-1">Crie e gerencie seus assistentes de IA</p>
|
||||||
</div>
|
</div>
|
||||||
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button>
|
<Button className="w-full md:w-auto text-sm">
|
||||||
<Bot className="w-4 h-4 mr-2" />
|
<Bot className="w-4 h-4 mr-2" />
|
||||||
Criar Bot
|
Criar Bot
|
||||||
</Button>
|
</Button>
|
||||||
@@ -167,7 +167,7 @@ export const BotView = ({ onStartChat }: BotViewProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto p-6">
|
<div className="flex-1 overflow-auto p-3 md:p-6">
|
||||||
{bots.length === 0 ? (
|
{bots.length === 0 ? (
|
||||||
<Card className="max-w-2xl mx-auto mt-12">
|
<Card className="max-w-2xl mx-auto mt-12">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -190,45 +190,48 @@ export const BotView = ({ onStartChat }: BotViewProps) => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Nome</TableHead>
|
<TableHead className="text-xs md:text-sm">Nome</TableHead>
|
||||||
<TableHead>Modelo</TableHead>
|
<TableHead className="hidden md:table-cell text-sm">Modelo</TableHead>
|
||||||
<TableHead className="text-right">Ações</TableHead>
|
<TableHead className="text-right text-xs md:text-sm">Ações</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{currentBots.map((bot) => (
|
{currentBots.map((bot) => (
|
||||||
<TableRow key={bot.id}>
|
<TableRow key={bot.id}>
|
||||||
<TableCell className="font-medium">{bot.name}</TableCell>
|
<TableCell className="font-medium text-xs md:text-sm">{bot.name}</TableCell>
|
||||||
<TableCell>{bot.model}</TableCell>
|
<TableCell className="hidden md:table-cell text-sm">{bot.model}</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<div className="flex gap-2 justify-end">
|
<div className="flex gap-1 justify-end flex-wrap">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => onStartChat(bot)}
|
onClick={() => onStartChat(bot)}
|
||||||
|
className="text-xs md:text-sm"
|
||||||
>
|
>
|
||||||
<MessageSquare className="w-4 h-4 mr-2" />
|
<MessageSquare className="w-3 h-3 md:w-4 md:h-4 md:mr-2" />
|
||||||
Conversar
|
<span className="hidden md:inline">Conversar</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => openEditDialog(bot)}
|
onClick={() => openEditDialog(bot)}
|
||||||
|
className="text-xs md:text-sm"
|
||||||
>
|
>
|
||||||
<Edit className="w-4 h-4 mr-2" />
|
<Edit className="w-3 h-3 md:w-4 md:h-4 md:mr-2" />
|
||||||
Editar
|
<span className="hidden md:inline">Editar</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => openDeleteDialog(bot)}
|
onClick={() => openDeleteDialog(bot)}
|
||||||
|
className="text-xs md:text-sm"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
<Trash2 className="w-3 h-3 md:w-4 md:h-4 md:mr-2" />
|
||||||
Excluir
|
<span className="hidden md:inline">Excluir</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -92,19 +92,19 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-t border-border bg-card/50 backdrop-blur-sm p-4">
|
<div className="border-t border-border bg-card/50 backdrop-blur-sm p-2 md:p-4">
|
||||||
<div className="max-w-4xl mx-auto space-y-3">
|
<div className="max-w-4xl mx-auto space-y-2 md:space-y-3">
|
||||||
{/* Attached Files Preview */}
|
{/* Attached Files Preview */}
|
||||||
{attachedFiles.length > 0 && (
|
{attachedFiles.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-1 md:gap-2">
|
||||||
{attachedFiles.map((file, index) => (
|
{attachedFiles.map((file, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-muted border border-border text-sm"
|
className="flex items-center gap-1 md:gap-2 px-2 md:px-3 py-1 md:py-1.5 rounded-lg bg-muted border border-border text-xs md:text-sm"
|
||||||
>
|
>
|
||||||
<Paperclip className="w-3 h-3 text-primary" />
|
<Paperclip className="w-3 h-3 text-primary" />
|
||||||
<span className="font-medium">{file.name}</span>
|
<span className="font-medium truncate max-w-[100px] md:max-w-none">{file.name}</span>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="hidden md:inline text-xs text-muted-foreground">
|
||||||
({(file.size / 1024).toFixed(1)} KB)
|
({(file.size / 1024).toFixed(1)} KB)
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
@@ -121,12 +121,12 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Message Input and Buttons */}
|
{/* Message Input and Buttons */}
|
||||||
<div className="flex gap-2 items-end">
|
<div className="flex gap-1 md:gap-2 items-end">
|
||||||
<div className="flex-1 glass-effect rounded-xl overflow-hidden">
|
<div className="flex-1 glass-effect rounded-xl overflow-hidden">
|
||||||
{/* Resize Handle */}
|
{/* Resize Handle - Hidden on mobile */}
|
||||||
<div
|
<div
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
className={`w-full h-6 flex items-center justify-center cursor-ns-resize hover:bg-primary/10 transition-colors ${
|
className={`hidden md:flex w-full h-6 items-center justify-center cursor-ns-resize hover:bg-primary/10 transition-colors ${
|
||||||
isDragging ? 'bg-primary/20' : ''
|
isDragging ? 'bg-primary/20' : ''
|
||||||
}`}
|
}`}
|
||||||
title="Arraste para cima para aumentar"
|
title="Arraste para cima para aumentar"
|
||||||
@@ -135,21 +135,21 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Textarea */}
|
{/* Textarea */}
|
||||||
<div className="px-3 pb-3">
|
<div className="px-2 md:px-3 pb-2 md:pb-3 pt-2 md:pt-0">
|
||||||
<Textarea
|
<Textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Digite sua mensagem..."
|
placeholder="Digite sua mensagem..."
|
||||||
style={{ height: `${textareaHeight}px` }}
|
style={{ height: window.innerWidth < 768 ? '80px' : `${textareaHeight}px` }}
|
||||||
className="resize-none border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 w-full p-0 overflow-y-auto"
|
className="resize-none border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 w-full p-0 overflow-y-auto text-sm md:text-base"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-1 md:gap-2">
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
@@ -161,24 +161,24 @@ export const ChatInput = ({ onSendMessage }: ChatInputProps) => {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-11 w-11 glass-effect"
|
className="h-9 w-9 md:h-11 md:w-11 glass-effect"
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
title="Anexar arquivo"
|
title="Anexar arquivo"
|
||||||
>
|
>
|
||||||
<Paperclip className="w-5 h-5" />
|
<Paperclip className="w-4 h-4 md:w-5 md:h-5" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-11 w-11 cyber-glow"
|
className="h-9 w-9 md:h-11 md:w-11 cyber-glow"
|
||||||
disabled={!message.trim() && attachedFiles.length === 0}
|
disabled={!message.trim() && attachedFiles.length === 0}
|
||||||
>
|
>
|
||||||
<Send className="w-5 h-5" />
|
<Send className="w-4 h-4 md:w-5 md:h-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-muted-foreground text-center">
|
<p className="hidden md:block text-xs text-muted-foreground text-center">
|
||||||
Pressione Enter para enviar, Shift + Enter para nova linha • Máx. 10MB por arquivo
|
Pressione Enter para enviar, Shift + Enter para nova linha • Máx. 10MB por arquivo
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,22 +30,22 @@ export const ChatMessage = ({ role, content, model, attachments }: ChatMessagePr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex gap-4 py-6 px-6 group hover:bg-muted/10 transition-colors ${
|
className={`flex gap-2 md:gap-4 py-4 md:py-6 px-3 md:px-6 group hover:bg-muted/10 transition-colors ${
|
||||||
isAssistant ? "justify-start" : "justify-end"
|
isAssistant ? "justify-start" : "justify-end"
|
||||||
}`}
|
}`}
|
||||||
onMouseEnter={() => setShowActions(true)}
|
onMouseEnter={() => setShowActions(true)}
|
||||||
onMouseLeave={() => setShowActions(false)}
|
onMouseLeave={() => setShowActions(false)}
|
||||||
>
|
>
|
||||||
{isAssistant && (
|
{isAssistant && (
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-1 md:gap-2">
|
||||||
<div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 bg-gradient-to-br from-primary to-secondary">
|
<div className="w-6 h-6 md:w-8 md:h-8 rounded-lg flex items-center justify-center flex-shrink-0 bg-gradient-to-br from-primary to-secondary">
|
||||||
<Bot className="w-5 h-5 text-primary-foreground" />
|
<Bot className="w-4 h-4 md:w-5 md:h-5 text-primary-foreground" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{model && (
|
{model && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="text-xs px-2 py-0.5 bg-primary/10 text-primary border border-primary/30"
|
className="hidden md:block text-xs px-2 py-0.5 bg-primary/10 text-primary border border-primary/30"
|
||||||
>
|
>
|
||||||
{model}
|
{model}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -53,7 +53,7 @@ export const ChatMessage = ({ role, content, model, attachments }: ChatMessagePr
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`flex-1 max-w-3xl space-y-2 ${isAssistant ? "" : "flex flex-col items-end"}`}>
|
<div className={`flex-1 max-w-3xl space-y-1 md:space-y-2 ${isAssistant ? "" : "flex flex-col items-end"}`}>
|
||||||
{/* Attachments */}
|
{/* Attachments */}
|
||||||
{attachments && attachments.length > 0 && (
|
{attachments && attachments.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-2 mb-2">
|
<div className="flex flex-wrap gap-2 mb-2">
|
||||||
@@ -73,13 +73,13 @@ export const ChatMessage = ({ role, content, model, attachments }: ChatMessagePr
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`inline-block px-4 py-3 rounded-xl ${
|
className={`inline-block px-3 md:px-4 py-2 md:py-3 rounded-xl ${
|
||||||
isAssistant
|
isAssistant
|
||||||
? "bg-card border border-border"
|
? "bg-card border border-border"
|
||||||
: "bg-gradient-to-br from-primary to-secondary text-primary-foreground"
|
: "bg-gradient-to-br from-primary to-secondary text-primary-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className={`leading-relaxed ${isAssistant ? "text-foreground" : "text-white"}`}>
|
<p className={`text-sm md:text-base leading-relaxed ${isAssistant ? "text-foreground" : "text-white"}`}>
|
||||||
{content}
|
{content}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,8 +100,8 @@ export const ChatMessage = ({ role, content, model, attachments }: ChatMessagePr
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isAssistant && (
|
{!isAssistant && (
|
||||||
<div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 bg-accent/20 border border-accent">
|
<div className="w-6 h-6 md:w-8 md:h-8 rounded-lg flex items-center justify-center flex-shrink-0 bg-accent/20 border border-accent">
|
||||||
<User className="w-5 h-5 text-accent" />
|
<User className="w-4 h-4 md:w-5 md:h-5 text-accent" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,16 +76,18 @@ export const ChatView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full">
|
<div className="flex h-full w-full pb-16 md:pb-0">
|
||||||
{/* Chat Sidebar */}
|
{/* Chat Sidebar - Hidden on mobile */}
|
||||||
<ChatSidebar
|
<div className="hidden md:block">
|
||||||
isCollapsed={isChatSidebarCollapsed}
|
<ChatSidebar
|
||||||
onToggleCollapse={() => setIsChatSidebarCollapsed(!isChatSidebarCollapsed)}
|
isCollapsed={isChatSidebarCollapsed}
|
||||||
onNewChat={handleNewChat}
|
onToggleCollapse={() => setIsChatSidebarCollapsed(!isChatSidebarCollapsed)}
|
||||||
/>
|
onNewChat={handleNewChat}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Main Chat Area */}
|
{/* Main Chat Area */}
|
||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col w-full">
|
||||||
<ChatHeader
|
<ChatHeader
|
||||||
showModelSelector={true}
|
showModelSelector={true}
|
||||||
selectedModel={selectedModel}
|
selectedModel={selectedModel}
|
||||||
@@ -95,7 +97,7 @@ export const ChatView = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto px-2 md:px-4">
|
||||||
{messages.map((message) => (
|
{messages.map((message) => (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={message.id}
|
key={message.id}
|
||||||
|
|||||||
@@ -70,25 +70,26 @@ export const ImageView = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full pb-16 md:pb-0">
|
||||||
<ChatHeader />
|
<ChatHeader />
|
||||||
|
|
||||||
<div className="flex-1 flex overflow-hidden">
|
<div className="flex-1 flex overflow-hidden">
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<ScrollArea className="flex-1">
|
<ScrollArea className="flex-1">
|
||||||
<div className="max-w-5xl mx-auto p-6">
|
<div className="max-w-5xl mx-auto p-3 md:p-6">
|
||||||
<Tabs defaultValue="generate" className="space-y-6">
|
<Tabs defaultValue="generate" className="space-y-4 md:space-y-6">
|
||||||
<TabsList className="grid w-full grid-cols-2 glass-effect">
|
<TabsList className="grid w-full grid-cols-2 glass-effect">
|
||||||
<TabsTrigger value="generate">Gerar Imagem</TabsTrigger>
|
<TabsTrigger value="generate" className="text-xs md:text-sm">Gerar Imagem</TabsTrigger>
|
||||||
<TabsTrigger value="history">
|
<TabsTrigger value="history" className="text-xs md:text-sm">
|
||||||
<Clock className="w-4 h-4 mr-2" />
|
<Clock className="w-3 h-3 md:w-4 md:h-4 mr-1 md:mr-2" />
|
||||||
Histórico
|
<span className="hidden md:inline">Histórico</span>
|
||||||
|
<span className="md:hidden">Hist.</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* Generate Tab */}
|
{/* Generate Tab */}
|
||||||
<TabsContent value="generate" className="space-y-6">
|
<TabsContent value="generate" className="space-y-4 md:space-y-6">
|
||||||
<div className="glass-effect rounded-xl p-6 space-y-4">
|
<div className="glass-effect rounded-xl p-4 md:p-6 space-y-3 md:space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label className="text-sm font-medium text-foreground">
|
||||||
Descreva a imagem
|
Descreva a imagem
|
||||||
@@ -101,7 +102,7 @@ export const ImageView = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-4 items-end">
|
<div className="flex flex-col md:flex-row gap-3 md:gap-4 md:items-end">
|
||||||
<div className="flex-1 space-y-2">
|
<div className="flex-1 space-y-2">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label className="text-sm font-medium text-foreground">
|
||||||
Tamanho
|
Tamanho
|
||||||
|
|||||||
Reference in New Issue
Block a user