first draft enhancement 2
This commit is contained in:
parent
c267681e7d
commit
99117e8e7a
21
.env.example
21
.env.example
@ -190,19 +190,22 @@ FORENSIC_AUDIT_RETENTION_HOURS=24
|
||||
FORENSIC_AUDIT_MAX_ENTRIES=50
|
||||
|
||||
# ============================================================================
|
||||
# 10. QUALITY CONTROL & BIAS DETECTION (ADVANCED)
|
||||
# 10. CONFIDENCE SCORING SYSTEM (Enhancement 2)
|
||||
# ============================================================================
|
||||
|
||||
# Confidence scoring weights (must sum to 1.0)
|
||||
CONFIDENCE_EMBEDDINGS_WEIGHT=0.3
|
||||
CONFIDENCE_CONSENSUS_WEIGHT=0.25
|
||||
CONFIDENCE_DOMAIN_MATCH_WEIGHT=0.25
|
||||
CONFIDENCE_FRESHNESS_WEIGHT=0.2
|
||||
# Confidence component weights (must sum to 1.0)
|
||||
CONFIDENCE_EMBEDDINGS_WEIGHT=0.3 # Weight for vector similarity quality
|
||||
CONFIDENCE_CONSENSUS_WEIGHT=0.25 # Weight for micro-task agreement
|
||||
CONFIDENCE_DOMAIN_MATCH_WEIGHT=0.25 # Weight for domain alignment
|
||||
CONFIDENCE_FRESHNESS_WEIGHT=0.2 # Weight for tool freshness/maintenance
|
||||
|
||||
# Confidence thresholds (0-100)
|
||||
CONFIDENCE_MINIMUM_THRESHOLD=40
|
||||
CONFIDENCE_MEDIUM_THRESHOLD=60
|
||||
CONFIDENCE_HIGH_THRESHOLD=80
|
||||
CONFIDENCE_MINIMUM_THRESHOLD=40 # Below this = weak recommendation
|
||||
CONFIDENCE_MEDIUM_THRESHOLD=60 # 40-59 = weak, 60-79 = moderate
|
||||
CONFIDENCE_HIGH_THRESHOLD=80 # 80+ = strong recommendation
|
||||
|
||||
# Domain keywords for confidence scoring (domain:keyword1,keyword2|domain:keyword3,keyword4)
|
||||
CONFIDENCE_DOMAIN_KEYWORDS="incident-response:incident,breach,attack,compromise,response|malware-analysis:malware,virus,trojan,reverse,analysis|network-forensics:network,traffic,packet,pcap,wireshark|mobile-forensics:mobile,android,ios,phone,app|cloud-forensics:cloud,aws,azure,saas,paas"
|
||||
|
||||
# ============================================================================
|
||||
# PERFORMANCE TUNING PRESETS
|
||||
|
102
check-unused-css.js
Normal file
102
check-unused-css.js
Normal file
@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env node
|
||||
// find-unused-css.js
|
||||
// Usage: node find-unused-css <cssFile> <sourceRoot> [--verbose]
|
||||
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import fg from 'fast-glob';
|
||||
import pc from 'picocolors';
|
||||
import postcss from 'postcss';
|
||||
import safeParser from 'postcss-safe-parser';
|
||||
|
||||
const [,, cssPath, srcRoot = '.', ...rest] = process.argv;
|
||||
const verbose = rest.includes('--verbose');
|
||||
if (!cssPath) {
|
||||
console.error('Usage: node find-unused-css <cssFile> <sourceRoot>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* 1. Parse the CSS and harvest class/id tokens */
|
||||
/* -------------------------------------------------- */
|
||||
const cssRaw = await fs.readFile(cssPath, 'utf8');
|
||||
const root = postcss().process(cssRaw, { parser: safeParser }).root;
|
||||
|
||||
const selectorTokens = new Map(); // selector → Set('.foo', '#bar')
|
||||
const CLASS = /\.([\w-]+)/g;
|
||||
const ID = /#([\w-]+)/g;
|
||||
|
||||
root.walkRules(rule => {
|
||||
rule.selectors.forEach(sel => {
|
||||
const tokens = new Set();
|
||||
sel.replace(CLASS, (_, c) => tokens.add('.'+c));
|
||||
sel.replace(ID, (_, i) => tokens.add('#'+i));
|
||||
if (tokens.size) selectorTokens.set(sel, tokens);
|
||||
});
|
||||
});
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* 2. Dynamic classes you add via JS (safe keep) */
|
||||
/* -------------------------------------------------- */
|
||||
const dynamicAllow = new Set([
|
||||
'hidden', 'active', 'loading', 'open', 'closed'
|
||||
]);
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* 3. Read every source file once */
|
||||
/* -------------------------------------------------- */
|
||||
const files = await fg([
|
||||
`${srcRoot}/**/*.{html,htm,js,jsx,ts,tsx,vue,svelte,astro}`,
|
||||
`!${srcRoot}/**/node_modules/**`
|
||||
]);
|
||||
const sources = await Promise.all(files.map(f => fs.readFile(f, 'utf8')));
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* 4. Fast search helpers */
|
||||
/* -------------------------------------------------- */
|
||||
const makeClassRE = cls =>
|
||||
new RegExp(
|
||||
`(class|className)=['"][^'"]*\\b${cls}\\b[^'"]*['"]|['"\`]${cls}['"\`]`,
|
||||
'i'
|
||||
);
|
||||
const makeIdRE = id =>
|
||||
new RegExp(`id=['"]${id}['"]|['"\`]${id}['"\`]`, 'i');
|
||||
|
||||
const tokenInSources = token => {
|
||||
// dynamic allow-list
|
||||
if (dynamicAllow.has(token)) return true;
|
||||
|
||||
const re = token.startsWith('.')
|
||||
? makeClassRE(token.slice(1))
|
||||
: makeIdRE(token.slice(1));
|
||||
|
||||
return sources.some(txt => re.test(txt));
|
||||
};
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* 5. Decide used vs unused */
|
||||
/* -------------------------------------------------- */
|
||||
const used = [];
|
||||
const unused = [];
|
||||
|
||||
for (const [selector, tokens] of selectorTokens.entries()) {
|
||||
const isUsed = [...tokens].some(tokenInSources); // **ANY** token keeps rule
|
||||
(isUsed ? used : unused).push(selector);
|
||||
if (verbose) {
|
||||
console.log(isUsed ? pc.green('✓ '+selector) : pc.red('✗ '+selector));
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* 6. Report & write list */
|
||||
/* -------------------------------------------------- */
|
||||
console.log(
|
||||
`\n${pc.bold(pc.blue('🎯 CSS usage summary'))}\n` +
|
||||
` selectors total : ${selectorTokens.size}\n` +
|
||||
` still used : ${pc.green(used.length)}\n` +
|
||||
` maybe unused : ${pc.red(unused.length)}\n`
|
||||
);
|
||||
|
||||
const outFile = path.resolve('unused-selectors.txt');
|
||||
await fs.writeFile(outFile, unused.join('\n'));
|
||||
console.log(`📝 Unused selector list → ${pc.yellow(outFile)}\n`);
|
@ -20,7 +20,11 @@
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.9"
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"fast-glob": "^3.3.3",
|
||||
"picocolors": "^1.1.1",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-safe-parser": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
import { getToolsData } from '../utils/dataService.js';
|
||||
import { isToolHosted } from '../utils/toolHelpers.js';
|
||||
|
||||
const data = await getToolsData();
|
||||
const tools = data.tools;
|
||||
@ -698,7 +699,10 @@ class AIQueryInterface {
|
||||
if (fullTool) {
|
||||
toolsByPhase[recTool.phase].push({
|
||||
...fullTool,
|
||||
recommendation: recTool
|
||||
recommendation: recTool,
|
||||
confidence: recTool.confidence,
|
||||
justification: recTool.justification,
|
||||
priority: recTool.priority
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -999,6 +1003,151 @@ class AIQueryInterface {
|
||||
return String(data);
|
||||
}
|
||||
|
||||
renderConfidenceIndicator(confidence) {
|
||||
if (!confidence || typeof confidence.overall !== 'number') return '';
|
||||
|
||||
const confidenceColor = confidence.overall >= 80 ? 'var(--color-accent)' :
|
||||
confidence.overall >= 60 ? 'var(--color-warning)' : 'var(--color-error)';
|
||||
|
||||
const strengthText = confidence.overall >= 80 ? 'HOCH' :
|
||||
confidence.overall >= 60 ? 'MITTEL' : 'NIEDRIG';
|
||||
|
||||
return `
|
||||
<div class="confidence-indicator-integrated" style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem;">
|
||||
<div class="confidence-bar-wrapper" style="flex: 1; height: 4px; background-color: var(--color-bg-tertiary); border-radius: 2px; overflow: hidden;">
|
||||
<div class="confidence-bar-fill" style="width: ${confidence.overall}%; height: 100%; background-color: ${confidenceColor}; transition: var(--transition-fast);"></div>
|
||||
</div>
|
||||
<div class="confidence-badge-compact" style="display: flex; align-items: center; gap: 0.375rem;">
|
||||
<span class="badge badge-mini" style="background-color: ${confidenceColor}; color: white; font-weight: 600; font-size: 0.625rem;">
|
||||
${confidence.overall}%
|
||||
</span>
|
||||
<button class="btn-icon" style="width: 16px; height: 16px; padding: 0.125rem; font-size: 0.625rem;" onclick="this.parentElement.nextElementSibling.style.display = this.parentElement.nextElementSibling.style.display === 'none' ? 'block' : 'none'; this.textContent = this.textContent === 'ℹ️' ? '✕' : 'ℹ️';" title="Vertrauens-Details">ℹ️</button>
|
||||
</div>
|
||||
<div style="display: none; position: absolute; z-index: 20; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 0.5rem; padding: 1rem; margin-top: 6rem; right: 0; min-width: 320px; box-shadow: var(--shadow-lg);" class="confidence-details-popup">
|
||||
<div style="margin-bottom: 0.75rem;">
|
||||
<div style="display: flex; justify-content: between; align-items: center; margin-bottom: 0.5rem;">
|
||||
<strong class="text-sm">Vertrauens-Komponenten</strong>
|
||||
<span class="badge badge-mini" style="background-color: ${confidenceColor}; color: white;">${strengthText}</span>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; font-size: 0.75rem;">
|
||||
<div style="display: flex; justify-content: space-between; padding: 0.25rem; background: var(--color-bg-secondary); border-radius: 0.25rem;">
|
||||
<span>Ähnlichkeit:</span>
|
||||
<span class="font-semibold">${confidence.embeddingsQuality}%</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding: 0.25rem; background: var(--color-bg-secondary); border-radius: 0.25rem;">
|
||||
<span>Domain:</span>
|
||||
<span class="font-semibold">${confidence.domainAlignment}%</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding: 0.25rem; background: var(--color-bg-secondary); border-radius: 0.25rem;">
|
||||
<span>Konsens:</span>
|
||||
<span class="font-semibold">${confidence.consensus}%</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; padding: 0.25rem; background: var(--color-bg-secondary); border-radius: 0.25rem;">
|
||||
<span>Aktualität:</span>
|
||||
<span class="font-semibold">${confidence.freshness}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${confidence.strengthIndicators && confidence.strengthIndicators.length > 0 ? `
|
||||
<div style="margin-bottom: 0.75rem;">
|
||||
<strong class="text-accent" style="font-size: 0.75rem;">✓ Stärken:</strong>
|
||||
<ul style="font-size: 0.6875rem; margin-top: 0.25rem; padding-left: 1rem; line-height: 1.3;">
|
||||
${confidence.strengthIndicators.slice(0, 2).map(strength => `<li style="margin-bottom: 0.25rem;">${this.sanitizeText(strength)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${confidence.uncertaintyFactors && confidence.uncertaintyFactors.length > 0 ? `
|
||||
<div style="padding-top: 0.75rem; border-top: 1px solid var(--color-border);">
|
||||
<strong class="text-warning" style="font-size: 0.75rem;">⚠ Unsicherheiten:</strong>
|
||||
<ul style="font-size: 0.6875rem; margin-top: 0.25rem; padding-left: 1rem; line-height: 1.3;">
|
||||
${confidence.uncertaintyFactors.slice(0, 2).map(factor => `<li style="margin-bottom: 0.25rem;">${this.sanitizeText(factor)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderWorkflowTool(tool) {
|
||||
const hasValidProjectUrl = isToolHosted(tool);
|
||||
const priorityColors = {
|
||||
high: 'var(--color-error)',
|
||||
medium: 'var(--color-warning)',
|
||||
low: 'var(--color-accent)'
|
||||
};
|
||||
|
||||
return `
|
||||
<div class="tool-recommendation ${this.getToolClass(tool, 'recommendation')}" onclick="window.showToolDetails('${tool.name}')" style="position: relative;">
|
||||
<div class="tool-rec-header">
|
||||
<h4 class="tool-rec-name">
|
||||
${tool.icon ? `<span style="margin-right: 0.5rem;">${tool.icon}</span>` : ''}
|
||||
${tool.name}
|
||||
</h4>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="tool-rec-priority ${tool.recommendation ? tool.recommendation.priority : tool.priority}"
|
||||
style="background-color: ${priorityColors[tool.recommendation ? tool.recommendation.priority : tool.priority]}; color: white; padding: 0.25rem 0.5rem; border-radius: 1rem; font-size: 0.75rem;">
|
||||
${tool.recommendation ? tool.recommendation.priority : tool.priority}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${tool.confidence ? this.renderConfidenceIndicator(tool.confidence) : ''}
|
||||
|
||||
<div class="tool-rec-justification" style="background-color: var(--color-bg-tertiary); padding: 0.75rem; border-radius: 0.375rem; border-left: 3px solid var(--color-primary); margin: 0.75rem 0; font-style: italic; white-space: pre-wrap; word-wrap: break-word;">
|
||||
"${this.sanitizeText(tool.justification || (tool.recommendation && tool.recommendation.justification) || `Empfohlen für ${tool.phase}`)}"
|
||||
</div>
|
||||
|
||||
<div style="font-size: 0.75rem; color: var(--color-text-secondary);">
|
||||
${this.renderToolBadges(tool)}
|
||||
<div style="margin-top: 0.5rem;">
|
||||
${tool.type === 'method' ? 'Methode' : tool.platforms.join(', ') + ' • ' + tool.license}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderDetailedTool(tool, recommendation, rank) {
|
||||
const rankColors = { 1: 'var(--color-accent)', 2: 'var(--color-primary)', 3: 'var(--color-warning)' };
|
||||
const suitabilityColors = { high: 'var(--color-accent)', medium: 'var(--color-warning)', low: 'var(--color-text-secondary)' };
|
||||
|
||||
return `
|
||||
<div class="card ${this.getToolClass(tool, 'card')}" style="cursor: pointer; position: relative;" onclick="window.showToolDetails('${tool.name}')">
|
||||
<div style="position: absolute; top: -8px; right: -8px; width: 32px; height: 32px; background-color: ${rankColors[rank]}; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 1.125rem;">
|
||||
${rank}
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<h3 style="margin: 0 0 0.5rem 0;">${tool.name}</h3>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-bottom: 0.75rem;">
|
||||
<span class="badge" style="background-color: ${suitabilityColors[recommendation.suitability_score]}; color: white;">
|
||||
${this.getSuitabilityText(recommendation.suitability_score)}
|
||||
</span>
|
||||
${this.renderToolBadges(tool)}
|
||||
</div>
|
||||
${recommendation.confidence ? this.renderConfidenceIndicator(recommendation.confidence) : ''}
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 1.5rem;">
|
||||
<h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-accent);">Warum diese Methode?</h4>
|
||||
<div style="margin: 0; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word;">${this.sanitizeText(recommendation.detailed_explanation)}</div>
|
||||
|
||||
${recommendation.implementation_approach ? `
|
||||
<h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-primary);">Anwendungsansatz</h4>
|
||||
<div style="margin: 0; line-height: 1.6; white-space: pre-wrap; word-wrap: break-word;">${this.sanitizeText(recommendation.implementation_approach)}</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
${this.renderProsAndCons(recommendation.pros, recommendation.cons)}
|
||||
${this.renderToolMetadata(tool)}
|
||||
${recommendation.alternatives ? this.renderAlternatives(recommendation.alternatives) : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderHeader(title, query) {
|
||||
return `
|
||||
<div style="text-align: center; margin-bottom: 2rem; padding: 1.5rem; background: linear-gradient(135deg, var(--color-primary) 0%, #525252 100%); color: white; border-radius: 0.75rem;">
|
||||
@ -1099,7 +1248,7 @@ class AIQueryInterface {
|
||||
}
|
||||
|
||||
renderWorkflowTool(tool) {
|
||||
const hasValidProjectUrl = this.isToolHosted(tool);
|
||||
const hasValidProjectUrl = isToolHosted(tool);
|
||||
const priorityColors = {
|
||||
high: 'var(--color-error)',
|
||||
medium: 'var(--color-warning)',
|
||||
@ -1142,7 +1291,10 @@ class AIQueryInterface {
|
||||
const fullTool = tools.find(t => t.name === toolRec.name);
|
||||
if (!fullTool) return '';
|
||||
|
||||
return this.renderDetailedTool(fullTool, toolRec, index + 1);
|
||||
return this.renderDetailedTool(fullTool, {
|
||||
...toolRec,
|
||||
confidence: toolRec.confidence
|
||||
}, index + 1);
|
||||
}).join('')}
|
||||
</div>
|
||||
`;
|
||||
@ -1256,7 +1408,7 @@ class AIQueryInterface {
|
||||
|
||||
renderToolBadges(tool) {
|
||||
const isMethod = tool.type === 'method';
|
||||
const hasValidProjectUrl = this.isToolHosted(tool);
|
||||
const hasValidProjectUrl = isToolHosted(tool);
|
||||
|
||||
let badges = '';
|
||||
if (isMethod) {
|
||||
@ -1300,16 +1452,9 @@ class AIQueryInterface {
|
||||
.trim();
|
||||
}
|
||||
|
||||
isToolHosted(tool) {
|
||||
return tool.projectUrl !== undefined &&
|
||||
tool.projectUrl !== null &&
|
||||
tool.projectUrl !== "" &&
|
||||
tool.projectUrl.trim() !== "";
|
||||
}
|
||||
|
||||
getToolClass(tool, context = 'card') {
|
||||
const isMethod = tool.type === 'method';
|
||||
const hasValidProjectUrl = this.isToolHosted(tool);
|
||||
const hasValidProjectUrl = isToolHosted(tool);
|
||||
|
||||
if (context === 'recommendation') {
|
||||
if (isMethod) return 'method';
|
||||
@ -1402,5 +1547,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
aiInterface.hideError();
|
||||
}
|
||||
};
|
||||
window.isToolHosted = window.isToolHosted || isToolHosted
|
||||
});
|
||||
</script>
|
@ -144,20 +144,11 @@ a:hover {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container-wide {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Section Utilities */
|
||||
.section {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.section-sm {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.section-lg {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
@ -2218,6 +2209,10 @@ input[type="checkbox"] {
|
||||
border-color: var(--color-method);
|
||||
}
|
||||
|
||||
.tool-recommendation:hover .confidence-bar-fill {
|
||||
box-shadow: 0 0 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.tool-rec-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -2264,6 +2259,57 @@ input[type="checkbox"] {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
CONFIDENCE INTEGRATION STYLES
|
||||
================================================================= */
|
||||
|
||||
.confidence-indicator-integrated {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.confidence-bar-wrapper {
|
||||
position: relative;
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.confidence-bar-fill {
|
||||
transition: width 0.6s ease-out;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.confidence-bar-fill::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg, transparent 25%, rgba(255,255,255,0.1) 25%, rgba(255,255,255,0.1) 50%, transparent 50%, transparent 75%, rgba(255,255,255,0.1) 75%);
|
||||
background-size: 8px 8px;
|
||||
animation: confidence-shimmer 2s linear infinite;
|
||||
}
|
||||
|
||||
.confidence-badge-compact .btn-icon {
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.confidence-badge-compact .btn-icon:hover {
|
||||
background-color: var(--color-bg-secondary);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.confidence-details-popup {
|
||||
animation: confidence-popup-in 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* Hover effect for confidence bars */
|
||||
.confidence-indicator-integrated:hover .confidence-bar-fill {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
18. APPROACH SELECTION (CONSOLIDATED)
|
||||
================================================================= */
|
||||
@ -2665,11 +2711,26 @@ footer {
|
||||
animation: ai-spotlight-pulse 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes confidence-shimmer {
|
||||
0% { transform: translateX(-8px); }
|
||||
100% { transform: translateX(8px); }
|
||||
}
|
||||
|
||||
@keyframes confidence-popup-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
21. SMART PROMPTING INTERFACE (MISSING STYLES ADDED BACK)
|
||||
================================================================= */
|
||||
|
||||
/* Smart Prompting Container */
|
||||
.smart-prompting-container {
|
||||
height: 100%;
|
||||
animation: smartPromptSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@ -3599,6 +3660,26 @@ footer {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.confidence-details-popup {
|
||||
position: fixed !important;
|
||||
top: 50% !important;
|
||||
left: 50% !important;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
margin: 0 !important;
|
||||
width: 90vw !important;
|
||||
max-width: 300px !important;
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
.confidence-indicator-integrated {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.confidence-badge-compact .btn-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
@ -3924,28 +4005,6 @@ footer {
|
||||
background: linear-gradient(to bottom, var(--color-border) 0%, transparent 100%);
|
||||
}
|
||||
|
||||
.confidence-indicator {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.confidence-indicator::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg, transparent 25%, rgba(255,255,255,0.1) 25%, rgba(255,255,255,0.1) 50%, transparent 50%, transparent 75%, rgba(255,255,255,0.1) 75%);
|
||||
background-size: 4px 4px;
|
||||
animation: confidence-shimmer 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes confidence-shimmer {
|
||||
0% { transform: translateX(-4px); }
|
||||
100% { transform: translateX(4px); }
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
@ -4000,7 +4059,4 @@ footer {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.confidence-indicator {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
import { getCompressedToolsDataForAI } from './dataService.js';
|
||||
import { embeddingsService, type EmbeddingData } from './embeddings.js';
|
||||
import { AI_PROMPTS, getPrompt } from '../config/prompts.js';
|
||||
import { isToolHosted } from './toolHelpers.js';
|
||||
|
||||
interface AIConfig {
|
||||
endpoint: string;
|
||||
@ -67,6 +68,16 @@ interface SimilarityResult extends EmbeddingData {
|
||||
similarity: number;
|
||||
}
|
||||
|
||||
interface ConfidenceMetrics {
|
||||
overall: number; // 0-100: Combined confidence score
|
||||
embeddingsQuality: number; // How well embeddings matched
|
||||
domainAlignment: number; // How well tools match scenario domain
|
||||
consensus: number; // How much micro-tasks agree
|
||||
freshness: number; // How recent/up-to-date the selection is
|
||||
uncertaintyFactors: string[]; // What could make this wrong
|
||||
strengthIndicators: string[]; // What makes this recommendation strong
|
||||
}
|
||||
|
||||
|
||||
class ImprovedMicroTaskAIPipeline {
|
||||
private config: AIConfig;
|
||||
@ -92,6 +103,16 @@ class ImprovedMicroTaskAIPipeline {
|
||||
detailLevel: 'minimal' | 'standard' | 'verbose';
|
||||
retentionHours: number;
|
||||
};
|
||||
|
||||
private confidenceConfig: {
|
||||
embeddingsWeight: number;
|
||||
consensusWeight: number;
|
||||
domainMatchWeight: number;
|
||||
freshnessWeight: number;
|
||||
minimumThreshold: number;
|
||||
mediumThreshold: number;
|
||||
highThreshold: number;
|
||||
};
|
||||
|
||||
private tempAuditEntries: AuditEntry[] = [];
|
||||
|
||||
@ -131,6 +152,21 @@ class ImprovedMicroTaskAIPipeline {
|
||||
noEmbeddingsLimits: `${this.noEmbeddingsToolLimit || 'unlimited'} tools, ${this.noEmbeddingsConceptLimit || 'unlimited'} concepts`,
|
||||
auditEnabled: this.auditConfig.enabled
|
||||
});
|
||||
|
||||
this.confidenceConfig = {
|
||||
embeddingsWeight: parseFloat(process.env.CONFIDENCE_EMBEDDINGS_WEIGHT || '0.3'),
|
||||
consensusWeight: parseFloat(process.env.CONFIDENCE_CONSENSUS_WEIGHT || '0.25'),
|
||||
domainMatchWeight: parseFloat(process.env.CONFIDENCE_DOMAIN_MATCH_WEIGHT || '0.25'),
|
||||
freshnessWeight: parseFloat(process.env.CONFIDENCE_FRESHNESS_WEIGHT || '0.2'),
|
||||
minimumThreshold: parseInt(process.env.CONFIDENCE_MINIMUM_THRESHOLD || '40', 10),
|
||||
mediumThreshold: parseInt(process.env.CONFIDENCE_MEDIUM_THRESHOLD || '60', 10),
|
||||
highThreshold: parseInt(process.env.CONFIDENCE_HIGH_THRESHOLD || '80', 10)
|
||||
};
|
||||
|
||||
console.log('[AI PIPELINE] Confidence scoring enabled:', {
|
||||
weights: `E:${this.confidenceConfig.embeddingsWeight} C:${this.confidenceConfig.consensusWeight} D:${this.confidenceConfig.domainMatchWeight} F:${this.confidenceConfig.freshnessWeight}`,
|
||||
thresholds: `${this.confidenceConfig.minimumThreshold}/${this.confidenceConfig.mediumThreshold}/${this.confidenceConfig.highThreshold}`
|
||||
});
|
||||
}
|
||||
|
||||
private getEnv(key: string): string {
|
||||
@ -662,6 +698,40 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
private calculateRecommendationConfidence(
|
||||
tool: any,
|
||||
embeddingsSimilarity: number,
|
||||
domainMatch: boolean,
|
||||
microTaskAgreement: number,
|
||||
context: AnalysisContext
|
||||
): ConfidenceMetrics {
|
||||
|
||||
const embeddingsQuality = Math.min(100, embeddingsSimilarity * 100 * 2); // Scale 0.5 similarity to 100%
|
||||
const domainAlignment = domainMatch ? 90 : (tool.domains?.length > 0 ? 60 : 30);
|
||||
const consensus = Math.min(100, microTaskAgreement * 100);
|
||||
const freshness = this.calculateToolFreshness(tool);
|
||||
|
||||
const overall = (
|
||||
embeddingsQuality * this.confidenceConfig.embeddingsWeight +
|
||||
domainAlignment * this.confidenceConfig.domainMatchWeight +
|
||||
consensus * this.confidenceConfig.consensusWeight +
|
||||
freshness * this.confidenceConfig.freshnessWeight
|
||||
);
|
||||
|
||||
const uncertaintyFactors = this.identifyUncertaintyFactors(tool, context, overall);
|
||||
const strengthIndicators = this.identifyStrengthIndicators(tool, context, overall);
|
||||
|
||||
return {
|
||||
overall: Math.round(overall),
|
||||
embeddingsQuality: Math.round(embeddingsQuality),
|
||||
domainAlignment: Math.round(domainAlignment),
|
||||
consensus: Math.round(consensus),
|
||||
freshness: Math.round(freshness),
|
||||
uncertaintyFactors,
|
||||
strengthIndicators
|
||||
};
|
||||
}
|
||||
|
||||
private async analyzeScenario(context: AnalysisContext): Promise<MicroTaskResult> {
|
||||
const isWorkflow = context.mode === 'workflow';
|
||||
const prompt = getPrompt('scenarioAnalysis', isWorkflow, context.userQuery);
|
||||
@ -1010,6 +1080,124 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
private calculateToolFreshness(tool: any): number {
|
||||
// Base freshness score
|
||||
let freshness = 70; // Default for tools without specific freshness data
|
||||
|
||||
// Boost for tools with knowledge base (more maintained)
|
||||
if (tool.knowledgebase === true) freshness += 20;
|
||||
|
||||
// Boost for hosted tools (actively maintained)
|
||||
if (isToolHosted(tool)) freshness += 15;
|
||||
|
||||
// Slight boost for open source (community maintained)
|
||||
if (tool.license && tool.license !== 'Proprietary') freshness += 5;
|
||||
|
||||
return Math.min(100, freshness);
|
||||
}
|
||||
|
||||
private checkDomainMatch(tool: any, userQuery: string): boolean {
|
||||
if (!tool.domains || tool.domains.length === 0) return false;
|
||||
|
||||
const queryLower = userQuery.toLowerCase();
|
||||
|
||||
const domainKeywordsEnv = process.env.CONFIDENCE_DOMAIN_KEYWORDS || '';
|
||||
|
||||
const domainKeywords = domainKeywordsEnv.split('|').reduce((acc, pair) => {
|
||||
const [domain, keywords] = pair.split(':');
|
||||
acc[domain] = keywords.split(',');
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return tool.domains.some(domain => {
|
||||
const keywords = domainKeywords[domain] || [domain.replace('-', ' ')];
|
||||
return keywords.some(keyword => queryLower.includes(keyword));
|
||||
});
|
||||
}
|
||||
|
||||
private getMicroTaskAgreement(toolName: string, context: AnalysisContext): number {
|
||||
// Check how many micro-tasks selected this tool
|
||||
const microTaskEntries = context.auditTrail.filter(entry =>
|
||||
entry.phase === 'micro-task' &&
|
||||
entry.action.includes('selection') &&
|
||||
entry.output &&
|
||||
typeof entry.output === 'object' &&
|
||||
Array.isArray(entry.output.selectedTools) &&
|
||||
entry.output.selectedTools.includes(toolName)
|
||||
);
|
||||
|
||||
const totalMicroTasks = context.auditTrail.filter(entry =>
|
||||
entry.phase === 'micro-task' && entry.action.includes('selection')
|
||||
).length;
|
||||
|
||||
return totalMicroTasks > 0 ? microTaskEntries.length / totalMicroTasks : 0.8; // Default high agreement
|
||||
}
|
||||
|
||||
private getEmbeddingsSimilarity(toolName: string, context: AnalysisContext): number {
|
||||
// Extract similarity from audit trail embeddings entry
|
||||
const embeddingsEntry = context.auditTrail.find(entry =>
|
||||
entry.phase === 'retrieval' && entry.action === 'embeddings-search'
|
||||
);
|
||||
|
||||
if (!embeddingsEntry || !embeddingsEntry.output) return 0.5; // Default medium similarity
|
||||
|
||||
// Look for similarity data in the output (implementation specific)
|
||||
// This would need to be populated during embeddings search
|
||||
return 0.7; // Placeholder - would need actual similarity data from embeddings
|
||||
}
|
||||
|
||||
private identifyUncertaintyFactors(tool: any, context: AnalysisContext, confidence: number): string[] {
|
||||
const factors: string[] = [];
|
||||
|
||||
if (confidence < this.confidenceConfig.mediumThreshold) {
|
||||
factors.push('Low overall confidence - consider manual validation');
|
||||
}
|
||||
|
||||
if (!this.checkDomainMatch(tool, context.userQuery)) {
|
||||
factors.push('Domain mismatch detected - tool may not be specifically designed for this scenario');
|
||||
}
|
||||
|
||||
if (tool.skillLevel === 'expert' && /rapid|quick|urgent|triage/i.test(context.userQuery)) {
|
||||
factors.push('Expert-level tool for rapid scenario - may be overcomplicated');
|
||||
}
|
||||
|
||||
if (tool.type === 'software' && !isToolHosted(tool) && !tool.url) {
|
||||
factors.push('Limited access information - availability uncertain');
|
||||
}
|
||||
|
||||
if (tool.skillLevel === 'novice' && /complex|advanced|deep/i.test(context.userQuery)) {
|
||||
factors.push('Novice-level tool for complex scenario - may lack required capabilities');
|
||||
}
|
||||
|
||||
return factors;
|
||||
}
|
||||
|
||||
private identifyStrengthIndicators(tool: any, context: AnalysisContext, confidence: number): string[] {
|
||||
const indicators: string[] = [];
|
||||
|
||||
if (confidence >= this.confidenceConfig.highThreshold) {
|
||||
indicators.push('High confidence recommendation based on multiple factors');
|
||||
}
|
||||
|
||||
if (this.checkDomainMatch(tool, context.userQuery)) {
|
||||
indicators.push('Strong domain alignment with scenario requirements');
|
||||
}
|
||||
|
||||
if (tool.knowledgebase === true) {
|
||||
indicators.push('Documentation and knowledge base available for guidance');
|
||||
}
|
||||
|
||||
if (isToolHosted(tool)) {
|
||||
indicators.push('Hosted solution available for immediate access');
|
||||
}
|
||||
|
||||
if (tool.type === 'method' && /methodology|approach|process/i.test(context.userQuery)) {
|
||||
indicators.push('Methodological approach matches procedural inquiry');
|
||||
}
|
||||
|
||||
return indicators;
|
||||
}
|
||||
|
||||
private buildRecommendation(context: AnalysisContext, mode: string, finalContent: string): any {
|
||||
const isWorkflow = mode === 'workflow';
|
||||
|
||||
@ -1025,20 +1213,71 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
|
||||
};
|
||||
|
||||
if (isWorkflow) {
|
||||
return {
|
||||
...base,
|
||||
recommended_tools: context.selectedTools?.map(st => ({
|
||||
const recommendedToolsWithConfidence = context.selectedTools?.map(st => {
|
||||
// Calculate confidence for each tool
|
||||
const confidence = this.calculateRecommendationConfidence(
|
||||
st.tool,
|
||||
this.getEmbeddingsSimilarity(st.tool.name, context),
|
||||
this.checkDomainMatch(st.tool, context.userQuery),
|
||||
this.getMicroTaskAgreement(st.tool.name, context),
|
||||
context
|
||||
);
|
||||
|
||||
// Add audit entry for confidence calculation
|
||||
this.addAuditEntry(context, 'validation', 'confidence-scoring',
|
||||
{ toolName: st.tool.name, phase: st.phase },
|
||||
{
|
||||
overall: confidence.overall,
|
||||
components: {
|
||||
embeddings: confidence.embeddingsQuality,
|
||||
domain: confidence.domainAlignment,
|
||||
consensus: confidence.consensus,
|
||||
freshness: confidence.freshness
|
||||
}
|
||||
},
|
||||
confidence.overall,
|
||||
Date.now(),
|
||||
{ uncertaintyCount: confidence.uncertaintyFactors.length }
|
||||
);
|
||||
|
||||
return {
|
||||
name: st.tool.name,
|
||||
phase: st.phase,
|
||||
priority: st.priority,
|
||||
justification: st.justification || `Empfohlen für ${st.phase}`
|
||||
})) || [],
|
||||
justification: st.justification || `Empfohlen für ${st.phase}`,
|
||||
confidence: confidence,
|
||||
recommendationStrength: confidence.overall >= this.confidenceConfig.highThreshold ? 'strong' :
|
||||
confidence.overall >= this.confidenceConfig.mediumThreshold ? 'moderate' : 'weak'
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return {
|
||||
...base,
|
||||
recommended_tools: recommendedToolsWithConfidence,
|
||||
workflow_suggestion: finalContent
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...base,
|
||||
recommended_tools: context.selectedTools?.map(st => ({
|
||||
const recommendedToolsWithConfidence = context.selectedTools?.map(st => {
|
||||
const confidence = this.calculateRecommendationConfidence(
|
||||
st.tool,
|
||||
this.getEmbeddingsSimilarity(st.tool.name, context),
|
||||
this.checkDomainMatch(st.tool, context.userQuery),
|
||||
this.getMicroTaskAgreement(st.tool.name, context),
|
||||
context
|
||||
);
|
||||
|
||||
this.addAuditEntry(context, 'validation', 'confidence-scoring',
|
||||
{ toolName: st.tool.name, rank: st.tool.evaluation?.rank || 1 },
|
||||
{
|
||||
overall: confidence.overall,
|
||||
suitabilityAlignment: st.priority === 'high' && confidence.overall >= this.confidenceConfig.highThreshold
|
||||
},
|
||||
confidence.overall,
|
||||
Date.now(),
|
||||
{ strengthCount: confidence.strengthIndicators.length }
|
||||
);
|
||||
|
||||
return {
|
||||
name: st.tool.name,
|
||||
rank: st.tool.evaluation?.rank || 1,
|
||||
suitability_score: st.priority,
|
||||
@ -1046,8 +1285,16 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
|
||||
implementation_approach: st.tool.evaluation?.implementation_approach || '',
|
||||
pros: st.tool.evaluation?.pros || [],
|
||||
cons: st.tool.evaluation?.cons || [],
|
||||
alternatives: st.tool.evaluation?.alternatives || ''
|
||||
})) || [],
|
||||
alternatives: st.tool.evaluation?.alternatives || '',
|
||||
confidence: confidence,
|
||||
recommendationStrength: confidence.overall >= this.confidenceConfig.highThreshold ? 'strong' :
|
||||
confidence.overall >= this.confidenceConfig.mediumThreshold ? 'moderate' : 'weak'
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return {
|
||||
...base,
|
||||
recommended_tools: recommendedToolsWithConfidence,
|
||||
additional_considerations: finalContent
|
||||
};
|
||||
}
|
||||
|
89
unused-selectors.txt
Normal file
89
unused-selectors.txt
Normal file
@ -0,0 +1,89 @@
|
||||
.container-wide
|
||||
.section-sm
|
||||
.section-lg
|
||||
.flex-center
|
||||
.flex-start
|
||||
.flex-end
|
||||
.grid-3
|
||||
.grid-auto-fit-sm
|
||||
.grid-auto-fit-lg
|
||||
.gap-6
|
||||
.m-1
|
||||
.m-2
|
||||
.m-3
|
||||
.m-4
|
||||
.m-6
|
||||
.m-8
|
||||
.mt-0
|
||||
.mt-6
|
||||
.mt-8
|
||||
.mr-1
|
||||
.mr-4
|
||||
.ml-1
|
||||
.ml-3
|
||||
.ml-4
|
||||
.p-0
|
||||
.p-1
|
||||
.px-1
|
||||
.px-2
|
||||
.py-1
|
||||
.py-3
|
||||
.py-4
|
||||
.py-6
|
||||
.pb-2
|
||||
.content-narrow
|
||||
.content-wide
|
||||
.card-info-md
|
||||
.card-info-lg
|
||||
.info-text
|
||||
.info-text-center
|
||||
.grid-auto-300
|
||||
.grid-auto-400
|
||||
.grid-auto-500
|
||||
.pros-cons-section
|
||||
.tool-metadata
|
||||
.text-left
|
||||
.font-normal
|
||||
.font-bold
|
||||
.text-error
|
||||
.inline-block
|
||||
.relative
|
||||
.absolute
|
||||
.sticky
|
||||
.top-0
|
||||
.left-0
|
||||
.z-10
|
||||
.z-100
|
||||
.overflow-auto
|
||||
.rounded-md
|
||||
.rounded-2xl
|
||||
.bg-tertiary
|
||||
.h-12
|
||||
.nav-link
|
||||
.nav-link:hover
|
||||
.btn-xs
|
||||
.card-sm
|
||||
.card-lg
|
||||
.card-gradient
|
||||
.card-hero
|
||||
.card-concept
|
||||
.badge-accent
|
||||
.tool-chip
|
||||
.tool-chip:hover
|
||||
.phase-grid
|
||||
.phase-chip
|
||||
.phase-chip:hover
|
||||
.filter-row
|
||||
.phase-card
|
||||
.phase-card::before
|
||||
.phase-card:hover
|
||||
.tool-rec-metadata
|
||||
.stat-unit
|
||||
.share-btn--small
|
||||
.collaboration-tool-compact
|
||||
.collaboration-tool-compact:hover
|
||||
.domain-phase-container
|
||||
.card-info-xl
|
||||
.text-3xl
|
||||
.audit-progress-step
|
||||
.audit-progress-step::before
|
Loading…
x
Reference in New Issue
Block a user