Merge pull request 'ai-enhancement' (#23) from ai-enhancement into main
Reviewed-on: mstoeck3/cc24-hub#23
This commit is contained in:
		
						commit
						0327f9cea8
					
				
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -878,23 +878,33 @@ tools:
 | 
			
		||||
      - reporting
 | 
			
		||||
      - data-processing
 | 
			
		||||
      - scripting
 | 
			
		||||
  - name: "Android Logical Imaging"
 | 
			
		||||
    icon: "📋"
 | 
			
		||||
    type: "method"
 | 
			
		||||
    description: "Es gibt immer wieder auch Fälle, wo man nicht allermodernste Mobilgeräte knacken muss - der Großvater, der im Nachlass ein altes Samsung mit wichtigen Daten hinterlassen hat.
 | 
			
		||||
      Es muss in diesn Fällen nicht der teure Hersteller aus Israel sein. Die Androis-ADB-Shell bietet genug Möglichkeiten, auch ohne viel Geld auszugeben an das logische Dateisystem zu gelangen.
 | 
			
		||||
      Die Erfolgsaussichten sinken jedoch massiv bei neuren Geräten."
 | 
			
		||||
    domains: ["mobile-forensics"]
 | 
			
		||||
    phases: ["data-collection"]
 | 
			
		||||
    platforms: []  
 | 
			
		||||
    skillLevel: "advanced"
 | 
			
		||||
  - name: Android Logical Imaging
 | 
			
		||||
    icon: 📋
 | 
			
		||||
    type: method
 | 
			
		||||
    description: >-
 | 
			
		||||
      Es gibt immer wieder auch Fälle, wo man nicht allermodernste Mobilgeräte
 | 
			
		||||
      knacken muss - der Großvater, der im Nachlass ein altes Samsung mit
 | 
			
		||||
      wichtigen Daten hinterlassen hat. Es muss in diesn Fällen nicht der teure
 | 
			
		||||
      Hersteller aus Israel sein. Die Androis-ADB-Shell bietet genug
 | 
			
		||||
      Möglichkeiten, auch ohne viel Geld auszugeben an das logische Dateisystem
 | 
			
		||||
      zu gelangen. Die Erfolgsaussichten sinken jedoch massiv bei neuren
 | 
			
		||||
      Geräten.
 | 
			
		||||
    domains:
 | 
			
		||||
      - mobile-forensics
 | 
			
		||||
    phases:
 | 
			
		||||
      - data-collection
 | 
			
		||||
    platforms: []
 | 
			
		||||
    skillLevel: advanced
 | 
			
		||||
    accessType: null
 | 
			
		||||
    url: "https://claude.ai/public/artifacts/66785e1f-62bb-4eb9-9269-b08648161742"
 | 
			
		||||
    url: https://claude.ai/public/artifacts/66785e1f-62bb-4eb9-9269-b08648161742
 | 
			
		||||
    projectUrl: null
 | 
			
		||||
    license: null
 | 
			
		||||
    knowledgebase: true
 | 
			
		||||
    related_concepts: null
 | 
			
		||||
    tags: ["imaging", "filesystem", "hardware-interface"]
 | 
			
		||||
    tags:
 | 
			
		||||
      - imaging
 | 
			
		||||
      - filesystem
 | 
			
		||||
      - hardware-interface
 | 
			
		||||
  - name: Microsoft Office 365
 | 
			
		||||
    type: software
 | 
			
		||||
    description: >-
 | 
			
		||||
@ -2081,6 +2091,29 @@ tools:
 | 
			
		||||
      - evidence-preservation
 | 
			
		||||
      - malware-identification
 | 
			
		||||
      - chain-of-custody
 | 
			
		||||
  - name: MSAB XRY
 | 
			
		||||
    type: software
 | 
			
		||||
    description: >-
 | 
			
		||||
      Die Smartphone-Extraktionssoftware der Firma MSAB positioniert sich als
 | 
			
		||||
      Konkurrenz zum Marktführer Cellebrite. MSAB wirbt mit dem Imaging selbst
 | 
			
		||||
      neuester Android- und IOS-Geräte.
 | 
			
		||||
    skillLevel: beginner
 | 
			
		||||
    url: https://www.msab.com/product/xry-extract/
 | 
			
		||||
    icon: 📦
 | 
			
		||||
    domains:
 | 
			
		||||
      - mobile-forensics
 | 
			
		||||
    phases:
 | 
			
		||||
      - data-collection
 | 
			
		||||
    tags:
 | 
			
		||||
      - law-enforcement
 | 
			
		||||
      - fast
 | 
			
		||||
      - SIM
 | 
			
		||||
      - image-recognition
 | 
			
		||||
    platforms:
 | 
			
		||||
      - Windows
 | 
			
		||||
    accessType: download
 | 
			
		||||
    license: Proprietary
 | 
			
		||||
    knowledgebase: false
 | 
			
		||||
domains:
 | 
			
		||||
  - id: incident-response
 | 
			
		||||
    name: Incident Response & Breach-Untersuchung
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										186
									
								
								src/pages/api/ai/enhance-input.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/pages/api/ai/enhance-input.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,186 @@
 | 
			
		||||
// src/pages/api/ai/enhance-input.ts
 | 
			
		||||
import type { APIRoute } from 'astro';
 | 
			
		||||
import { withAPIAuth } from '../../../utils/auth.js';
 | 
			
		||||
import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js';
 | 
			
		||||
import { enqueueApiCall } from '../../../utils/rateLimitedQueue.js';
 | 
			
		||||
 | 
			
		||||
export const prerender = false;
 | 
			
		||||
 | 
			
		||||
function getEnv(key: string): string {
 | 
			
		||||
  const value = process.env[key];
 | 
			
		||||
  if (!value) {
 | 
			
		||||
    throw new Error(`Missing environment variable: ${key}`);
 | 
			
		||||
  }
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const AI_MODEL = getEnv('AI_MODEL');
 | 
			
		||||
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
 | 
			
		||||
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
 | 
			
		||||
const RATE_LIMIT_MAX = 5; // 5 enhancement requests per minute per user
 | 
			
		||||
 | 
			
		||||
function sanitizeInput(input: string): string {
 | 
			
		||||
  return input
 | 
			
		||||
    .replace(/```[\s\S]*?```/g, '[CODE_BLOCK_REMOVED]')
 | 
			
		||||
    .replace(/\<\/?[^>]+(>|$)/g, '')
 | 
			
		||||
    .replace(/\b(system|assistant|user)\s*[:]/gi, '[ROLE_REMOVED]')
 | 
			
		||||
    .replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
 | 
			
		||||
    .trim()
 | 
			
		||||
    .slice(0, 1000); // Shorter limit for enhancement
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function checkRateLimit(userId: string): boolean {
 | 
			
		||||
  const now = Date.now();
 | 
			
		||||
  const userLimit = rateLimitStore.get(userId);
 | 
			
		||||
  
 | 
			
		||||
  if (!userLimit || now > userLimit.resetTime) {
 | 
			
		||||
    rateLimitStore.set(userId, { count: 1, resetTime: now + RATE_LIMIT_WINDOW });
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  if (userLimit.count >= RATE_LIMIT_MAX) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  userLimit.count++;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function cleanupExpiredRateLimits() {
 | 
			
		||||
  const now = Date.now();
 | 
			
		||||
  for (const [userId, limit] of rateLimitStore.entries()) {
 | 
			
		||||
    if (now > limit.resetTime) {
 | 
			
		||||
      rateLimitStore.delete(userId);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Clean up expired limits every 5 minutes
 | 
			
		||||
setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000);
 | 
			
		||||
 | 
			
		||||
function createEnhancementPrompt(input: string): string {
 | 
			
		||||
  return `
 | 
			
		||||
Du bist eine KI für digitale Forensik. Der Nutzer beschreibt ein forensisches Szenario. Analysiere die Eingabe.
 | 
			
		||||
 | 
			
		||||
Wenn die Beschreibung unvollständig oder vage ist, stelle bis zu drei präzise Rückfragen im JSON-Array-Format, um wichtige Details zu klären (z. B. Vorfalltyp, System, Ziel, Datenquellen, Zeit, Beteiligte, rechtlicher Rahmen).
 | 
			
		||||
 | 
			
		||||
Wenn die Eingabe bereits klar, spezifisch und vollständig ist, gib stattdessen nur eine leere Liste [] zurück.
 | 
			
		||||
 | 
			
		||||
Antwortformat strikt:
 | 
			
		||||
 | 
			
		||||
\`\`\`json
 | 
			
		||||
[
 | 
			
		||||
  "Frage 1?",
 | 
			
		||||
  "Frage 2?",
 | 
			
		||||
  "Frage 3?"
 | 
			
		||||
]
 | 
			
		||||
\`\`\`
 | 
			
		||||
 | 
			
		||||
Nutzer-Eingabe:
 | 
			
		||||
${input}
 | 
			
		||||
  `.trim();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const POST: APIRoute = async ({ request }) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const authResult = await withAPIAuth(request, 'ai');
 | 
			
		||||
    if (!authResult.authenticated) {
 | 
			
		||||
      return createAuthErrorResponse();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const userId = authResult.userId;
 | 
			
		||||
 | 
			
		||||
    if (!checkRateLimit(userId)) {
 | 
			
		||||
      return apiError.rateLimit('Enhancement rate limit exceeded');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const body = await request.json();
 | 
			
		||||
    const { input } = body;
 | 
			
		||||
 | 
			
		||||
    if (!input || typeof input !== 'string' || input.length < 20) {
 | 
			
		||||
      return apiError.badRequest('Input too short for enhancement');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const sanitizedInput = sanitizeInput(input);
 | 
			
		||||
    if (sanitizedInput.length < 20) {
 | 
			
		||||
      return apiError.badRequest('Input too short after sanitization');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const systemPrompt = createEnhancementPrompt(sanitizedInput);
 | 
			
		||||
    const taskId = `enhance_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`;
 | 
			
		||||
    
 | 
			
		||||
    const aiResponse = await enqueueApiCall(() =>
 | 
			
		||||
      fetch(process.env.AI_API_ENDPOINT + '/v1/chat/completions', {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
          'Authorization': `Bearer ${process.env.AI_API_KEY}`
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({
 | 
			
		||||
          model: AI_MODEL,
 | 
			
		||||
          messages: [
 | 
			
		||||
            {
 | 
			
		||||
              role: 'user',
 | 
			
		||||
              content: systemPrompt
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
          max_tokens: 200,
 | 
			
		||||
          temperature: 0.7
 | 
			
		||||
        })
 | 
			
		||||
      }), taskId);
 | 
			
		||||
 | 
			
		||||
    if (!aiResponse.ok) {
 | 
			
		||||
      console.error('AI enhancement error:', await aiResponse.text());
 | 
			
		||||
      return apiServerError.unavailable('Enhancement service unavailable');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const aiData = await aiResponse.json();
 | 
			
		||||
    const aiContent = aiData.choices?.[0]?.message?.content;
 | 
			
		||||
 | 
			
		||||
    if (!aiContent) {
 | 
			
		||||
      return apiServerError.unavailable('No enhancement response');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let questions;
 | 
			
		||||
    try {
 | 
			
		||||
    const cleanedContent = aiContent
 | 
			
		||||
        .replace(/^```json\s*/i, '')
 | 
			
		||||
        .replace(/\s*```\s*$/, '')
 | 
			
		||||
        .trim();
 | 
			
		||||
    questions = JSON.parse(cleanedContent);
 | 
			
		||||
      
 | 
			
		||||
      if (!Array.isArray(questions) || questions.length === 0) {
 | 
			
		||||
        throw new Error('Invalid questions format');
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Validate and clean questions
 | 
			
		||||
      questions = questions
 | 
			
		||||
        .filter(q => typeof q === 'string' && q.length > 5 && q.length < 120)
 | 
			
		||||
        .slice(0, 3);
 | 
			
		||||
        
 | 
			
		||||
      if (questions.length === 0) {
 | 
			
		||||
        throw new Error('No valid questions found');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error('Failed to parse enhancement response:', aiContent);
 | 
			
		||||
      return apiServerError.unavailable('Invalid enhancement response format');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    console.log(`[AI Enhancement] User: ${userId}, Questions: ${questions.length}, Input length: ${sanitizedInput.length}`);
 | 
			
		||||
 | 
			
		||||
    return new Response(JSON.stringify({
 | 
			
		||||
      success: true,
 | 
			
		||||
      questions,
 | 
			
		||||
      taskId
 | 
			
		||||
    }), {
 | 
			
		||||
      status: 200,
 | 
			
		||||
      headers: { 'Content-Type': 'application/json' }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Enhancement error:', error);
 | 
			
		||||
    return apiServerError.internal('Enhancement processing failed');
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@ -155,6 +155,11 @@ WICHTIGE REGELN:
 | 
			
		||||
8. WICHTIG: Erwähne relevante Hintergrundwissen-Konzepte wenn Tools verwendet werden, die related_concepts haben
 | 
			
		||||
9. Konzepte sind NICHT Tools - empfehle sie nicht als actionable Schritte, sondern als Wissensbasis
 | 
			
		||||
 | 
			
		||||
ENHANCED CONTEXTUAL ANALYSIS:
 | 
			
		||||
10. Analysiere das Szenario detailliert und identifiziere Schlüsselelemente, Bedrohungen und forensische Herausforderungen
 | 
			
		||||
11. Entwickle einen strategischen Untersuchungsansatz basierend auf dem spezifischen Szenario
 | 
			
		||||
12. Identifiziere zeitkritische oder besonders wichtige Faktoren für diesen Fall
 | 
			
		||||
 | 
			
		||||
SOFTWARE/METHODEN-AUSWAHL NACH PHASE:
 | 
			
		||||
${phaseDescriptions}
 | 
			
		||||
 | 
			
		||||
@ -163,16 +168,18 @@ ${domainAgnosticDescriptions}
 | 
			
		||||
 | 
			
		||||
ANTWORT-FORMAT (strict JSON):
 | 
			
		||||
{
 | 
			
		||||
  "scenario_analysis": "Detaillierte Analyse des Szenarios auf Deutsch/English",
 | 
			
		||||
  "scenario_analysis": "Detaillierte Analyse des Szenarios: Erkannte Schlüsselelemente, Art des Vorfalls, betroffene Systeme, potentielle Bedrohungen und forensische Herausforderungen",
 | 
			
		||||
  "investigation_approach": "Strategischer Untersuchungsansatz für dieses spezifische Szenario: Prioritäten, Reihenfolge der Phasen, besondere Überlegungen",
 | 
			
		||||
  "critical_considerations": "Zeitkritische Faktoren, wichtige Sicherheitsaspekte oder besondere Vorsichtsmaßnahmen für diesen Fall",
 | 
			
		||||
  "recommended_tools": [
 | 
			
		||||
    {
 | 
			
		||||
      "name": "EXAKTER Name aus der Tools-Database",
 | 
			
		||||
      "priority": "high|medium|low", 
 | 
			
		||||
      "phase": "${validPhases}",
 | 
			
		||||
      "justification": "Warum diese Methode für diese Phase und Szenario geeignet ist"
 | 
			
		||||
      "justification": "Warum diese Methode für diese Phase und dieses spezifische Szenario geeignet ist - mit Bezug zu den erkannten Schlüsselelementen"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "workflow_suggestion": "Vorgeschlagener Untersuchungsablauf",
 | 
			
		||||
  "workflow_suggestion": "Vorgeschlagener Untersuchungsablauf mit konkreten Schritten für dieses Szenario",
 | 
			
		||||
  "background_knowledge": [
 | 
			
		||||
    {
 | 
			
		||||
      "concept_name": "EXAKTER Name aus der Konzepte-Database",
 | 
			
		||||
@ -230,15 +237,22 @@ WICHTIGE REGELN:
 | 
			
		||||
10. WICHTIG: Erwähne relevante Hintergrundwissen-Konzepte wenn Tools verwendet werden, die related_concepts haben
 | 
			
		||||
11. Konzepte sind NICHT Tools - empfehle sie nicht als actionable Schritte, sondern als Wissensbasis
 | 
			
		||||
 | 
			
		||||
ENHANCED CONTEXTUAL ANALYSIS:
 | 
			
		||||
12. Analysiere das Problem detailliert und identifiziere technische Anforderungen, Herausforderungen und Erfolgsfaktoren
 | 
			
		||||
13. Entwickle einen strategischen Lösungsansatz basierend auf dem spezifischen Problem
 | 
			
		||||
14. Identifiziere wichtige Voraussetzungen oder Warnungen für die Anwendung
 | 
			
		||||
 | 
			
		||||
ANTWORT-FORMAT (strict JSON):
 | 
			
		||||
{
 | 
			
		||||
  "problem_analysis": "Detaillierte Analyse des Problems/der Anforderung",
 | 
			
		||||
  "problem_analysis": "Detaillierte Analyse des Problems: Erkannte technische Anforderungen, Herausforderungen, benötigte Fähigkeiten und Erfolgsfaktoren",
 | 
			
		||||
  "investigation_approach": "Strategischer Lösungsansatz für dieses spezifische Problem: Herangehensweise, Prioritäten, optimale Anwendungsreihenfolge",
 | 
			
		||||
  "critical_considerations": "Wichtige Voraussetzungen, potentielle Fallstricke oder Warnungen für die Anwendung der empfohlenen Lösungen",
 | 
			
		||||
  "recommended_tools": [
 | 
			
		||||
    {
 | 
			
		||||
      "name": "EXAKTER Name aus der Tools-Database",
 | 
			
		||||
      "rank": 1,
 | 
			
		||||
      "suitability_score": "high|medium|low",
 | 
			
		||||
      "detailed_explanation": "Detaillierte Erklärung, warum dieses Tool/diese Methode das Problem löst",
 | 
			
		||||
      "detailed_explanation": "Detaillierte Erklärung, warum dieses Tool/diese Methode das spezifische Problem löst - mit Bezug zu den erkannten Anforderungen",
 | 
			
		||||
      "implementation_approach": "Konkrete Schritte/Ansatz zur Anwendung für dieses spezifische Problem",
 | 
			
		||||
      "pros": ["Spezifische Vorteile für diesen Anwendungsfall", "Weitere Vorteile"],
 | 
			
		||||
      "cons": ["Potentielle Nachteile oder Limitationen", "Weitere Einschränkungen"],
 | 
			
		||||
@ -348,6 +362,10 @@ export const POST: APIRoute = async ({ request }) => {
 | 
			
		||||
    if (mode === 'workflow') {
 | 
			
		||||
      validatedRecommendation = {
 | 
			
		||||
        ...recommendation,
 | 
			
		||||
        // Ensure all new fields are included with fallbacks
 | 
			
		||||
        scenario_analysis: recommendation.scenario_analysis || recommendation.problem_analysis || '',
 | 
			
		||||
        investigation_approach: recommendation.investigation_approach || '',
 | 
			
		||||
        critical_considerations: recommendation.critical_considerations || '',
 | 
			
		||||
        recommended_tools: recommendation.recommended_tools?.filter((tool: any) => {
 | 
			
		||||
          if (!validToolNames.has(tool.name)) {
 | 
			
		||||
            console.warn(`AI recommended unknown tool: ${tool.name}`);
 | 
			
		||||
@ -366,6 +384,10 @@ export const POST: APIRoute = async ({ request }) => {
 | 
			
		||||
    } else {
 | 
			
		||||
      validatedRecommendation = {
 | 
			
		||||
        ...recommendation,
 | 
			
		||||
        // Ensure all new fields are included with fallbacks
 | 
			
		||||
        problem_analysis: recommendation.problem_analysis || recommendation.scenario_analysis || '',
 | 
			
		||||
        investigation_approach: recommendation.investigation_approach || '',
 | 
			
		||||
        critical_considerations: recommendation.critical_considerations || '',
 | 
			
		||||
        recommended_tools: recommendation.recommended_tools?.filter((tool: any) => {
 | 
			
		||||
          if (!validToolNames.has(tool.name)) {
 | 
			
		||||
            console.warn(`AI recommended unknown tool: ${tool.name}`);
 | 
			
		||||
 | 
			
		||||
@ -967,6 +967,23 @@ input[type="checkbox"] {
 | 
			
		||||
  transition: var(--transition-fast);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ai-input-layout {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  gap: 1.5rem;
 | 
			
		||||
  align-items: flex-start;
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ai-textarea-section {
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  min-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ai-suggestions-section {
 | 
			
		||||
  flex: 0 0 320px;
 | 
			
		||||
  min-height: 120px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ai-input-container textarea:focus {
 | 
			
		||||
  outline: none;
 | 
			
		||||
  border-color: var(--color-primary);
 | 
			
		||||
@ -1789,6 +1806,33 @@ footer {
 | 
			
		||||
  .form-grid.two-columns {
 | 
			
		||||
    grid-template-columns: 1fr;
 | 
			
		||||
  }
 | 
			
		||||
  .hint-card {
 | 
			
		||||
    padding: 1rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .hint-description {
 | 
			
		||||
    font-size: 0.6875rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .hint-trigger {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: 0.25rem;
 | 
			
		||||
  }
 | 
			
		||||
  .ai-input-layout {
 | 
			
		||||
    gap: 0.75rem;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
  }
 | 
			
		||||
  .ai-suggestions-section {
 | 
			
		||||
    flex: 0 0 auto;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    max-width: none;
 | 
			
		||||
  }
 | 
			
		||||
  .ai-textarea-section {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    min-width: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    min-height: 100px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (width <= 640px) {
 | 
			
		||||
@ -1922,4 +1966,444 @@ footer {
 | 
			
		||||
  .kb-entry .flex.gap-2 {
 | 
			
		||||
    gap: 0.5rem;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
  .prompting-card {
 | 
			
		||||
    padding: 0.75rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .suggestion-item {
 | 
			
		||||
    padding: 0.375rem 0.5rem;
 | 
			
		||||
    font-size: 0.75rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .position-badge {
 | 
			
		||||
    width: 28px;
 | 
			
		||||
    height: 28px;
 | 
			
		||||
    font-size: 0.875rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .queue-status-card {
 | 
			
		||||
    padding: 1rem;
 | 
			
		||||
  }
 | 
			
		||||
  .hint-card {
 | 
			
		||||
    padding: 0.75rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .hint-title {
 | 
			
		||||
    font-size: 0.8125rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .hint-description {
 | 
			
		||||
    font-size: 0.625rem;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Smart Prompting Styles - Simplified */
 | 
			
		||||
.smart-prompting-container {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  animation: smartPromptSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.prompting-card {
 | 
			
		||||
  background-color: var(--color-bg-tertiary);
 | 
			
		||||
  border: 1px solid var(--color-border);
 | 
			
		||||
  border-radius: 0.5rem;
 | 
			
		||||
  padding: 1rem;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  min-height: 120px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  opacity: 0.85;
 | 
			
		||||
  transition: var(--transition-fast);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.prompting-card:hover {
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
  border-color: var(--color-accent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.prompting-status {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  gap: 0.5rem;
 | 
			
		||||
  margin-bottom: 0.75rem;
 | 
			
		||||
  padding-bottom: 0.75rem;
 | 
			
		||||
  border-bottom: 1px solid var(--color-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.status-icon {
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
  flex-shrink: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.status-text {
 | 
			
		||||
  font-size: 0.8125rem;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  color: var(--color-text);
 | 
			
		||||
  flex: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.prompting-spinner {
 | 
			
		||||
  flex-shrink: 0;
 | 
			
		||||
  animation: spin 1s linear infinite;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Smart Prompting Hint */
 | 
			
		||||
.smart-prompting-hint {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  min-height: 120px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  animation: hintFadeIn 0.3s ease-in-out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hint-card {
 | 
			
		||||
  background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-tertiary) 100%);
 | 
			
		||||
  border: 1px dashed var(--color-border);
 | 
			
		||||
  border-radius: 0.5rem;
 | 
			
		||||
  padding: 1.25rem;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  opacity: 0.7;
 | 
			
		||||
  transition: var(--transition-medium);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hint-card:hover {
 | 
			
		||||
  opacity: 0.9;
 | 
			
		||||
  border-color: var(--color-accent);
 | 
			
		||||
  transform: translateY(-1px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hint-icon {
 | 
			
		||||
  margin-bottom: 0.75rem;
 | 
			
		||||
  opacity: 0.8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hint-content {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  gap: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hint-title {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  font-size: 0.875rem;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  color: var(--color-text);
 | 
			
		||||
  letter-spacing: 0.025em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hint-description {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  font-size: 0.75rem;
 | 
			
		||||
  line-height: 1.5;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hint-trigger {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  gap: 0.5rem;
 | 
			
		||||
  margin-top: 0.25rem;
 | 
			
		||||
  padding-top: 0.75rem;
 | 
			
		||||
  border-top: 1px solid var(--color-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hint-label {
 | 
			
		||||
  font-size: 0.6875rem;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
  letter-spacing: 0.05em;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hint-value {
 | 
			
		||||
  font-size: 0.75rem;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  color: var(--color-accent);
 | 
			
		||||
  background-color: var(--color-bg);
 | 
			
		||||
  padding: 0.125rem 0.5rem;
 | 
			
		||||
  border-radius: 1rem;
 | 
			
		||||
  border: 1px solid var(--color-accent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Hide hint when smart prompting is active */
 | 
			
		||||
.smart-prompting-container[style*="block"] ~ .smart-prompting-hint,
 | 
			
		||||
.smart-prompting-container:not([style*="none"]) ~ .smart-prompting-hint {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes hintFadeIn {
 | 
			
		||||
  from {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    transform: translateY(10px);
 | 
			
		||||
  }
 | 
			
		||||
  to {
 | 
			
		||||
    opacity: 0.7;
 | 
			
		||||
    transform: translateY(0);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.suggested-questions {
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.suggestions-header {
 | 
			
		||||
  margin-bottom: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.suggestions-label {
 | 
			
		||||
  font-size: 0.75rem;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
  letter-spacing: 0.025em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.questions-list {
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  gap: 0.5rem;
 | 
			
		||||
  margin-bottom: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.suggestion-item {
 | 
			
		||||
  background-color: var(--color-bg);
 | 
			
		||||
  border: 1px solid transparent;
 | 
			
		||||
  border-radius: 0.375rem;
 | 
			
		||||
  padding: 0.5rem 0.75rem;
 | 
			
		||||
  font-size: 0.8125rem;
 | 
			
		||||
  line-height: 1.4;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
  border-left: 2px solid var(--color-accent);
 | 
			
		||||
  transition: var(--transition-fast);
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.suggestion-item::before {
 | 
			
		||||
  content: counter(suggestion-counter);
 | 
			
		||||
  counter-increment: suggestion-counter;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: -8px;
 | 
			
		||||
  top: -6px;
 | 
			
		||||
  background-color: var(--color-accent);
 | 
			
		||||
  color: white;
 | 
			
		||||
  width: 16px;
 | 
			
		||||
  height: 16px;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  font-size: 0.625rem;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
.questions-list {
 | 
			
		||||
  counter-reset: suggestion-counter;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.suggestion-number {
 | 
			
		||||
  color: var(--color-accent);
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  margin-right: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dismiss-button {
 | 
			
		||||
  align-self: flex-end;
 | 
			
		||||
  background: none;
 | 
			
		||||
  border: none;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  padding: 0.25rem;
 | 
			
		||||
  border-radius: 0.25rem;
 | 
			
		||||
  transition: var(--transition-fast);
 | 
			
		||||
  opacity: 0.7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dismiss-button:hover {
 | 
			
		||||
  background-color: var(--color-bg-secondary);
 | 
			
		||||
  color: var(--color-text);
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Queue Status - Improved Design */
 | 
			
		||||
.queue-status-card {
 | 
			
		||||
  max-width: 400px;
 | 
			
		||||
  margin: 1.5rem auto 0;
 | 
			
		||||
  background: linear-gradient(135deg, var(--color-bg-secondary) 0%, var(--color-bg-tertiary) 100%);
 | 
			
		||||
  border: 1px solid var(--color-border);
 | 
			
		||||
  border-radius: 0.75rem;
 | 
			
		||||
  padding: 1.25rem;
 | 
			
		||||
  box-shadow: var(--shadow-sm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.queue-header {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.queue-position-display {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  gap: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.position-badge {
 | 
			
		||||
  width: 32px;
 | 
			
		||||
  height: 32px;
 | 
			
		||||
  background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
 | 
			
		||||
  color: white;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
  box-shadow: var(--shadow-sm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.position-label {
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  color: var(--color-text);
 | 
			
		||||
  font-size: 0.9375rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.queue-details {
 | 
			
		||||
  display: grid;
 | 
			
		||||
  grid-template-columns: 1fr 1fr;
 | 
			
		||||
  gap: 1rem;
 | 
			
		||||
  margin-bottom: 1.25rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.queue-stat {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  padding: 0.75rem;
 | 
			
		||||
  background-color: var(--color-bg);
 | 
			
		||||
  border-radius: 0.5rem;
 | 
			
		||||
  border: 1px solid var(--color-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.stat-label {
 | 
			
		||||
  display: block;
 | 
			
		||||
  font-size: 0.75rem;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
  margin-bottom: 0.25rem;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.stat-value {
 | 
			
		||||
  font-size: 1.125rem;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  color: var(--color-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.stat-unit {
 | 
			
		||||
  font-size: 0.75rem;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
  margin-left: 0.25rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.queue-progress-container {
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.queue-progress-track {
 | 
			
		||||
  background-color: var(--color-bg);
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  height: 6px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  border: 1px solid var(--color-border);
 | 
			
		||||
  margin-bottom: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.queue-progress-fill {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  background: linear-gradient(90deg, var(--color-primary) 0%, var(--color-accent) 100%);
 | 
			
		||||
  width: 0%;
 | 
			
		||||
  transition: width 0.3s ease;
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.task-id-display {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  padding: 0.5rem;
 | 
			
		||||
  background-color: var(--color-bg);
 | 
			
		||||
  border-radius: 0.375rem;
 | 
			
		||||
  border: 1px solid var(--color-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.task-label {
 | 
			
		||||
  font-size: 0.6875rem;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
  margin-right: 0.5rem;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
  letter-spacing: 0.025em;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.task-id {
 | 
			
		||||
  font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
 | 
			
		||||
  font-size: 0.6875rem;
 | 
			
		||||
  color: var(--color-text);
 | 
			
		||||
  background-color: var(--color-bg-secondary);
 | 
			
		||||
  padding: 0.125rem 0.375rem;
 | 
			
		||||
  border-radius: 0.25rem;
 | 
			
		||||
  border: 1px solid var(--color-border);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes smartPromptSlideIn {
 | 
			
		||||
  from {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    transform: translateX(20px);
 | 
			
		||||
    max-height: 0;
 | 
			
		||||
  }
 | 
			
		||||
  to {
 | 
			
		||||
    opacity: 0.85;
 | 
			
		||||
    transform: translateX(0);
 | 
			
		||||
    max-height: 300px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Enhanced contextual analysis cards */
 | 
			
		||||
.contextual-analysis-card {
 | 
			
		||||
  margin-bottom: 2rem;
 | 
			
		||||
  border-left: 4px solid;
 | 
			
		||||
  transition: var(--transition-fast);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.contextual-analysis-card:hover {
 | 
			
		||||
  transform: translateY(-1px);
 | 
			
		||||
  box-shadow: var(--shadow-md);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.contextual-analysis-card.scenario {
 | 
			
		||||
  border-left-color: var(--color-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.contextual-analysis-card.approach {
 | 
			
		||||
  border-left-color: var(--color-accent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.contextual-analysis-card.critical {
 | 
			
		||||
  border-left-color: var(--color-warning);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.analysis-header {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  gap: 0.5rem;
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.analysis-header.scenario { color: var(--color-primary); }
 | 
			
		||||
.analysis-header.approach { color: var(--color-accent); }
 | 
			
		||||
.analysis-header.critical { color: var(--color-warning); }
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user