chore: expurgo de blobs excedentes e realinhamento de estado
release.yml (push) Invalid workflow file
release.yml (push) Invalid workflow file
This commit is contained in:
1 parent
593efd42e0
commit
a7cbd1a3b4
114 files changed
+20716
-4258
No files matched your search
+312
-124
@@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { MessageSquare, Settings, Server, Moon, Sun, PanelLeftClose, PanelLeftOpen, Loader2, ChevronDown, Plus, RefreshCw, Cpu } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { ModelSelector } from "@/components/chat/model-selector";
|
||||
import { ChatInput } from "@/components/chat/chat-input";
|
||||
import { ChatMessage } from "@/components/chat/chat-message";
|
||||
import { ReasoningChain } from "@/components/chat/reasoning-chain";
|
||||
@@ -13,6 +14,7 @@ import { TitleBar } from "@/components/titlebar";
|
||||
import { SidebarList } from "@/components/chat/sidebar-list";
|
||||
import { useChat } from "@/hooks/use-chat";
|
||||
import { useLocalModels } from "@/hooks/use-local-models";
|
||||
import { useModelSelection } from "@/hooks/use-model-selection";
|
||||
import { useChatStorage } from "@/hooks/use-chat-storage";
|
||||
import { useAutoLabelingModel } from "@/hooks/use-auto-labeling-model";
|
||||
import { useWebSearch } from "@/hooks/use-web-search";
|
||||
@@ -24,9 +26,10 @@ import { useDeepResearch } from "@/hooks/use-deep-research";
|
||||
import { DEEP_RESEARCH_PROMPTS } from "@/data/prompts/deep-research";
|
||||
import { useSettingsStore } from "@/store/settings-store";
|
||||
import type { ScrapedContent } from "@/services/webSearch";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { useState, useEffect, useRef, useCallback, startTransition } from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { ImperativePanelHandle } from "react-resizable-panels";
|
||||
import { ModelDownloadDialog } from "@/components/chat/model-download-dialog";
|
||||
import { chatLog } from "@/lib/terminal-logger";
|
||||
@@ -43,6 +46,7 @@ export default function ChatPage() {
|
||||
const { messages, setMessages, sendMessage, isLoading, stop, clearChat } = useChat();
|
||||
const { models, refresh } = useLocalModels();
|
||||
const { theme, setTheme } = useTheme();
|
||||
const { isSetupCompleted } = useSettingsStore();
|
||||
|
||||
const {
|
||||
sessions,
|
||||
@@ -112,9 +116,68 @@ export default function ChatPage() {
|
||||
}));
|
||||
}, [setMessages]);
|
||||
|
||||
const [selectedModel, setSelectedModel] = useState("");
|
||||
const { selectedModel, setSelectedModel } = useModelSelection();
|
||||
|
||||
// Sincronizar eventos de busca com thinking messages
|
||||
useEffect(() => {
|
||||
if (webSearch.activeQueries.length > 0 || webSearch.activeUrls.length > 0) {
|
||||
updateThinkingMessage('web-research', {
|
||||
activeQueries: webSearch.activeQueries,
|
||||
activeUrls: webSearch.activeUrls,
|
||||
});
|
||||
}
|
||||
}, [webSearch.activeQueries, webSearch.activeUrls, updateThinkingMessage]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const initializedRef = useRef(false);
|
||||
|
||||
// Refs para evitar loops e execuções múltiplas
|
||||
const hasCheckedIntegrityRef = useRef(false);
|
||||
const isRedirectingRef = useRef(false);
|
||||
|
||||
// Guarda de roteamento: verificar se setup foi completado
|
||||
useEffect(() => {
|
||||
if (!isSetupCompleted && !isRedirectingRef.current) {
|
||||
isRedirectingRef.current = true;
|
||||
startTransition(() => {
|
||||
router.replace("/setup"); // Mudar de push para replace para consistência
|
||||
});
|
||||
}
|
||||
}, [isSetupCompleted, router]);
|
||||
|
||||
// Guarda de integridade: verificar se Ollama está funcional
|
||||
// Executa SEMPRE que o componente montar, independente do status do setup
|
||||
useEffect(() => {
|
||||
if (hasCheckedIntegrityRef.current) return; // Proteção contra múltiplas execuções
|
||||
if (isRedirectingRef.current) return; // Evitar redirecionamentos múltiplos
|
||||
|
||||
const checkIntegrity = async () => {
|
||||
try {
|
||||
const isOllamaIntact = await invoke<boolean>('verify_ollama_integrity_command');
|
||||
|
||||
if (!isOllamaIntact && !isRedirectingRef.current) {
|
||||
isRedirectingRef.current = true;
|
||||
// Ollama não está funcional - redirecionar para setup
|
||||
console.warn("Integridade do Ollama falhou no chat. Redirecionando para Setup.");
|
||||
startTransition(() => {
|
||||
router.replace('/setup');
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Em caso de erro na verificação, redirecionar para setup (fallback seguro)
|
||||
if (!isRedirectingRef.current) {
|
||||
isRedirectingRef.current = true;
|
||||
console.error("Falha ao verificar integridade do Ollama no chat:", e);
|
||||
startTransition(() => {
|
||||
router.replace('/setup');
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
hasCheckedIntegrityRef.current = true; // Marcar como executado
|
||||
}
|
||||
};
|
||||
|
||||
checkIntegrity();
|
||||
}, [router]); // Remover isSetupCompleted das dependências - executar sempre
|
||||
// Initialize with default format prompt
|
||||
const [systemPrompt] = useState(defaultFormatPrompt || "Você é um assistente útil e prestativo.");
|
||||
const [isChatsSidebarCollapsed, setIsChatsSidebarCollapsed] = useState(true); // Iniciar colapsada
|
||||
@@ -137,25 +200,10 @@ export default function ChatPage() {
|
||||
}
|
||||
}, [searchQuery, currentSessionId]);
|
||||
|
||||
// Auto-select first model
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (initializedRef.current) return;
|
||||
const stored = settings.selectedModel;
|
||||
const first = models.length > 0 ? models[0].name : "";
|
||||
const next = stored || first || "";
|
||||
if (next) {
|
||||
setSelectedModel(next);
|
||||
if (!stored) {
|
||||
settings.setSelectedModel(next);
|
||||
}
|
||||
}
|
||||
initializedRef.current = true;
|
||||
}, [settings, models]);
|
||||
|
||||
// Auto-scroll para o final durante streaming
|
||||
useEffect(() => {
|
||||
if (messagesEndRef.current && (isLoading || messages.length > 0)) {
|
||||
@@ -425,7 +473,10 @@ export default function ChatPage() {
|
||||
|
||||
// Executar busca progressiva para cada query do plano
|
||||
const searchPromises = searchPlan.map(async (query, idx) => {
|
||||
chatLog.info(` → Starting progressive search ${idx + 1}/${searchPlan.length}: "${query}"`);
|
||||
const queryStartTime = Date.now();
|
||||
chatLog.info(`\n[WebResearch] ========== QUERY ${idx + 1}/${searchPlan.length} ==========`);
|
||||
chatLog.info(`[WebResearch] Query: "${query}"`);
|
||||
chatLog.info(`[WebResearch] Timestamp: ${new Date().toISOString()}`);
|
||||
|
||||
// Atualizar progresso
|
||||
updateThinkingMessage('web-research', {
|
||||
@@ -438,6 +489,22 @@ export default function ChatPage() {
|
||||
const context = deepResearch.state.context;
|
||||
const enrichedQueries = deepResearch.state.enrichedQueries;
|
||||
|
||||
if (context) {
|
||||
chatLog.info(`[WebResearch] Using contextual analysis:`, {
|
||||
intent: context.intent,
|
||||
entities: context.entities.length,
|
||||
topics: context.topics.length,
|
||||
});
|
||||
}
|
||||
|
||||
if (enrichedQueries) {
|
||||
chatLog.info(`[WebResearch] Using enriched queries:`, {
|
||||
literal: enrichedQueries.literal.length,
|
||||
semantic: enrichedQueries.semantic.length,
|
||||
related: enrichedQueries.related.length,
|
||||
});
|
||||
}
|
||||
|
||||
// Executar busca progressiva com semantic search se disponível
|
||||
const fallbackResult = await executeProgressiveSearch(
|
||||
query,
|
||||
@@ -461,22 +528,47 @@ export default function ChatPage() {
|
||||
}
|
||||
);
|
||||
|
||||
// Log do resultado
|
||||
chatLog.info(` ✓ Progressive search ${idx + 1} complete:`);
|
||||
chatLog.info(` - Success: ${fallbackResult.success}`);
|
||||
chatLog.info(` - Total results: ${fallbackResult.scrapedSources.length}`);
|
||||
chatLog.info(` - Total analyzed: ${fallbackResult.totalResultsAnalyzed}`);
|
||||
chatLog.info(` - Attempts: ${fallbackResult.attempts.length}`);
|
||||
chatLog.info(` - Used fallback: ${fallbackResult.usedFallback}`);
|
||||
const queryDuration = Date.now() - queryStartTime;
|
||||
|
||||
// Log detalhado de cada resultado
|
||||
fallbackResult.scrapedSources.forEach((result, resultIdx) => {
|
||||
chatLog.info(` → Result ${resultIdx + 1}: ${result.title}`);
|
||||
chatLog.info(` URL: ${result.url}`);
|
||||
const contentLength = result.markdown?.length || 0;
|
||||
chatLog.info(` Content length: ${contentLength} chars`);
|
||||
// Log detalhado do resultado
|
||||
chatLog.info(`\n[WebResearch] ========== QUERY ${idx + 1} RESULTS ==========`);
|
||||
chatLog.info(`[WebResearch] Duration: ${queryDuration}ms`);
|
||||
chatLog.info(`[WebResearch] Success: ${fallbackResult.success}`);
|
||||
chatLog.info(`[WebResearch] Total results: ${fallbackResult.scrapedSources.length}`);
|
||||
chatLog.info(`[WebResearch] Total analyzed: ${fallbackResult.totalResultsAnalyzed}`);
|
||||
chatLog.info(`[WebResearch] Attempts: ${fallbackResult.attempts.length}`);
|
||||
chatLog.info(`[WebResearch] Used fallback: ${fallbackResult.usedFallback}`);
|
||||
|
||||
// Log detalhado de cada tentativa
|
||||
fallbackResult.attempts.forEach((attempt, attemptIdx) => {
|
||||
chatLog.info(`\n Attempt ${attemptIdx + 1}:`);
|
||||
chatLog.info(` Round: ${attempt.round}`);
|
||||
chatLog.info(` Strategy: ${attempt.strategy || 'N/A'}`);
|
||||
chatLog.info(` Query: "${attempt.query}"`);
|
||||
chatLog.info(` Results: ${attempt.results.length}`);
|
||||
chatLog.info(` Relevance score: ${attempt.relevanceScore.toFixed(3)}`);
|
||||
chatLog.info(` Duration: ${attempt.duration}ms`);
|
||||
});
|
||||
|
||||
// Log detalhado de cada resultado extraído
|
||||
if (fallbackResult.scrapedSources.length > 0) {
|
||||
chatLog.info(`\n[WebResearch] Extracted Sources:`);
|
||||
fallbackResult.scrapedSources.forEach((result, resultIdx) => {
|
||||
chatLog.info(`\n [${resultIdx + 1}] ${result.title}`);
|
||||
chatLog.info(` URL: ${result.url}`);
|
||||
const contentLength = result.markdown?.length || result.content?.length || 0;
|
||||
const snippetLength = result.snippet?.length || 0;
|
||||
chatLog.info(` Markdown length: ${contentLength} chars`);
|
||||
chatLog.info(` Snippet length: ${snippetLength} chars`);
|
||||
chatLog.info(` Cached: ${result.cached ? 'Yes' : 'No'}`);
|
||||
if (result.markdown) {
|
||||
const preview = result.markdown.substring(0, 200);
|
||||
chatLog.info(` Preview: ${preview}${contentLength > 200 ? '...' : ''}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
chatLog.info(`[WebResearch] ============================================\n`);
|
||||
|
||||
// Adicionar resultados à Knowledge Base
|
||||
if (fallbackResult.scrapedSources.length > 0) {
|
||||
deepResearch.addToKnowledgeBase(query, fallbackResult.scrapedSources);
|
||||
@@ -516,13 +608,14 @@ export default function ChatPage() {
|
||||
chatLog.info(`All searches complete. Knowledge Base size: ${deepResearch.state.knowledgeBase.length}`);
|
||||
|
||||
// Coletar todas as fontes para exibição
|
||||
scrapedSources = deepResearch.state.knowledgeBase.map(entry => ({
|
||||
url: entry.sourceUrl,
|
||||
title: entry.title,
|
||||
content: entry.content,
|
||||
markdown: entry.content,
|
||||
snippet: entry.content.substring(0, 200)
|
||||
}));
|
||||
scrapedSources = deepResearch.state.knowledgeBase.map(entry => ({
|
||||
url: entry.sourceUrl,
|
||||
title: entry.title,
|
||||
content: entry.content,
|
||||
markdown: entry.content,
|
||||
snippet: entry.content.substring(0, 200),
|
||||
cached: false
|
||||
}));
|
||||
|
||||
// Adicionar mensagem: Fontes encontradas
|
||||
if (scrapedSources.length > 0) {
|
||||
@@ -535,56 +628,84 @@ export default function ChatPage() {
|
||||
|
||||
if (deepResearch.state.knowledgeBase.length > 0) {
|
||||
// Passo 3: Validação e Contexto
|
||||
chatLog.info('\nStep 3: Validation');
|
||||
chatLog.info('\n[DeepResearch] ========== STEP 3: VALIDATION ==========');
|
||||
deepResearch.setStep('aggregating');
|
||||
|
||||
// Adicionar mensagem: Processamento
|
||||
addThinkingMessage('processing', 'Processando contexto...', 'running');
|
||||
const validationStart = Date.now();
|
||||
validationReport = await deepResearch.validate(selectedModel, content);
|
||||
chatLog.info(`Validation Report Length: ${validationReport.length} chars`);
|
||||
const validationDuration = Date.now() - validationStart;
|
||||
|
||||
chatLog.info(`[DeepResearch] Validation duration: ${validationDuration}ms`);
|
||||
chatLog.info(`[DeepResearch] Validation report length: ${validationReport.length} chars`);
|
||||
if (validationReport) {
|
||||
const preview = validationReport.substring(0, 500);
|
||||
chatLog.info(`[DeepResearch] Validation preview: ${preview}${validationReport.length > 500 ? '...' : ''}`);
|
||||
}
|
||||
|
||||
// Passo 4: Obter contexto curado (usando versão otimizada)
|
||||
chatLog.info('\nStep 4: Getting Curated Context (Optimized)');
|
||||
chatLog.info('\n[DeepResearch] ========== STEP 4: CONTEXT CONDENSATION ==========');
|
||||
|
||||
// Calcular tokens disponíveis dinamicamente
|
||||
const { getModelContextInfo } = await import('@/lib/model-context');
|
||||
const contextInfo = await getModelContextInfo(selectedModel, systemPrompt, messages);
|
||||
const availableTokens = contextInfo.recommendedContextWindow;
|
||||
|
||||
chatLog.info(`Model Context Window: ${contextInfo.maxContextWindow} tokens`);
|
||||
chatLog.info(`Available Tokens for KB: ${availableTokens} tokens`);
|
||||
chatLog.info(`[DeepResearch] Model: ${selectedModel}`);
|
||||
chatLog.info(`[DeepResearch] Max context window: ${contextInfo.maxContextWindow} tokens`);
|
||||
chatLog.info(`[DeepResearch] Available tokens for KB: ${availableTokens} tokens`);
|
||||
|
||||
const condensationStart = Date.now();
|
||||
const optimizedResult = deepResearch.getCuratedContextOptimized(content, availableTokens);
|
||||
const condensationDuration = Date.now() - condensationStart;
|
||||
knowledgeBaseContext = optimizedResult.context;
|
||||
|
||||
chatLog.info(`Knowledge Base Context Length: ${knowledgeBaseContext.length} chars`);
|
||||
chatLog.info(`Condensation Method: ${optimizedResult.result.method}`);
|
||||
chatLog.info(`Chunks Used: ${optimizedResult.result.chunksUsed} / ${optimizedResult.result.chunksTotal}`);
|
||||
chatLog.info(`Compression Ratio: ${(optimizedResult.result.compressionRatio * 100).toFixed(1)}%`);
|
||||
chatLog.info(`\n[DeepResearch] ========== CONDENSATION SUMMARY ==========`);
|
||||
chatLog.info(`[DeepResearch] Duration: ${condensationDuration}ms`);
|
||||
chatLog.info(`[DeepResearch] Final context length: ${knowledgeBaseContext.length} chars`);
|
||||
chatLog.info(`[DeepResearch] Method: ${optimizedResult.result.method}`);
|
||||
chatLog.info(`[DeepResearch] Chunks used: ${optimizedResult.result.chunksUsed} / ${optimizedResult.result.chunksTotal}`);
|
||||
chatLog.info(`[DeepResearch] Compression ratio: ${(optimizedResult.result.compressionRatio * 100).toFixed(1)}%`);
|
||||
chatLog.info(`[DeepResearch] Original tokens: ${optimizedResult.result.originalTokens}`);
|
||||
chatLog.info(`[DeepResearch] Final tokens: ${optimizedResult.result.totalTokens}`);
|
||||
|
||||
// Log detalhado por fonte
|
||||
if (optimizedResult.result.sources.length > 0) {
|
||||
chatLog.info(`[DeepResearch] Chunks per source:`);
|
||||
optimizedResult.result.sources.forEach((source, idx) => {
|
||||
chatLog.info(` [${idx + 1}] ${source.title}: ${source.chunksUsed} chunks`);
|
||||
chatLog.info(` URL: ${source.url}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Log preview do contexto se não for muito longo
|
||||
if (knowledgeBaseContext.length > 0) {
|
||||
chatLog.info(`\n[DeepResearch] Context preview:`);
|
||||
if (knowledgeBaseContext.length > 2000) {
|
||||
chatLog.info(`Context Preview (first 2000 chars):\n${knowledgeBaseContext.substring(0, 2000)}...`);
|
||||
chatLog.info(`${knowledgeBaseContext.substring(0, 2000)}...`);
|
||||
chatLog.info(`[DeepResearch] (truncated, ${knowledgeBaseContext.length} chars total)`);
|
||||
} else {
|
||||
chatLog.info(`Full Context:\n${knowledgeBaseContext}`);
|
||||
chatLog.info(knowledgeBaseContext);
|
||||
}
|
||||
|
||||
// Validar contexto
|
||||
const { validateCondensedContext } = await import('@/lib/knowledge-base-processor');
|
||||
const validation = validateCondensedContext(optimizedResult.result, content);
|
||||
|
||||
chatLog.info(`\n[DeepResearch] ========== CONTEXT VALIDATION ==========`);
|
||||
if (!validation.isValid) {
|
||||
chatLog.error(`Context validation failed: ${validation.warnings.join(', ')}`);
|
||||
chatLog.error(`[DeepResearch] Validation failed: ${validation.warnings.join(', ')}`);
|
||||
} else if (validation.warnings.length > 0) {
|
||||
validation.warnings.forEach(warning => {
|
||||
chatLog.warn(`⚠️ ${warning}`);
|
||||
chatLog.warn(`[DeepResearch] ⚠️ ${warning}`);
|
||||
});
|
||||
} else {
|
||||
chatLog.info('✓ Context validation passed');
|
||||
chatLog.info(`[DeepResearch] ✓ Context validation passed`);
|
||||
}
|
||||
chatLog.info(`[DeepResearch] ============================================\n`);
|
||||
} else {
|
||||
chatLog.warn('⚠️ Knowledge Base Context is empty!');
|
||||
chatLog.warn('[DeepResearch] ⚠️ Knowledge Base Context is empty!');
|
||||
}
|
||||
|
||||
|
||||
@@ -623,17 +744,21 @@ export default function ChatPage() {
|
||||
} catch (error) {
|
||||
// Tratar erro graciosamente sem quebrar o fluxo
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
chatLog.warn(`⚠️ Error in Deep Research pipeline (continuing with partial results): ${errorMsg}`);
|
||||
chatLog.warn(`⚠️ Error in Deep Research pipeline (using local model fallback): ${errorMsg}`);
|
||||
|
||||
// Atualizar mensagem de processo com aviso (não erro fatal)
|
||||
updateThinkingMessage('web-research', {
|
||||
status: 'completed', // Marcar como completed mesmo com erro parcial
|
||||
details: `Alguns erros ocorreram durante a pesquisa: ${errorMsg}. Continuando com resultados parciais...`,
|
||||
status: 'completed', // Marcar como completed mesmo com erro
|
||||
details: `Erro na pesquisa web (${errorMsg}). Usando modelo local como fallback.`,
|
||||
duration: Date.now() - webResearchStart,
|
||||
});
|
||||
|
||||
// Garantir que o estado de erro seja registrado para ativar fallback
|
||||
// O erro já será detectado pelo hasWebSearchError através do estado do hook
|
||||
chatLog.info('Web search error registered - will use local model fallback');
|
||||
|
||||
// Não definir erro global - permitir que o fluxo continue
|
||||
// O sistema continuará com conhecimento interno se necessário
|
||||
// O sistema continuará com conhecimento interno (fallback automático)
|
||||
}
|
||||
} else {
|
||||
if (!webSearch.isEnabled) {
|
||||
@@ -694,8 +819,9 @@ export default function ChatPage() {
|
||||
let enhancedSystemPrompt = systemPrompt;
|
||||
|
||||
// Se tivermos uma Knowledge Base, usar o prompt STRICT_GENERATION
|
||||
// Verificar se houve fallback (nenhum resultado relevante encontrado)
|
||||
// Verificar se houve fallback (nenhum resultado relevante encontrado ou erro na pesquisa)
|
||||
const usedFallback = knowledgeBaseContext.length === 0 || knowledgeBaseContext.length < 100;
|
||||
const hasWebSearchError = webSearch.status === 'error' || webSearch.error !== null;
|
||||
|
||||
if (knowledgeBaseContext && knowledgeBaseContext.length > 100) {
|
||||
const strictPrompt = DEEP_RESEARCH_PROMPTS.STRICT_GENERATION
|
||||
@@ -704,25 +830,36 @@ export default function ChatPage() {
|
||||
.replace('{{userQuery}}', content);
|
||||
|
||||
enhancedSystemPrompt = strictPrompt;
|
||||
} else if (usedFallback) {
|
||||
} else if (usedFallback || hasWebSearchError) {
|
||||
// Fallback: usar prompt sem contexto web, mas informando que não encontrou fontes
|
||||
// Isso garante que o modelo local sempre gere uma resposta mesmo sem dados web
|
||||
const errorContext = hasWebSearchError && webSearch.error
|
||||
? `\n**Motivo:** ${webSearch.error}`
|
||||
: '';
|
||||
|
||||
enhancedSystemPrompt = `${systemPrompt}
|
||||
|
||||
## ⚠️ AVISO IMPORTANTE
|
||||
## ⚠️ FALLBACK PARA CONHECIMENTO INTERNO
|
||||
|
||||
Não foi possível encontrar informações relevantes nas fontes web consultadas após múltiplas tentativas de busca.
|
||||
Não foi possível acessar fontes web para esta consulta após múltiplas tentativas de busca.${errorContext}
|
||||
|
||||
**INSTRUÇÕES:**
|
||||
- Responda usando APENAS seu conhecimento interno (treinamento)
|
||||
- Seja honesto e diga: "Não encontrei essa informação nas fontes consultadas."
|
||||
- Se tiver conhecimento sobre o tópico, compartilhe, mas deixe claro que não há fontes externas verificadas
|
||||
- Não invente informações ou cite fontes que não foram consultadas
|
||||
**INSTRUÇÕES CRÍTICAS:**
|
||||
- Você DEVE responder usando APENAS seu conhecimento interno (treinamento do modelo)
|
||||
- Seja transparente: informe que não foi possível acessar fontes web, mas que você responderá com base no seu conhecimento
|
||||
- Se tiver conhecimento sobre o tópico, compartilhe-o de forma útil e detalhada
|
||||
- Seja honesto sobre limitações: se não souber algo, diga claramente
|
||||
- NÃO invente informações ou cite fontes que não foram consultadas
|
||||
- NÃO mencione "fontes consultadas" ou "pesquisa realizada" - apenas responda com seu conhecimento
|
||||
|
||||
**IMPORTANTE:** Este é um fallback legítimo. O usuário espera uma resposta útil mesmo sem dados web. Use seu conhecimento para ajudar da melhor forma possível.
|
||||
|
||||
## DATA E HORA ATUAL DO SISTEMA
|
||||
|
||||
**DATA/HORA FORMATADA:** ${currentDateTime}
|
||||
**DATA/HORA NUMÉRICA:** ${currentDateExplicit}
|
||||
**ISO 8601:** ${currentDateISO}
|
||||
|
||||
Use esta data exata para referências temporais.`;
|
||||
Use esta data exata para referências temporais quando relevante.`;
|
||||
}
|
||||
// Fallback para lógica antiga se não tiver Knowledge Base
|
||||
else if (knowledgeBaseContext) {
|
||||
@@ -792,6 +929,9 @@ Ao responder sobre fatos atuais ou notícias, inicie mencionando explicitamente
|
||||
let finalUserContent = content;
|
||||
if (knowledgeBaseContext && knowledgeBaseContext.length > 100) {
|
||||
finalUserContent = `[KNOWLEDGE BASE - ÚNICA FONTE DE VERDADE]\n${knowledgeBaseContext}\n[/KNOWLEDGE BASE]\n\nResponda a pergunta usando APENAS os dados acima. Se a informação não estiver na Knowledge Base, diga "Não encontrei essa informação nas fontes consultadas."\n\nPergunta: ${content}`;
|
||||
} else if (usedFallback || hasWebSearchError) {
|
||||
// Quando usar fallback, garantir que o modelo saiba que deve usar conhecimento interno
|
||||
finalUserContent = `Pergunta do usuário: ${content}\n\nNota: Não foi possível acessar fontes web para esta consulta. Responda usando seu conhecimento interno de forma útil e detalhada.`;
|
||||
}
|
||||
|
||||
chatLog.info(`Final User Content Length: ${finalUserContent.length} chars`);
|
||||
@@ -822,6 +962,75 @@ Ao responder sobre fatos atuais ou notícias, inicie mencionando explicitamente
|
||||
chatLog.info('✅ Response generation complete');
|
||||
chatLog.info('========== QUERY COMPLETE ==========\n');
|
||||
|
||||
<<<<<<< HEAD
|
||||
// Capturar dados antes do setMessages para garantir que estão no escopo correto
|
||||
const currentScrapedSources = scrapedSources.length > 0
|
||||
? scrapedSources
|
||||
: deepResearch.state.knowledgeBase.map(entry => ({
|
||||
url: entry.sourceUrl,
|
||||
title: entry.title,
|
||||
content: entry.content,
|
||||
markdown: entry.content,
|
||||
cached: false,
|
||||
}));
|
||||
|
||||
// Coletar thinking steps das mensagens (apenas os relacionados a esta query)
|
||||
const allThinkingSteps: ThinkingMessageMetadata[] = [];
|
||||
const currentMessages = messages; // Capturar referência atual
|
||||
currentMessages.forEach((msg, idx) => {
|
||||
// Coletar thinking steps das últimas mensagens (relacionadas a esta query)
|
||||
if (idx >= currentMessages.length - 10) { // Últimas 10 mensagens
|
||||
if (msg.metadata && typeof msg.metadata === 'object' && 'type' in msg.metadata && msg.metadata.type === 'thinking') {
|
||||
allThinkingSteps.push(msg.metadata as ThinkingMessageMetadata);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Coletar sites pesquisados com detalhes completos
|
||||
const sitesResearched = currentScrapedSources.map((source, idx) => {
|
||||
// Tentar encontrar informações de duração e status dos logs
|
||||
const sourceLog = deepResearch.state.logs.find(log => {
|
||||
if (!log.parsedOutput || typeof log.parsedOutput !== 'object') return false;
|
||||
const parsed = log.parsedOutput as Record<string, unknown>;
|
||||
return 'url' in parsed && parsed.url === source.url;
|
||||
});
|
||||
|
||||
let duration: number | undefined = undefined;
|
||||
if (sourceLog?.parsedOutput && typeof sourceLog.parsedOutput === 'object') {
|
||||
const parsed = sourceLog.parsedOutput as Record<string, unknown>;
|
||||
if ('duration' in parsed && typeof parsed.duration === 'number') {
|
||||
duration = parsed.duration;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
url: source.url,
|
||||
title: source.title || 'Sem título',
|
||||
status: source.cached ? 'cached' : 'scraped',
|
||||
source: source.cached ? 'cache' : 'nodriver',
|
||||
contentLength: source.content?.length || source.markdown?.length || 0,
|
||||
scrapedAt: sourceLog?.timestamp || Date.now(),
|
||||
duration: duration,
|
||||
};
|
||||
});
|
||||
|
||||
// Coletar dados de raciocínio dos logs do deep research
|
||||
const reasoningSteps = deepResearch.state.logs
|
||||
.filter(log => log.stage === 'generation' || log.stage === 'validation')
|
||||
.map(log => ({
|
||||
stage: log.stage,
|
||||
input: log.input,
|
||||
output: log.rawOutput || JSON.stringify(log.parsedOutput),
|
||||
timestamp: log.timestamp,
|
||||
}));
|
||||
|
||||
// Estimar token usage (aproximado)
|
||||
const estimatedInputTokens = Math.ceil((enhancedSystemPrompt.length + finalUserContent.length) / 4);
|
||||
const lastAssistantMsg = currentMessages.find(m => m.role === 'assistant');
|
||||
const estimatedOutputTokens = Math.ceil((lastAssistantMsg?.content?.length || 0) / 4);
|
||||
|
||||
=======
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
// Construir dados de debug para a última mensagem do assistente
|
||||
setMessages(prev => {
|
||||
const newMessages = [...prev];
|
||||
@@ -834,6 +1043,16 @@ Ao responder sobre fatos atuais ou notícias, inicie mencionando explicitamente
|
||||
latency: Date.now() - responseStart,
|
||||
systemPrompt: enhancedSystemPrompt,
|
||||
userQuery: content,
|
||||
<<<<<<< HEAD
|
||||
contextUsed: prev.filter(m => m.role !== 'system' && m.role !== 'assistant'),
|
||||
webResearch: {
|
||||
queries: deepResearch.state.plan || [],
|
||||
enrichedQueries: deepResearch.state.enrichedQueries || undefined,
|
||||
sources: currentScrapedSources,
|
||||
sitesResearched: sitesResearched,
|
||||
logs: deepResearch.state.logs || [],
|
||||
plan: deepResearch.state.plan || [],
|
||||
=======
|
||||
contextUsed: messages.filter(m => m.role !== 'system'),
|
||||
webResearch: {
|
||||
queries: deepResearch.state.plan || [],
|
||||
@@ -841,15 +1060,39 @@ Ao responder sobre fatos atuais ou notícias, inicie mencionando explicitamente
|
||||
sources: scrapedSources,
|
||||
logs: deepResearch.state.logs,
|
||||
plan: deepResearch.state.plan,
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
knowledgeBase: deepResearch.state.knowledgeBase.map(entry => ({
|
||||
sourceUrl: entry.sourceUrl,
|
||||
title: entry.title,
|
||||
content: entry.content,
|
||||
})),
|
||||
<<<<<<< HEAD
|
||||
activeQueries: webSearch.activeQueries.length > 0 ? webSearch.activeQueries : undefined,
|
||||
activeUrls: webSearch.activeUrls.length > 0 ? webSearch.activeUrls : undefined,
|
||||
},
|
||||
deepResearchState: deepResearch.state,
|
||||
thinkingSteps: allThinkingSteps.length > 0 ? allThinkingSteps : undefined,
|
||||
reasoning: reasoningSteps.length > 0 ? {
|
||||
steps: reasoningSteps.map(r => `${r.stage}: ${r.input.substring(0, 100)}...`),
|
||||
intermediateResults: reasoningSteps.map(r => ({
|
||||
stage: r.stage,
|
||||
output: r.output,
|
||||
timestamp: r.timestamp,
|
||||
})),
|
||||
} : undefined,
|
||||
finalResponse: lastMsg.content || '',
|
||||
rawResponse: lastMsg.content || '',
|
||||
tokenUsage: {
|
||||
input: estimatedInputTokens,
|
||||
output: estimatedOutputTokens,
|
||||
total: estimatedInputTokens + estimatedOutputTokens,
|
||||
},
|
||||
=======
|
||||
},
|
||||
deepResearchState: deepResearch.state,
|
||||
finalResponse: lastMsg.content,
|
||||
rawResponse: lastMsg.content,
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
};
|
||||
|
||||
// Armazenar debug data usando o índice da mensagem
|
||||
@@ -1030,7 +1273,7 @@ Ao responder sobre fatos atuais ou notícias, inicie mencionando explicitamente
|
||||
<TitleBar />
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
<div className="flex-1 flex overflow-hidden pt-8">
|
||||
<ResizablePanelGroup direction="horizontal" className="flex-1">
|
||||
|
||||
<ResizablePanel
|
||||
@@ -1137,61 +1380,7 @@ Ao responder sobre fatos atuais ou notícias, inicie mencionando explicitamente
|
||||
|
||||
<div className="flex-1 max-w-full sm:max-w-[420px] flex items-center gap-2 min-w-0">
|
||||
<div className="flex-1 min-w-0">
|
||||
<TooltipProvider>
|
||||
<Select
|
||||
value={selectedModel || undefined}
|
||||
onValueChange={(v) => {
|
||||
if (v === "__add_model__") {
|
||||
setShowDownloadDialog(true);
|
||||
// Não alterar selectedModel quando abrir dialog
|
||||
} else {
|
||||
setSelectedModel(v);
|
||||
settings.setSelectedModel(v);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<SelectTrigger className="h-9 min-w-0 max-w-full">
|
||||
<SelectValue placeholder="Selecione um modelo..." className="truncate" />
|
||||
</SelectTrigger>
|
||||
</TooltipTrigger>
|
||||
{selectedModel && (
|
||||
<TooltipContent side="top" className="max-w-[70vw] break-words">
|
||||
<p className="text-sm">{selectedModel}</p>
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
<SelectContent className="max-w-[90vw] sm:max-w-[420px]">
|
||||
{models.map(m => (
|
||||
<Tooltip key={m.name}>
|
||||
<TooltipTrigger asChild>
|
||||
<SelectItem value={m.name} className="min-w-0">
|
||||
<span className="flex items-center justify-between w-full min-w-0 gap-2">
|
||||
<span className="flex items-center gap-2 truncate flex-1 min-w-0" style={{ maxWidth: '70vw' }}>
|
||||
<Cpu className="w-4 h-4 text-muted-foreground shrink-0" />
|
||||
<span className="truncate">{m.name}</span>
|
||||
</span>
|
||||
<span className="ml-2 text-xs text-muted-foreground shrink-0">{m.size}</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
</TooltipTrigger>
|
||||
{m.name.length > 40 && (
|
||||
<TooltipContent side="right" className="max-w-[70vw] break-words">
|
||||
<p className="text-sm">{m.name}</p>
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
))}
|
||||
<SelectItem value="__add_model__" className="text-primary font-medium">
|
||||
<span className="flex items-center gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
Adicionar modelo...
|
||||
</span>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TooltipProvider>
|
||||
<ModelSelector />
|
||||
</div>
|
||||
<Button variant="outline" size="icon" className="h-9 w-9" onClick={refresh} title="Atualizar modelos">
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
@@ -1463,4 +1652,3 @@ Ao responder sobre fatos atuais ou notícias, inicie mencionando explicitamente
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -376,6 +376,16 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Utility class para ocultar scrollbar */
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari and Opera */
|
||||
}
|
||||
|
||||
/* Reasoning Chain animations */
|
||||
@keyframes reasoning-fade-in {
|
||||
from {
|
||||
|
||||
@@ -4,6 +4,13 @@ import "./globals.css";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { AppUpdaterProvider } from "@/components/app-updater-provider";
|
||||
import { KeyboardShortcuts } from "@/components/keyboard-shortcuts";
|
||||
<<<<<<< HEAD
|
||||
import { DownloadProvider } from "@/contexts/download-context";
|
||||
import { GlobalDownloadStatusBar } from "@/components/global-download-status-bar";
|
||||
import { OllamaHealthProvider } from "@/contexts/ollama-health-context";
|
||||
import { ServiceInterruptionOverlay } from "@/components/ui/service-interruption-overlay";
|
||||
=======
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@@ -40,9 +47,21 @@ export default function RootLayout({
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<<<<<<< HEAD
|
||||
<OllamaHealthProvider>
|
||||
<DownloadProvider>
|
||||
<KeyboardShortcuts />
|
||||
{children}
|
||||
<AppUpdaterProvider />
|
||||
<GlobalDownloadStatusBar />
|
||||
<ServiceInterruptionOverlay />
|
||||
</DownloadProvider>
|
||||
</OllamaHealthProvider>
|
||||
=======
|
||||
<KeyboardShortcuts />
|
||||
{children}
|
||||
<AppUpdaterProvider />
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+130
@@ -1,5 +1,15 @@
|
||||
'use client';
|
||||
|
||||
<<<<<<< HEAD
|
||||
import { TitleBar } from "@/components/titlebar";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState, startTransition, useRef } from "react";
|
||||
import { useSettingsStore } from "@/store/settings-store";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
export default function Home() {
|
||||
=======
|
||||
import { useSetupCheck } from "@/hooks/use-setup-check";
|
||||
import { Hero } from "@/components/landing/hero";
|
||||
import { InstallModal } from "@/components/modals/install-modal";
|
||||
@@ -11,8 +21,111 @@ import { useEffect, startTransition } from "react";
|
||||
|
||||
export default function Home() {
|
||||
const { status, ollamaStatus, checkingStep, recheck } = useSetupCheck();
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
const router = useRouter();
|
||||
const { setSetupCompleted } = useSettingsStore();
|
||||
const [isHydrated, setIsHydrated] = useState(false);
|
||||
const [isChecking, setIsChecking] = useState(true);
|
||||
|
||||
// Refs para evitar loops e execuções múltiplas
|
||||
const hasCheckedRef = useRef(false);
|
||||
const isRedirectingRef = useRef(false);
|
||||
|
||||
<<<<<<< HEAD
|
||||
// Hydration check: aguardar Zustand carregar do localStorage
|
||||
useEffect(() => {
|
||||
setIsHydrated(true);
|
||||
}, []);
|
||||
|
||||
// Guarda de roteamento: verificação de integridade do Ollama + flag persistente
|
||||
// Prioridade: Integridade do Sistema > Flag de Setup
|
||||
useEffect(() => {
|
||||
if (!isHydrated) return;
|
||||
if (hasCheckedRef.current) return; // Proteção contra múltiplas execuções
|
||||
|
||||
const performCheck = async () => {
|
||||
if (isRedirectingRef.current) return; // Evitar múltiplos redirecionamentos
|
||||
|
||||
setIsChecking(true);
|
||||
|
||||
try {
|
||||
// 1. Verificação Real do Sistema (Fonte da Verdade)
|
||||
const isOllamaIntact = await invoke<boolean>('verify_ollama_integrity_command');
|
||||
|
||||
// Ler isSetupCompleted diretamente do store (não usar como dependência)
|
||||
const currentSetupStatus = useSettingsStore.getState().isSetupCompleted;
|
||||
|
||||
if (!isOllamaIntact) {
|
||||
// Se o motor não existe, força o setup, não importa o que o storage diga
|
||||
console.warn("Integridade do Ollama falhou. Redirecionando para Setup.");
|
||||
|
||||
if (!isRedirectingRef.current) {
|
||||
isRedirectingRef.current = true;
|
||||
// Resetar flag de setup se Ollama não está íntegro (ANTES de redirecionar)
|
||||
setSetupCompleted(false);
|
||||
|
||||
// Pequeno delay para garantir que o estado seja persistido no localStorage
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
startTransition(() => {
|
||||
router.replace('/setup');
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Verificação de Fluxo de UI (apenas se Ollama está OK)
|
||||
if (!isRedirectingRef.current) {
|
||||
isRedirectingRef.current = true;
|
||||
|
||||
if (currentSetupStatus) {
|
||||
startTransition(() => {
|
||||
router.replace('/chat');
|
||||
});
|
||||
} else {
|
||||
startTransition(() => {
|
||||
router.replace('/setup');
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Falha crítica no check de inicialização", e);
|
||||
// Fallback seguro: sempre ir para setup em caso de erro
|
||||
if (!isRedirectingRef.current) {
|
||||
isRedirectingRef.current = true;
|
||||
setSetupCompleted(false);
|
||||
|
||||
// Pequeno delay para garantir que o estado seja persistido no localStorage
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
startTransition(() => {
|
||||
router.replace('/setup');
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setIsChecking(false);
|
||||
hasCheckedRef.current = true; // Marcar como executado
|
||||
}
|
||||
};
|
||||
|
||||
performCheck();
|
||||
}, [isHydrated, router, setSetupCompleted]);
|
||||
|
||||
// Enquanto não hidratou ou está verificando, retornar skeleton simples
|
||||
if (!isHydrated || isChecking) {
|
||||
return (
|
||||
<div className="h-screen w-full bg-background overflow-hidden flex flex-col">
|
||||
<TitleBar />
|
||||
<div className="flex-1 flex items-center justify-center pt-8">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{!isHydrated ? 'Carregando...' : 'Verificando sistema...'}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">Aguarde um momento...</span>
|
||||
</div>
|
||||
=======
|
||||
// Redirecionar automaticamente se tudo estiver pronto
|
||||
useEffect(() => {
|
||||
if (status === 'ready') {
|
||||
@@ -54,13 +167,29 @@ export default function Home() {
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<span className="text-sm font-medium text-foreground">{checkingStep}</span>
|
||||
<span className="text-xs text-muted-foreground">Aguarde um momento...</span>
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Após verificação, mostrar loading de redirecionamento (não deve aparecer por muito tempo)
|
||||
return (
|
||||
<<<<<<< HEAD
|
||||
<div className="h-screen w-full bg-background overflow-hidden flex flex-col">
|
||||
<TitleBar />
|
||||
<div className="flex-1 flex items-center justify-center pt-8">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<span className="text-sm font-medium text-foreground">Redirecionando...</span>
|
||||
<span className="text-xs text-muted-foreground">Aguarde um momento...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
=======
|
||||
<main className="flex min-h-screen flex-col items-center justify-center p-4 bg-background text-foreground transition-colors duration-300">
|
||||
{/* Notificação de boas-vindas */}
|
||||
<WelcomeNotification />
|
||||
@@ -108,5 +237,6 @@ export default function Home() {
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
);
|
||||
}
|
||||
+1550
-1049
File diff suppressed because it is too large.
Load diff
+11
-25
@@ -1,5 +1,11 @@
|
||||
'use client';
|
||||
|
||||
<<<<<<< HEAD
|
||||
import { SetupWizard } from '@/components/setup/setup-wizard';
|
||||
import { TitleBar } from '@/components/titlebar';
|
||||
|
||||
export default function SetupPage() {
|
||||
=======
|
||||
import { useHardware } from "@/hooks/use-hardware";
|
||||
import { HardwareScan } from "@/components/setup/hardware-scan";
|
||||
import { GpuSelector } from "@/components/setup/gpu-selector";
|
||||
@@ -24,32 +30,12 @@ export default function SetupPage() {
|
||||
});
|
||||
};
|
||||
|
||||
>>>>>>> 593efd42e091a845dea82ee6646e027bce1e18c5
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-center p-4 bg-background text-foreground">
|
||||
<div className="w-full max-w-2xl space-y-8 animate-in fade-in duration-700">
|
||||
<div className="text-center space-y-2">
|
||||
<h1 className="text-3xl font-bold tracking-tighter">Configuração Inicial</h1>
|
||||
<p className="text-muted-foreground">Vamos preparar o ambiente ideal para você.</p>
|
||||
</div>
|
||||
|
||||
<HardwareScan specs={specs} loading={loading} />
|
||||
|
||||
{!loading && specs && gpus.length > 0 && (
|
||||
<div className="animate-in slide-in-from-bottom-8 duration-700 delay-200 fill-mode-backwards">
|
||||
<GpuSelector gpus={gpus} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && specs && (
|
||||
<div className="animate-in slide-in-from-bottom-8 duration-700 delay-300 fill-mode-backwards">
|
||||
<ModelSelector
|
||||
recommendation={getRecommendation(specs)}
|
||||
onComplete={handleComplete}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
<div className="h-screen w-full bg-background overflow-hidden flex flex-col">
|
||||
<TitleBar />
|
||||
<SetupWizard />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ export default function TasksPage() {
|
||||
return (
|
||||
<div className="h-screen w-full bg-background overflow-hidden flex flex-col">
|
||||
<TitleBar />
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="flex-1 overflow-y-auto pt-8">
|
||||
<div className="container mx-auto p-4 sm:p-6 max-w-6xl w-full overflow-x-hidden">
|
||||
<div className="mb-4 sm:mb-6">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
|
||||
Reference in new issue
Block a user