diff --git a/find-duplicates.mjs b/find-duplicates.mjs new file mode 100644 index 0000000..00dfa39 --- /dev/null +++ b/find-duplicates.mjs @@ -0,0 +1,333 @@ +#!/usr/bin/env node +// find-duplicate-functions.mjs +// Usage: +// node find-duplicate-functions.mjs [rootDir] [--mode exact|struct] [--min-lines N] [--json] +// Example: +// node find-duplicate-functions.mjs . --mode struct --min-lines 3 + +import fs from "fs"; +import path from "path"; +import * as url from "url"; +import ts from "typescript"; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +/** -------- CLI OPTIONS -------- */ +const args = process.argv.slice(2); +let rootDir = "."; +let mode = "struct"; // "exact" | "struct" +let minLines = 3; +let outputJson = false; + +for (let i = 0; i < args.length; i++) { + const a = args[i]; + if (!a.startsWith("--") && rootDir === ".") { + rootDir = a; + } else if (a === "--mode") { + mode = (args[++i] || "struct").toLowerCase(); + if (!["exact", "struct"].includes(mode)) { + console.error("Invalid --mode. Use 'exact' or 'struct'."); + process.exit(1); + } + } else if (a === "--min-lines") { + minLines = parseInt(args[++i] || "3", 10); + } else if (a === "--json") { + outputJson = true; + } +} + +/** -------- FILE DISCOVERY -------- */ +const DEFAULT_IGNORES = new Set([ + "node_modules", + ".git", + ".next", + ".vercel", + "dist", + "build", + ".astro", // Astro's generated cache dir +]); + +const VALID_EXTS = new Set([".ts", ".tsx", ".astro", ".mts", ".cts"]); + +function walk(dir) { + /** @type {string[]} */ + const out = []; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const e of entries) { + const p = path.join(dir, e.name); + if (e.isDirectory()) { + if (DEFAULT_IGNORES.has(e.name)) continue; + out.push(...walk(p)); + } else if (e.isFile() && VALID_EXTS.has(path.extname(e.name))) { + out.push(p); + } + } + return out; +} + +/** -------- ASTRO CODE EXTRACTION -------- + * Extract TS/JS code from: + * - frontmatter: --- ... --- + * - + */ +function extractCodeFromAstro(source) { + /** @type {{code:string, offset:number}[]} */ + const blocks = []; + + // Frontmatter (must be at top in Astro) + // Match the FIRST pair of --- ... --- + const fm = source.startsWith("---") + ? (() => { + const end = source.indexOf("\n---", 3); + if (end !== -1) { + const front = source.slice(3, end + 1); // include trailing \n + return { start: 0, end: end + 4, code: front }; + } + return null; + })() + : null; + if (fm) { + // offset for line numbers is after the first '---\n' + blocks.push({ code: fm.code, offset: 4 }); // rough; we’ll fix line numbers via positions later + } + + // + const scriptRe = /]*>([\s\S]*?)<\/script>/gi; + let m; + while ((m = scriptRe.exec(source))) { + const code = m[1] || ""; + blocks.push({ code, offset: indexToLine(source, m.index) }); + } + + return blocks; +} + +/** -------- UTIL: index -> 1-based line -------- */ +function indexToLine(text, idx) { + let line = 1; + for (let i = 0; i < idx && i < text.length; i++) { + if (text.charCodeAt(i) === 10) line++; + } + return line; +} + +/** -------- AST HELPERS -------- */ +function createSourceFile(virtualPath, code) { + return ts.createSourceFile( + virtualPath, + code, + ts.ScriptTarget.Latest, + /*setParentNodes*/ true, + virtualPath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS + ); +} + +// Normalize AST to a structural signature string +function structuralSignature(node) { + /** @type {string[]} */ + const parts = []; + const visit = (n) => { + // Skip trivia: comments/whitespace are already not in AST + const kindName = ts.SyntaxKind[n.kind] || `K${n.kind}`; + switch (n.kind) { + case ts.SyntaxKind.Identifier: + parts.push("Id"); + return; + case ts.SyntaxKind.PrivateIdentifier: + parts.push("PrivId"); + return; + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.NoSubstitutionTemplateLiteral: + case ts.SyntaxKind.TemplateHead: + case ts.SyntaxKind.TemplateMiddle: + case ts.SyntaxKind.TemplateTail: + parts.push("Str"); + return; + case ts.SyntaxKind.NumericLiteral: + parts.push("Num"); + return; + case ts.SyntaxKind.TrueKeyword: + case ts.SyntaxKind.FalseKeyword: + parts.push("Bool"); + return; + case ts.SyntaxKind.NullKeyword: + case ts.SyntaxKind.UndefinedKeyword: + parts.push("Nil"); + return; + case ts.SyntaxKind.PropertyAssignment: + case ts.SyntaxKind.ShorthandPropertyAssignment: + case ts.SyntaxKind.MethodDeclaration: + case ts.SyntaxKind.MethodSignature: + parts.push("Prop"); + break; + default: + parts.push(kindName); + } + n.forEachChild(visit); + }; + visit(node); + return parts.join("|"); +} + +function getFunctionInfo(sf, filePath) { + /** @type {Array<{ + name: string, + bodyText: string, + structKey: string, + start: number, + end: number, + startLine: number, + endLine: number + }>} */ + const out = []; + + const addFunc = (nameNode, bodyNode) => { + if (!bodyNode) return; + const bodyText = bodyNode.getText(sf).trim(); + const start = bodyNode.getStart(sf); + const end = bodyNode.getEnd(); + const { line: startLine } = sf.getLineAndCharacterOfPosition(start); + const { line: endLine } = sf.getLineAndCharacterOfPosition(end); + const name = + nameNode && ts.isIdentifier(nameNode) ? nameNode.escapedText.toString() : "(anonymous)"; + + // min-lines filter + const lines = bodyText.split(/\r?\n/).filter(Boolean); + if (lines.length < minLines) return; + + // structural signature from the body + const structKey = structuralSignature(bodyNode); + + out.push({ + name, + bodyText, + structKey, + start, + end, + startLine: startLine + 1, + endLine: endLine + 1, + }); + }; + + const visit = (node) => { + if (ts.isFunctionDeclaration(node) && node.body) { + addFunc(node.name ?? null, node.body); + } else if ( + ts.isFunctionExpression(node) || + ts.isArrowFunction(node) + ) { + // find name if it’s assigned: const foo = () => {} + let name = null; + if (node.parent && ts.isVariableDeclaration(node.parent) && node.parent.name) { + name = node.parent.name; + } else if ( + node.parent && + ts.isPropertyAssignment(node.parent) && + ts.isIdentifier(node.parent.name) + ) { + name = node.parent.name; + } else if (node.name) { + name = node.name; + } + if (node.body) addFunc(name, node.body); + } else if (ts.isMethodDeclaration(node) && node.body) { + addFunc(node.name, node.body); + } + node.forEachChild(visit); + }; + + visit(sf); + return out; +} + +/** -------- MAIN SCAN -------- */ +const files = walk(path.resolve(process.cwd(), rootDir)); + +/** Maps from hash -> occurrences */ +const groups = new Map(); +/** Helper for exact hash */ +import crypto from "crypto"; +const exactHash = (text) => crypto.createHash("sha1").update(text.replace(/\s+/g, " ").trim()).digest("hex"); + +for (const file of files) { + try { + const ext = path.extname(file).toLowerCase(); + const raw = fs.readFileSync(file, "utf8"); + + /** @type {Array<{virtualPath:string, code:string, lineOffset:number}>} */ + const codeUnits = []; + + if (ext === ".astro") { + const blocks = extractCodeFromAstro(raw); + blocks.forEach((b, i) => { + codeUnits.push({ + virtualPath: file + `#astro${i + 1}.ts`, + code: b.code, + lineOffset: b.offset || 1, + }); + }); + } else { + codeUnits.push({ virtualPath: file, code: raw, lineOffset: 1 }); + } + + for (const { virtualPath, code, lineOffset } of codeUnits) { + const sf = createSourceFile(virtualPath, code); + const funcs = getFunctionInfo(sf, file); + for (const f of funcs) { + const key = + mode === "exact" ? exactHash(f.bodyText) : crypto.createHash("sha1").update(f.structKey).digest("hex"); + const item = { + file, + where: + ext === ".astro" + ? `${path.relative(process.cwd(), file)}:${f.startLine + lineOffset - 1}-${f.endLine + lineOffset - 1}` + : `${path.relative(process.cwd(), file)}:${f.startLine}-${f.endLine}`, + name: f.name, + lines: f.endLine - f.startLine + 1, + preview: f.bodyText.split(/\r?\n/).slice(0, 5).join("\n") + (f.endLine - f.startLine + 1 > 5 ? "\n..." : ""), + }; + if (!groups.has(key)) groups.set(key, []); + groups.get(key).push(item); + } + } + } catch (e) { + console.warn(`⚠️ Skipping ${file}: ${e.message}`); + } +} + +/** -------- REPORT -------- */ +const dupes = [...groups.entries()] + .map(([key, arr]) => ({ key, items: arr })) + .filter((g) => g.items.length > 1) + .sort((a, b) => b.items.length - a.items.length); + +if (outputJson) { + console.log(JSON.stringify({ mode, minLines, groups: dupes }, null, 2)); + process.exit(0); +} + +if (dupes.length === 0) { + console.log(`✅ No duplicate functions found (mode=${mode}, min-lines=${minLines}).`); + process.exit(0); +} + +console.log(`\nFound ${dupes.length} duplicate group(s) (mode=${mode}, min-lines=${minLines}):\n`); +dupes.forEach((g, i) => { + console.log(`== Group ${i + 1} (${g.items.length} matches) ==`); + const example = g.items[0]; + console.log(` Sample (${example.lines} lines) from ${example.where}${example.name ? ` [${example.name}]` : ""}`); + console.log(" ---"); + console.log(indent(example.preview, " ")); + console.log(" ---"); + g.items.forEach((it) => { + console.log(` • ${it.where}${it.name ? ` [${it.name}]` : ""} (${it.lines} lines)`); + }); + console.log(); +}); + +function indent(s, pre) { + return s + .split("\n") + .map((l) => pre + l) + .join("\n"); +} diff --git a/src/components/AIQueryInterface.astro b/src/components/AIQueryInterface.astro index fac8f6b..d787530 100644 --- a/src/components/AIQueryInterface.astro +++ b/src/components/AIQueryInterface.astro @@ -1247,99 +1247,6 @@ class AIQueryInterface { }; } - createSpecificSummary(data, action, type) { - if (!data) return 'Leer'; - - // Action-specific summaries that provide meaningful information - switch (action) { - case 'selection-decision': - if (type === 'input') { - if (data.availableTools && Array.isArray(data.availableTools)) { - const preview = data.availableTools.slice(0, 5).join(', '); - return `${data.totalAvailable || data.availableTools.length} Tools verfügbar: ${preview}${data.availableTools.length > 5 ? '...' : ''}`; - } - return `${data.totalAvailable || 0} Tools verfügbar`; - } else { - return `Ausgewählt: ${Array.isArray(data.selectedTools) ? data.selectedTools.join(', ') : 'keine'}`; - } - - case 'phase-tool-selection': - if (type === 'input') { - if (data.availableTools && Array.isArray(data.availableTools)) { - return `${data.availableTools.length} Tools für Phase: ${data.availableTools.slice(0, 3).join(', ')}${data.availableTools.length > 3 ? '...' : ''}`; - } - return `Phase: ${data.phaseName || data.phaseId || 'unbekannt'} (${data.toolCount || 0} verfügbar)`; - } else { - if (data.selectedTools && Array.isArray(data.selectedTools)) { - return `Ausgewählt: ${data.selectedTools.join(', ')}`; - } - return `${data.selectionCount || 0} Tools ausgewählt (Ø ${data.avgTaskRelevance || 0}% Relevanz)`; - } - - case 'similarity-search': - if (type === 'input') { - return `Suche: "${data.query}" (Schwelle: ${data.threshold})`; - } else { - if (data.topMatches && Array.isArray(data.topMatches)) { - return `${data.resultsCount} Treffer: ${data.topMatches.slice(0, 3).join(', ')}`; - } - return `${data.resultsCount || 0} Treffer gefunden`; - } - - case 'phase-enhancement': - if (type === 'input') { - return `Phase: ${data.phaseName || data.phaseId} (${data.searchStrategy || 'Standard'})`; - } else { - const toolsAdded = Array.isArray(data.addedTools) ? data.addedTools : []; - return `${data.toolsAddedCount || toolsAdded.length} Tools hinzugefügt: ${toolsAdded.join(', ') || 'keine'}`; - } - - case 'ai-decision': - if (type === 'input') { - return data.prompt ? `KI-Prompt: ${data.prompt.slice(0, 100)}...` : 'KI-Analyse durchgeführt'; - } else { - return data.response ? `KI-Antwort: ${data.response.slice(0, 100)}...` : 'Antwort erhalten'; - } - - case 'tool-confidence': - if (type === 'input') { - return `Tool: ${data.toolName} (Semantik: ${data.semanticSimilarity}%, Aufgabe: ${data.taskRelevance}%)`; - } else { - return `Vertrauen: ${data.overallConfidence}% (Stärken: ${data.strengthIndicators?.length || 0}, Unsicherheiten: ${data.uncertaintyFactors?.length || 0})`; - } - - case 'tool-added-to-phase': - if (type === 'input') { - return `Tool: ${data.toolName} für ${data.phaseId} (${data.taskRelevance}% Relevanz, ${data.priority} Priorität)`; - } else { - const justificationPreview = data.justification ? data.justification.slice(0, 80) + '...' : 'Keine Begründung'; - return `Begründung: ${justificationPreview}`; - } - - case 'concept-selection': - if (type === 'input') { - const availableCount = Array.isArray(data.availableConcepts) ? data.availableConcepts.length : 0; - return `${availableCount} Konzepte verfügbar für methodische Fundierung`; - } else { - const selectedConcepts = Array.isArray(data.selectedConcepts) ? data.selectedConcepts : []; - return `${selectedConcepts.length} ausgewählt: ${selectedConcepts.slice(0, 3).join(', ')}${selectedConcepts.length > 3 ? '...' : ''}`; - } - } - - // Fallback to generic handling for other actions - if (typeof data === 'string') { - return data.length > 100 ? data.slice(0, 100) + '...' : data; - } - - if (Array.isArray(data)) { - if (data.length === 0) return 'Leeres Array'; - if (data.length <= 3) return data.join(', '); - return `${data.slice(0, 3).join(', ')} und ${data.length - 3} weitere`; - } - - return `${Object.keys(data).length} Eigenschaften`; - } - renderPhaseGroups(auditTrail, stats) { const phaseGroups = new Map(); @@ -1439,26 +1346,23 @@ class AIQueryInterface { renderDetailedEntryInfo(entry) { const details = []; - const input = entry.input || {}; - const output = entry.output || {}; const metadata = entry.metadata || {}; - // Show input summary with action-specific formatting + // The backend now provides meaningful inputSummary and outputSummary if (metadata.inputSummary && metadata.inputSummary !== 'Leer') { details.push(`
Eingabe: ${escapeHtml(metadata.inputSummary)}
`); } - // Show output summary with action-specific formatting if (metadata.outputSummary && metadata.outputSummary !== 'Leer') { details.push(`
Ausgabe: ${escapeHtml(metadata.outputSummary)}
`); } - // Show reasoning - this is now meaningful, not generic + // Show meaningful reasoning (backend now avoids generic "completed with X%" messages) if (metadata.reasoning && !metadata.reasoning.includes('completed with')) { details.push(`
Begründung: ${escapeHtml(metadata.reasoning)}
`); } - // Show specific details based on action type + // Action-specific additional details if (entry.action === 'similarity-search' && metadata.similarityScores) { const topScores = Object.entries(metadata.similarityScores) .sort(([,a], [,b]) => (b) - (a)) @@ -1477,10 +1381,13 @@ class AIQueryInterface { } } - if (entry.action === 'selection-decision' && metadata.selectionMethod) { - details.push(`
Auswahlmethode: ${metadata.selectionMethod}
`); + if (entry.action === 'selection-decision') { + if (metadata.selectionMethod) { + const methodDisplay = metadata.selectionMethod === 'embeddings_candidates' ? 'Semantische Filterung' : 'Vollständige Analyse'; + details.push(`
Methode: ${methodDisplay}
`); + } if (metadata.reductionRatio) { - details.push(`
Reduktion: ${(metadata.reductionRatio * 100).toFixed(1)}% der verfügbaren Tools
`); + details.push(`
Filterung: ${(metadata.reductionRatio * 100).toFixed(1)}% der verfügbaren Tools
`); } } @@ -1501,6 +1408,15 @@ class AIQueryInterface { } } + if (entry.action === 'phase-enhancement') { + if (metadata.semanticSimilarity) { + details.push(`
Semantische Ähnlichkeit: ${(metadata.semanticSimilarity * 100).toFixed(1)}%
`); + } + if (metadata.aiReasoningUsed) { + details.push(`
KI-Begründung: Verwendet für Auswahl
`); + } + } + if (details.length === 0) return ''; return ` diff --git a/src/utils/auditService.ts b/src/utils/auditService.ts index 345ab95..c17b857 100644 --- a/src/utils/auditService.ts +++ b/src/utils/auditService.ts @@ -378,65 +378,105 @@ class AuditService { private createSpecificSummary(data: any, action: string, type: 'input' | 'output'): string { if (!data) return 'Leer'; - // Action-specific summaries + // Action-specific summaries that show actual meaningful data switch (action) { case 'selection-decision': if (type === 'input') { if (data.availableTools && Array.isArray(data.availableTools)) { - const preview = data.availableTools.slice(0, 5).join(', '); - return `${data.totalAvailable || data.availableTools.length} Tools verfügbar: ${preview}${data.availableTools.length > 5 ? '...' : ''}`; + const preview = data.availableTools.slice(0, 3).join(', '); + return `${data.totalAvailable || data.availableTools.length} Tools: ${preview}${data.availableTools.length > 3 ? '...' : ''}`; + } else if (data.totalAvailable) { + return `${data.totalAvailable} Tools verfügbar, Methode: ${data.selectionMethod}`; } - return `${data.totalAvailable || 0} Tools verfügbar`; + return `${data.candidateCount || 0} Kandidaten für Auswahl`; } else { - return `Ausgewählt: ${Array.isArray(data.selectedTools) ? data.selectedTools.join(', ') : 'keine'}`; + if (Array.isArray(data.selectedTools)) { + return `${data.selectedTools.length} ausgewählt: ${data.selectedTools.slice(0, 3).join(', ')}${data.selectedTools.length > 3 ? '...' : ''}`; + } + return `Auswahl: ${data.selectionRatio ? Math.round(data.selectionRatio * 100) + '%' : 'unbekannt'}`; } case 'phase-tool-selection': if (type === 'input') { - if (data.availableTools && Array.isArray(data.availableTools)) { - return `${data.availableTools.length} Tools für Phase: ${data.availableTools.slice(0, 3).join(', ')}${data.availableTools.length > 3 ? '...' : ''}`; + if (Array.isArray(data.availableTools)) { + const toolPreview = data.availableTools.slice(0, 3).join(', '); + return `${data.availableTools.length} Tools für ${data.phaseName || data.phaseId}: ${toolPreview}${data.availableTools.length > 3 ? '...' : ''}`; } - return `Phase: ${data.phaseName || data.phaseId || 'unbekannt'}`; + return `Phase: ${data.phaseName || data.phaseId} (${data.toolCount || 0} Tools)`; } else { - if (data.selectedTools && Array.isArray(data.selectedTools)) { - return `Ausgewählt: ${data.selectedTools.join(', ')}`; + if (Array.isArray(data.selectedTools)) { + return `${data.selectedTools.length} ausgewählt: ${data.selectedTools.join(', ')}`; } - return `${data.selectionCount || 0} Tools ausgewählt`; + return `${data.selectionCount || 0} Tools, Ø ${data.avgTaskRelevance || 0}% Relevanz`; } case 'similarity-search': if (type === 'input') { - return `Suche: "${data.query}" (Schwelle: ${data.threshold})`; + return `Suche: "${data.query}" (Min. ${data.threshold} Ähnlichkeit)`; } else { - if (data.topMatches && Array.isArray(data.topMatches)) { - return `${data.resultsCount} Treffer: ${data.topMatches.slice(0, 3).join(', ')}`; + if (Array.isArray(data.topMatches)) { + return `${data.resultsCount} Treffer: ${data.topMatches.slice(0, 2).join(', ')}${data.topMatches.length > 2 ? '...' : ''}`; } - return `${data.resultsCount || 0} Treffer gefunden`; + return `${data.resultsCount || 0} semantische Treffer gefunden`; } case 'phase-enhancement': if (type === 'input') { - return `Phase: ${data.phaseName || data.phaseId} (${data.searchStrategy || 'Standard'})`; + return `Vervollständige Phase: ${data.phaseName || data.phaseId}`; } else { - return `${data.toolsAddedCount} Tools hinzugefügt: ${Array.isArray(data.addedTools) ? data.addedTools.join(', ') : 'keine'}`; + if (Array.isArray(data.addedTools) && data.addedTools.length > 0) { + return `${data.addedTools.length} hinzugefügt: ${data.addedTools.join(', ')}`; + } + return `${data.toolsAddedCount || 0} Tools für Phase hinzugefügt`; } case 'ai-decision': if (type === 'input') { - return data.prompt ? `KI-Prompt: ${data.prompt.slice(0, 100)}...` : 'KI-Analyse durchgeführt'; + if (data.prompt) { + const promptPreview = data.prompt.slice(0, 80).replace(/\n/g, ' '); + return `KI-Prompt: ${promptPreview}...`; + } + return 'KI-Analyse angefordert'; } else { - return data.response ? `KI-Antwort: ${data.response.slice(0, 100)}...` : 'Antwort erhalten'; + if (data.response) { + const responsePreview = data.response.slice(0, 80).replace(/\n/g, ' '); + return `KI-Antwort: ${responsePreview}...`; + } + return 'KI-Analyse abgeschlossen'; } case 'tool-confidence': if (type === 'input') { - return `Tool: ${data.toolName} (Semantik: ${data.semanticSimilarity}%, Aufgabe: ${data.taskRelevance}%)`; + return `Tool: ${data.toolName} (Sem: ${data.semanticSimilarity}%, Task: ${data.taskRelevance}%)`; } else { - return `Vertrauen: ${data.overallConfidence}% (Stärken: ${data.strengthIndicators?.length || 0}, Unsicherheiten: ${data.uncertaintyFactors?.length || 0})`; + const strengthCount = data.strengthIndicators?.length || 0; + const uncertaintyCount = data.uncertaintyFactors?.length || 0; + return `${data.overallConfidence}% Vertrauen (${strengthCount} Stärken, ${uncertaintyCount} Unsicherheiten)`; + } + + case 'tool-added-to-phase': + if (type === 'input') { + return `${data.toolName} → ${data.phaseId} (${data.taskRelevance}% Relevanz, ${data.priority})`; + } else { + const justificationPreview = data.justification ? + data.justification.slice(0, 60).replace(/\n/g, ' ') + '...' : 'Hinzugefügt'; + return `Begründung: ${justificationPreview}`; + } + + case 'concept-selection': + if (type === 'input') { + const conceptCount = Array.isArray(data.availableConcepts) ? data.availableConcepts.length : 0; + const toolContext = Array.isArray(data.selectedToolsContext) ? data.selectedToolsContext.length : 0; + return `${conceptCount} Konzepte verfügbar, ${toolContext} Tools als Kontext`; + } else { + if (Array.isArray(data.selectedConcepts)) { + return `${data.selectedConcepts.length} ausgewählt: ${data.selectedConcepts.slice(0, 2).join(', ')}${data.selectedConcepts.length > 2 ? '...' : ''}`; + } + return `Konzeptauswahl abgeschlossen`; } } - // Fallback to generic handling + // Enhanced fallback that shows actual key-value content instead of just "X Eigenschaften" if (typeof data === 'string') { return data.length > 100 ? data.slice(0, 100) + '...' : data; } @@ -444,10 +484,44 @@ class AuditService { if (Array.isArray(data)) { if (data.length === 0) return 'Leeres Array'; if (data.length <= 3) return data.join(', '); - return `${data.slice(0, 3).join(', ')} und ${data.length - 3} weitere`; + return `${data.slice(0, 3).join(', ')} + ${data.length - 3} weitere`; } - return `${Object.keys(data).length} Eigenschaften`; + if (typeof data === 'object') { + const keys = Object.keys(data); + if (keys.length === 0) return 'Leeres Objekt'; + + // Show actual key-value pairs for small objects instead of just counting properties + if (keys.length <= 2) { + const pairs = keys.map(key => { + const value = data[key]; + if (typeof value === 'string' && value.length > 30) { + return `${key}: ${value.slice(0, 30)}...`; + } else if (Array.isArray(value)) { + return `${key}: [${value.length} Items]`; + } else { + return `${key}: ${value}`; + } + }); + return pairs.join(', '); + } else { + // For larger objects, show key names and some sample values + const sampleKeys = keys.slice(0, 3); + const sampleValues = sampleKeys.map(key => { + const value = data[key]; + if (typeof value === 'string' && value.length > 20) { + return `${key}: ${value.slice(0, 20)}...`; + } else if (Array.isArray(value)) { + return `${key}: [${value.length}]`; + } else { + return `${key}: ${value}`; + } + }); + return `${sampleValues.join(', ')}${keys.length > 3 ? ` + ${keys.length - 3} weitere` : ''}`; + } + } + + return String(data); } private generateSpecificReasoning(