atualizacoes codex
This commit is contained in:
@@ -40,6 +40,7 @@ export function AreasView() {
|
||||
const [loadingList, setLoadingList] = useState(true);
|
||||
|
||||
const totalPages = Math.max(1, totalPaginas);
|
||||
const isFiltering = filterNome.trim().length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -262,7 +263,7 @@ export function AreasView() {
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto p-3 md:p-6">
|
||||
{!loadingList && areasList.length === 0 && totalRegistros === 0 ? (
|
||||
{!loadingList && totalRegistros === 0 && !isFiltering ? (
|
||||
<Card className="max-w-2xl mx-auto mt-12">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
@@ -297,6 +298,12 @@ export function AreasView() {
|
||||
Carregando...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : totalRegistros === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} className="text-center text-muted-foreground py-8">
|
||||
Nenhum resultado para o filtro {filterNome.trim() ? `"${filterNome.trim()}"` : "atual"}.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
areasList.map((item) => (
|
||||
<TableRow key={item.id}>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { audioGenerationService } from "@/services/audioGeneration";
|
||||
|
||||
const SUPPORTED_FORMATS = ['flac', 'm4a', 'mp3', 'mp4', 'mpeg', 'mpga', 'oga', 'ogg', 'wav', 'webm'];
|
||||
const MAX_FILE_SIZE = 25 * 1024 * 1024; // 25 MB
|
||||
@@ -186,19 +187,23 @@ export const AudioView = () => {
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const handleDownloadAudio = (audio: GeneratedAudio) => {
|
||||
// In real implementation, download the actual audio file
|
||||
const a = document.createElement('a');
|
||||
a.href = audio.audioUrl;
|
||||
a.download = `audio_${audio.voiceLabel}_${new Date(audio.timestamp).getTime()}.mp3`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
toast({
|
||||
title: "Download iniciado",
|
||||
description: "O arquivo de áudio está sendo baixado.",
|
||||
});
|
||||
const handleDownloadAudio = async (audio: GeneratedAudio) => {
|
||||
const filename = `audio_${audio.voiceLabel}_${new Date(audio.timestamp).getTime()}.mp3`;
|
||||
try {
|
||||
await audioGenerationService.downloadAudioFile(audio.audioUrl, filename);
|
||||
toast({
|
||||
title: "Download iniciado",
|
||||
description: "O arquivo de áudio está sendo baixado.",
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const message =
|
||||
error && typeof error === "object" && "message" in error ? String((error as { message: string }).message) : "Não foi possível baixar o áudio.";
|
||||
toast({
|
||||
title: "Erro ao baixar",
|
||||
description: message,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteAudio = (audioId: string) => {
|
||||
@@ -482,10 +487,11 @@ export const AudioView = () => {
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="gap-1"
|
||||
onClick={() => handleDownloadAudio(generatedAudio)}
|
||||
onClick={() => void handleDownloadAudio(generatedAudio)}
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
Baixar MP3
|
||||
@@ -658,9 +664,10 @@ export const AudioView = () => {
|
||||
</div>
|
||||
<div className="flex gap-2 shrink-0">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleDownloadAudio(item)}
|
||||
onClick={() => void handleDownloadAudio(item)}
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
</Button>
|
||||
|
||||
@@ -134,18 +134,23 @@ export const GenerationView = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadAudio = (audio: AudioRecord) => {
|
||||
const a = document.createElement('a');
|
||||
a.href = audio.audio_url;
|
||||
a.download = `audio_${audio.voice}_${new Date(audio.created_at).getTime()}.mp3`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
toast({
|
||||
title: "Download iniciado",
|
||||
description: "O arquivo de áudio está sendo baixado.",
|
||||
});
|
||||
const handleDownloadAudio = async (audio: AudioRecord) => {
|
||||
const filename = `audio_${audio.voice}_${new Date(audio.created_at).getTime()}.mp3`;
|
||||
try {
|
||||
await audioGenerationService.downloadAudioFile(audio.audio_url, filename);
|
||||
toast({
|
||||
title: "Download iniciado",
|
||||
description: "O arquivo de áudio está sendo baixado.",
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const message =
|
||||
error && typeof error === "object" && "message" in error ? String((error as { message: string }).message) : "Não foi possível baixar o áudio.";
|
||||
toast({
|
||||
title: "Erro ao baixar",
|
||||
description: message,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteAudio = async (audioId: string) => {
|
||||
@@ -290,10 +295,11 @@ export const GenerationView = () => {
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="gap-1"
|
||||
onClick={() => handleDownloadAudio(lastGeneratedAudio)}
|
||||
onClick={() => void handleDownloadAudio(lastGeneratedAudio)}
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
Baixar
|
||||
@@ -385,9 +391,10 @@ export const GenerationView = () => {
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleDownloadAudio(audio)}
|
||||
onClick={() => void handleDownloadAudio(audio)}
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Bot, User, Copy, File } from "lucide-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useState } from "react";
|
||||
@@ -53,7 +54,7 @@ export const ChatMessage = ({ role, content, model, attachments }: ChatMessagePr
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`flex-1 max-w-3xl space-y-1 md:space-y-2 ${isAssistant ? "" : "flex flex-col items-end"}`}>
|
||||
<div className={`flex-1 max-w-4xl space-y-1 md:space-y-2 ${isAssistant ? "" : "flex flex-col items-end"}`}>
|
||||
{/* Attachments */}
|
||||
{attachments && attachments.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mb-2">
|
||||
@@ -73,15 +74,19 @@ export const ChatMessage = ({ role, content, model, attachments }: ChatMessagePr
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`inline-block px-3 md:px-4 py-2 md:py-3 rounded-xl ${
|
||||
className={`inline-block max-w-[90%] px-3 md:px-4 py-2 md:py-3 rounded-xl break-words ${
|
||||
isAssistant
|
||||
? "bg-card border border-border"
|
||||
? "bg-card border border-border text-foreground"
|
||||
: "bg-gradient-to-br from-primary to-secondary text-primary-foreground"
|
||||
}`}
|
||||
>
|
||||
<p className={`text-sm md:text-base leading-relaxed ${isAssistant ? "text-foreground" : "text-white"}`}>
|
||||
{content}
|
||||
</p>
|
||||
{isAssistant ? (
|
||||
<div className="prose prose-sm md:prose-base prose-neutral dark:prose-invert max-w-none prose-headings:font-semibold prose-p:leading-relaxed">
|
||||
<ReactMarkdown>{content}</ReactMarkdown>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm md:text-base leading-relaxed text-white">{content}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showActions && (
|
||||
|
||||
@@ -133,8 +133,9 @@ export function ParecerJuridicoDetailView() {
|
||||
new Date(a.created_at.replace(" ", "T")).getTime() -
|
||||
new Date(b.created_at.replace(" ", "T")).getTime()
|
||||
);
|
||||
const nonEmptyMessages = sorted.filter((m) => (m.content ?? "").trim().length > 0);
|
||||
setMessages(
|
||||
sorted.map((m) => ({
|
||||
nonEmptyMessages.map((m) => ({
|
||||
id: m.id,
|
||||
role: m.role,
|
||||
content: m.content,
|
||||
|
||||
@@ -155,18 +155,32 @@ export function ParecerJuridicoFormView() {
|
||||
<div className="flex-1 overflow-auto p-3 md:p-6">
|
||||
<div className="max-w-4xl mx-auto flex flex-col gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="titulo-parecer">Título do parecer</Label>
|
||||
<Label htmlFor="titulo-parecer" className="flex flex-wrap items-baseline gap-1">
|
||||
<span>Título do parecer</span>
|
||||
<span className="text-destructive font-semibold" aria-hidden>
|
||||
*
|
||||
</span>
|
||||
<span className="sr-only">obrigatório</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="titulo-parecer"
|
||||
value={tituloParecer}
|
||||
onChange={(e) => setTituloParecer(e.target.value)}
|
||||
placeholder="Ex.: Parecer sobre contrato de prestação de serviços"
|
||||
className="w-full"
|
||||
required
|
||||
aria-required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="area">Área</Label>
|
||||
<Label htmlFor="area" className="flex flex-wrap items-baseline gap-1">
|
||||
<span>Área</span>
|
||||
<span className="text-destructive font-semibold" aria-hidden>
|
||||
*
|
||||
</span>
|
||||
<span className="sr-only">obrigatório</span>
|
||||
</Label>
|
||||
<Select value={areaId || "none"} onValueChange={(v) => setAreaId(v === "none" ? "" : v)} disabled={areasLoading}>
|
||||
<SelectTrigger id="area" className="w-full">
|
||||
<SelectValue
|
||||
@@ -190,7 +204,7 @@ export function ParecerJuridicoFormView() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="prompt">Prompt</Label>
|
||||
<Label htmlFor="prompt">Prompt da base</Label>
|
||||
<Select
|
||||
value={promptId || "none"}
|
||||
onValueChange={(v) => setPromptId(v === "none" ? "" : v)}
|
||||
@@ -221,29 +235,45 @@ export function ParecerJuridicoFormView() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="conteudo-prompt">Conteúdo do prompt (editável)</Label>
|
||||
<Label htmlFor="conteudo-prompt" className="flex flex-wrap items-baseline gap-1">
|
||||
<span>Conteúdo do prompt</span>
|
||||
<span className="text-destructive font-semibold" aria-hidden>
|
||||
*
|
||||
</span>
|
||||
<span className="sr-only">obrigatório</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
id="conteudo-prompt"
|
||||
value={conteudoPrompt}
|
||||
onChange={(e) => setConteudoPrompt(e.target.value)}
|
||||
placeholder="Selecione um prompt acima ou edite o conteúdo aqui. Este valor será enviado na geração do parecer."
|
||||
placeholder="Selecione um prompt acima ou edite o conteúdo aqui."
|
||||
className="min-h-[200px] resize-y w-full"
|
||||
required
|
||||
aria-required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="instrucao">Instrução</Label>
|
||||
<Label htmlFor="instrucao" className="flex flex-wrap items-baseline gap-1">
|
||||
<span>Instrução</span>
|
||||
<span className="text-destructive font-semibold" aria-hidden>
|
||||
*
|
||||
</span>
|
||||
<span className="sr-only">obrigatório</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
id="instrucao"
|
||||
value={instrucao}
|
||||
onChange={(e) => setInstrucao(e.target.value)}
|
||||
placeholder="Instruções adicionais para a geração do parecer..."
|
||||
className="min-h-[100px] resize-y w-full"
|
||||
required
|
||||
aria-required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="anexo">Anexo (máx. 1 arquivo)</Label>
|
||||
<Label htmlFor="anexo">Anexo</Label>
|
||||
<p className="text-xs text-muted-foreground">Formatos permitidos: PDF, TXT.</p>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex items-center gap-2 w-full min-h-10 rounded-md border border-input bg-background px-3 py-2">
|
||||
|
||||
@@ -183,10 +183,12 @@ export const PromptsView = () => {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button onClick={() => navigate("/codex/prompts/novo")}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Novo prompt
|
||||
</Button>
|
||||
<div className="flex flex-col sm:flex-row gap-2 sm:items-center">
|
||||
<Button onClick={() => navigate("/codex/prompts/novo")}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Novo prompt
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -207,10 +209,10 @@ export const PromptsView = () => {
|
||||
Carregando...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : promptsList.length === 0 ? (
|
||||
) : totalRegistros === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="text-center text-muted-foreground py-10 text-base">
|
||||
Não há resultados.
|
||||
{hasActiveFilters ? "Nenhum prompt encontrado para os filtros selecionados." : "Não há resultados."}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user