feat: enhance UI and functionality across chat and settings

- Added custom scrollbar styles for improved aesthetics and usability across the interface.
- Implemented responsive adjustments for chat sidebar and input components to enhance mobile experience.
- Integrated tooltip components in select elements for better user guidance and information display.
- Updated chat input and message components for improved styling and functionality.
- Enhanced settings page with semantic query expansion options and improved model selection UI.
- Refactored sidebar list to include better date formatting and tooltip support for session titles.
- Introduced GPU stats monitoring in the system panel for better hardware insights.
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-26 19:04:46 -03:00
1 parent 84ea2dec19
commit e5f062040c
33 files changed
+4670 -465

No files matched your search

+157 -4
View File
@@ -1,4 +1,5 @@
import type { Chunk } from './content-condenser';
import type { QueryContext } from './contextual-analyzer';
import { normalizeText } from './text-normalization';
/**
@@ -72,14 +73,162 @@ export function calculateSimilarity(query: string, chunk: string): number {
return Math.min(Math.max(score, 0), 1);
}
/**
* Calcula similaridade semântica avançada usando contexto completo
* Considera entidades, tópicos, relacionamentos e intenção
*
* @param context - Contexto analisado da query
* @param chunk - Chunk de conteúdo
* @returns Score de similaridade semântica (0-1)
*/
export function calculateSemanticSimilarity(
context: QueryContext,
chunk: string
): number {
if (!chunk || !context) {
return 0;
}
const normalizedChunk = normalizeText(chunk).toLowerCase();
let totalScore = 0;
let weightSum = 0;
// 1. Match de keywords (peso: 0.3)
if (context.keywords.length > 0) {
let keywordMatches = 0;
for (const keyword of context.keywords) {
const keywordLower = keyword.toLowerCase();
if (normalizedChunk.includes(keywordLower)) {
keywordMatches++;
}
}
const keywordScore = keywordMatches / context.keywords.length;
totalScore += keywordScore * 0.3;
weightSum += 0.3;
}
// 2. Match de entidades (peso: 0.3)
if (context.entities.length > 0) {
let entityMatches = 0;
for (const entity of context.entities) {
const entityLower = entity.value.toLowerCase();
// Buscar entidade completa (case-insensitive)
if (normalizedChunk.includes(entityLower)) {
entityMatches += entity.confidence; // Ponderar por confiança
}
}
const entityScore = entityMatches / context.entities.length;
totalScore += entityScore * 0.3;
weightSum += 0.3;
}
// 3. Match de tópicos (peso: 0.2)
if (context.topics.length > 0) {
let topicMatches = 0;
for (const topic of context.topics) {
const topicLower = topic.toLowerCase();
// Verificar se tópico aparece no chunk (pode ser parcial)
const topicWords = topicLower.split(/\s+/);
const matches = topicWords.filter(word =>
word.length > 3 && normalizedChunk.includes(word)
);
if (matches.length >= topicWords.length * 0.5) { // 50% das palavras do tópico
topicMatches++;
}
}
const topicScore = context.topics.length > 0
? topicMatches / context.topics.length
: 0;
totalScore += topicScore * 0.2;
weightSum += 0.2;
}
// 4. Match de sinônimos (peso: 0.1)
if (context.synonyms.length > 0) {
let synonymMatches = 0;
for (const synonym of context.synonyms) {
const synonymLower = synonym.toLowerCase();
if (normalizedChunk.includes(synonymLower)) {
synonymMatches++;
}
}
const synonymScore = context.synonyms.length > 0
? Math.min(synonymMatches / context.synonyms.length, 0.5) // Cap em 0.5 para não dominar
: 0;
totalScore += synonymScore * 0.1;
weightSum += 0.1;
}
// 5. Relevância contextual (peso: 0.1)
// Verificar se contexto temporal/geográfico aparece no chunk
let contextualScore = 0;
if (context.temporalContext?.period) {
if (normalizedChunk.includes(context.temporalContext.period)) {
contextualScore += 0.5;
}
}
if (context.temporalContext?.relative) {
const relativeTerms = ['recente', 'atual', 'novo', 'último', 'atualizado'];
if (relativeTerms.some(term => normalizedChunk.includes(term))) {
contextualScore += 0.3;
}
}
if (context.geographicContext?.location) {
const locationLower = context.geographicContext.location.toLowerCase();
if (normalizedChunk.includes(locationLower)) {
contextualScore += 0.5;
}
}
totalScore += Math.min(contextualScore, 1) * 0.1;
weightSum += 0.1;
// Normalizar pelo peso total
const finalScore = weightSum > 0 ? totalScore / weightSum : 0;
// Garantir que score esteja entre 0 e 1
return Math.min(Math.max(finalScore, 0), 1);
}
/**
* Calcula similaridade combinada (keywords + semântica)
*
* @param query - Query original
* @param context - Contexto analisado (opcional)
* @param chunk - Chunk de conteúdo
* @returns Score combinado (0-1)
*/
export function calculateCombinedSimilarity(
query: string,
chunk: string,
context?: QueryContext
): number {
// Score baseado em keywords
const keywordScore = calculateSimilarity(query, chunk);
// Se temos contexto, calcular score semântico também
if (context) {
const semanticScore = calculateSemanticSimilarity(context, chunk);
// Combinar: 60% keywords, 40% semântica
return keywordScore * 0.6 + semanticScore * 0.4;
}
return keywordScore;
}
/**
* Calcula scores de relevância para todos os chunks
*
* @param chunks - Array de chunks
* @param query - Query do usuário
* @param context - Contexto analisado (opcional, para ranqueamento semântico)
* @returns Chunks com scores atribuídos, ordenados por relevância
*/
export function scoreChunks(chunks: Chunk[], query: string): Chunk[] {
export function scoreChunks(
chunks: Chunk[],
query: string,
context?: QueryContext
): Chunk[] {
if (!query || chunks.length === 0) {
return chunks;
}
@@ -87,7 +236,9 @@ export function scoreChunks(chunks: Chunk[], query: string): Chunk[] {
// Calcular score para cada chunk
const scoredChunks = chunks.map(chunk => ({
...chunk,
score: calculateSimilarity(query, chunk.content),
score: context
? calculateCombinedSimilarity(query, chunk.content, context)
: calculateSimilarity(query, chunk.content),
}));
// Ordenar por score (maior primeiro)
@@ -170,16 +321,18 @@ export function filterByMinScore(chunks: Chunk[], minScore: number = 0.1): Chunk
* @param query - Query do usuário
* @param maxTokens - Limite máximo de tokens
* @param minScore - Score mínimo (opcional)
* @param context - Contexto analisado (opcional, para ranqueamento semântico)
* @returns Chunks selecionados e ordenados por relevância
*/
export function scoreAndSelectChunks(
chunks: Chunk[],
query: string,
maxTokens: number,
minScore?: number
minScore?: number,
context?: QueryContext
): Chunk[] {
// Calcular scores
let scoredChunks = scoreChunks(chunks, query);
let scoredChunks = scoreChunks(chunks, query, context);
// Filtrar por score mínimo se especificado
if (minScore !== undefined) {