main #11

Merged
mstoeck3 merged 66 commits from main into forensic-ai 2025-08-11 12:02:56 +00:00
18 changed files with 2477 additions and 6325 deletions
Showing only changes of commit 5164aa640a - Show all commits

File diff suppressed because one or more lines are too long

View File

@ -1,79 +1,257 @@
# =========================================== # ============================================================================
# ForensicPathways Environment Configuration # ForensicPathways Environment Configuration - COMPLETE
# =========================================== # ============================================================================
# Copy this file to .env and adjust the values below.
# This file covers ALL environment variables used in the codebase.
# === Authentication Configuration === # ============================================================================
# 1. CORE APPLICATION SETTINGS (REQUIRED)
# ============================================================================
# Your application's public URL (used for redirects and links)
PUBLIC_BASE_URL=http://localhost:4321
# Application environment
NODE_ENV=development
# Secret key for session encryption (CHANGE IN PRODUCTION!)
AUTH_SECRET=your-secret-key-change-in-production-please
# ============================================================================
# 2. AI SERVICES CONFIGURATION (REQUIRED FOR AI FEATURES)
# ============================================================================
# Main AI Analysis Service (for query processing and recommendations)
# Examples: http://localhost:11434 (Ollama), https://api.mistral.ai, https://api.openai.com
AI_ANALYZER_ENDPOINT=https://api.mistral.ai/v1/chat/completions
AI_ANALYZER_API_KEY=
AI_ANALYZER_MODEL=mistral/mistral-small-latest
# Vector Embeddings Service (for semantic search)
# Leave API_KEY empty for Ollama, use actual key for cloud services
AI_EMBEDDINGS_ENABLED=true
AI_EMBEDDINGS_ENDPOINT=https://api.mistral.ai/v1/embeddings
AI_EMBEDDINGS_API_KEY=
AI_EMBEDDINGS_MODEL=mistral-embed
# ============================================================================
# 3. AI PIPELINE CONFIGURATION (CONTEXT & PERFORMANCE TUNING)
# ============================================================================
# === SIMILARITY SEARCH STAGE ===
# How many similar tools/concepts embeddings search returns as candidates
# 🔍 This is the FIRST filter - vector similarity matching
# Lower = faster, less comprehensive | Higher = slower, more comprehensive
AI_EMBEDDING_CANDIDATES=50
# Minimum similarity score threshold (0.0-1.0)
# Lower = more results but less relevant | Higher = fewer but more relevant
AI_SIMILARITY_THRESHOLD=0.3
# === AI SELECTION FROM EMBEDDINGS ===
# When embeddings are enabled, how many top tools to send with full context
# 🎯 This is the SECOND filter - take best N from embeddings results
AI_EMBEDDING_SELECTION_LIMIT=30
AI_EMBEDDING_CONCEPTS_LIMIT=15
# Maximum tools/concepts sent to AI when embeddings are DISABLED
# Set to 0 for no limit (WARNING: may cause token overflow with large datasets)
AI_NO_EMBEDDINGS_TOOL_LIMIT=0
AI_NO_EMBEDDINGS_CONCEPT_LIMIT=0
# === AI SELECTION STAGE ===
# Maximum tools the AI can select from embedding candidates
# 🤖 This is the SECOND filter - AI intelligent selection
# Should be ≤ AI_EMBEDDING_CANDIDATES
AI_MAX_SELECTED_ITEMS=25
# === EMBEDDINGS EFFICIENCY THRESHOLDS ===
# Minimum tools required for embeddings to be considered useful
AI_EMBEDDINGS_MIN_TOOLS=8
# Maximum percentage of total tools that embeddings can return to be considered "filtering"
AI_EMBEDDINGS_MAX_REDUCTION_RATIO=0.75
# === CONTEXT FLOW SUMMARY ===
# 1. Vector Search: 111 total tools → AI_EMBEDDING_CANDIDATES (40) most similar
# 2. AI Selection: 40 candidates → AI_MAX_SELECTED_ITEMS (25) best matches
# 3. Final Output: Recommendations based on analyzed subset
# ============================================================================
# 4. AI PERFORMANCE & RATE LIMITING
# ============================================================================
# === USER RATE LIMITS (per minute) ===
# Main queries per user per minute
AI_RATE_LIMIT_MAX_REQUESTS=4
# Total AI micro-task calls per user per minute (across all micro-tasks)
AI_MICRO_TASK_TOTAL_LIMIT=30
# === PIPELINE TIMING ===
# Delay between micro-tasks within a single query (milliseconds)
# Higher = gentler on AI service | Lower = faster responses
AI_MICRO_TASK_DELAY_MS=500
# Delay between queued requests (milliseconds)
AI_RATE_LIMIT_DELAY_MS=2000
# === EMBEDDINGS BATCH PROCESSING ===
# How many embeddings to generate per API call
AI_EMBEDDINGS_BATCH_SIZE=10
# Delay between embedding batches (milliseconds)
AI_EMBEDDINGS_BATCH_DELAY_MS=1000
# Maximum tools sent to AI for detailed analysis (micro-tasks)
AI_MAX_TOOLS_TO_ANALYZE=20
AI_MAX_CONCEPTS_TO_ANALYZE=10
# ============================================================================
# 5. AI CONTEXT & TOKEN MANAGEMENT
# ============================================================================
# Maximum context tokens to maintain across micro-tasks
# Controls how much conversation history is preserved between AI calls
AI_MAX_CONTEXT_TOKENS=4000
# Maximum tokens per individual AI prompt
# Larger = more context per call | Smaller = faster responses
AI_MAX_PROMPT_TOKENS=2500
# ============================================================================
# 6. AUTHENTICATION & AUTHORIZATION (OPTIONAL)
# ============================================================================
# Enable authentication for different features
AUTHENTICATION_NECESSARY=false AUTHENTICATION_NECESSARY=false
AUTHENTICATION_NECESSARY_CONTRIBUTIONS=false AUTHENTICATION_NECESSARY_CONTRIBUTIONS=false
AUTHENTICATION_NECESSARY_AI=false AUTHENTICATION_NECESSARY_AI=false
AUTH_SECRET=your-secret-key-change-in-production
# OIDC Configuration (if authentication enabled) # OIDC Provider Settings (only needed if authentication enabled)
OIDC_ENDPOINT=https://your-oidc-provider.com OIDC_ENDPOINT=https://your-oidc-provider.com
OIDC_CLIENT_ID=your-client-id OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret OIDC_CLIENT_SECRET=your-client-secret
# =================================================================== # ============================================================================
# AI CONFIGURATION - Complete Reference for Improved Pipeline # 7. FILE UPLOADS - NEXTCLOUD INTEGRATION (OPTIONAL)
# =================================================================== # ============================================================================
# === CORE AI ENDPOINTS & MODELS === # Nextcloud server for file uploads (knowledgebase contributions)
AI_API_ENDPOINT=https://llm.mikoshi.de # Leave empty to disable file upload functionality
AI_API_KEY=sREDACTED3w
AI_MODEL='mistral/mistral-small-latest'
# === IMPROVED PIPELINE: Use separate analyzer model (mistral-small is fine) ===
AI_ANALYZER_ENDPOINT=https://llm.mikoshi.de
AI_ANALYZER_API_KEY=skREDACTEDw3w
AI_ANALYZER_MODEL='mistral/mistral-small-latest'
# === EMBEDDINGS CONFIGURATION ===
AI_EMBEDDINGS_ENABLED=true
AI_EMBEDDINGS_ENDPOINT=https://api.mistral.ai/v1/embeddings
AI_EMBEDDINGS_API_KEY=ZREDACTED3wL
AI_EMBEDDINGS_MODEL=mistral-embed
AI_EMBEDDINGS_BATCH_SIZE=20
AI_EMBEDDINGS_BATCH_DELAY_MS=1000
# === PIPELINE: VectorIndex (HNSW) Configuration ===
AI_MAX_SELECTED_ITEMS=60 # Tools visible to each micro-task
AI_EMBEDDING_CANDIDATES=60 # VectorIndex candidates (HNSW is more efficient)
AI_SIMILARITY_THRESHOLD=0.3 # Not used by VectorIndex (uses cosine distance internally)
# === MICRO-TASK CONFIGURATION ===
AI_MICRO_TASK_DELAY_MS=500 # Delay between micro-tasks
AI_MICRO_TASK_TIMEOUT_MS=25000 # Timeout per micro-task (increased for full context)
# === RATE LIMITING ===
AI_RATE_LIMIT_DELAY_MS=3000 # Main rate limit delay
AI_RATE_LIMIT_MAX_REQUESTS=6 # Main requests per minute (reduced - fewer but richer calls)
AI_MICRO_TASK_RATE_LIMIT=15 # Micro-task requests per minute (was 30)
# === QUEUE MANAGEMENT ===
AI_QUEUE_MAX_SIZE=50
AI_QUEUE_CLEANUP_INTERVAL_MS=300000
# === PERFORMANCE & MONITORING ===
AI_MICRO_TASK_DEBUG=true
AI_PERFORMANCE_METRICS=true
AI_RESPONSE_CACHE_TTL_MS=3600000
# ===================================================================
# LEGACY VARIABLES (still used but less important)
# ===================================================================
# These are still used by other parts of the system:
AI_RESPONSE_CACHE_TTL_MS=3600000 # For caching responses
AI_QUEUE_MAX_SIZE=50 # Queue management
AI_QUEUE_CLEANUP_INTERVAL_MS=300000 # Queue cleanup
# === Application Configuration ===
PUBLIC_BASE_URL=http://localhost:4321
NODE_ENV=development
# Nextcloud Integration (Optional)
NEXTCLOUD_ENDPOINT=https://your-nextcloud.com NEXTCLOUD_ENDPOINT=https://your-nextcloud.com
# Nextcloud credentials (app password recommended)
NEXTCLOUD_USERNAME=your-username NEXTCLOUD_USERNAME=your-username
NEXTCLOUD_PASSWORD=your-password NEXTCLOUD_PASSWORD=your-app-password
# Upload directory on Nextcloud (will be created if doesn't exist)
NEXTCLOUD_UPLOAD_PATH=/kb-media NEXTCLOUD_UPLOAD_PATH=/kb-media
NEXTCLOUD_PUBLIC_URL=https://your-nextcloud.com/s/
# Public URL base for sharing uploaded files
# Usually your Nextcloud base URL + share path
NEXTCLOUD_PUBLIC_URL=https://your-nextcloud.com/s/
# ============================================================================
# 8. GIT CONTRIBUTIONS - ISSUE CREATION (OPTIONAL)
# ============================================================================
# Git provider: gitea, github, or gitlab
GIT_PROVIDER=gitea
# Repository URL (used to extract owner/name)
# Example: https://git.example.com/owner/forensic-pathways.git
GIT_REPO_URL=https://git.example.com/owner/forensic-pathways.git
# API endpoint for your git provider
# Gitea: https://git.example.com/api/v1
# GitHub: https://api.github.com
# GitLab: https://gitlab.example.com/api/v4
GIT_API_ENDPOINT=https://git.example.com/api/v1
# Personal access token or API token for creating issues
# Generate this in your git provider's settings
GIT_API_TOKEN=your-git-api-token
# ============================================================================
# 9. AUDIT & DEBUGGING (OPTIONAL)
# ============================================================================
# Enable detailed audit trail of AI decision-making
FORENSIC_AUDIT_ENABLED=true
# Audit detail level: minimal, standard, verbose
FORENSIC_AUDIT_DETAIL_LEVEL=standard
# Audit retention time (hours)
FORENSIC_AUDIT_RETENTION_HOURS=24
# Maximum audit entries per request
FORENSIC_AUDIT_MAX_ENTRIES=50
# ============================================================================
# 10. SIMPLIFIED CONFIDENCE SCORING SYSTEM
# ============================================================================
# Confidence component weights (must sum to 1.0)
CONFIDENCE_SEMANTIC_WEIGHT=0.5 # Weight for vector similarity quality
CONFIDENCE_SUITABILITY_WEIGHT=0.5 # Weight for AI-determined task fitness
# Confidence thresholds (0-100)
CONFIDENCE_MINIMUM_THRESHOLD=50 # Below this = weak recommendation
CONFIDENCE_MEDIUM_THRESHOLD=70 # 40-59 = weak, 60-79 = moderate
CONFIDENCE_HIGH_THRESHOLD=80 # 80+ = strong recommendation
# ============================================================================
# PERFORMANCE TUNING PRESETS
# ============================================================================
# 🚀 FOR FASTER RESPONSES (prevent token overflow):
# AI_NO_EMBEDDINGS_TOOL_LIMIT=25
# AI_NO_EMBEDDINGS_CONCEPT_LIMIT=10
# 🎯 FOR FULL DATABASE ACCESS (risk of truncation):
# AI_NO_EMBEDDINGS_TOOL_LIMIT=0
# AI_NO_EMBEDDINGS_CONCEPT_LIMIT=0
# 🔋 FOR LOW-POWER SYSTEMS:
# AI_NO_EMBEDDINGS_TOOL_LIMIT=15
# ============================================================================
# FEATURE COMBINATIONS GUIDE
# ============================================================================
# 📝 BASIC SETUP (AI only):
# - Configure AI_ANALYZER_* and AI_EMBEDDINGS_*
# - Leave authentication, file uploads, and git disabled
# 🔐 WITH AUTHENTICATION:
# - Set AUTHENTICATION_NECESSARY_* to true
# - Configure OIDC_* settings
# 📁 WITH FILE UPLOADS:
# - Configure all NEXTCLOUD_* settings
# - Test connection before enabling in UI
# 🔄 WITH CONTRIBUTIONS:
# - Configure all GIT_* settings
# - Test API token permissions for issue creation
# 🔍 WITH FULL MONITORING:
# - Enable FORENSIC_AUDIT_ENABLED=true
# - Configure audit retention and detail level
# ============================================================================
# SETUP CHECKLIST
# ============================================================================
# ✅ 1. Set PUBLIC_BASE_URL to your domain
# ✅ 2. Change AUTH_SECRET to a secure random string
# ✅ 3. Configure AI endpoints (Ollama: leave API_KEY empty)
# ✅ 4. Start with default AI values, tune based on performance
# ✅ 5. Enable authentication if needed (configure OIDC)
# ✅ 6. Configure Nextcloud if file uploads needed
# ✅ 7. Configure Git provider if contributions needed
# ✅ 8. Test with a simple query to verify pipeline works
# ✅ 9. Enable audit trail for transparency if desired
# ✅ 10. Tune performance settings based on usage patterns
# ============================================================================

View File

@ -62,7 +62,7 @@ Ein kuratiertes Verzeichnis für Digital Forensics und Incident Response (DFIR)
### AI Service (Mistral/OpenAI-kompatibel) ### AI Service (Mistral/OpenAI-kompatibel)
- **Zweck:** KI-gestützte Tool-Empfehlungen - **Zweck:** KI-gestützte Tool-Empfehlungen
- **Konfiguration:** `AI_API_ENDPOINT`, `AI_API_KEY`, `AI_MODEL` - **Konfiguration:** `AI_ANALYZER_ENDPOINT`, `AI_ANALYZER_API_KEY`, `AI_ANALYZER_MODEL`
### Uptime Kuma ### Uptime Kuma
- **Zweck:** Status-Monitoring für gehostete Services - **Zweck:** Status-Monitoring für gehostete Services
@ -157,9 +157,9 @@ PUBLIC_BASE_URL=https://your-domain.com
NODE_ENV=production NODE_ENV=production
# AI Service Configuration (Required for AI features) # AI Service Configuration (Required for AI features)
AI_MODEL=mistral-large-latest AI_ANALYZER_MODEL=mistral-large-latest
AI_API_ENDPOINT=https://api.mistral.ai AI_ANALYZER_ENDPOINT=https://api.mistral.ai
AI_API_KEY=your-mistral-api-key AI_ANALYZER_API_KEY=your-mistral-api-key
AI_RATE_LIMIT_DELAY_MS=1000 AI_RATE_LIMIT_DELAY_MS=1000
# Git Integration (Required for contributions) # Git Integration (Required for contributions)

View File

@ -1,337 +0,0 @@
# ForensicPathways Architecture System Prompt
## Project Overview
ForensicPathways is a curated directory of Digital Forensics and Incident Response (DFIR) tools, methods, and concepts built with Astro and TypeScript. It serves as an educational and professional resource for forensic investigators, following the NIST SP 800-86 framework (Kent, Chevalier, Grance & Dang).
## Core Technology Stack
- **Framework**: Astro (static site generator with islands architecture)
- **Language**: TypeScript with strict typing
- **Styling**: Vanilla CSS with custom properties (CSS variables)
- **Data Storage**: YAML files for tool catalog
- **Content**: Astro Content Collections for knowledge base articles
- **Authentication**: OIDC (OpenID Connect) with configurable requirements
- **AI Integration**: Mistral API for workflow recommendations
- **File Storage**: Nextcloud with local fallback
- **Version Control**: Git integration for community contributions
## Data Model Architecture
### Core Entity: Tool
```yaml
name: string # Tool identifier
icon: string? # Emoji icon
type: 'software'|'method'|'concept' # Tool classification
description: string # Detailed description
domains: string[] # Forensic domains (incident-response, malware-analysis, etc.)
phases: string[] # NIST framework phases (data-collection, examination, analysis, reporting)
platforms: string[] # Operating systems (for software only)
skillLevel: string # novice|beginner|intermediate|advanced|expert
url: string # Primary documentation/homepage
projectUrl: string? # Hosted instance URL (CC24 server)
license: string? # Software license
knowledgebase: boolean? # Has detailed documentation
tags: string[] # Searchable keywords
related_concepts: string[]? # Links to concept-type tools
related_software: string[]? #Links to software-type-tools
```
### Taxonomies
- **Domains**: Forensic specializations (7 main domains)
- **Phases**: NIST investigation phases (4 phases)
- **Domain-Agnostic-Software**: Cross-cutting tools and platforms
- **Skill Levels**: Standardized competency requirements
## Component Architecture
### Layout System
- **BaseLayout.astro**: Global layout with theme system, authentication setup, shared utilities
- **Navigation.astro**: Main navigation with active state management
- **Footer.astro**: Site footer with links and licensing info
### Core Components
- **ToolCard.astro**: Individual tool display with metadata, actions, and type-specific styling
- **ToolMatrix.astro**: Matrix view showing tools by domain/phase intersection
- **ToolFilters.astro**: Search, filtering, and view controls
- **AIQueryInterface.astro**: AI-powered workflow recommendation system
### Utility Components
- **ShareButton.astro**: URL sharing with multiple view options
- **ContributionButton.astro**: Authenticated contribution links
- **ThemeToggle.astro**: Light/dark/auto theme switching
## Feature Systems
### 1. Authentication System (`src/utils/auth.ts`)
- **OIDC Integration**: Uses environment-configured provider
- **Contextual Requirements**: Different auth requirements for contributions vs AI features
- **Session Management**: JWT-based with configurable expiration
- **Client-Side Utilities**: Window-level auth checking functions
### 2. AI Recommendation System
- **Dual Modes**:
- Workflow mode: Multi-phase recommendations for scenarios
- Tool mode: Specific tool recommendations for problems
- **Rate Limiting**: Queue-based system with status updates
- **Data Integration**: Uses compressed tool database for AI context
- **Response Validation**: Ensures AI only recommends existing tools
### 3. Contribution System
- **Git Integration**: Automated issue creation via Gitea/GitHub/GitLab APIs
- **Tool Contributions**: Form-based tool submissions
- **Knowledge Base**: Rich text with file upload support
- **Validation**: Client and server-side validation with Zod schemas
### 4. File Upload System
- **Primary**: Nextcloud integration with public link generation
- **Fallback**: Local file storage with public URL generation
- **Validation**: File type and size restrictions
- **Rate Limiting**: Per-user upload quotas
## Styling Architecture
### CSS Custom Properties System
```css
/* Core color system */
--color-primary: /* Adaptive based on theme */
--color-accent: /* Secondary brand color */
--color-bg: /* Main background */
--color-text: /* Primary text */
/* Component-specific colors */
--color-hosted: /* CC24 server hosted tools */
--color-oss: /* Open source tools */
--color-method: /* Methodology entries */
--color-concept: /* Knowledge concepts */
/* Theme system */
[data-theme="light"] { /* Light theme values */ }
[data-theme="dark"] { /* Dark theme values */ }
```
### Component Styling Patterns
- **Card System**: Consistent `.card` base with type-specific variants
- **Badge System**: Status and metadata indicators
- **Button System**: Semantic button classes with size variants
- **Grid System**: CSS Grid with responsive breakpoints
## Data Flow Architecture
### 1. Data Loading (`src/utils/dataService.ts`)
```typescript
// Daily randomization with seeded shuffle
getToolsData() → shuffled tools array
getCompressedToolsDataForAI() → AI-optimized dataset
clearCache() → cache invalidation
```
### 2. Client-Side State Management
- **Global Tools Data**: Attached to `window.toolsData`
- **Filter State**: Component-local state with event emission
- **View State**: Grid/Matrix/AI view switching
- **Modal State**: Tool detail overlays with multi-modal support
### 3. Search and Filtering
- **Text Search**: Name, description, tags
- **Faceted Filtering**: Domain, phase, skill level, license type
- **Tag Cloud**: Frequency-weighted tag selection
- **Real-time Updates**: Immediate filtering with event system
## API Architecture
### Endpoint Structure
```
/api/auth/ # Authentication endpoints
login.ts # OIDC initiation
process.ts # OIDC callback processing
status.ts # Auth status checking
/api/contribute/ # Contribution endpoints
tool.ts # Tool submissions
knowledgebase.ts # KB article submissions
/api/ai/ # AI features
query.ts # Workflow recommendations
queue-status.ts # Queue monitoring
/api/upload/ # File handling
media.ts # File upload processing
/api/health.ts # System health check
```
### Response Patterns
All APIs use consolidated response utilities (`src/utils/api.ts`):
- **Success**: `apiResponse.success()`, `apiResponse.created()`
- **Errors**: `apiError.badRequest()`, `apiError.unauthorized()`, etc.
- **Server Errors**: `apiServerError.internal()`, etc.
## File Organization Patterns
### Page Structure
- **Static Pages**: About, impressum, status
- **Dynamic Pages**: Tool details, knowledge base articles
- **Authenticated Pages**: Contribution forms
- **API Routes**: RESTful endpoints with consistent naming
### Utility Organization
- **Tool Operations**: `toolHelpers.ts` - Core tool manipulation
- **Data Management**: `dataService.ts` - YAML loading and caching
- **Authentication**: `auth.ts` - OIDC flow and session management
- **External APIs**: `gitContributions.ts`, `nextcloud.ts`
- **Rate Limiting**: `rateLimitedQueue.ts` - AI request queuing
## Key Architectural Decisions
### 1. Static-First with Dynamic Islands
- Astro's islands architecture for interactivity
- Static generation for performance
- Selective hydration for complex components
### 2. YAML-Based Data Management
- Human-readable tool catalog
- Git-friendly versioning
- Type-safe loading with Zod validation
### 3. Contextual Authentication
- Optional authentication based on feature
- Graceful degradation for unauthenticated users
- Environment-configurable requirements
### 4. Multi-Modal UI Patterns
- Grid view for browsing
- Matrix view for relationship visualization
- AI interface for guided recommendations
### 5. Progressive Enhancement
- Core functionality works without JavaScript
- Enhanced features require client-side hydration
- Responsive design with mobile-first approach
## Development Patterns
### Component Design
- Props interfaces with TypeScript
- Consistent styling via CSS classes
- Event-driven communication between components
- Server-side rendering with client-side enhancement
### Error Handling
- Comprehensive try-catch in API routes
- User-friendly error messages
- Graceful fallbacks for external services
- Logging for debugging and monitoring
### Performance Optimizations
- Daily data randomization with caching
- Compressed datasets for AI context
- Rate limiting for external API calls
- Efficient DOM updates with targeted selectors
This architecture emphasizes maintainability, user experience, and extensibility while managing the complexity of a multi-feature forensics tool directory.
## Absolutely Essential (Core Architecture - 8 files)
**File**: `src/data/tools.yaml.example`
**Why**: Defines the core data model - what a "tool" is, the schema, domains, phases, etc. Without this, an AI can't understand what the application manages.
**File**: `src/pages/index.astro`
**Why**: Main application entry point showing the core functionality (filters, matrix, AI interface, tool grid). Contains the primary application logic flow.
**File**: `src/layouts/BaseLayout.astro`
**Why**: Global layout with theme system, authentication setup, and shared utility functions. Shows the overall app structure.
**File**: `src/utils/dataService.ts`
**Why**: Core data loading and processing logic. Essential for understanding how data flows through the application.
**File**: `src/utils/toolHelpers.ts`
**Why**: Core utility functions used throughout the app (slug creation, tool identification, hosting checks).
**File**: `src/styles/global.css`
**Why**: Complete styling system - defines visual architecture, component styling, theme system, responsive design.
**File**: `src/env.d.ts`
**Why**: Global TypeScript definitions and window interface extensions. Shows the global API surface.
**File**: `src/components/ToolCard.astro`
**Why**: Shows how the core entity (tools) are rendered and structured. Represents the component architecture pattern.
## Secondary Priority (Major Features - 4 files)
**File**: `src/components/ToolMatrix.astro` - Matrix view functionality
**File**: `src/components/AIQueryInterface.astro` - AI recommendation system
**File**: `src/utils/auth.ts` - Authentication system
**File**: `src/content/config.ts` - Content collection schema
## Strategy for Context Management
1. **Always provide**: The 8 essential files above
2. **Add selectively**: Include 1-3 secondary files based on the specific development task
3. **Reference others**: Mention other relevant files by name/purpose without including full content
user01@altiera /v/h/u/P/forensic-pathways (main)> tree src
src
├── components
│ ├── AIQueryInterface.astro
│ ├── ContributionButton.astro
│ ├── Footer.astro
│ ├── Navigation.astro
│ ├── ShareButton.astro
│ ├── ThemeToggle.astro
│ ├── ToolCard.astro
│ ├── ToolFilters.astro
│ └── ToolMatrix.astro
├── content
│ ├── config.ts
│ └── knowledgebase
│ ├── android-logical-imaging.md
│ ├── kali-linux.md
│ ├── misp.md
│ ├── nextcloud.md
│ ├── regular-expressions-regex.md
│ └── velociraptor.md
├── data
│ ├── tools.yaml
│ └── tools.yaml.example
├── env.d.ts
├── layouts
│ └── BaseLayout.astro
├── pages
│ ├── about.astro
│ ├── api
│ │ ├── ai
│ │ │ ├── query.ts
│ │ │ └── queue-status.ts
│ │ ├── auth
│ │ │ ├── login.ts
│ │ │ ├── process.ts
│ │ │ └── status.ts
│ │ ├── contribute
│ │ │ ├── knowledgebase.ts
│ │ │ └── tool.ts
│ │ ├── health.ts
│ │ └── upload
│ │ └── media.ts
│ ├── auth
│ │ └── callback.astro
│ ├── contribute
│ │ ├── index.astro
│ │ ├── knowledgebase.astro
│ │ └── tool.astro
│ ├── impressum.astro
│ ├── index.astro
│ ├── knowledgebase
│ │ └── [slug].astro
│ ├── knowledgebase.astro
│ └── status.astro
├── styles
│ └── global.css
└── utils
├── api.ts
├── auth.ts
├── dataService.ts
├── gitContributions.ts
├── nextcloud.ts
├── rateLimitedQueue.ts
└── toolHelpers.ts
17 directories, 47 files

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,11 @@
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
"devDependencies": { "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": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"

View File

@ -1,5 +1,6 @@
--- ---
import { getToolsData } from '../utils/dataService.js'; import { getToolsData } from '../utils/dataService.js';
import { isToolHosted } from '../utils/toolHelpers.js';
const data = await getToolsData(); const data = await getToolsData();
const tools = data.tools; const tools = data.tools;
@ -15,7 +16,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/> <path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/> <path d="M9 11V7a3 3 0 0 1 6 0v4"/>
</svg> </svg>
KI-gestützte Workflow-Empfehlungen Forensic AI
</h2> </h2>
<p id="ai-description" class="text-muted" style="max-width: 700px; margin: 0 auto; line-height: 1.6;"> <p id="ai-description" class="text-muted" style="max-width: 700px; margin: 0 auto; line-height: 1.6;">
Beschreiben Sie Ihr forensisches Szenario und erhalten Sie maßgeschneiderte Workflow-Empfehlungen Beschreiben Sie Ihr forensisches Szenario und erhalten Sie maßgeschneiderte Workflow-Empfehlungen
@ -169,16 +170,16 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
<!-- Micro-task Progress --> <!-- Micro-task Progress -->
<div id="micro-task-progress" class="micro-task-progress hidden"> <div id="micro-task-progress" class="micro-task-progress hidden">
<div class="micro-task-header"> <div class="micro-task-header">
<span class="micro-task-label">🔬 Micro-Task Analyse</span> <span class="micro-task-label">🔬 micro-Agent-Analysis</span>
<span id="micro-task-counter" class="micro-task-counter">1/6</span> <span id="micro-task-counter" class="micro-task-counter">1/6</span>
</div> </div>
<div class="micro-task-steps"> <div class="micro-task-steps">
<div class="micro-step" data-step="scenario">📋 Szenario</div> <div class="micro-step" data-step="scenario">📋 Problemanalyse</div>
<div class="micro-step" data-step="approach">🎯 Ansatz</div> <div class="micro-step" data-step="approach">🎯 Ermittlungsansatz</div>
<div class="micro-step" data-step="considerations">⚠️ Kritisches</div> <div class="micro-step" data-step="considerations">⚠️ Herausforderungen</div>
<div class="micro-step" data-step="tools">🔧 Tools</div> <div class="micro-step" data-step="tools">🔧 Methoden</div>
<div class="micro-step" data-step="knowledge">📚 Wissen</div> <div class="micro-step" data-step="knowledge">📚 Evaluation</div>
<div class="micro-step" data-step="final">✅ Final</div> <div class="micro-step" data-step="final">✅ Audit-Trail</div>
</div> </div>
</div> </div>
@ -292,13 +293,13 @@ class AIQueryInterface {
return { return {
workflow: { workflow: {
placeholder: "Beschreiben Sie Ihr forensisches Szenario... z.B. 'Verdacht auf Ransomware-Angriff auf Windows-Domänencontroller'", placeholder: "Beschreiben Sie Ihr forensisches Szenario... z.B. 'Verdacht auf Ransomware-Angriff auf Windows-Domänencontroller'",
description: "Beschreiben Sie Ihr forensisches Szenario und erhalten Sie maßgeschneiderte Workflow-Empfehlungen.", description: "Beschreiben Sie Ihre Untersuchungssituation und erhalten Empfehlungen für alle Phasen der Untersuchung.",
submitText: "Empfehlungen generieren", submitText: "Empfehlungen generieren",
loadingText: "Analysiere Szenario und generiere Empfehlungen..." loadingText: "Analysiere Szenario und generiere Empfehlungen..."
}, },
tool: { tool: {
placeholder: "Beschreiben Sie Ihr Problem... z.B. 'Analyse von Android-Backups mit WhatsApp-Nachrichten'", placeholder: "Beschreiben Sie Ihr Problem... z.B. 'Analyse von Android-Backups mit WhatsApp-Nachrichten'",
description: "Beschreiben Sie Ihr Problem und erhalten Sie 1-3 gezielt passende Empfehlungen.", description: "Beschreiben Sie Ihre Untersuchungssituation und erhalten Empfehlungen für eine spezifische Aufgabenstellung.",
submitText: "Empfehlungen finden", submitText: "Empfehlungen finden",
loadingText: "Analysiere Anforderungen und suche passende Methode..." loadingText: "Analysiere Anforderungen und suche passende Methode..."
} }
@ -671,6 +672,15 @@ class AIQueryInterface {
} }
displayResults(recommendation, originalQuery) { displayResults(recommendation, originalQuery) {
console.log('[AI DEBUG] Full recommendation object:', recommendation);
console.log('[AI DEBUG] Recommended tools:', recommendation.recommended_tools);
if (recommendation.recommended_tools) {
recommendation.recommended_tools.forEach((tool, index) => {
console.log(`[AI DEBUG] Tool ${index}:`, tool.name, 'Confidence:', tool.confidence);
});
}
if (this.currentMode === 'workflow') { if (this.currentMode === 'workflow') {
this.displayWorkflowResults(recommendation, originalQuery); this.displayWorkflowResults(recommendation, originalQuery);
} else { } else {
@ -692,13 +702,22 @@ class AIQueryInterface {
toolsByPhase[phase] = []; toolsByPhase[phase] = [];
}); });
console.log('[AI Results] Recommendation structure:', recommendation);
console.log('[AI Results] Recommended tools:', recommendation.recommended_tools);
recommendation.recommended_tools?.forEach(recTool => { recommendation.recommended_tools?.forEach(recTool => {
console.log('[AI Results] Tool confidence data:', recTool.name, recTool.confidence);
if (toolsByPhase[recTool.phase]) { if (toolsByPhase[recTool.phase]) {
const fullTool = tools.find(t => t.name === recTool.name); const fullTool = tools.find(t => t.name === recTool.name);
if (fullTool) { if (fullTool) {
toolsByPhase[recTool.phase].push({ toolsByPhase[recTool.phase].push({
...fullTool, ...fullTool,
recommendation: recTool recommendation: recTool,
confidence: recTool.confidence,
justification: recTool.justification,
priority: recTool.priority,
recommendationStrength: recTool.recommendationStrength
}); });
} }
} }
@ -706,11 +725,12 @@ class AIQueryInterface {
const html = ` const html = `
<div class="workflow-container"> <div class="workflow-container">
${this.renderHeader('Empfohlener DFIR-Workflow', originalQuery)} ${this.renderHeader('Untersuchungsansatz', originalQuery)}
${this.renderContextualAnalysis(recommendation, 'workflow')} ${this.renderContextualAnalysis(recommendation, 'workflow')}
${this.renderBackgroundKnowledge(recommendation.background_knowledge)} ${this.renderBackgroundKnowledge(recommendation.background_knowledge)}
${this.renderWorkflowPhases(toolsByPhase, phaseOrder, phaseNames)} ${this.renderWorkflowPhases(toolsByPhase, phaseOrder, phaseNames)}
${this.renderWorkflowSuggestion(recommendation.workflow_suggestion)} ${this.renderWorkflowSuggestion(recommendation.workflow_suggestion)}
${this.renderAuditTrail(recommendation.auditTrail)}
</div> </div>
`; `;
@ -719,18 +739,434 @@ class AIQueryInterface {
displayToolResults(recommendation, originalQuery) { displayToolResults(recommendation, originalQuery) {
const html = ` const html = `
<div class="tool-results-container"> <div class="workflow-container">
${this.renderHeader('Passende Empfehlungen', originalQuery)} ${this.renderHeader('Handlungsempfehlung', originalQuery)}
${this.renderContextualAnalysis(recommendation, 'tool')} ${this.renderContextualAnalysis(recommendation, 'tool')}
${this.renderBackgroundKnowledge(recommendation.background_knowledge)} ${this.renderBackgroundKnowledge(recommendation.background_knowledge)}
${this.renderToolRecommendations(recommendation.recommended_tools)} ${this.renderToolRecommendations(recommendation.recommended_tools)}
${this.renderAdditionalConsiderations(recommendation.additional_considerations)} ${this.renderAdditionalConsiderations(recommendation.additional_considerations)}
${this.renderAuditTrail(recommendation.auditTrail)}
</div> </div>
`; `;
this.elements.results.innerHTML = html; this.elements.results.innerHTML = html;
} }
renderConfidenceTooltip(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 tooltipId = `tooltip-${Math.random().toString(36).substr(2, 9)}`;
return `
<span class="confidence-tooltip-trigger"
style="display: inline-flex; align-items: center; gap: 0.125rem; cursor: help; margin-left: 0.25rem;"
onmouseenter="document.getElementById('${tooltipId}').style.display = 'block'"
onmouseleave="document.getElementById('${tooltipId}').style.display = 'none'"
onclick="event.stopPropagation();">
<div style="width: 6px; height: 6px; border-radius: 50%; background-color: ${confidenceColor}; flex-shrink: 0;"></div>
<span style="font-size: 0.625rem; color: white; font-weight: 600; text-shadow: 0 1px 2px rgba(0,0,0,0.5);">${confidence.overall}%</span>
<div id="${tooltipId}" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1001; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 0.5rem; padding: 1rem; min-width: 320px; max-width: 400px; box-shadow: var(--shadow-lg); font-size: 0.75rem; color: var(--color-text);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
<strong style="font-size: 0.875rem;">KI-Vertrauenswertung</strong>
<span class="badge badge-mini" style="background-color: ${confidenceColor}; color: white; font-weight: 600;">${confidence.overall}%</span>
</div>
<div style="display: grid; grid-template-columns: 1fr; gap: 0.625rem; margin-bottom: 0.75rem;">
<div style="background: var(--color-bg-secondary); padding: 0.5rem; border-radius: 0.375rem; border-left: 3px solid var(--color-accent);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.25rem;">
<span style="font-weight: 600; font-size: 0.6875rem;">🔍 Semantische Relevanz</span>
<strong style="color: var(--color-accent);">${confidence.semanticRelevance}%</strong>
</div>
<div style="font-size: 0.625rem; color: var(--color-text-secondary); line-height: 1.3;">
Wie gut die Tool-Beschreibung semantisch zu Ihrer Anfrage passt (Vektor-Ähnlichkeit)
</div>
</div>
<div style="background: var(--color-bg-secondary); padding: 0.5rem; border-radius: 0.375rem; border-left: 3px solid var(--color-primary);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.25rem;">
<span style="font-weight: 600; font-size: 0.6875rem;">🎯 Aufgaben-Eignung</span>
<strong style="color: var(--color-primary);">${confidence.taskSuitability}%</strong>
</div>
<div style="font-size: 0.625rem; color: var(--color-text-secondary); line-height: 1.3;">
KI-bewertete Eignung des Tools für Ihre spezifische forensische Aufgabenstellung
</div>
</div>
</div>
${confidence.strengthIndicators && confidence.strengthIndicators.length > 0 ? `
<div style="margin-bottom: 0.75rem; padding: 0.5rem; background: var(--color-oss-bg); border-radius: 0.375rem; border-left: 3px solid var(--color-accent);">
<strong style="color: var(--color-accent); font-size: 0.6875rem; display: flex; align-items: center; gap: 0.25rem; margin-bottom: 0.375rem;">
<span>✓</span> Stärken dieser Empfehlung:
</strong>
<ul style="margin: 0; padding-left: 1rem; font-size: 0.625rem; line-height: 1.4;">
${confidence.strengthIndicators.slice(0, 3).map(s => `<li style="margin-bottom: 0.25rem;">${this.sanitizeText(s)}</li>`).join('')}
</ul>
</div>
` : ''}
${confidence.uncertaintyFactors && confidence.uncertaintyFactors.length > 0 ? `
<div style="padding: 0.5rem; background: var(--color-hosted-bg); border-radius: 0.375rem; border-left: 3px solid var(--color-warning);">
<strong style="color: var(--color-warning); font-size: 0.6875rem; display: flex; align-items: center; gap: 0.25rem; margin-bottom: 0.375rem;">
<span>⚠</span> Mögliche Einschränkungen:
</strong>
<ul style="margin: 0; padding-left: 1rem; font-size: 0.625rem; line-height: 1.4;">
${confidence.uncertaintyFactors.slice(0, 3).map(f => `<li style="margin-bottom: 0.25rem;">${this.sanitizeText(f)}</li>`).join('')}
</ul>
</div>
` : ''}
<div style="margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border); font-size: 0.625rem; color: var(--color-text-secondary); text-align: center;">
Forensisch fundierte KI-Analyse
</div>
</div>
</span>
`;
}
renderAuditTrail(auditTrail) {
if (!auditTrail || !Array.isArray(auditTrail) || auditTrail.length === 0) {
return '';
}
const totalTime = auditTrail.reduce((sum, entry) => sum + entry.processingTimeMs, 0);
const avgConfidence = auditTrail.reduce((sum, entry) => sum + entry.confidence, 0) / auditTrail.length;
const lowConfidenceSteps = auditTrail.filter(entry => entry.confidence < 60).length;
const highConfidenceSteps = auditTrail.filter(entry => entry.confidence >= 80).length;
const groupedEntries = auditTrail.reduce((groups, entry) => {
if (!groups[entry.phase]) groups[entry.phase] = [];
groups[entry.phase].push(entry);
return groups;
}, {});
return `
<div class="card-info-sm mt-4" style="border-left: 4px solid var(--color-accent);">
<div class="flex items-center justify-between mb-3 cursor-pointer" onclick="const container = this.closest('.card-info-sm'); const details = container.querySelector('.audit-trail-details'); const isHidden = details.style.display === 'none'; details.style.display = isHidden ? 'block' : 'none'; this.querySelector('.toggle-icon').style.transform = isHidden ? 'rotate(180deg)' : 'rotate(0deg)';">
<div class="flex items-center gap-3">
<div class="flex items-center gap-2">
<div style="width: 24px; height: 24px; background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-primary) 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 0.75rem; font-weight: bold;">
</div>
<h4 class="text-sm font-semibold text-accent mb-0">KI-Entscheidungspfad</h4>
</div>
<div class="flex gap-3 text-xs">
<div class="flex items-center gap-1">
<div class="w-2 h-2 rounded-full" style="background-color: var(--color-accent);"></div>
<span class="text-muted">${this.formatDuration(totalTime)}</span>
</div>
<div class="flex items-center gap-1">
<div class="w-2 h-2 rounded-full" style="background-color: ${avgConfidence >= 80 ? 'var(--color-accent)' : avgConfidence >= 60 ? 'var(--color-warning)' : 'var(--color-error)'};"></div>
<span class="text-muted">${Math.round(avgConfidence)}% Vertrauen</span>
</div>
<div class="flex items-center gap-1">
<span class="text-muted">${auditTrail.length} Schritte</span>
</div>
</div>
</div>
<div class="toggle-icon" style="transition: transform 0.2s ease;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</div>
</div>
<div style="display: none;" class="audit-trail-details">
<div class="grid gap-4">
<!-- Summary Section -->
<div class="p-3 rounded-lg" style="background-color: var(--color-bg-tertiary);">
<div class="text-xs font-medium mb-2 text-accent">📊 Analyse-Qualität</div>
<div class="grid grid-cols-3 gap-3 text-xs">
<div class="text-center">
<div class="font-semibold" style="color: var(--color-accent);">${highConfidenceSteps}</div>
<div class="text-muted">Hohe Sicherheit</div>
</div>
<div class="text-center">
<div class="font-semibold" style="color: ${lowConfidenceSteps > 0 ? 'var(--color-warning)' : 'var(--color-accent)'};">${lowConfidenceSteps}</div>
<div class="text-muted">Unsichere Schritte</div>
</div>
<div class="text-center">
<div class="font-semibold">${this.formatDuration(totalTime)}</div>
<div class="text-muted">Verarbeitungszeit</div>
</div>
</div>
</div>
<!-- Process Flow -->
<div class="audit-process-flow">
${Object.entries(groupedEntries).map(([phase, entries]) => this.renderPhaseGroup(phase, entries)).join('')}
</div>
<!-- Technical Details Toggle -->
<div class="text-center">
<button class="text-xs text-muted hover:text-primary transition-colors cursor-pointer border-none bg-none" onclick="const techDetails = this.nextElementSibling; const isHidden = techDetails.style.display === 'none'; techDetails.style.display = isHidden ? 'block' : 'none'; this.textContent = isHidden ? '🔧 Technische Details ausblenden' : '🔧 Technische Details anzeigen';">
🔧 Technische Details anzeigen
</button>
<div style="display: none;" class="mt-3 p-3 rounded-lg bg-gray-50 dark:bg-gray-800 text-xs">
${auditTrail.map(entry => this.renderTechnicalEntry(entry)).join('')}
</div>
</div>
</div>
</div>
</div>
`;
}
renderPhaseGroup(phase, entries) {
const phaseIcons = {
'initialization': '🚀',
'retrieval': '🔍',
'selection': '🎯',
'micro-task': '⚡',
'completion': '✅'
};
const phaseNames = {
'initialization': 'Initialisierung',
'retrieval': 'Datensuche',
'selection': 'Tool-Auswahl',
'micro-task': 'Detail-Analyse',
'completion': 'Finalisierung'
};
const avgConfidence = entries.reduce((sum, entry) => sum + entry.confidence, 0) / entries.length;
const totalTime = entries.reduce((sum, entry) => sum + entry.processingTimeMs, 0);
return `
<div class="phase-group mb-4">
<div class="flex items-center gap-3 mb-3">
<div class="flex items-center gap-2">
<span class="text-lg">${phaseIcons[phase] || '📋'}</span>
<span class="font-medium text-sm">${phaseNames[phase] || phase}</span>
</div>
<div class="flex-1 h-px bg-border"></div>
<div class="flex items-center gap-2 text-xs text-muted">
<div class="confidence-indicator w-12 h-2 rounded-full overflow-hidden" style="background-color: var(--color-bg-tertiary);">
<div class="h-full rounded-full transition-all" style="width: ${avgConfidence}%; background-color: ${avgConfidence >= 80 ? 'var(--color-accent)' : avgConfidence >= 60 ? 'var(--color-warning)' : 'var(--color-error)'};"></div>
</div>
<span>${Math.round(avgConfidence)}%</span>
</div>
</div>
<div class="grid gap-2 ml-6">
${entries.map(entry => this.renderSimplifiedEntry(entry)).join('')}
</div>
</div>
`;
}
renderSimplifiedEntry(entry) {
const actionIcons = {
'pipeline-start': '▶️',
'embeddings-search': '🔍',
'ai-tool-selection': '🎯',
'ai-analysis': '🧠',
'phase-tool-selection': '⚙️',
'tool-evaluation': '📊',
'background-knowledge-selection': '📚',
'pipeline-end': '🏁'
};
const actionNames = {
'pipeline-start': 'Analyse gestartet',
'embeddings-search': 'Ähnliche Tools gesucht',
'ai-tool-selection': 'Tools automatisch ausgewählt',
'ai-analysis': 'KI-Analyse durchgeführt',
'phase-tool-selection': 'Phasen-Tools evaluiert',
'tool-evaluation': 'Tool-Bewertung erstellt',
'background-knowledge-selection': 'Hintergrundwissen ausgewählt',
'pipeline-end': 'Analyse abgeschlossen'
};
const confidenceColor = entry.confidence >= 80 ? 'var(--color-accent)' :
entry.confidence >= 60 ? 'var(--color-warning)' : 'var(--color-error)';
return `
<div class="flex items-center gap-3 py-2 px-3 rounded-lg hover:bg-secondary transition-colors">
<span class="text-sm">${actionIcons[entry.action] || '📋'}</span>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium">${actionNames[entry.action] || entry.action}</div>
${entry.output && typeof entry.output === 'object' && entry.output.selectedToolCount ?
`<div class="text-xs text-muted">${entry.output.selectedToolCount} Tools ausgewählt</div>` : ''}
</div>
<div class="flex items-center gap-2 text-xs">
<div class="w-8 h-1.5 rounded-full overflow-hidden" style="background-color: var(--color-bg-tertiary);">
<div class="h-full rounded-full" style="width: ${entry.confidence}%; background-color: ${confidenceColor};"></div>
</div>
<span class="text-muted w-8 text-right">${entry.confidence}%</span>
<span class="text-muted w-12 text-right">${entry.processingTimeMs}ms</span>
</div>
</div>
`;
}
renderTechnicalEntry(entry) {
const formattedTime = new Date(entry.timestamp).toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
return `
<div class="border rounded p-2 mb-2" style="border-color: var(--color-border);">
<div class="flex justify-between items-center mb-1">
<span class="font-mono text-xs">${entry.phase}/${entry.action}</span>
<span class="text-xs text-muted">${formattedTime} • ${entry.processingTimeMs}ms</span>
</div>
${entry.input && Object.keys(entry.input).length > 0 ? `
<div class="text-xs mb-1">
<strong>Input:</strong> ${this.formatAuditData(entry.input)}
</div>
` : ''}
${entry.output && Object.keys(entry.output).length > 0 ? `
<div class="text-xs">
<strong>Output:</strong> ${this.formatAuditData(entry.output)}
</div>
` : ''}
</div>
`;
}
renderAuditEntry(entry) {
const confidenceColor = entry.confidence >= 80 ? 'var(--color-accent)' :
entry.confidence >= 60 ? 'var(--color-warning)' : 'var(--color-error)';
const formattedTime = new Date(entry.timestamp).toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
return `
<div class="border-l-2 pl-3 py-2 mb-2" style="border-left-color: ${confidenceColor};">
<div class="flex justify-between items-center mb-1">
<span class="text-xs font-medium">${entry.phase} → ${entry.action}</span>
<div class="flex items-center gap-2">
<span class="badge badge-mini" style="background-color: ${confidenceColor}; color: white;">
${entry.confidence}% confidence
</span>
<span class="text-xs text-muted">${entry.processingTimeMs}ms</span>
<span class="text-xs text-muted">${formattedTime}</span>
</div>
</div>
<div class="text-xs text-muted grid-cols-2 gap-2" style="display: grid;">
<div><strong>Input:</strong> ${this.formatAuditData(entry.input)}</div>
<div><strong>Output:</strong> ${this.formatAuditData(entry.output)}</div>
</div>
${entry.metadata && Object.keys(entry.metadata).length > 0 ? `
<div class="text-xs text-muted mt-1 pt-1 border-t border-dashed">
<strong>Metadata:</strong> ${this.formatAuditData(entry.metadata)}
</div>
` : ''}
</div>
`;
}
formatAuditData(data) {
if (data === null || data === undefined) return 'null';
if (typeof data === 'string') {
return data.length > 100 ? data.slice(0, 100) + '...' : data;
}
if (typeof data === 'number') return data.toString();
if (typeof data === 'boolean') return data.toString();
if (Array.isArray(data)) {
if (data.length === 0) return '[]';
if (data.length <= 3) return JSON.stringify(data);
return `[${data.slice(0, 3).map(i => typeof i === 'string' ? i : JSON.stringify(i)).join(', ')}, ...+${data.length - 3}]`;
}
if (typeof data === 'object') {
const keys = Object.keys(data);
if (keys.length === 0) return '{}';
if (keys.length <= 3) {
return '{' + keys.map(k => `${k}: ${typeof data[k] === 'string' ? data[k].slice(0, 20) + (data[k].length > 20 ? '...' : '') : JSON.stringify(data[k])}`).join(', ') + '}';
}
return `{${keys.slice(0, 3).join(', ')}, ...+${keys.length - 3} keys}`;
}
return String(data);
}
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; position: relative;">
${tool.recommendation ? tool.recommendation.priority : tool.priority}
${tool.confidence ? this.renderConfidenceTooltip(tool.confidence, 'priority') : ''}
</span>
</div>
</div>
<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; position: relative;">
${this.getSuitabilityText(recommendation.suitability_score, recommendation.confidence)}
</span>
${this.renderToolBadges(tool)}
</div>
</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) { renderHeader(title, query) {
return ` 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;"> <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;">
@ -813,6 +1249,8 @@ class AIQueryInterface {
const phaseTools = toolsByPhase[phase]; const phaseTools = toolsByPhase[phase];
if (phaseTools.length === 0) return ''; if (phaseTools.length === 0) return '';
console.log(`[AI DEBUG] Phase ${phase} tools:`, phaseTools.map(t => ({name: t.name, hasConfidence: !!t.confidence})));
return ` return `
<div class="workflow-phase"> <div class="workflow-phase">
<div class="phase-header"> <div class="phase-header">
@ -820,7 +1258,10 @@ class AIQueryInterface {
<div class="phase-info"> <div class="phase-info">
<h3 class="phase-title">${phaseNames[phase]}</h3> <h3 class="phase-title">${phaseNames[phase]}</h3>
<div class="phase-tools"> <div class="phase-tools">
${phaseTools.map(tool => this.renderWorkflowTool(tool)).join('')} ${phaseTools.map(tool => {
console.log(`[AI DEBUG] Rendering tool ${tool.name} with confidence:`, tool.confidence);
return this.renderWorkflowTool(tool);
}).join('')}
</div> </div>
</div> </div>
</div> </div>
@ -831,28 +1272,38 @@ class AIQueryInterface {
} }
renderWorkflowTool(tool) { renderWorkflowTool(tool) {
const hasValidProjectUrl = this.isToolHosted(tool); console.log(`[AI DEBUG] renderWorkflowTool called for ${tool.name}, confidence:`, tool.confidence);
const hasValidProjectUrl = isToolHosted(tool);
const priorityColors = { const priorityColors = {
high: 'var(--color-error)', high: 'var(--color-error)',
medium: 'var(--color-warning)', medium: 'var(--color-warning)',
low: 'var(--color-accent)' low: 'var(--color-accent)'
}; };
const priority = tool.recommendation ? tool.recommendation.priority : tool.priority;
const confidenceTooltip = tool.confidence ? this.renderConfidenceTooltip(tool.confidence) : '';
console.log(`[AI DEBUG] Priority: ${priority}, Confidence tooltip:`, confidenceTooltip ? 'generated' : 'empty');
return ` return `
<div class="tool-recommendation ${this.getToolClass(tool, 'recommendation')}" onclick="window.showToolDetails('${tool.name}')"> <div class="tool-recommendation ${this.getToolClass(tool, 'recommendation')}" onclick="window.showToolDetails('${tool.name}')" style="position: relative;">
<div class="tool-rec-header"> <div class="tool-rec-header">
<h4 class="tool-rec-name"> <h4 class="tool-rec-name">
${tool.icon ? `<span style="margin-right: 0.5rem;">${tool.icon}</span>` : ''} ${tool.icon ? `<span style="margin-right: 0.5rem;">${tool.icon}</span>` : ''}
${tool.name} ${tool.name}
</h4> </h4>
<span class="tool-rec-priority ${tool.recommendation.priority}" <div class="flex items-center gap-2">
style="background-color: ${priorityColors[tool.recommendation.priority]}; color: white; padding: 0.25rem 0.5rem; border-radius: 1rem; font-size: 0.75rem;"> <span class="tool-rec-priority ${priority}"
${tool.recommendation.priority} style="background-color: ${priorityColors[priority]}; color: white; padding: 0.25rem 0.5rem; border-radius: 1rem; font-size: 0.75rem; position: relative; display: flex; align-items: center; gap: 0.25rem;">
</span> ${priority}
${confidenceTooltip}
</span>
</div>
</div> </div>
<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;"> <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.recommendation.justification)}" "${this.sanitizeText(tool.justification || (tool.recommendation && tool.recommendation.justification) || `Empfohlen für ${tool.phase}`)}"
</div> </div>
<div style="font-size: 0.75rem; color: var(--color-text-secondary);"> <div style="font-size: 0.75rem; color: var(--color-text-secondary);">
@ -874,16 +1325,23 @@ class AIQueryInterface {
const fullTool = tools.find(t => t.name === toolRec.name); const fullTool = tools.find(t => t.name === toolRec.name);
if (!fullTool) return ''; if (!fullTool) return '';
return this.renderDetailedTool(fullTool, toolRec, index + 1); return this.renderDetailedTool(fullTool, {
...toolRec,
confidence: toolRec.confidence
}, index + 1);
}).join('')} }).join('')}
</div> </div>
`; `;
} }
renderDetailedTool(tool, recommendation, rank) { renderDetailedTool(tool, recommendation, rank) {
console.log(`[AI DEBUG] renderDetailedTool called for ${tool.name}, recommendation confidence:`, recommendation.confidence);
const rankColors = { 1: 'var(--color-accent)', 2: 'var(--color-primary)', 3: 'var(--color-warning)' }; 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)' }; const suitabilityColors = { high: 'var(--color-accent)', medium: 'var(--color-warning)', low: 'var(--color-text-secondary)' };
const confidenceTooltip = recommendation.confidence ? this.renderConfidenceTooltip(recommendation.confidence) : '';
return ` return `
<div class="card ${this.getToolClass(tool, 'card')}" style="cursor: pointer; position: relative;" onclick="window.showToolDetails('${tool.name}')"> <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;"> <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;">
@ -892,9 +1350,10 @@ class AIQueryInterface {
<div style="margin-bottom: 1rem;"> <div style="margin-bottom: 1rem;">
<h3 style="margin: 0 0 0.5rem 0;">${tool.name}</h3> <h3 style="margin: 0 0 0.5rem 0;">${tool.name}</h3>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;"> <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;"> <span class="badge" style="background-color: ${suitabilityColors[recommendation.suitability_score]}; color: white; position: relative; display: flex; align-items: center; gap: 0.25rem;">
${this.getSuitabilityText(recommendation.suitability_score)} ${this.getSuitabilityText(recommendation.suitability_score)}
${confidenceTooltip}
</span> </span>
${this.renderToolBadges(tool)} ${this.renderToolBadges(tool)}
</div> </div>
@ -988,7 +1447,7 @@ class AIQueryInterface {
renderToolBadges(tool) { renderToolBadges(tool) {
const isMethod = tool.type === 'method'; const isMethod = tool.type === 'method';
const hasValidProjectUrl = this.isToolHosted(tool); const hasValidProjectUrl = isToolHosted(tool);
let badges = ''; let badges = '';
if (isMethod) { if (isMethod) {
@ -1032,16 +1491,9 @@ class AIQueryInterface {
.trim(); .trim();
} }
isToolHosted(tool) {
return tool.projectUrl !== undefined &&
tool.projectUrl !== null &&
tool.projectUrl !== "" &&
tool.projectUrl.trim() !== "";
}
getToolClass(tool, context = 'card') { getToolClass(tool, context = 'card') {
const isMethod = tool.type === 'method'; const isMethod = tool.type === 'method';
const hasValidProjectUrl = this.isToolHosted(tool); const hasValidProjectUrl = isToolHosted(tool);
if (context === 'recommendation') { if (context === 'recommendation') {
if (isMethod) return 'method'; if (isMethod) return 'method';
@ -1056,13 +1508,18 @@ class AIQueryInterface {
} }
} }
getSuitabilityText(score) { getSuitabilityText(score, confidence = null) {
const texts = { const texts = {
high: 'GUT GEEIGNET', high: 'GUT GEEIGNET',
medium: 'GEEIGNET', medium: 'GEEIGNET',
low: 'VIELLEICHT GEEIGNET' low: 'VIELLEICHT GEEIGNET'
}; };
return texts[score] || 'GEEIGNET'; const baseText = texts[score] || 'GEEIGNET';
if (confidence) {
return `${baseText} ${this.renderConfidenceTooltip(confidence, 'suitability')}`;
}
return baseText;
} }
escapeHtml(text) { escapeHtml(text) {
@ -1134,5 +1591,6 @@ document.addEventListener('DOMContentLoaded', () => {
aiInterface.hideError(); aiInterface.hideError();
} }
}; };
window.isToolHosted = window.isToolHosted || isToolHosted
}); });
</script> </script>

243
src/config/prompts.ts Normal file
View File

@ -0,0 +1,243 @@
// src/config/prompts.ts - Centralized German prompts for AI pipeline
export const AI_PROMPTS = {
toolSelection: (mode: string, userQuery: string, selectionMethod: string, maxSelectedItems: number) => {
const modeInstruction = mode === 'workflow'
? 'Der Benutzer möchte einen UMFASSENDEN WORKFLOW mit mehreren Tools/Methoden über verschiedene Phasen. Wählen Sie 15-25 Tools aus, die den vollständigen Untersuchungslebenszyklus abdecken.'
: 'Der Benutzer möchte SPEZIFISCHE TOOLS/METHODEN, die ihr konkretes Problem direkt lösen. Wählen Sie 3-8 Tools aus, die am relevantesten und effektivsten sind.';
return `Sie sind ein DFIR-Experte mit Zugang zur kompletten forensischen Tool-Datenbank. Sie müssen die relevantesten Tools und Konzepte für diese spezifische Anfrage auswählen.
AUSWAHLMETHODE: ${selectionMethod}
${selectionMethod === 'embeddings_candidates' ?
'Diese Tools wurden durch Vektor-Ähnlichkeit vorgefiltert, sie sind bereits relevant. Ihre Aufgabe ist es, die BESTEN aus diesem relevanten Set auszuwählen.' :
'Sie haben Zugang zur vollständigen Tool-Datenbank. Wählen Sie die relevantesten Tools für die Anfrage aus.'}
${modeInstruction}
BENUTZER-ANFRAGE: "${userQuery}"
KRITISCHE AUSWAHLPRINZIPIEN:
1. **KONTEXT ÜBER POPULARITÄT**: Verwenden Sie nicht automatisch "berühmte" Tools wie Volatility, Wireshark oder Autopsy nur weil sie bekannt sind. Wählen Sie basierend auf den SPEZIFISCHEN Szenario-Anforderungen.
2. **METHODOLOGIE vs SOFTWARE**:
- Für SCHNELLE/DRINGENDE Szenarien Priorisieren Sie METHODEN und schnelle Antwort-Ansätze
- Für ZEITKRITISCHE Vorfälle Wählen Sie Triage-Methoden über tiefe Analyse-Tools
- Für UMFASSENDE Analysen Dann betrachten Sie detaillierte Software-Tools
- METHODEN (Typ: "method") sind oft besser als SOFTWARE für prozedurale Anleitung
3. **SZENARIO-SPEZIFISCHE LOGIK**:
- "Schnell/Quick/Dringend/Triage" Szenarien Rapid Incident Response und Triage METHODE > Volatility
- "Industrial/SCADA/ICS" Szenarien Spezialisierte ICS-Tools > generische Netzwerk-Tools
- "Mobile/Android/iOS" Szenarien Mobile-spezifische Tools > Desktop-Forensik-Tools
- "Speicher-Analyse dringend benötigt" Schnelle Speicher-Tools/Methoden > umfassende Volatility-Analyse
ANALYSE-ANWEISUNGEN:
1. Lesen Sie die VOLLSTÄNDIGE Beschreibung jedes Tools/Konzepts
2. Berücksichtigen Sie ALLE Tags, Plattformen, verwandte Tools und Metadaten
3. **PASSENDE DRINGLICHKEIT**: Schnelle Szenarien brauchen schnelle Methoden, nicht tiefe Analyse-Tools
4. **PASSENDE SPEZIFITÄT**: Spezialisierte Szenarien brauchen spezialisierte Tools, nicht generische
5. **BERÜCKSICHTIGEN SIE DEN TYP**: Methoden bieten prozedurale Anleitung, Software bietet technische Fähigkeiten
Wählen Sie die relevantesten Elemente aus (max ${maxSelectedItems} insgesamt).
Antworten Sie NUR mit diesem JSON-Format:
{
"selectedTools": ["Tool Name 1", "Tool Name 2", ...],
"selectedConcepts": ["Konzept Name 1", "Konzept Name 2", ...],
"reasoning": "Detaillierte Erklärung, warum diese spezifischen Tools für diese Anfrage ausgewählt wurden, und warum bestimmte populäre Tools NICHT ausgewählt wurden, falls sie für den Szenario-Kontext ungeeignet waren"
}`;
},
scenarioAnalysis: (isWorkflow: boolean, userQuery: string) => {
const analysisType = isWorkflow ? 'forensische Szenario' : 'technische Problem';
const considerations = isWorkflow ?
`- Angriffsvektoren und Bedrohungsmodellierung nach MITRE ATT&CK
- Betroffene Systeme und kritische Infrastrukturen
- Zeitkritische Faktoren und Beweiserhaltung
- Forensische Artefakte und Datenquellen` :
`- Spezifische forensische Herausforderungen
- Verfügbare Datenquellen und deren Integrität
- Methodische Anforderungen für rechtssichere Analyse`;
return `Sie sind ein erfahrener DFIR-Experte. Analysieren Sie das folgende ${analysisType}.
${isWorkflow ? 'FORENSISCHES SZENARIO' : 'TECHNISCHES PROBLEM'}: "${userQuery}"
Führen Sie eine systematische ${isWorkflow ? 'Szenario-Analyse' : 'Problem-Analyse'} durch und berücksichtigen Sie dabei:
${considerations}
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählungen oder Markdown-Formatierung. Maximum 150 Wörter.`;
},
investigationApproach: (isWorkflow: boolean, userQuery: string) => {
const approachType = isWorkflow ? 'Untersuchungsansatz' : 'Lösungsansatz';
const considerations = isWorkflow ?
`- Triage-Prioritäten nach forensischer Dringlichkeit
- Phasenabfolge nach NIST-Methodik
- Kontaminationsvermeidung und forensische Isolierung` :
`- Methodik-Auswahl nach wissenschaftlichen Kriterien
- Validierung und Verifizierung der gewählten Ansätze
- Integration in bestehende forensische Workflows`;
return `Basierend auf der Analyse entwickeln Sie einen fundierten ${approachType} nach NIST SP 800-86 Methodik.
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
Entwickeln Sie einen systematischen ${approachType} unter Berücksichtigung von:
${considerations}
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 150 Wörter.`;
},
criticalConsiderations: (isWorkflow: boolean, userQuery: string) => {
const considerationType = isWorkflow ? 'kritische forensische Überlegungen' : 'wichtige methodische Voraussetzungen';
const aspects = isWorkflow ?
`- Time-sensitive evidence preservation
- Chain of custody requirements und rechtliche Verwertbarkeit
- Incident containment vs. evidence preservation Dilemma
- Privacy- und Compliance-Anforderungen` :
`- Tool-Validierung und Nachvollziehbarkeit
- False positive/negative Risiken bei der gewählten Methodik
- Qualifikationsanforderungen für die Durchführung
- Dokumentations- und Reporting-Standards`;
return `Identifizieren Sie ${considerationType} für diesen Fall.
${isWorkflow ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
Berücksichtigen Sie folgende forensische Aspekte:
${aspects}
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 120 Wörter.`;
},
phaseToolSelection: (userQuery: string, phase: any, phaseTools: any[]) => {
return `Wählen Sie 2-3 Methoden/Tools für die Phase "${phase.name}" und bewerten Sie deren Aufgaben-Eignung VERGLEICHEND.
SZENARIO: "${userQuery}"
SPEZIFISCHE PHASE: ${phase.name} - ${phase.description || 'Forensische Untersuchungsphase'}
VERFÜGBARE TOOLS FÜR ${phase.name.toUpperCase()}:
${phaseTools.map((tool: any, index: number) => `${index + 1}. ${tool.name}: ${tool.description.slice(0, 150)}...
- Plattformen: ${tool.platforms?.join(', ') || 'N/A'}
- Skill Level: ${tool.skillLevel}
- Tags: ${tool.tags?.join(', ') || 'N/A'}`).join('\n\n')}
Bewerten Sie ALLE Tools vergleichend für diese spezifische Aufgabe UND Phase. Wählen Sie die 2-3 besten aus.
BEWERTUNGSKRITERIEN:
- Wie gut löst das Tool das forensische Problem im SZENARIO-Kontext?
- Wie gut passt es zur spezifischen PHASE "${phase.name}"?
- Wie vergleicht es sich mit den anderen verfügbaren Tools für diese Phase?
Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format:
[
{
"toolName": "Exakter Tool-Name",
"taskRelevance": 85,
"justification": "Vergleichende Begründung warum dieses Tool für diese Phase und Aufgabe besser/schlechter als die anderen geeignet ist",
"limitations": ["Spezifische Einschränkung 1", "Einschränkung 2"]
}
]
WICHTIG:
- taskRelevance: 0-100 Score basierend auf Szenario-Eignung UND Phasen-Passung im VERGLEICH zu anderen Tools
- Nur die 2-3 BESTEN Tools auswählen und bewerten
- justification soll VERGLEICHEND sein ("besser als X weil...", "für diese Phase ideal weil...")`;
},
toolEvaluation: (userQuery: string, tool: any, rank: number, taskRelevance: number) => {
return `Sie sind ein DFIR-Experte. Erklären Sie DETAILLIERT die Anwendung dieses bereits bewerteten Tools.
PROBLEM: "${userQuery}"
TOOL: ${tool.name} (bereits bewertet mit ${taskRelevance}% Aufgaben-Eignung)
BESCHREIBUNG: ${tool.description}
Das Tool wurde bereits als Rang ${rank} für diese Aufgabe bewertet. Erklären Sie nun:
Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format:
{
"detailed_explanation": "Detaillierte Erklärung warum und wie dieses Tool für diese spezifische Aufgabe eingesetzt wird",
"implementation_approach": "Konkrete Schritt-für-Schritt Anleitung zur korrekten Anwendung",
"pros": ["Spezifischer Vorteil 1", "Spezifischer Vorteil 2"],
"limitations": ["Spezifische Einschränkung 1", "Spezifische Einschränkung 2"],
"alternatives": "Alternative Ansätze oder Tools falls dieses nicht verfügbar ist"
}
WICHTIG:
- Keine erneute Bewertung - nur detaillierte Erklärung der bereits bewerteten Eignung
- "limitations" soll spezifische technische/methodische Einschränkungen des Tools auflisten
- "pros" soll die Stärken für diese spezifische Aufgabe hervorheben`;
},
backgroundKnowledgeSelection: (userQuery: string, mode: string, selectedToolNames: string[], availableConcepts: any[]) => {
return `Wählen Sie relevante forensische Konzepte für das Verständnis der empfohlenen Methodik.
${mode === 'workflow' ? 'SZENARIO' : 'PROBLEM'}: "${userQuery}"
EMPFOHLENE TOOLS: ${selectedToolNames.join(', ')}
VERFÜGBARE KONZEPTE:
${availableConcepts.slice(0, 15).map((concept: any) => `- ${concept.name}: ${concept.description.slice(0, 80)}...`).join('\n')}
Wählen Sie 2-4 Konzepte aus, die für das Verständnis der forensischen Methodik essentiell sind.
Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format:
[
{
"conceptName": "Exakter Konzept-Name",
"relevance": "Forensische Relevanz: Warum dieses Konzept für das Verständnis der Methodik kritisch ist"
}
]`;
},
finalRecommendations: (isWorkflow: boolean, userQuery: string, selectedToolNames: string[]) => {
const prompt = isWorkflow ?
`Erstellen Sie eine Workflow-Empfehlung basierend auf DFIR-Prinzipien.
SZENARIO: "${userQuery}"
AUSGEWÄHLTE TOOLS: ${selectedToolNames.join(', ') || 'Keine Tools ausgewählt'}
Erstellen Sie konkrete methodische Workflow-Schritte für dieses spezifische Szenario unter Berücksichtigung forensischer Best Practices, Objektivität und rechtlicher Verwertbarkeit.
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 120 Wörter.` :
`Erstellen Sie wichtige methodische Überlegungen für die korrekte Methoden-/Tool-Anwendung.
PROBLEM: "${userQuery}"
EMPFOHLENE TOOLS: ${selectedToolNames.join(', ') || 'Keine Methoden/Tools ausgewählt'}
Geben Sie kritische methodische Überlegungen, Validierungsanforderungen und Qualitätssicherungsmaßnahmen für die korrekte Anwendung der empfohlenen Methoden/Tools.
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 100 Wörter.`;
return prompt;
}
} as const;
export function getPrompt(key: 'toolSelection', mode: string, userQuery: string, selectionMethod: string, maxSelectedItems: number): string;
export function getPrompt(key: 'scenarioAnalysis', isWorkflow: boolean, userQuery: string): string;
export function getPrompt(key: 'investigationApproach', isWorkflow: boolean, userQuery: string): string;
export function getPrompt(key: 'criticalConsiderations', isWorkflow: boolean, userQuery: string): string;
export function getPrompt(key: 'phaseToolSelection', userQuery: string, phase: any, phaseTools: any[]): string;
export function getPrompt(key: 'toolEvaluation', userQuery: string, tool: any, rank: number, taskRelevance: number): string;
export function getPrompt(key: 'backgroundKnowledgeSelection', userQuery: string, mode: string, selectedToolNames: string[], availableConcepts: any[]): string;
export function getPrompt(key: 'finalRecommendations', isWorkflow: boolean, userQuery: string, selectedToolNames: string[]): string;
export function getPrompt(promptKey: keyof typeof AI_PROMPTS, ...args: any[]): string {
try {
const promptFunction = AI_PROMPTS[promptKey];
if (typeof promptFunction === 'function') {
return (promptFunction as (...args: any[]) => string)(...args);
} else {
console.error(`[PROMPTS] Invalid prompt key: ${promptKey}`);
return 'Error: Invalid prompt configuration';
}
} catch (error) {
console.error(`[PROMPTS] Error generating prompt ${promptKey}:`, error);
return 'Error: Failed to generate prompt';
}
}

View File

@ -113,64 +113,6 @@ tools:
accessType: download accessType: download
license: VSL license: VSL
knowledgebase: false knowledgebase: false
- name: TheHive 5
icon: 🐝
type: software
description: >-
Die zentrale Incident-Response-Plattform orchestriert komplexe
Sicherheitsvorfälle vom ersten Alert bis zum Abschlussbericht. Jeder Case
wird strukturiert durch Observables (IOCs), Tasks und Zeitleisten
abgebildet. Die Cortex-Integration automatisiert Analysen durch Dutzende
Analyzer - von VirusTotal-Checks bis Sandbox-Detonation.
MISP-Synchronisation reichert Cases mit Threat-Intelligence an. Das
ausgeklügelte Rollen- und Rechtesystem ermöglicht sichere Zusammenarbeit
zwischen SOC-Analysten, Forensikern und Management. Templates
standardisieren Response-Prozesse nach Incident-Typ. Die RESTful API
integriert nahtlos mit SIEM, SOAR und Ticketing-Systemen. Metrics und
KPIs messen die Team-Performance. Die Community Edition bleibt kostenlos
für kleinere Teams, während Gold/Platinum-Lizenzen Enterprise-Features
bieten.
domains:
- incident-response
- static-investigations
- malware-analysis
- network-forensics
- fraud-investigation
phases:
- data-collection
- examination
- analysis
- reporting
platforms:
- Web
related_software:
- MISP
- Cortex
- Elasticsearch
domain-agnostic-software:
- collaboration-general
skillLevel: intermediate
accessType: server-based
url: https://strangebee.com/thehive/
projectUrl: ''
license: Community Edition (Discontinued) / Commercial
knowledgebase: false
statusUrl: https://uptime.example.lab/api/badge/1/status
tags:
- web-interface
- case-management
- collaboration
- api
- workflow
- multi-user-support
- cortex-analyzer
- misp-integration
- playbooks
- metrics
- rbac
- template-driven
related_concepts:
- Digital Evidence Chain of Custody
- name: MISP - name: MISP
icon: 🌐 icon: 🌐
type: software type: software
@ -223,7 +165,6 @@ tools:
related_concepts: related_concepts:
- Hash Functions & Digital Signatures - Hash Functions & Digital Signatures
related_software: related_software:
- TheHive 5
- Cortex - Cortex
- OpenCTI - OpenCTI
- name: DFIR-IRIS - name: DFIR-IRIS
@ -260,7 +201,6 @@ tools:
platforms: platforms:
- Web - Web
related_software: related_software:
- TheHive 5
- MISP - MISP
- OpenCTI - OpenCTI
domain-agnostic-software: domain-agnostic-software:
@ -3427,6 +3367,244 @@ tools:
accessType: download accessType: download
license: "MPL\_/ AGPL" license: "MPL\_/ AGPL"
knowledgebase: false knowledgebase: false
- name: ShadowExplorer
icon: 🗂️
type: software
description: >-
Das schlanke Windows-Tool macht Volume-Shadow-Copy-Snapshots auch in Home-Editionen sichtbar und erlaubt das komfortable Durchstöbern sowie Wiederherstellen früherer Datei-Versionen. Damit lassen sich versehentlich gelöschte oder überschriebene Dateien in Sekunden zurückholen geeignet für schnelle Triage und klassische Datenträgerforensik.
domains:
- static-investigations
- incident-response
phases:
- examination
- analysis
platforms:
- Windows
related_software:
- OSFMount
- PhotoRec
domain-agnostic-software: null
skillLevel: novice
accessType: download
url: https://www.shadowexplorer.com/
license: Freeware
knowledgebase: false
tags:
- gui
- shadow-copy
- snapshot-browsing
- file-recovery
- previous-versions
- scenario:file_recovery
- point-in-time-restore
related_concepts:
- Digital Evidence Chain of Custody
- name: Sonic Visualiser
icon: 🎵
type: software
description: >-
Die Open-Source-Audio-Analyse-Suite wird in der Forensik eingesetzt,
um Wave- und Kompressionsformate bis auf Sample-Ebene zu untersuchen.
Spektrogramm-Visualisierung, Zeit-/Frequenz-Annotationen und
Transkriptions-Plugins (Vamp) helfen, Manipulationen wie
Bandpass-Filter, Time-Stretching oder Insert-Edits nachzuweisen.
FFT- und Mel-Spectral-Views decken versteckte Audio-Watermarks oder
Steganografie auf. Export-Funktionen in CSV/JSON erlauben die
Weiterverarbeitung in Python-Notebooks oder SIEM-Pipelines.
Ideal für Voice-Authentication-Checks, Deep-Fake-Erkennung
und Beweisaufbereitung vor Gericht.
skillLevel: intermediate
url: https://www.sonicvisualiser.org/
domains:
- static-investigations
- fraud-investigation
phases:
- examination
- analysis
- reporting
platforms:
- Windows
- Linux
- macOS
accessType: download
license: GPL-2.0
knowledgebase: false
tags:
- gui
- audio-forensics
- spectrogram
- plugin-support
- annotation
- csv-export
related_concepts: []
related_software:
- Audacity
- name: Dissect
icon: 🧩
type: software
description: >-
Fox-ITs Python-Framework abstrahiert Windows- und Linux-Speicherabbilder
in virtuelle Objekte (Prozesse, Dateien, Registry, Kernel-Strukturen),
ohne zuvor ein Profil definieren zu müssen. Modularer
Hypervisor-Layer erlaubt das Mounten und gleichzeitige Analysieren
mehrerer Memory-Dumps perfekt für großflächige Incident-Response.
Plugins dekodieren PTEs, handle tables, APC-Queues und liefern
YARA-kompatible Scans. Die Zero-Copy-Architektur beschleunigt Queries auf
Multi-GB-Images signifikant. Unterstützt Windows 11 24H2-Kernel sowie
Linux 6.x-schichten ab Juli 2025.
skillLevel: advanced
url: https://github.com/fox-it/dissect
domains:
- incident-response
- malware-analysis
- static-investigations
phases:
- examination
- analysis
platforms:
- Windows
- Linux
- macOS
accessType: download
license: Apache 2.0
knowledgebase: false
tags:
- command-line
- memory-analysis
- plugin-support
- python-library
- zero-copy
- profile-less
related_concepts:
- Regular Expressions (Regex)
related_software:
- Volatility 3
- Rekall
- name: Docker Explorer
icon: 🐳
type: software
description: >-
Googles Forensik-Toolkit zerlegt Offline-Docker-Volumes und
Overlay-Dateisysteme ohne laufenden Daemon. Es extrahiert
Container-Config, Image-Layer, ENV-Variablen, Mounted-Secrets
und schreibt Timeline-fähige Metadata-JSONs. Unterstützt btrfs,
overlay2 und zfs Storage-Driver sowie Docker Desktop (macOS/Windows).
Perfekt, um bösartige Images nach Supply-Chain-Attacken zu enttarnen
oder flüchtige Container nach einem Incident nachträglich zu analysieren.
skillLevel: intermediate
url: https://github.com/google/docker-explorer
domains:
- cloud-forensics
- incident-response
- static-investigations
phases:
- data-collection
- examination
- analysis
platforms:
- Linux
- macOS
- Windows
accessType: download
license: Apache 2.0
knowledgebase: false
tags:
- command-line
- container-forensics
- docker
- timeline
- json-export
- supply-chain
related_concepts: []
related_software:
- Velociraptor
- osquery
- name: Ghiro
icon: 🖼️
type: software
description: >-
Die Web-basierte Bild­forensik-Plattform automatisiert EXIF-Analyse,
Hash-Matching, Error-Level-Evaluation (ELA) und
Steganografie-Erkennung für große Dateibatches. Unterstützt
Gesichts- und NSFW-Detection sowie GPS-Reverse-Geocoding für
Bewegungsprofile. Reports sind gerichtsfest
versioniert, REST-API und Celery-Worker skalieren auf
Millionen Bilder ideal für CSAM-Ermittlungen oder Fake-News-Prüfung.
skillLevel: intermediate
url: https://getghiro.org/
domains:
- static-investigations
- fraud-investigation
- mobile-forensics
phases:
- examination
- analysis
- reporting
platforms:
- Web
- Linux
accessType: server-based
license: GPL-2.0
knowledgebase: false
tags:
- web-interface
- image-forensics
- exif-analysis
- steganography
- nsfw-detection
- batch-processing
related_concepts:
- Hash Functions & Digital Signatures
related_software:
- ExifTool
- PhotoRec
- name: Sherloq
icon: 🔍
type: software
description: >-
Das Python-GUI-Toolkit für visuelle Datei-Analyse kombiniert
klassische Reverse-Steganografie-Techniken (LSB, Palette-Tweaking,
DCT-Coefficient-Scanning) mit modernen CV-Algorithmen.
Heatmaps und Histogramm-Diffs zeigen Manipulations-Hotspots,
während eine „Carve-All-Layers“-Funktion versteckte Daten in PNG,
JPEG, BMP, GIF und Audio-Spectra aufspürt. Plugins für zsteg,
binwalk und exiftool erweitern die Pipeline.
Eine Must-have-Ergänzung zu Ghidra & friends, wenn
Malware Dateien als Dead-Drop nutzt.
skillLevel: intermediate
url: https://github.com/GuidoBartoli/sherloq
domains:
- malware-analysis
- static-investigations
phases:
- examination
- analysis
platforms:
- Windows
- Linux
- macOS
accessType: download
license: MIT
knowledgebase: false
tags:
- gui
- image-forensics
- steganography
- lsb-extraction
- histogram-analysis
- plugin-support
related_concepts:
- Regular Expressions (Regex)
related_software:
- Ghiro
- CyberChef
- name: Cortex - name: Cortex
type: software type: software
description: >- description: >-
@ -3797,7 +3975,7 @@ tools:
- name: KAPE - name: KAPE
type: software type: software
description: >- description: >-
Kroll Artifact Parser and Extractor revolutioniert Windows-Forensik durch Kroll Artifact Parser and Extractor versucht sich an Windows-Forensik durch
intelligente Ziel-basierte Sammlung. Statt Full-Disk-Images extrahiert intelligente Ziel-basierte Sammlung. Statt Full-Disk-Images extrahiert
KAPE gezielt kritische Artefakte: Registry-Hives, Event-Logs, Prefetch, KAPE gezielt kritische Artefakte: Registry-Hives, Event-Logs, Prefetch,
Browser- Daten, Scheduled-Tasks in Minuten statt Stunden. Die Target-Files Browser- Daten, Scheduled-Tasks in Minuten statt Stunden. Die Target-Files
@ -3805,12 +3983,10 @@ tools:
Besonders clever: Compound-Targets gruppieren zusammengehörige Artefakte Besonders clever: Compound-Targets gruppieren zusammengehörige Artefakte
(z.B. "Browser" sammelt Chrome+Firefox+Edge), die gKAPE-GUI macht es auch (z.B. "Browser" sammelt Chrome+Firefox+Edge), die gKAPE-GUI macht es auch
für Nicht-Techniker zugänglich. Batch-Mode verarbeitet mehrere Images für Nicht-Techniker zugänglich. Batch-Mode verarbeitet mehrere Images
parallel. Output direkt kompatibel zu Timeline-Tools wie Plaso. Die parallel. Output direkt kompatibel zu Timeline-Tools wie Plaso.
ständigen Community-Updates halten mit Windows-Entwicklungen Schritt.
VSS-Processing analysiert Shadow- Copies automatisch. Der VSS-Processing analysiert Shadow- Copies automatisch. Der
Remote-Collection-Mode sammelt über Netzwerk. Kostenlos aber Remote-Collection-Mode sammelt über Netzwerk. Kostenlos (mit Registrierung) aber
Enterprise-Support verfügbar. Der neue Standard für effiziente Enterprise-Support verfügbar.
Windows-Forensik-Triage.
skillLevel: intermediate skillLevel: intermediate
url: https://www.kroll.com/kape url: https://www.kroll.com/kape
icon: 🧰 icon: 🧰
@ -3825,7 +4001,7 @@ tools:
platforms: platforms:
- Windows - Windows
accessType: download accessType: download
license: Freeware license: Proprietary
knowledgebase: false knowledgebase: false
- name: Kibana - name: Kibana
type: software type: software

View File

@ -1,4 +1,5 @@
// src/pages/api/ai/enhance-input.ts - ENHANCED with forensics methodology // src/pages/api/ai/enhance-input.ts - Enhanced AI service compatibility
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
import { withAPIAuth } from '../../../utils/auth.js'; import { withAPIAuth } from '../../../utils/auth.js';
import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js'; import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js';
@ -15,12 +16,12 @@ function getEnv(key: string): string {
} }
const AI_ENDPOINT = getEnv('AI_ANALYZER_ENDPOINT'); const AI_ENDPOINT = getEnv('AI_ANALYZER_ENDPOINT');
const AI_API_KEY = getEnv('AI_ANALYZER_API_KEY'); const AI_ANALYZER_API_KEY = getEnv('AI_ANALYZER_API_KEY');
const AI_MODEL = getEnv('AI_ANALYZER_MODEL'); const AI_ANALYZER_MODEL = getEnv('AI_ANALYZER_MODEL');
const rateLimitStore = new Map<string, { count: number; resetTime: number }>(); const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const RATE_LIMIT_MAX = 5; const RATE_LIMIT_MAX = 5;
function sanitizeInput(input: string): string { function sanitizeInput(input: string): string {
return input return input
@ -93,6 +94,39 @@ ${input}
`.trim(); `.trim();
} }
async function callAIService(prompt: string): Promise<Response> {
const endpoint = AI_ENDPOINT;
const apiKey = AI_ANALYZER_API_KEY;
const model = AI_ANALYZER_MODEL;
let headers: Record<string, string> = {
'Content-Type': 'application/json'
};
if (apiKey) {
headers['Authorization'] = `Bearer ${apiKey}`;
console.log('[ENHANCE API] Using API key authentication');
} else {
console.log('[ENHANCE API] No API key - making request without authentication');
}
const requestBody = {
model,
messages: [{ role: 'user', content: prompt }],
max_tokens: 300,
temperature: 0.7,
top_p: 0.9,
frequency_penalty: 0.2,
presence_penalty: 0.1
};
return fetch(`${endpoint}/v1/chat/completions`, {
method: 'POST',
headers,
body: JSON.stringify(requestBody)
});
}
export const POST: APIRoute = async ({ request }) => { export const POST: APIRoute = async ({ request }) => {
try { try {
const authResult = await withAPIAuth(request, 'ai'); const authResult = await withAPIAuth(request, 'ai');
@ -121,31 +155,11 @@ export const POST: APIRoute = async ({ request }) => {
const systemPrompt = createEnhancementPrompt(sanitizedInput); const systemPrompt = createEnhancementPrompt(sanitizedInput);
const taskId = `enhance_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`; const taskId = `enhance_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`;
const aiResponse = await enqueueApiCall(() => const aiResponse = await enqueueApiCall(() => callAIService(systemPrompt), taskId);
fetch(`${AI_ENDPOINT}/v1/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${AI_API_KEY}`
},
body: JSON.stringify({
model: AI_MODEL,
messages: [
{
role: 'user',
content: systemPrompt
}
],
max_tokens: 300,
temperature: 0.7,
top_p: 0.9,
frequency_penalty: 0.2,
presence_penalty: 0.1
})
}), taskId);
if (!aiResponse.ok) { if (!aiResponse.ok) {
console.error('AI enhancement error:', await aiResponse.text()); const errorText = await aiResponse.text();
console.error('[ENHANCE API] AI enhancement error:', errorText, 'Status:', aiResponse.status);
return apiServerError.unavailable('Enhancement service unavailable'); return apiServerError.unavailable('Enhancement service unavailable');
} }
@ -188,13 +202,13 @@ export const POST: APIRoute = async ({ request }) => {
questions = []; questions = [];
} }
console.log(`[AI Enhancement] User: ${userId}, Forensics Questions: ${questions.length}, Input length: ${sanitizedInput.length}`); console.log(`[ENHANCE API] User: ${userId}, Forensics Questions: ${questions.length}, Input length: ${sanitizedInput.length}`);
return new Response(JSON.stringify({ return new Response(JSON.stringify({
success: true, success: true,
questions, questions,
taskId, taskId,
inputComplete: questions.length === 0 // Flag to indicate if input seems complete inputComplete: questions.length === 0
}), { }), {
status: 200, status: 200,
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }

View File

@ -21,6 +21,39 @@ const phases = data.phases;
Systematische digitale Forensik nach bewährter NIST SP 800-86 Methodik.<br> Systematische digitale Forensik nach bewährter NIST SP 800-86 Methodik.<br>
Wählen Sie Ihren Ansatz für die Werkzeugauswahl: Wählen Sie Ihren Ansatz für die Werkzeugauswahl:
</p> </p>
<div class="ai-hero-spotlight">
<div class="ai-spotlight-content">
<div class="ai-spotlight-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
<circle cx="12" cy="12" r="2"/>
</svg>
</div>
<div class="ai-spotlight-text">
<h3>Forensic AI-Beratung</h3>
<p>Analyse des Untersuchungsszenarios mit Empfehlungen zum Vorgehen</p>
</div>
</div>
<button id="ai-query-btn" class="btn btn-accent btn-lg ai-primary-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
</svg>
KI-Beratung starten
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ml-2">
<line x1="7" y1="17" x2="17" y2="7"/>
<polyline points="7,7 17,7 17,17"/>
</svg>
</button>
<div class="ai-features-mini">
<span class="badge badge-secondary">Workflow-Empfehlungen</span>
<span class="badge badge-secondary">Transparenz</span>
<span class="badge badge-secondary">Sofortige Analyse</span>
</div>
</div>
<div class="approach-selector"> <div class="approach-selector">
<div class="approach-card methodology" onclick="selectApproach('methodology')"> <div class="approach-card methodology" onclick="selectApproach('methodology')">
@ -63,7 +96,7 @@ const phases = data.phases;
Teilnehmer der Seminargruppe CC24-w1 (oder andere Berechtigte) können die gehostete Infrastruktur nutzen. Teilnehmer der Seminargruppe CC24-w1 (oder andere Berechtigte) können die gehostete Infrastruktur nutzen.
<a href="/about#support">Kontakt bei Problemen</a> <a href="/about#support">Kontakt bei Problemen</a>
</p> </p>
<div class="quick-actions"> <div class="quick-actions">
<a href="/about" class="btn btn-secondary"> <a href="/about" class="btn btn-secondary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@ -74,14 +107,6 @@ const phases = data.phases;
Infos, SSO & Zugang Infos, SSO & Zugang
</a> </a>
<button id="ai-query-btn" class="btn btn-accent">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
</svg>
KI-Beratung
</button>
<a href="/contribute" class="btn" style="background-color: var(--color-warning); color: white; border-color: var(--color-warning);" data-contribute-button="new"> <a href="/contribute" class="btn" style="background-color: var(--color-warning); color: white; border-color: var(--color-warning);" data-contribute-button="new">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/> <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
@ -182,7 +207,9 @@ const phases = data.phases;
document.querySelectorAll('.approach-card').forEach(card => { document.querySelectorAll('.approach-card').forEach(card => {
card.classList.remove('selected'); card.classList.remove('selected');
}); });
document.querySelector(`.approach-card.${approach}`).classList.add('selected');
const selectedCard = document.querySelector(`.approach-card.${approach}`);
if (selectedCard) selectedCard.classList.add('selected');
if (approach === 'methodology') { if (approach === 'methodology') {
const methodologySection = document.getElementById('methodology-section'); const methodologySection = document.getElementById('methodology-section');
@ -239,60 +266,49 @@ const phases = data.phases;
} }
if (aiQueryBtn) { if (aiQueryBtn) {
aiQueryBtn.addEventListener('click', async () => { aiQueryBtn.addEventListener('click', () => {
if (typeof window.requireClientAuth === 'function') { aiQueryBtn.classList.add('activated');
await window.requireClientAuth(() => switchToView('ai'), `${window.location.pathname}?view=ai`, 'ai'); setTimeout(() => aiQueryBtn.classList.remove('activated'), 400);
switchToView('ai');
window.dispatchEvent(new CustomEvent('viewChanged', { detail: 'ai' }));
if (window.scrollToElementById) {
window.scrollToElementById('ai-interface');
} else { } else {
console.warn('[AUTH] requireClientAuth not available'); aiInterface.scrollIntoView({ behavior: 'smooth', block: 'start' });
switchToView('ai');
} }
}); });
} }
function switchToView(view) { function switchToView(view) {
toolsGrid.style.display = 'none'; const toolsGrid = document.getElementById('tools-grid');
matrixContainer.style.display = 'none'; const matrixContainer = document.getElementById('matrix-container');
aiInterface.style.display = 'none'; const aiInterface = document.getElementById('ai-interface');
filtersSection.style.display = 'none'; const filtersSection = document.getElementById('filters-section');
const noResults = document.getElementById('no-results');
const viewToggles = document.querySelectorAll('.view-toggle');
viewToggles.forEach(btn => { if (toolsGrid) toolsGrid.style.display = 'none';
btn.classList.toggle('active', btn.getAttribute('data-view') === view); if (matrixContainer) matrixContainer.style.display = 'none';
}); if (aiInterface) aiInterface.style.display = 'none';
if (filtersSection) filtersSection.style.display = 'none';
switch (view) { if (noResults) noResults.style.display = 'none';
case 'ai':
aiInterface.style.display = 'block'; switch (view) {
filtersSection.style.display = 'block'; case 'grid':
hideFilterControls(); if (toolsGrid) toolsGrid.style.display = 'block';
if (window.restoreAIResults) { if (filtersSection) filtersSection.style.display = 'block';
window.restoreAIResults(); break;
} case 'matrix':
const aiInput = document.getElementById('ai-query-input'); if (matrixContainer) matrixContainer.style.display = 'block';
if (aiInput) { if (filtersSection) filtersSection.style.display = 'block';
setTimeout(() => aiInput.focus(), 100); break;
} case 'ai':
break; if (aiInterface) aiInterface.style.display = 'block';
case 'matrix': break;
matrixContainer.style.display = 'block';
filtersSection.style.display = 'block';
showFilterControls();
break;
default:
toolsGrid.style.display = 'block';
filtersSection.style.display = 'block';
showFilterControls();
break;
}
setTimeout(() => {
window.scrollToElementById('filters-section');
}, 150);
if (window.location.search) {
window.history.replaceState({}, '', window.location.pathname);
}
} }
}
function hideFilterControls() { function hideFilterControls() {
const filterSections = document.querySelectorAll('.filter-section'); const filterSections = document.querySelectorAll('.filter-section');

File diff suppressed because it is too large Load Diff

View File

@ -144,24 +144,11 @@ a:hover {
margin: 0 auto; margin: 0 auto;
} }
.container-wide {
max-width: 1400px;
margin: 0 auto;
}
/* Section Utilities */ /* Section Utilities */
.section { .section {
padding: 2rem 0; padding: 2rem 0;
} }
.section-sm {
padding: 1rem 0;
}
.section-lg {
padding: 3rem 0;
}
/* Flex Utilities */ /* Flex Utilities */
.flex { .flex {
display: flex; display: flex;
@ -209,10 +196,6 @@ a:hover {
align-items: center; align-items: center;
} }
.items-start {
align-items: flex-start;
}
.justify-center { .justify-center {
justify-content: center; justify-content: center;
} }
@ -231,11 +214,6 @@ a:hover {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
.grid-3 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
.grid-4 { .grid-4 {
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
@ -248,24 +226,11 @@ a:hover {
justify-content: center; justify-content: center;
} }
.grid-auto-fit-sm {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.grid-auto-fit-lg {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 1rem;
}
/* Gap Utilities */ /* Gap Utilities */
.gap-1 { gap: 0.25rem; } .gap-1 { gap: 0.25rem; }
.gap-2 { gap: 0.5rem; } .gap-2 { gap: 0.5rem; }
.gap-3 { gap: 0.75rem; } .gap-3 { gap: 0.75rem; }
.gap-4 { gap: 1rem; } .gap-4 { gap: 1rem; }
.gap-6 { gap: 1.5rem; }
.gap-8 { gap: 2rem; } .gap-8 { gap: 2rem; }
/* =================================================================== /* ===================================================================
@ -273,29 +238,16 @@ a:hover {
================================================================= */ ================================================================= */
/* Margin Utilities */ /* Margin Utilities */
.m-0 { margin: 0; }
.m-1 { margin: 0.25rem; }
.m-2 { margin: 0.5rem; }
.m-3 { margin: 0.75rem; }
.m-4 { margin: 1rem; }
.m-6 { margin: 1.5rem; }
.m-8 { margin: 2rem; }
.mx-auto { margin-left: auto; margin-right: auto; } .mx-auto { margin-left: auto; margin-right: auto; }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 0.25rem; } .mt-1 { margin-top: 0.25rem; }
.mt-2 { margin-top: 0.5rem; } .mt-2 { margin-top: 0.5rem; }
.mt-3 { margin-top: 0.75rem; } .mt-3 { margin-top: 0.75rem; }
.mt-4 { margin-top: 1rem; } .mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; }
.mt-8 { margin-top: 2rem; }
.mt-auto { margin-top: auto; } .mt-auto { margin-top: auto; }
.mr-1 { margin-right: 0.25rem; }
.mr-2 { margin-right: 0.5rem; } .mr-2 { margin-right: 0.5rem; }
.mr-3 { margin-right: 0.75rem; } .mr-3 { margin-right: 0.75rem; }
.mr-4 { margin-right: 1rem; }
.mb-0 { margin-bottom: 0; } .mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: 0.25rem; } .mb-1 { margin-bottom: 0.25rem; }
@ -305,34 +257,22 @@ a:hover {
.mb-6 { margin-bottom: 1.5rem; } .mb-6 { margin-bottom: 1.5rem; }
.mb-8 { margin-bottom: 2rem; } .mb-8 { margin-bottom: 2rem; }
.ml-1 { margin-left: 0.25rem; }
.ml-2 { margin-left: 0.5rem; } .ml-2 { margin-left: 0.5rem; }
.ml-3 { margin-left: 0.75rem; }
.ml-4 { margin-left: 1rem; }
/* Padding Utilities */ /* Padding Utilities */
.p-0 { padding: 0; }
.p-1 { padding: 0.25rem; }
.p-2 { padding: 0.5rem; } .p-2 { padding: 0.5rem; }
.p-3 { padding: 0.75rem; } .p-3 { padding: 0.75rem; }
.p-4 { padding: 1rem; } .p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; } .p-6 { padding: 1.5rem; }
.p-8 { padding: 2rem; } .p-8 { padding: 2rem; }
.px-1 { padding-left: 0.25rem; padding-right: 0.25rem; }
.px-2 { padding-left: 0.5rem; padding-right: 0.5rem; }
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; } .px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
.px-4 { padding-left: 1rem; padding-right: 1rem; } .px-4 { padding-left: 1rem; padding-right: 1rem; }
.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; } .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
.py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
.py-8 { padding-top: 2rem; padding-bottom: 2rem; } .py-8 { padding-top: 2rem; padding-bottom: 2rem; }
.pt-3 { padding-top: 0.75rem; } .pt-3 { padding-top: 0.75rem; }
.pb-2 { padding-bottom: 0.5rem; }
/* =================================================================== /* ===================================================================
6. TARGETED UTILITY CLASSES (FOR INLINE STYLE CONSOLIDATION) 6. TARGETED UTILITY CLASSES (FOR INLINE STYLE CONSOLIDATION)
@ -342,8 +282,6 @@ a:hover {
.section-padding { padding: 2rem 0; } .section-padding { padding: 2rem 0; }
.content-center { text-align: center; margin-bottom: 1rem; } .content-center { text-align: center; margin-bottom: 1rem; }
.content-center-lg { text-align: center; margin-bottom: 2rem; } .content-center-lg { text-align: center; margin-bottom: 2rem; }
.content-narrow { max-width: 900px; margin: 0 auto; }
.content-wide { max-width: 1200px; margin: 0 auto; }
/* Card Info Variants (for inline background/padding combinations) */ /* Card Info Variants (for inline background/padding combinations) */
.card-info-sm { .card-info-sm {
@ -352,20 +290,6 @@ a:hover {
border-radius: 0.5rem; border-radius: 0.5rem;
} }
.card-info-md {
background-color: var(--color-bg-secondary);
padding: 1.5rem;
border-radius: 0.75rem;
border: 1px solid var(--color-border);
}
.card-info-lg {
background-color: var(--color-bg-secondary);
padding: 2rem;
border-radius: 1rem;
border: 1px solid var(--color-border);
}
/* Header/Title Combinations */ /* Header/Title Combinations */
.header-center { .header-center {
text-align: center; text-align: center;
@ -384,42 +308,6 @@ a:hover {
color: white; color: white;
} }
/* Metadata/Info Text Combinations */
.info-text {
font-size: 0.875rem;
color: var(--color-text-secondary);
line-height: 1.6;
}
.info-text-center {
font-size: 0.875rem;
color: var(--color-text-secondary);
text-align: center;
line-height: 1.6;
}
/* Grid Auto-fit Variants for common inline grid patterns */
.grid-auto-300 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.grid-auto-400 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 1rem;
}
.grid-auto-500 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 1.5rem;
}
/* Add to global.css */
.pros-cons-section { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.tool-metadata { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.75rem; }
.grid-cols-2 { grid-template-columns: 1fr 1fr; } .grid-cols-2 { grid-template-columns: 1fr 1fr; }
.flex-shrink-1 { flex-shrink: 1; } .flex-shrink-1 { flex-shrink: 1; }
@ -429,7 +317,6 @@ a:hover {
/* Text Utilities */ /* Text Utilities */
.text-center { text-align: center; } .text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; } .text-right { text-align: right; }
.text-xs { font-size: 0.75rem; } .text-xs { font-size: 0.75rem; }
@ -439,10 +326,8 @@ a:hover {
.text-xl { font-size: 1.25rem; } .text-xl { font-size: 1.25rem; }
.text-2xl { font-size: 1.5rem; } .text-2xl { font-size: 1.5rem; }
.font-normal { font-weight: 400; }
.font-medium { font-weight: 500; } .font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; } .font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.leading-tight { line-height: 1.25; } .leading-tight { line-height: 1.25; }
.leading-normal { line-height: 1.5; } .leading-normal { line-height: 1.5; }
@ -454,12 +339,10 @@ a:hover {
.text-secondary { color: var(--color-text-secondary); } .text-secondary { color: var(--color-text-secondary); }
.text-accent { color: var(--color-accent); } .text-accent { color: var(--color-accent); }
.text-warning { color: var(--color-warning); } .text-warning { color: var(--color-warning); }
.text-error { color: var(--color-error); }
/* Display Utilities */ /* Display Utilities */
.block { display: block; } .block { display: block; }
.inline { display: inline; } .inline { display: inline; }
.inline-block { display: inline-block; }
.hidden { display: none; } .hidden { display: none; }
/* Size Utilities */ /* Size Utilities */
@ -475,18 +358,13 @@ a:hover {
.fixed { position: fixed; } .fixed { position: fixed; }
.sticky { position: sticky; } .sticky { position: sticky; }
.top-0 { top: 0; }
.bottom-8 { bottom: 2rem; } .bottom-8 { bottom: 2rem; }
.right-8 { right: 2rem; } .right-8 { right: 2rem; }
.left-0 { left: 0; }
.z-10 { z-index: 10; }
.z-50 { z-index: 50; } .z-50 { z-index: 50; }
.z-100 { z-index: 100; }
/* Overflow Utilities */ /* Overflow Utilities */
.overflow-hidden { overflow: hidden; } .overflow-hidden { overflow: hidden; }
.overflow-auto { overflow: auto; }
/* Border Utilities */ /* Border Utilities */
.border { border: 1px solid var(--color-border); } .border { border: 1px solid var(--color-border); }
@ -494,19 +372,15 @@ a:hover {
.border-l-4 { border-left: 4px solid; } .border-l-4 { border-left: 4px solid; }
.rounded { border-radius: 0.25rem; } .rounded { border-radius: 0.25rem; }
.rounded-md { border-radius: 0.375rem; }
.rounded-lg { border-radius: 0.5rem; } .rounded-lg { border-radius: 0.5rem; }
.rounded-xl { border-radius: 0.75rem; } .rounded-xl { border-radius: 0.75rem; }
.rounded-2xl { border-radius: 1rem; }
/* Background Utilities */ /* Background Utilities */
.bg-secondary { background-color: var(--color-bg-secondary); } .bg-secondary { background-color: var(--color-bg-secondary); }
.bg-tertiary { background-color: var(--color-bg-tertiary); }
/* Cursor Utilities */ /* Cursor Utilities */
.cursor-pointer { cursor: pointer; } .cursor-pointer { cursor: pointer; }
.h-12 { height: 3rem; }
.align-middle { vertical-align: middle; } .align-middle { vertical-align: middle; }
@ -646,11 +520,6 @@ nav {
} }
/* Button Sizes */ /* Button Sizes */
.btn-xs {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
.btn-sm { .btn-sm {
padding: 0.375rem 0.75rem; padding: 0.375rem 0.75rem;
font-size: 0.8125rem; font-size: 0.8125rem;
@ -808,14 +677,6 @@ input[type="checkbox"] {
} }
/* Card Variants */ /* Card Variants */
.card-sm {
padding: 1rem;
}
.card-lg {
padding: 2rem;
}
.card-info { .card-info {
background-color: var(--color-bg-secondary); background-color: var(--color-bg-secondary);
border-left: 4px solid var(--color-primary); border-left: 4px solid var(--color-primary);
@ -826,15 +687,6 @@ input[type="checkbox"] {
color: white; color: white;
} }
.card-gradient {
background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-tertiary) 100%);
}
.card-hero {
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
color: white;
}
/* Card Type Modifiers */ /* Card Type Modifiers */
.card-hosted { .card-hosted {
background-color: var(--color-hosted-bg); background-color: var(--color-hosted-bg);
@ -878,7 +730,6 @@ input[type="checkbox"] {
.badge-primary { background-color: var(--color-primary); color: white; } .badge-primary { background-color: var(--color-primary); color: white; }
.badge-secondary { background-color: var(--color-bg-secondary); color: var(--color-text); border: 1px solid var(--color-border); } .badge-secondary { background-color: var(--color-bg-secondary); color: var(--color-text); border: 1px solid var(--color-border); }
.badge-success { background-color: var(--color-accent); color: white; } .badge-success { background-color: var(--color-accent); color: white; }
.badge-accent { background-color: var(--color-accent); color: white; }
.badge-warning { background-color: var(--color-warning); color: white; } .badge-warning { background-color: var(--color-warning); color: white; }
.badge-error { background-color: var(--color-error); color: white; } .badge-error { background-color: var(--color-error); color: white; }
@ -1750,7 +1601,93 @@ input[type="checkbox"] {
border-color: var(--color-accent) !important; border-color: var(--color-accent) !important;
} }
.ai-hero-spotlight {
background: linear-gradient(135deg, var(--color-bg) 0%, var(--color-bg-secondary) 100%);
border: 2px solid var(--color-accent);
border-radius: 1rem;
padding: 2rem;
margin: 1.5rem auto;
text-align: center;
position: relative;
overflow: hidden;
max-width: 600px;
width: 100%;
}
.ai-hero-spotlight::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--color-accent) 0%, var(--color-primary) 100%);
}
.ai-spotlight-content {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1.5rem;
}
.ai-spotlight-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-primary) 100%);
border-radius: 0.75rem;
display: flex;
align-items: center;
justify-content: center;
color: white;
box-shadow: var(--shadow-md);
flex-shrink: 0;
}
.ai-spotlight-text h3 {
margin: 0 0 0.25rem 0;
font-size: 1.25rem;
color: var(--color-text);
}
.ai-spotlight-text p {
margin: 0;
color: var(--color-text-secondary);
font-size: 0.875rem;
}
.ai-primary-btn {
padding: 0.875rem 2rem;
margin: 0 0 1rem 0;
position: relative;
overflow: hidden;
}
.ai-primary-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg), 0 8px 20px rgba(5, 150, 105, 0.3);
}
.ai-primary-btn svg:last-child {
transition: var(--transition-fast);
}
.ai-primary-btn:hover svg:last-child {
transform: translate(2px, -2px);
}
/* Mini features display */
.ai-features-mini {
display: flex;
justify-content: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.ai-features-mini .badge {
font-size: 0.75rem;
}
/* =================================================================== /* ===================================================================
16. AI INTERFACE (CONSOLIDATED) 16. AI INTERFACE (CONSOLIDATED)
@ -1985,21 +1922,7 @@ input[type="checkbox"] {
} }
} }
/* Animation for micro-task progress */
@keyframes micro-task-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.micro-step.active {
animation: micro-task-pulse 2s ease-in-out infinite;
}
@keyframes micro-task-complete {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.micro-step.completed { .micro-step.completed {
animation: micro-task-complete 0.6s ease-out; animation: micro-task-complete 0.6s ease-out;
@ -2015,6 +1938,7 @@ input[type="checkbox"] {
gap: 1rem; gap: 1rem;
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
margin-top: 1rem;
} }
.phase-header { .phase-header {
@ -2145,6 +2069,10 @@ input[type="checkbox"] {
border-color: var(--color-method); border-color: var(--color-method);
} }
.tool-recommendation:hover {
box-shadow: 0 0 8px rgba(0,0,0,0.1);
}
.tool-rec-header { .tool-rec-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -2183,14 +2111,6 @@ input[type="checkbox"] {
border-left: 3px solid var(--color-primary); border-left: 3px solid var(--color-primary);
} }
.tool-rec-metadata {
display: flex;
flex-direction: column;
gap: 0.375rem;
font-size: 0.75rem;
color: var(--color-text-secondary);
}
/* =================================================================== /* ===================================================================
18. APPROACH SELECTION (CONSOLIDATED) 18. APPROACH SELECTION (CONSOLIDATED)
================================================================= */ ================================================================= */
@ -2566,11 +2486,36 @@ footer {
} }
} }
/* Animation for micro-task progress */
@keyframes micro-task-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.micro-step.active {
animation: micro-task-pulse 2s ease-in-out infinite;
}
@keyframes micro-task-complete {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
@keyframes ai-spotlight-pulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.ai-primary-btn.activated {
animation: ai-spotlight-pulse 0.4s ease-out;
}
/* =================================================================== /* ===================================================================
21. SMART PROMPTING INTERFACE (MISSING STYLES ADDED BACK) 21. SMART PROMPTING INTERFACE (MISSING STYLES ADDED BACK)
================================================================= */ ================================================================= */
/* Smart Prompting Container */
.smart-prompting-container { .smart-prompting-container {
height: 100%; height: 100%;
animation: smartPromptSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1); animation: smartPromptSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
@ -2852,12 +2797,6 @@ footer {
color: var(--color-primary); color: var(--color-primary);
} }
.stat-unit {
font-size: 0.75rem;
color: var(--color-text-secondary);
margin-left: 0.25rem;
}
.queue-progress-container { .queue-progress-container {
position: relative; position: relative;
} }
@ -3273,7 +3212,7 @@ footer {
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 0.5rem; gap: 0.5rem;
} }
.phase-chip { .phase-chip {
padding: 0.625rem 0.75rem; padding: 0.625rem 0.75rem;
font-size: 0.8125rem; font-size: 0.8125rem;
@ -3353,6 +3292,30 @@ footer {
width: 100%; width: 100%;
min-height: 100px; min-height: 100px;
} }
.ai-spotlight-content {
flex-direction: column;
gap: 0.75rem;
margin-bottom: 1rem;
}
.ai-spotlight-icon {
width: 40px;
height: 40px;
}
.ai-spotlight-text h3 {
font-size: 1.125rem;
}
.ai-primary-btn {
width: 100%;
max-width: 280px;
}
.ai-features-mini {
gap: 0.375rem;
}
.approach-hero { .approach-hero {
padding: 2rem 1rem; padding: 2rem 1rem;
@ -3406,7 +3369,6 @@ footer {
} }
.grid-2, .grid-2,
.grid-3,
.grid-4 { .grid-4 {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@ -3436,10 +3398,6 @@ footer {
.advanced-filters-compact { .advanced-filters-compact {
gap: 0.5rem; gap: 0.5rem;
} }
.filter-row {
grid-template-columns: 1fr;
}
.filter-toggles-compact { .filter-toggles-compact {
flex-direction: column; flex-direction: column;
@ -3625,6 +3583,14 @@ footer {
flex: 1 1 100%; flex: 1 1 100%;
justify-content: flex-end; justify-content: flex-end;
} }
.ai-hero-spotlight {
padding: 1.5rem 1rem;
}
.ai-features-mini {
flex-direction: column;
align-items: center;
}
} }
@ -3632,9 +3598,6 @@ footer {
MIGRATION UTILITIES - Additional classes for inline style migration MIGRATION UTILITIES - Additional classes for inline style migration
================================================================= */ ================================================================= */
/* Height utilities */
.h-12 { height: 3rem; }
/* Alignment utilities */ /* Alignment utilities */
.align-middle { vertical-align: middle; } .align-middle { vertical-align: middle; }
@ -3659,17 +3622,8 @@ footer {
color: white; color: white;
} }
/* Enhanced card info variants (if not already present) */
.card-info-xl {
background-color: var(--color-bg-secondary);
padding: 2.5rem;
border-radius: 1.25rem;
border: 1px solid var(--color-border);
}
/* Ensure we have all text size variants */ /* Ensure we have all text size variants */
.text-2xl { font-size: 1.5rem; } .text-2xl { font-size: 1.5rem; }
.text-3xl { font-size: 1.875rem; }
/* Additional spacing utilities that might be missing */ /* Additional spacing utilities that might be missing */
.py-8 { padding-top: 2rem; padding-bottom: 2rem; } .py-8 { padding-top: 2rem; padding-bottom: 2rem; }
@ -3681,7 +3635,6 @@ footer {
/* Additional rounded variants */ /* Additional rounded variants */
.rounded-xl { border-radius: 0.75rem; } .rounded-xl { border-radius: 0.75rem; }
.rounded-2xl { border-radius: 1rem; }
/* =================================================================== /* ===================================================================
23. MARKDOWN CONTENT 23. MARKDOWN CONTENT
@ -3769,4 +3722,50 @@ footer {
border: none; border: none;
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
margin: 2rem 0; margin: 2rem 0;
}
/* ===================================================================
26. ENHANCED AUDIT TRAIL STYLES
================================================================= */
.audit-process-flow {
position: relative;
}
.phase-group {
position: relative;
}
.phase-group:not(:last-child)::after {
content: '';
position: absolute;
left: 13px;
bottom: -8px;
width: 2px;
height: 16px;
background: linear-gradient(to bottom, var(--color-border) 0%, transparent 100%);
}
.toggle-icon {
transition: transform 0.2s ease;
}
/* Hover effects for audit entries */
.audit-trail-details .hover\\:bg-secondary:hover {
background-color: var(--color-bg-secondary);
}
/* Responsive adjustments for audit trail */
@media (width <= 768px) {
.audit-process-flow .grid-cols-3 {
grid-template-columns: 1fr;
gap: 1rem;
}
.phase-group .flex {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -77,33 +77,8 @@ interface EnhancedCompressedToolsData {
domains: any[]; domains: any[];
phases: any[]; phases: any[];
'domain-agnostic-software': any[]; 'domain-agnostic-software': any[];
scenarios?: any[]; // Optional for AI processing scenarios?: any[];
skill_levels: any; skill_levels: any;
// Enhanced context for micro-tasks
domain_relationships: DomainRelationship[];
phase_dependencies: PhaseDependency[];
tool_compatibility_matrix: CompatibilityMatrix[];
}
interface DomainRelationship {
domain_id: string;
tool_count: number;
common_tags: string[];
skill_distribution: Record<string, number>;
}
interface PhaseDependency {
phase_id: string;
order: number;
depends_on: string | null;
enables: string | null;
is_parallel_capable: boolean;
typical_duration: string;
}
interface CompatibilityMatrix {
type: string;
groups: Record<string, string[]>;
} }
let cachedData: ToolsData | null = null; let cachedData: ToolsData | null = null;
@ -146,104 +121,6 @@ function generateDataVersion(data: any): string {
return Math.abs(hash).toString(36); return Math.abs(hash).toString(36);
} }
// Enhanced: Generate domain relationships for better AI understanding
function generateDomainRelationships(domains: any[], tools: any[]): DomainRelationship[] {
const relationships: DomainRelationship[] = [];
for (const domain of domains) {
const domainTools = tools.filter(tool =>
tool.domains && tool.domains.includes(domain.id)
);
const commonTags = domainTools
.flatMap(tool => tool.tags || [])
.reduce((acc: any, tag: string) => {
acc[tag] = (acc[tag] || 0) + 1;
return acc;
}, {});
const topTags = Object.entries(commonTags)
.sort(([,a], [,b]) => (b as number) - (a as number))
.slice(0, 5)
.map(([tag]) => tag);
relationships.push({
domain_id: domain.id,
tool_count: domainTools.length,
common_tags: topTags,
skill_distribution: domainTools.reduce((acc: any, tool: any) => {
acc[tool.skillLevel] = (acc[tool.skillLevel] || 0) + 1;
return acc;
}, {})
});
}
return relationships;
}
// Enhanced: Generate phase dependencies
function generatePhaseDependencies(phases: any[]): PhaseDependency[] {
const dependencies: PhaseDependency[] = [];
for (let i = 0; i < phases.length; i++) {
const phase = phases[i];
const nextPhase = phases[i + 1];
const prevPhase = phases[i - 1];
dependencies.push({
phase_id: phase.id,
order: i + 1,
depends_on: prevPhase?.id || null,
enables: nextPhase?.id || null,
is_parallel_capable: ['examination', 'analysis'].includes(phase.id), // Some phases can run in parallel
typical_duration: phase.id === 'data-collection' ? 'hours-days' :
phase.id === 'examination' ? 'hours-weeks' :
phase.id === 'analysis' ? 'days-weeks' :
'hours-days'
});
}
return dependencies;
}
// Enhanced: Generate tool compatibility matrix
function generateToolCompatibilityMatrix(tools: any[]): CompatibilityMatrix[] {
const matrix: CompatibilityMatrix[] = [];
// Group tools by common characteristics
const platformGroups = tools.reduce((acc: any, tool: any) => {
if (tool.platforms) {
tool.platforms.forEach((platform: string) => {
if (!acc[platform]) acc[platform] = [];
acc[platform].push(tool.name);
});
}
return acc;
}, {});
const phaseGroups = tools.reduce((acc: any, tool: any) => {
if (tool.phases) {
tool.phases.forEach((phase: string) => {
if (!acc[phase]) acc[phase] = [];
acc[phase].push(tool.name);
});
}
return acc;
}, {});
matrix.push({
type: 'platform_compatibility',
groups: platformGroups
});
matrix.push({
type: 'phase_synergy',
groups: phaseGroups
});
return matrix;
}
async function loadRawData(): Promise<ToolsData> { async function loadRawData(): Promise<ToolsData> {
if (!cachedData) { if (!cachedData) {
const yamlPath = path.join(process.cwd(), 'src/data/tools.yaml'); const yamlPath = path.join(process.cwd(), 'src/data/tools.yaml');
@ -253,7 +130,6 @@ async function loadRawData(): Promise<ToolsData> {
try { try {
cachedData = ToolsDataSchema.parse(rawData); cachedData = ToolsDataSchema.parse(rawData);
// Enhanced: Add default skill level descriptions if not provided
if (!cachedData.skill_levels || Object.keys(cachedData.skill_levels).length === 0) { if (!cachedData.skill_levels || Object.keys(cachedData.skill_levels).length === 0) {
cachedData.skill_levels = { cachedData.skill_levels = {
novice: "Minimal technical background required, guided interfaces", novice: "Minimal technical background required, guided interfaces",
@ -301,21 +177,18 @@ export async function getCompressedToolsDataForAI(): Promise<EnhancedCompressedT
if (!cachedCompressedData) { if (!cachedCompressedData) {
const data = await getToolsData(); const data = await getToolsData();
// Enhanced: More detailed tool information for micro-tasks
const compressedTools = data.tools const compressedTools = data.tools
.filter(tool => tool.type !== 'concept') .filter(tool => tool.type !== 'concept')
.map(tool => { .map(tool => {
const { projectUrl, statusUrl, ...compressedTool } = tool; const { projectUrl, statusUrl, ...compressedTool } = tool;
return { return {
...compressedTool, ...compressedTool,
// Enhanced: Add computed fields for AI
is_hosted: projectUrl !== undefined && projectUrl !== null && projectUrl !== "" && projectUrl.trim() !== "", is_hosted: projectUrl !== undefined && projectUrl !== null && projectUrl !== "" && projectUrl.trim() !== "",
is_open_source: tool.license && tool.license !== 'Proprietary', is_open_source: tool.license && tool.license !== 'Proprietary',
complexity_score: tool.skillLevel === 'expert' ? 5 : complexity_score: tool.skillLevel === 'expert' ? 5 :
tool.skillLevel === 'advanced' ? 4 : tool.skillLevel === 'advanced' ? 4 :
tool.skillLevel === 'intermediate' ? 3 : tool.skillLevel === 'intermediate' ? 3 :
tool.skillLevel === 'beginner' ? 2 : 1, tool.skillLevel === 'beginner' ? 2 : 1,
// Enhanced: Phase-specific suitability hints
phase_suitability: tool.phases?.map(phase => ({ phase_suitability: tool.phases?.map(phase => ({
phase, phase,
primary_use: tool.tags?.find(tag => tag.includes(phase)) ? 'primary' : 'secondary' primary_use: tool.tags?.find(tag => tag.includes(phase)) ? 'primary' : 'secondary'
@ -329,7 +202,6 @@ export async function getCompressedToolsDataForAI(): Promise<EnhancedCompressedT
const { projectUrl, statusUrl, platforms, accessType, license, ...compressedConcept } = concept; const { projectUrl, statusUrl, platforms, accessType, license, ...compressedConcept } = concept;
return { return {
...compressedConcept, ...compressedConcept,
// Enhanced: Learning difficulty indicator
learning_complexity: concept.skillLevel === 'expert' ? 'very_high' : learning_complexity: concept.skillLevel === 'expert' ? 'very_high' :
concept.skillLevel === 'advanced' ? 'high' : concept.skillLevel === 'advanced' ? 'high' :
concept.skillLevel === 'intermediate' ? 'medium' : concept.skillLevel === 'intermediate' ? 'medium' :
@ -337,27 +209,16 @@ export async function getCompressedToolsDataForAI(): Promise<EnhancedCompressedT
}; };
}); });
// Enhanced: Add rich context data
const domainRelationships = generateDomainRelationships(data.domains, compressedTools);
const phaseDependencies = generatePhaseDependencies(data.phases);
const toolCompatibilityMatrix = generateToolCompatibilityMatrix(compressedTools);
cachedCompressedData = { cachedCompressedData = {
tools: compressedTools, tools: compressedTools,
concepts: concepts, concepts: concepts,
domains: data.domains, domains: data.domains,
phases: data.phases, phases: data.phases,
'domain-agnostic-software': data['domain-agnostic-software'], 'domain-agnostic-software': data['domain-agnostic-software'],
scenarios: data.scenarios, // Include scenarios for context scenarios: data.scenarios,
skill_levels: data.skill_levels || {}, skill_levels: data.skill_levels || {},
// Enhanced context for micro-tasks
domain_relationships: domainRelationships,
phase_dependencies: phaseDependencies,
tool_compatibility_matrix: toolCompatibilityMatrix
}; };
console.log(`[DATA SERVICE] Generated enhanced compressed data: ${compressedTools.length} tools, ${concepts.length} concepts`);
console.log(`[DATA SERVICE] Added context: ${domainRelationships.length} domain relationships, ${phaseDependencies.length} phase dependencies`);
} }
return cachedCompressedData; return cachedCompressedData;

View File

@ -24,9 +24,14 @@ interface EmbeddingsDatabase {
embeddings: EmbeddingData[]; embeddings: EmbeddingData[];
} }
interface SimilarityResult extends EmbeddingData {
similarity: number;
}
class EmbeddingsService { class EmbeddingsService {
private embeddings: EmbeddingData[] = []; private embeddings: EmbeddingData[] = [];
private isInitialized = false; private isInitialized = false;
private initializationPromise: Promise<void> | null = null;
private readonly embeddingsPath = path.join(process.cwd(), 'data', 'embeddings.json'); private readonly embeddingsPath = path.join(process.cwd(), 'data', 'embeddings.json');
private readonly batchSize: number; private readonly batchSize: number;
private readonly batchDelay: number; private readonly batchDelay: number;
@ -39,6 +44,19 @@ class EmbeddingsService {
} }
async initialize(): Promise<void> { async initialize(): Promise<void> {
if (this.initializationPromise) {
return this.initializationPromise;
}
if (this.isInitialized) {
return Promise.resolve();
}
this.initializationPromise = this.performInitialization();
return this.initializationPromise;
}
private async performInitialization(): Promise<void> {
if (!this.enabled) { if (!this.enabled) {
console.log('[EMBEDDINGS] Embeddings disabled, skipping initialization'); console.log('[EMBEDDINGS] Embeddings disabled, skipping initialization');
return; return;
@ -47,13 +65,11 @@ class EmbeddingsService {
try { try {
console.log('[EMBEDDINGS] Initializing embeddings system...'); console.log('[EMBEDDINGS] Initializing embeddings system...');
// Create data directory if it doesn't exist
await fs.mkdir(path.dirname(this.embeddingsPath), { recursive: true }); await fs.mkdir(path.dirname(this.embeddingsPath), { recursive: true });
const toolsData = await getCompressedToolsDataForAI(); const toolsData = await getCompressedToolsDataForAI();
const currentDataHash = this.hashData(toolsData); const currentDataHash = this.hashData(toolsData);
// Try to load existing embeddings
const existingEmbeddings = await this.loadEmbeddings(); const existingEmbeddings = await this.loadEmbeddings();
if (existingEmbeddings && existingEmbeddings.version === currentDataHash) { if (existingEmbeddings && existingEmbeddings.version === currentDataHash) {
@ -70,9 +86,29 @@ class EmbeddingsService {
} catch (error) { } catch (error) {
console.error('[EMBEDDINGS] Failed to initialize:', error); console.error('[EMBEDDINGS] Failed to initialize:', error);
this.isInitialized = false; this.isInitialized = false;
throw error;
} finally {
this.initializationPromise = null;
} }
} }
async waitForInitialization(): Promise<void> {
if (!this.enabled) {
return Promise.resolve();
}
if (this.isInitialized) {
return Promise.resolve();
}
if (this.initializationPromise) {
await this.initializationPromise;
return;
}
return this.initialize();
}
private hashData(data: any): string { private hashData(data: any): string {
return Buffer.from(JSON.stringify(data)).toString('base64').slice(0, 32); return Buffer.from(JSON.stringify(data)).toString('base64').slice(0, 32);
} }
@ -115,16 +151,21 @@ class EmbeddingsService {
const apiKey = process.env.AI_EMBEDDINGS_API_KEY; const apiKey = process.env.AI_EMBEDDINGS_API_KEY;
const model = process.env.AI_EMBEDDINGS_MODEL; const model = process.env.AI_EMBEDDINGS_MODEL;
if (!endpoint || !apiKey || !model) { if (!endpoint || !model) {
throw new Error('Missing embeddings API configuration'); throw new Error('Missing embeddings API configuration');
} }
const headers: Record<string, string> = {
'Content-Type': 'application/json'
};
if (apiKey) {
headers['Authorization'] = `Bearer ${apiKey}`;
}
const response = await fetch(endpoint, { const response = await fetch(endpoint, {
method: 'POST', method: 'POST',
headers: { headers,
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({ body: JSON.stringify({
model, model,
input: contents input: contents
@ -137,7 +178,16 @@ class EmbeddingsService {
} }
const data = await response.json(); const data = await response.json();
return data.data.map((item: any) => item.embedding);
if (Array.isArray(data.embeddings)) {
return data.embeddings;
}
if (Array.isArray(data.data)) {
return data.data.map((item: any) => item.embedding);
}
throw new Error('Unknown embeddings API response format');
} }
private async generateEmbeddings(toolsData: any, version: string): Promise<void> { private async generateEmbeddings(toolsData: any, version: string): Promise<void> {
@ -149,7 +199,6 @@ class EmbeddingsService {
const contents = allItems.map(item => this.createContentString(item)); const contents = allItems.map(item => this.createContentString(item));
this.embeddings = []; this.embeddings = [];
// Process in batches to respect rate limits
for (let i = 0; i < contents.length; i += this.batchSize) { for (let i = 0; i < contents.length; i += this.batchSize) {
const batch = contents.slice(i, i + this.batchSize); const batch = contents.slice(i, i + this.batchSize);
const batchItems = allItems.slice(i, i + this.batchSize); const batchItems = allItems.slice(i, i + this.batchSize);
@ -177,7 +226,6 @@ class EmbeddingsService {
}); });
}); });
// Rate limiting delay between batches
if (i + this.batchSize < contents.length) { if (i + this.batchSize < contents.length) {
await new Promise(resolve => setTimeout(resolve, this.batchDelay)); await new Promise(resolve => setTimeout(resolve, this.batchDelay));
} }
@ -192,7 +240,6 @@ class EmbeddingsService {
} }
public async embedText(text: string): Promise<number[]> { public async embedText(text: string): Promise<number[]> {
// Reuse the private batch helper to avoid auth duplication
const [embedding] = await this.generateEmbeddingsBatch([text.toLowerCase()]); const [embedding] = await this.generateEmbeddingsBatch([text.toLowerCase()]);
return embedding; return embedding;
} }
@ -211,28 +258,56 @@ class EmbeddingsService {
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
} }
async findSimilar(query: string, maxResults: number = 30, threshold: number = 0.3): Promise<EmbeddingData[]> { async findSimilar(query: string, maxResults: number = 30, threshold: number = 0.3): Promise<SimilarityResult[]> {
if (!this.enabled || !this.isInitialized || this.embeddings.length === 0) { if (!this.enabled || !this.isInitialized || this.embeddings.length === 0) {
console.log('[EMBEDDINGS] Service not available for similarity search');
return []; return [];
} }
try { try {
// Generate embedding for query
const queryEmbeddings = await this.generateEmbeddingsBatch([query.toLowerCase()]); const queryEmbeddings = await this.generateEmbeddingsBatch([query.toLowerCase()]);
const queryEmbedding = queryEmbeddings[0]; const queryEmbedding = queryEmbeddings[0];
// Calculate similarities console.log(`[EMBEDDINGS] Computing similarities for ${this.embeddings.length} items`);
const similarities = this.embeddings.map(item => ({
const similarities: SimilarityResult[] = this.embeddings.map(item => ({
...item, ...item,
similarity: this.cosineSimilarity(queryEmbedding, item.embedding) similarity: this.cosineSimilarity(queryEmbedding, item.embedding)
})); }));
// Filter by threshold and sort by similarity const results = similarities
return similarities
.filter(item => item.similarity >= threshold) .filter(item => item.similarity >= threshold)
.sort((a, b) => b.similarity - a.similarity) .sort((a, b) => b.similarity - a.similarity)
.slice(0, maxResults); .slice(0, maxResults);
const orderingValid = results.every((item, index) => {
if (index === 0) return true;
return item.similarity <= results[index - 1].similarity;
});
if (!orderingValid) {
console.error('[EMBEDDINGS] CRITICAL: Similarity ordering is broken!');
results.forEach((item, idx) => {
console.error(` ${idx}: ${item.name} = ${item.similarity.toFixed(4)}`);
});
}
console.log(`[EMBEDDINGS] Found ${results.length} similar items (threshold: ${threshold})`);
if (results.length > 0) {
console.log('[EMBEDDINGS] Top 10 similarity matches:');
results.slice(0, 10).forEach((item, idx) => {
console.log(` ${idx + 1}. ${item.name} (${item.type}) = ${item.similarity.toFixed(4)}`);
});
const topSimilarity = results[0].similarity;
const hasHigherSimilarity = results.some(item => item.similarity > topSimilarity);
if (hasHigherSimilarity) {
console.error('[EMBEDDINGS] CRITICAL: Top result is not actually the highest similarity!');
}
}
return results;
} catch (error) { } catch (error) {
console.error('[EMBEDDINGS] Failed to find similar items:', error); console.error('[EMBEDDINGS] Failed to find similar items:', error);
return []; return [];
@ -254,12 +329,10 @@ class EmbeddingsService {
// Global instance
const embeddingsService = new EmbeddingsService(); const embeddingsService = new EmbeddingsService();
export { embeddingsService, type EmbeddingData }; export { embeddingsService, type EmbeddingData, type SimilarityResult };
// Auto-initialize on import in server environment
if (typeof window === 'undefined' && process.env.NODE_ENV !== 'test') { if (typeof window === 'undefined' && process.env.NODE_ENV !== 'test') {
embeddingsService.initialize().catch(error => { embeddingsService.initialize().catch(error => {
console.error('[EMBEDDINGS] Auto-initialization failed:', error); console.error('[EMBEDDINGS] Auto-initialization failed:', error);

View File

@ -1,10 +1,14 @@
// src/utils/rateLimitedQueue.ts - FIXED: Memory leak and better cleanup // src/utils/rateLimitedQueue.ts
import dotenv from "dotenv"; import dotenv from "dotenv";
dotenv.config(); dotenv.config();
const RATE_LIMIT_DELAY_MS = Number.parseInt(process.env.AI_RATE_LIMIT_DELAY_MS ?? "2000", 10) || 2000; const RATE_LIMIT_DELAY_MS =
Number.parseInt(process.env.AI_RATE_LIMIT_DELAY_MS ?? "2000", 10) || 2000;
const TASK_TIMEOUT_MS =
Number.parseInt(process.env.AI_TASK_TIMEOUT_MS ?? "300000", 10) || 300000;
export type Task<T = unknown> = () => Promise<T>; export type Task<T = unknown> = () => Promise<T>;
@ -12,7 +16,7 @@ interface QueuedTask {
id: string; id: string;
task: Task; task: Task;
addedAt: number; addedAt: number;
status: 'queued' | 'processing' | 'completed' | 'failed'; status: "queued" | "processing" | "completed" | "failed" | "timedout";
startedAt?: number; startedAt?: number;
completedAt?: number; completedAt?: number;
} }
@ -29,11 +33,12 @@ class RateLimitedQueue {
private tasks: QueuedTask[] = []; private tasks: QueuedTask[] = [];
private isProcessing = false; private isProcessing = false;
private delayMs = RATE_LIMIT_DELAY_MS; private delayMs = RATE_LIMIT_DELAY_MS;
private taskTimeoutMs = TASK_TIMEOUT_MS;
private lastProcessedAt = 0; private lastProcessedAt = 0;
private currentlyProcessingTaskId: string | null = null; private currentlyProcessingTaskId: string | null = null;
private cleanupInterval: NodeJS.Timeout; private cleanupInterval: NodeJS.Timeout;
private readonly TASK_RETENTION_MS = 30000; private readonly TASK_RETENTION_MS = 300000; // 5 minutes
constructor() { constructor() {
this.cleanupInterval = setInterval(() => { this.cleanupInterval = setInterval(() => {
@ -44,19 +49,19 @@ class RateLimitedQueue {
private cleanupOldTasks(): void { private cleanupOldTasks(): void {
const now = Date.now(); const now = Date.now();
const initialLength = this.tasks.length; const initialLength = this.tasks.length;
this.tasks = this.tasks.filter(task => { this.tasks = this.tasks.filter((task) => {
if (task.status === 'queued' || task.status === 'processing') { if (task.status === "queued" || task.status === "processing") {
return true; return true;
} }
if (task.completedAt && (now - task.completedAt) > this.TASK_RETENTION_MS) { if (task.completedAt && now - task.completedAt > this.TASK_RETENTION_MS) {
return false; return false;
} }
return true; return true;
}); });
const cleaned = initialLength - this.tasks.length; const cleaned = initialLength - this.tasks.length;
if (cleaned > 0) { if (cleaned > 0) {
console.log(`[QUEUE] Cleaned up ${cleaned} old tasks, ${this.tasks.length} remaining`); console.log(`[QUEUE] Cleaned up ${cleaned} old tasks, ${this.tasks.length} remaining`);
@ -86,11 +91,11 @@ class RateLimitedQueue {
} }
}, },
addedAt: Date.now(), addedAt: Date.now(),
status: 'queued' status: "queued",
}; };
this.tasks.push(queuedTask); this.tasks.push(queuedTask);
setTimeout(() => { setTimeout(() => {
this.processQueue(); this.processQueue();
}, 100); }, 100);
@ -98,12 +103,12 @@ class RateLimitedQueue {
} }
getStatus(taskId?: string): QueueStatus { getStatus(taskId?: string): QueueStatus {
const queuedTasks = this.tasks.filter(t => t.status === 'queued'); const queuedTasks = this.tasks.filter((t) => t.status === "queued");
const processingTasks = this.tasks.filter(t => t.status === 'processing'); const processingTasks = this.tasks.filter((t) => t.status === "processing");
const queueLength = queuedTasks.length + processingTasks.length; const queueLength = queuedTasks.length + processingTasks.length;
const now = Date.now(); const now = Date.now();
let estimatedWaitTime = 0; let estimatedWaitTime = 0;
if (queueLength > 0) { if (queueLength > 0) {
if (this.isProcessing && this.lastProcessedAt > 0) { if (this.isProcessing && this.lastProcessedAt > 0) {
@ -118,38 +123,34 @@ class RateLimitedQueue {
const status: QueueStatus = { const status: QueueStatus = {
queueLength, queueLength,
isProcessing: this.isProcessing, isProcessing: this.isProcessing,
estimatedWaitTime estimatedWaitTime,
}; };
if (taskId) { if (taskId) {
const task = this.tasks.find(t => t.id === taskId); const task = this.tasks.find((t) => t.id === taskId);
if (task) { if (task) {
status.taskStatus = task.status; status.taskStatus = task.status;
if (task.status === 'processing') { if (task.status === "processing") {
status.currentPosition = 1; status.currentPosition = 1;
} else if (task.status === 'queued') { } else if (task.status === "queued") {
const queuedTasksInOrder = this.tasks const queuedTasksInOrder = this.tasks
.filter(t => t.status === 'queued') .filter((t) => t.status === "queued")
.sort((a, b) => a.addedAt - b.addedAt); .sort((a, b) => a.addedAt - b.addedAt);
const positionInQueue = queuedTasksInOrder.findIndex(t => t.id === taskId); const positionInQueue = queuedTasksInOrder.findIndex((t) => t.id === taskId);
if (positionInQueue >= 0) { if (positionInQueue >= 0) {
const processingOffset = processingTasks.length > 0 ? 1 : 0; const processingOffset = processingTasks.length > 0 ? 1 : 0;
status.currentPosition = processingOffset + positionInQueue + 1; status.currentPosition = processingOffset + positionInQueue + 1;
} }
} }
} else { } else {
const taskTimestamp = taskId.match(/ai_(\d+)_/)?.[1]; const taskTimestamp = taskId.match(/ai_(\d+)_/)?.[1];
if (taskTimestamp) { if (taskTimestamp) {
const taskAge = now - parseInt(taskTimestamp); const taskAge = now - parseInt(taskTimestamp);
if (taskAge < 30000) { status.taskStatus = taskAge < 300000 ? "starting" : "unknown";
status.taskStatus = 'starting';
} else {
status.taskStatus = 'unknown';
}
} }
} }
} }
@ -157,51 +158,48 @@ class RateLimitedQueue {
return status; return status;
} }
setDelay(ms: number): void {
if (!Number.isFinite(ms) || ms < 0) return;
this.delayMs = ms;
}
getDelay(): number {
return this.delayMs;
}
private async processQueue(): Promise<void> { private async processQueue(): Promise<void> {
if (this.isProcessing) { if (this.isProcessing) return;
return;
}
this.isProcessing = true; this.isProcessing = true;
try { try {
while (true) { while (true) {
const nextTask = this.tasks const nextTask = this.tasks
.filter(t => t.status === 'queued') .filter((t) => t.status === "queued")
.sort((a, b) => a.addedAt - b.addedAt)[0]; .sort((a, b) => a.addedAt - b.addedAt)[0];
if (!nextTask) { if (!nextTask) break;
break;
} nextTask.status = "processing";
nextTask.status = 'processing';
nextTask.startedAt = Date.now(); nextTask.startedAt = Date.now();
this.currentlyProcessingTaskId = nextTask.id; this.currentlyProcessingTaskId = nextTask.id;
this.lastProcessedAt = Date.now(); this.lastProcessedAt = Date.now();
try { try {
await nextTask.task(); await Promise.race([
nextTask.status = 'completed'; nextTask.task(),
new Promise((_, reject) =>
setTimeout(
() => reject(new Error(`Task ${nextTask.id} timed out after ${this.taskTimeoutMs} ms`)),
this.taskTimeoutMs,
),
),
]);
nextTask.status = "completed";
nextTask.completedAt = Date.now(); nextTask.completedAt = Date.now();
console.log(`[QUEUE] Task ${nextTask.id} completed`); console.log(`[QUEUE] Task ${nextTask.id} completed`);
} catch (error) { } catch (error) {
nextTask.status = 'failed'; const err = error as Error;
nextTask.status = err.message.includes("timed out") ? "timedout" : "failed";
nextTask.completedAt = Date.now(); nextTask.completedAt = Date.now();
console.error(`[QUEUE] Task ${nextTask.id} failed:`, error); console.error(`[QUEUE] Task ${nextTask.id} failed:`, error);
} }
this.currentlyProcessingTaskId = null; this.currentlyProcessingTaskId = null;
const hasMoreQueued = this.tasks.some(t => t.status === 'queued'); const hasMoreQueued = this.tasks.some((t) => t.status === "queued");
if (hasMoreQueued) { if (hasMoreQueued) {
console.log(`[QUEUE] Waiting ${this.delayMs}ms before next task`); console.log(`[QUEUE] Waiting ${this.delayMs}ms before next task`);
await new Promise((r) => setTimeout(r, this.delayMs)); await new Promise((r) => setTimeout(r, this.delayMs));
@ -232,4 +230,4 @@ export function shutdownQueue(): void {
queue.shutdown(); queue.shutdown();
} }
export default queue; export default queue;

View File

@ -1,8 +1,3 @@
/**
* CONSOLIDATED Tool utility functions for consistent tool operations across the app
* Works in both server (Node.js) and client (browser) environments
*/
export interface Tool { export interface Tool {
name: string; name: string;
type?: 'software' | 'method' | 'concept'; type?: 'software' | 'method' | 'concept';
@ -18,10 +13,6 @@ export interface Tool {
related_concepts?: string[]; related_concepts?: string[];
} }
/**
* Creates a URL-safe slug from a tool name
* Used for URLs, IDs, and file names consistently across the app
*/
export function createToolSlug(toolName: string): string { export function createToolSlug(toolName: string): string {
if (!toolName || typeof toolName !== 'string') { if (!toolName || typeof toolName !== 'string') {
console.warn('[toolHelpers] Invalid toolName provided to createToolSlug:', toolName); console.warn('[toolHelpers] Invalid toolName provided to createToolSlug:', toolName);
@ -35,9 +26,6 @@ export function createToolSlug(toolName: string): string {
.replace(/^-|-$/g, ''); // Remove leading/trailing hyphens .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
} }
/**
* Finds a tool by name or slug from tools array
*/
export function findToolByIdentifier(tools: Tool[], identifier: string): Tool | undefined { export function findToolByIdentifier(tools: Tool[], identifier: string): Tool | undefined {
if (!identifier || !Array.isArray(tools)) return undefined; if (!identifier || !Array.isArray(tools)) return undefined;
@ -47,23 +35,9 @@ export function findToolByIdentifier(tools: Tool[], identifier: string): Tool |
); );
} }
/**
* Checks if tool has a valid project URL (hosted on CC24 server)
*/
export function isToolHosted(tool: Tool): boolean { export function isToolHosted(tool: Tool): boolean {
return tool.projectUrl !== undefined && return tool.projectUrl !== undefined &&
tool.projectUrl !== null && tool.projectUrl !== null &&
tool.projectUrl !== "" && tool.projectUrl !== "" &&
tool.projectUrl.trim() !== ""; tool.projectUrl.trim() !== "";
}
/**
* Determines tool category for styling/logic
*/
export function getToolCategory(tool: Tool): 'concept' | 'method' | 'hosted' | 'oss' | 'proprietary' {
if (tool.type === 'concept') return 'concept';
if (tool.type === 'method') return 'method';
if (isToolHosted(tool)) return 'hosted';
if (tool.license && tool.license !== 'Proprietary') return 'oss';
return 'proprietary';
} }