chore: expurgo de blobs excedentes e realinhamento de estado
release.yml (push) Invalid workflow file

This commit is contained in:
Evandro Fonseca Junior committed 2026-02-22 14:06:01 -03:00
1 parent 593efd42e0
commit a7cbd1a3b4
114 files changed
+20716 -4258

No files matched your search

+46
View File
@@ -18,7 +18,11 @@ import {
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
<<<<<<< HEAD
import { Message, ThinkingMessageMetadata } from '@/hooks/use-chat';
=======
import { Message } from '@/hooks/use-chat';
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
import type { DeepResearchState } from '@/hooks/use-deep-research';
import type { ScrapedContent } from '@/services/webSearch';
@@ -39,6 +43,18 @@ export interface DebugData {
contextual: string[];
};
sources: ScrapedContent[];
<<<<<<< HEAD
sitesResearched?: Array<{
url: string;
title: string;
status: string;
source: string;
duration?: number;
contentLength: number;
scrapedAt: number;
}>;
=======
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
logs: Array<{
stage: string;
timestamp: number;
@@ -53,6 +69,35 @@ export interface DebugData {
title: string;
content: string;
}>;
<<<<<<< HEAD
activeQueries?: Array<{
query: string;
source: string;
round?: number;
startedAt: number;
}>;
activeUrls?: Array<{
url: string;
title?: string;
status: string;
source?: string;
startedAt: number;
duration?: number;
}>;
};
deepResearchState?: DeepResearchState;
thinkingSteps?: ThinkingMessageMetadata[];
reasoning?: {
steps?: string[];
intermediateResults?: any[];
};
finalResponse: string;
rawResponse: string;
tokenUsage?: {
input: number;
output: number;
total: number;
=======
};
deepResearchState?: DeepResearchState;
finalResponse: string;
@@ -60,6 +105,7 @@ export interface DebugData {
tokenUsage?: {
input: number;
output: number;
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
};
}
+23 -2
View File
@@ -18,6 +18,8 @@ import {
import { cn } from '@/lib/utils';
import { useState } from 'react';
import type { ThinkingMessageMetadata } from '@/hooks/use-chat';
import { SearchQueriesMonitor } from './search-queries-monitor';
import { ScrapingUrlsMonitor } from './scraping-urls-monitor';
interface MessageStepThinkingProps {
metadata: ThinkingMessageMetadata;
@@ -105,13 +107,31 @@ export function MessageStepThinking({ metadata }: MessageStepThinkingProps) {
{/* Conteúdo Expandido */}
{isExpanded && (
<div className="mt-2 ml-1 p-3 border-l-2 border-muted/30 space-y-2 text-sm text-muted-foreground animate-in fade-in">
<div className="mt-2 ml-1 p-3 border-l-2 border-muted/30 space-y-4 text-sm text-muted-foreground animate-in fade-in">
{metadata.details && (
<div className="whitespace-pre-wrap">{metadata.details}</div>
)}
{/* Monitoramento em Tempo Real - Queries */}
{metadata.activeQueries && metadata.activeQueries.length > 0 && (
<div className="pt-2 border-t border-muted/30">
<SearchQueriesMonitor queries={metadata.activeQueries} />
</div>
)}
{/* Monitoramento em Tempo Real - URLs */}
{metadata.activeUrls && metadata.activeUrls.length > 0 && (
<div className="pt-2 border-t border-muted/30">
<ScrapingUrlsMonitor urls={metadata.activeUrls} />
</div>
)}
{metadata.sources && metadata.sources.length > 0 && (
<div className="flex flex-wrap gap-2 pt-1">
<div className="pt-2 border-t border-muted/30">
<div className="text-xs font-medium text-muted-foreground mb-2">
Fontes Encontradas ({metadata.sources.length})
</div>
<div className="flex flex-wrap gap-2">
{metadata.sources.map((source, idx) => (
<div key={idx} className="flex items-center gap-1 bg-background/50 border border-muted/50 px-2 py-1 rounded text-xs max-w-[200px]">
<ExternalLink className="w-3 h-3 opacity-50" />
@@ -121,6 +141,7 @@ export function MessageStepThinking({ metadata }: MessageStepThinkingProps) {
</button>
</div>
))}
</div>
</div>
)}
+317
View File
@@ -0,0 +1,317 @@
'use client';
import * as React from 'react';
import { Check, ChevronsUpDown, Search, DownloadCloud, Cpu, Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useModelSelection } from '@/hooks/use-model-selection';
import { useDownloadContext } from '@/contexts/download-context';
import { Badge } from '@/components/ui/badge';
import { useToast } from '@/hooks/use-toast';
export function ModelSelector() {
const [open, setOpen] = React.useState(false);
const [searchQuery, setSearchQuery] = React.useState('');
const { myModels, availableModels, selectedModel, setSelectedModel, installedVariantsByFamily, refreshModels } = useModelSelection();
const { startDownload, state: downloadState } = useDownloadContext();
const { toast } = useToast();
// Track selected variant for each model in the catalog
const [selectedVariants, setSelectedVariants] = React.useState<Record<string, string>>({});
const handleDownload = async (e: React.MouseEvent, modelName: string) => {
e.stopPropagation();
try {
await startDownload(modelName);
toast({
title: "Download Iniciado",
description: `Baixando ${modelName}...`,
});
} catch (error) {
toast({
title: "Erro",
description: "Falha ao iniciar download.",
variant: "destructive",
});
}
};
const handleSmartDownload = async () => {
const term = searchQuery.trim();
if (!term) return;
try {
await startDownload(term);
setOpen(false);
setSearchQuery("");
toast({
title: "Download Iniciado",
description: `Baixando ${term} da biblioteca Ollama...`,
});
} catch (error) {
toast({
title: "Erro",
description: "Falha ao iniciar download.",
variant: "destructive",
});
}
};
React.useEffect(() => {
if (downloadState?.isSuccess) {
refreshModels();
}
}, [downloadState?.isSuccess, refreshModels]);
const filteredMyModels = React.useMemo(() => {
if (!searchQuery) return myModels;
return myModels.filter(m =>
m.model.toLowerCase().includes(searchQuery.toLowerCase())
);
}, [myModels, searchQuery]);
const filteredAvailableModels = React.useMemo(() => {
if (!searchQuery) return availableModels;
return availableModels.filter(m =>
m.model.toLowerCase().includes(searchQuery.toLowerCase())
);
}, [availableModels, searchQuery]);
// Helper to check if a model is currently selected (partial match for variants)
const isSelected = (modelName: string) => {
if (!selectedModel) return false;
return selectedModel.startsWith(modelName) || selectedModel === modelName;
};
// Helper to extract tag/parameters from model name
const getVariantDisplay = (name: string) => {
if (name.includes(':')) {
return name.split(':')[1];
}
return 'latest';
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-[260px] justify-between"
>
{selectedModel ? (
<span className="flex items-center gap-2 truncate">
<Cpu className="w-4 h-4 text-muted-foreground shrink-0" />
<span className="truncate">{selectedModel}</span>
</span>
) : (
"Selecione um modelo..."
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[400px] p-0" align="start">
<div className="flex flex-col">
{/* Search Input */}
<div className="flex items-center border-b border-border/50 px-3 bg-muted/30">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<input
className="flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
placeholder="Buscar modelos..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && searchQuery && filteredAvailableModels.length === 0) {
handleSmartDownload();
}
}}
/>
</div>
<Tabs defaultValue="installed" className="w-full">
<TabsList className="w-full rounded-none border-b bg-transparent p-0">
<TabsTrigger
value="installed"
className="flex-1 rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent h-10"
>
Instalados
</TabsTrigger>
<TabsTrigger
value="available"
className="flex-1 rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent h-10"
>
Catálogo
</TabsTrigger>
</TabsList>
<TabsContent value="installed" className="m-0 max-h-[400px] overflow-y-auto p-2">
{filteredMyModels.length === 0 ? (
<div className="py-8 text-center text-sm text-muted-foreground">
{searchQuery ? (
<span>Nenhum modelo instalado encontrado.</span>
) : (
<span>Nenhum modelo instalado.</span>
)}
</div>
) : (
<div className="flex flex-col gap-2">
{filteredMyModels.map((modelDef) => {
const family = modelDef.model;
const installedTags = installedVariantsByFamily[family] || [];
const isFamilySelected = selectedModel ? (selectedModel === family || selectedModel.startsWith(family + ":")) : false;
return (
<div
key={family}
className="relative flex flex-col rounded-md border border-transparent hover:border-border/50 bg-card/50 hover:bg-accent/30 p-3 text-sm transition-all"
>
<div className="flex items-start justify-between w-full mb-2">
<div className="flex flex-col gap-0.5 overflow-hidden pr-2">
<span className="font-bold text-base truncate flex items-center gap-2">
{modelDef.model}
{isFamilySelected && (
<Check className="h-3 w-3 text-primary animate-in fade-in zoom-in" />
)}
</span>
<span className="text-xs text-muted-foreground line-clamp-2" title={modelDef.description}>
{modelDef.description}
</span>
</div>
</div>
<div className="flex flex-wrap gap-1.5 mt-1">
{installedTags.map((fullTag) => {
const tag = getVariantDisplay(fullTag);
const isExactSelected = selectedModel === fullTag;
// Find variant def for size info if available
const variantDef = modelDef.variants.find(v => v.name === fullTag);
return (
<Badge
key={fullTag}
variant="outline"
className={cn(
"cursor-pointer text-[10px] px-2 h-5 transition-all border-transparent",
isExactSelected
? "bg-foreground text-background hover:bg-foreground/90"
: "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground"
)}
title={variantDef ? `Tamanho: ${variantDef.size}` : undefined}
onClick={(e) => {
e.stopPropagation();
setSelectedModel(fullTag);
setOpen(false);
}}
>
{tag}
</Badge>
);
})}
</div>
</div>
);
})}
</div>
)}
</TabsContent>
<TabsContent value="available" className="m-0 max-h-[400px] overflow-y-auto p-2">
{filteredAvailableModels.length === 0 ? (
<div className="py-6 text-center text-sm">
{searchQuery ? (
<div className="flex flex-col items-center gap-2">
<p className="text-muted-foreground">Modelo não encontrado no catálogo.</p>
<Button
variant="outline"
className="h-auto py-2 px-4 border-dashed border-zinc-500/50 hover:border-primary hover:bg-primary/10"
onClick={handleSmartDownload}
>
<DownloadCloud className="mr-2 h-4 w-4" />
<span>Baixar <span className="font-bold ml-1">{searchQuery}</span></span>
</Button>
</div>
) : (
<span className="text-muted-foreground">Nenhum modelo encontrado.</span>
)}
</div>
) : (
<div className="flex flex-col gap-2">
{filteredAvailableModels.map((modelDef) => {
// Determine active variant for this item
const activeVariantName = selectedVariants[modelDef.model] || modelDef.variants[0]?.name;
const activeVariant = modelDef.variants.find(v => v.name === activeVariantName) || modelDef.variants[0];
return (
<div
key={modelDef.model}
className="relative flex flex-col rounded-md border border-transparent hover:border-border/50 bg-card/50 hover:bg-accent/30 p-3 text-sm transition-all"
>
<div className="flex items-start justify-between w-full mb-2">
<div className="flex flex-col gap-0.5 overflow-hidden pr-2">
<span className="font-bold text-base truncate">{modelDef.model}</span>
<span className="text-xs text-muted-foreground line-clamp-2" title={modelDef.description}>
{modelDef.description}
</span>
</div>
<Button
size="icon"
variant="ghost"
className="h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground"
onClick={(e) => handleDownload(e, activeVariant.name)}
disabled={downloadState.isDownloading && downloadState.currentModel === activeVariant.name}
title={`Baixar ${activeVariant.name}`}
>
{downloadState.isDownloading && downloadState.currentModel === activeVariant.name ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<DownloadCloud className="h-4 w-4" />
)}
</Button>
</div>
{/* Variants Pills */}
<div className="flex flex-wrap gap-1.5 mt-1">
{modelDef.variants.map((variant) => {
const isVariantSelected = activeVariant.name === variant.name;
return (
<Badge
key={variant.name}
variant="outline"
className={cn(
"cursor-pointer text-[10px] px-2 h-5 transition-all border-transparent",
isVariantSelected
? "bg-foreground text-background hover:bg-foreground/90"
: "bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground"
)}
title={`Tamanho em disco: ${variant.size}`}
onClick={(e) => {
e.stopPropagation();
setSelectedVariants(prev => ({
...prev,
[modelDef.model]: variant.name
}));
}}
>
{getVariantDisplay(variant.name)}
</Badge>
);
})}
</div>
</div>
)})}
</div>
)}
</TabsContent>
</Tabs>
</div>
</PopoverContent>
</Popover>
);
}
+107
View File
@@ -0,0 +1,107 @@
'use client';
import { Loader2, CheckCircle2, XCircle, ExternalLink, Globe } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
import type { ActiveUrl } from '@/hooks/use-web-search-events';
interface ScrapingUrlsMonitorProps {
urls: ActiveUrl[];
className?: string;
}
export function ScrapingUrlsMonitor({ urls, className }: ScrapingUrlsMonitorProps) {
if (urls.length === 0) return null;
const getFaviconUrl = (url: string): string => {
try {
const domain = new URL(url).hostname;
return `https://www.google.com/s2/favicons?domain=${domain}&sz=16`;
} catch {
return '';
}
};
const getStatusIcon = (status: ActiveUrl['status']) => {
switch (status) {
case 'started':
return <Loader2 className="w-3 h-3 animate-spin text-primary" />;
case 'completed':
case 'cached':
return <CheckCircle2 className="w-3 h-3 text-green-500" />;
case 'failed':
return <XCircle className="w-3 h-3 text-red-500" />;
default:
return <Globe className="w-3 h-3 text-muted-foreground" />;
}
};
const getSourceBadge = (source?: string) => {
if (!source) return null;
const colors: Record<string, string> = {
static: 'bg-blue-500/10 text-blue-600 border-blue-500/20',
nodriver: 'bg-purple-500/10 text-purple-600 border-purple-500/20',
playwright: 'bg-orange-500/10 text-orange-600 border-orange-500/20',
cached: 'bg-gray-500/10 text-gray-600 border-gray-500/20',
};
return (
<Badge variant="outline" className={cn('text-xs', colors[source] || 'bg-muted')}>
{source}
</Badge>
);
};
return (
<div className={cn('space-y-2', className)}>
<div className="text-xs font-medium text-muted-foreground mb-2">
Sites Sendo Acessados ({urls.length})
</div>
{urls.map((url, idx) => (
<div
key={`${url.url}-${idx}`}
className="flex items-start gap-2 p-2 rounded-md border bg-muted/30 hover:bg-muted/50 transition-colors"
>
<div className="flex-shrink-0 mt-0.5">
{getStatusIcon(url.status)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start gap-2">
<img
src={getFaviconUrl(url.url)}
alt=""
className="w-4 h-4 rounded mt-0.5 flex-shrink-0"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<a
href={url.url}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium text-primary hover:underline line-clamp-1 flex items-center gap-1"
>
{url.title || url.url}
<ExternalLink className="w-3 h-3 flex-shrink-0" />
</a>
{getSourceBadge(url.source)}
{url.duration && (
<span className="text-xs text-muted-foreground">
{url.duration}ms
</span>
)}
</div>
<p className="text-xs text-muted-foreground truncate mt-0.5">
{url.url}
</p>
</div>
</div>
</div>
</div>
))}
</div>
);
}
@@ -0,0 +1,70 @@
'use client';
import { Loader2, CheckCircle2, Globe, Search } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
import type { ActiveQuery } from '@/hooks/use-web-search-events';
interface SearchQueriesMonitorProps {
queries: ActiveQuery[];
className?: string;
}
export function SearchQueriesMonitor({ queries, className }: SearchQueriesMonitorProps) {
if (queries.length === 0) return null;
const getSourceColor = (source: string) => {
switch (source) {
case 'brave':
return 'bg-blue-500/10 text-blue-600 border-blue-500/20';
case 'searxng':
return 'bg-green-500/10 text-green-600 border-green-500/20';
case 'duckduckgo':
return 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20';
default:
return 'bg-muted text-muted-foreground border-muted';
}
};
const getSourceLabel = (source: string) => {
switch (source) {
case 'brave':
return 'Brave';
case 'searxng':
return 'SearXNG';
case 'duckduckgo':
return 'DuckDuckGo';
default:
return source;
}
};
return (
<div className={cn('space-y-2', className)}>
<div className="text-xs font-medium text-muted-foreground mb-2">
Consultas Ativas ({queries.length})
</div>
{queries.map((query, idx) => (
<div
key={`${query.query}-${query.source}-${idx}`}
className="flex items-center gap-2 p-2 rounded-md border bg-muted/30 hover:bg-muted/50 transition-colors"
>
<Loader2 className="w-3 h-3 animate-spin text-primary flex-shrink-0" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="text-xs truncate">{query.query}</span>
<Badge variant="outline" className={cn('text-xs', getSourceColor(query.source))}>
{getSourceLabel(query.source)}
</Badge>
{query.round && (
<Badge variant="outline" className="text-xs">
Round {query.round}
</Badge>
)}
</div>
</div>
</div>
))}
</div>
);
}