| @@ -209,8 +209,18 @@ | ||
| 209 | 209 | // Update action matches |
| 210 | 210 | this.updateActionMatches(testingData.action_matches || []); |
| 211 | 211 | |
| 212 | 212 | // Log summary |
| 213 | + // 58f8b4: URL-guard outcome — surface silent stripping. | |
| 214 | + if (testingData.url_validation) { | |
| 215 | + var uv = testingData.url_validation; | |
| 216 | + if (uv.removed_count > 0) { | |
| 217 | + this.log('URL guard stripped ' + uv.removed_count + ' unapproved link' + (uv.removed_count !== 1 ? 's' : '') + ': ' + (uv.removed_urls || []).join(', ')); | |
| 218 | + } else if (uv.checked > 0) { | |
| 219 | + this.log('URL guard: all ' + uv.checked + ' link' + (uv.checked !== 1 ? 's' : '') + ' in the answer approved (' + (uv.strict ? 'strict' : 'lenient') + ' mode)'); | |
| 220 | + } | |
| 221 | + } | |
| 222 | + | |
| 213 | 223 | if (testingData.knowledge_base_type) { |
| 214 | 224 | this.log('Knowledge Base: ' + testingData.knowledge_base_type); |
| 215 | 225 | } |
| 216 | 226 | if (testingData.similarity_threshold) { |
| @@ -318,8 +328,12 @@ | ||
| 318 | 328 | el.html('<div class="mxch-testing-no-data">No similarity data available</div>'); |
| 319 | 329 | return; |
| 320 | 330 | } |
| 321 | 331 | |
| 332 | + // Hybrid keyword boost (38ffa1): rows carry matched_via + fused_rank | |
| 333 | + // when hybrid retrieval was on for this test message. | |
| 334 | + var hybridOn = topMatches.some(function(m) { return m.matched_via; }); | |
| 335 | + | |
| 322 | 336 | // Group matches by source URL |
| 323 | 337 | var groupedByUrl = {}; |
| 324 | 338 | topMatches.forEach(function(match) { |
| 325 | 339 | var url = match.source_display || 'Unknown'; |
| @@ -330,9 +344,11 @@ | ||
| 330 | 344 | bestScore: 0, |
| 331 | 345 | usedForContext: false, |
| 332 | 346 | totalChunks: match.total_chunks || 1, |
| 333 | 347 | matchedChunks: [], |
| 334 | - isChunked: match.is_chunk || false | |
| 348 | + isChunked: match.is_chunk || false, | |
| 349 | + bestFusedRank: Infinity, | |
| 350 | + viaSet: {} | |
| 335 | 351 | }; |
| 336 | 352 | } |
| 337 | 353 | |
| 338 | 354 | if (match.similarity_percentage > groupedByUrl[url].bestScore) { |
| @@ -342,8 +358,15 @@ | ||
| 342 | 358 | if (match.used_for_context) { |
| 343 | 359 | groupedByUrl[url].usedForContext = true; |
| 344 | 360 | } |
| 345 | 361 | |
| 362 | + if (match.fused_rank && match.fused_rank < groupedByUrl[url].bestFusedRank) { | |
| 363 | + groupedByUrl[url].bestFusedRank = match.fused_rank; | |
| 364 | + } | |
| 365 | + if (match.matched_via) { | |
| 366 | + groupedByUrl[url].viaSet[match.matched_via] = true; | |
| 367 | + } | |
| 368 | + | |
| 346 | 369 | groupedByUrl[url].matchedChunks.push({ |
| 347 | 370 | chunkIndex: match.chunk_index, |
| 348 | 371 | score: match.similarity_percentage, |
| 349 | 372 | usedForContext: match.used_for_context, |
| @@ -350,9 +373,14 @@ | ||
| 350 | 373 | aboveThreshold: match.above_threshold |
| 351 | 374 | }); |
| 352 | 375 | }); |
| 353 | 376 | |
| 354 | - var urlGroups = Object.values(groupedByUrl).sort(function(a, b) { return b.bestScore - a.bestScore; }); | |
| 377 | + var urlGroups = Object.values(groupedByUrl).sort(function(a, b) { | |
| 378 | + if (hybridOn && a.bestFusedRank !== b.bestFusedRank) { | |
| 379 | + return a.bestFusedRank - b.bestFusedRank; | |
| 380 | + } | |
| 381 | + return b.bestScore - a.bestScore; | |
| 382 | + }); | |
| 355 | 383 | var usedUrlCount = sourcesUsed > 0 ? sourcesUsed : urlGroups.filter(function(g) { return g.usedForContext; }).length; |
| 356 | 384 | var chunksInfo = totalChunksUsed > 0 ? totalChunksUsed + ' chunks sent to AI' : topMatches.length + ' chunk matches'; |
| 357 | 385 | |
| 358 | 386 | var html = '<div class="matches-header">' + |
| @@ -377,13 +405,24 @@ | ||
| 377 | 405 | var expandToggle = hasMultipleChunks |
| 378 | 406 | ? '<span class="chunk-expand-toggle" data-group="' + groupIndex + '">▶ Show chunks</span>' |
| 379 | 407 | : ''; |
| 380 | 408 | |
| 409 | + var viaChip = ''; | |
| 410 | + if (hybridOn) { | |
| 411 | + var vias = Object.keys(group.viaSet); | |
| 412 | + if (vias.length) { | |
| 413 | + var viaLabel = (vias.length > 1 || vias[0] === 'both') ? 'Both' | |
| 414 | + : (vias[0] === 'keyword' ? 'Keyword' : 'Vector'); | |
| 415 | + viaChip = '<span class="mxch-rag-via-chip mxch-rag-via-' + viaLabel.toLowerCase() + '">' + viaLabel + '</span>'; | |
| 416 | + } | |
| 417 | + } | |
| 418 | + | |
| 381 | 419 | html += '<div class="match-card ' + cardClass + '">' + |
| 382 | 420 | '<div class="match-header">' + |
| 383 | 421 | '<div class="match-title">' + |
| 384 | 422 | '<span class="status-icon">' + statusIcon + '</span>' + |
| 385 | 423 | '<span class="similarity-score">' + group.bestScore + '%</span>' + |
| 424 | + viaChip + | |
| 386 | 425 | chunkSummary + |
| 387 | 426 | '</div>' + |
| 388 | 427 | '<span class="context-label">' + contextLabel + '</span>' + |
| 389 | 428 | '</div>' + |