Compare commits
2 Commits
0bf47247ff
...
07f9f304a4
| Author | SHA1 | Date | |
|---|---|---|---|
| 07f9f304a4 | |||
| 9817fde8c4 |
@@ -143,28 +143,37 @@ function assemble_content(int $cid): array {
|
||||
}
|
||||
|
||||
function get_article_files(int $cid): array {
|
||||
// sitepart_id=5 verweist auf filegallery_header
|
||||
$sql = "SELECT mcl.main_sitepart_header_id
|
||||
// sitepart_id 5, 6, 10 sind filegallery-Typen; sitepart_id=1 ist textcontent_header (nicht joinen!)
|
||||
$sql = "SELECT DISTINCT fl.filename, fl.extension, fl.description
|
||||
FROM main_collection_link mcl
|
||||
WHERE mcl.main_collection_id = $cid AND mcl.main_sitepart_id = 5";
|
||||
JOIN filegallery_line fl ON fl.header_id = mcl.main_sitepart_header_id
|
||||
WHERE mcl.main_collection_id = $cid
|
||||
AND mcl.main_sitepart_id IN (5, 6, 10)
|
||||
AND mcl.main_sitepart_header_id > 0
|
||||
AND fl.filename != ''
|
||||
ORDER BY fl.sorting";
|
||||
$res = mysqli_query($GLOBALS['mysql_con'], $sql);
|
||||
$files = [];
|
||||
while ($row = mysqli_fetch_assoc($res)) {
|
||||
$hid = (int)$row['main_sitepart_header_id'];
|
||||
if ($hid === 0) continue;
|
||||
$fres = mysqli_query($GLOBALS['mysql_con'],
|
||||
"SELECT filename, extension, description FROM filegallery_line WHERE header_id = $hid ORDER BY sorting");
|
||||
while ($frow = mysqli_fetch_assoc($fres)) {
|
||||
if (trim($frow['filename']) !== '') {
|
||||
$files[] = $frow;
|
||||
}
|
||||
}
|
||||
$files[] = $row;
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
function extract_pdf_text(string $filePath): string {
|
||||
if (!file_exists($filePath) || filesize($filePath) === 0) return '';
|
||||
|
||||
// pdftotext ist robuster bei gesicherten PDFs (nur Kopier-Schutz, kein Passwort)
|
||||
$pdftotext = trim(shell_exec('which pdftotext 2>/dev/null') ?? '');
|
||||
if ($pdftotext !== '') {
|
||||
$escaped = escapeshellarg($filePath);
|
||||
$out = shell_exec("$pdftotext -nopgbrk $escaped - 2>/dev/null");
|
||||
if ($out !== null && strlen(trim($out)) > 10) {
|
||||
return preg_replace('/\s+/', ' ', trim($out));
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: pdfparser
|
||||
try {
|
||||
$parser = new \Smalot\PdfParser\Parser();
|
||||
$pdf = $parser->parseFile($filePath);
|
||||
@@ -175,23 +184,31 @@ function extract_pdf_text(string $filePath): string {
|
||||
}
|
||||
}
|
||||
|
||||
function generate_summary(string $htmlContent, array $files, string $userdataDir, string $apiKey, string $title): ?string {
|
||||
// Fließtext: HTML vollständig entfernen, keine Zeichenbegrenzung
|
||||
$plainText = html_entity_decode(strip_tags($htmlContent), ENT_HTML5 | ENT_QUOTES, 'UTF-8');
|
||||
$plainText = trim(preg_replace('/\s+/', ' ', $plainText));
|
||||
|
||||
function generate_summary(string $htmlContent, array $files, string $userdataDir, string $apiKey, string $title, string $teaser = ''): ?string {
|
||||
$parts = [];
|
||||
if (strlen($plainText) > 30) {
|
||||
$parts[] = $plainText;
|
||||
|
||||
// Teaser bevorzugen: bereits plain text, sehr kurz → spart Input-Tokens
|
||||
if (strlen($teaser) > 30) {
|
||||
$parts[] = $teaser;
|
||||
}
|
||||
|
||||
// PDFs: Text extrahieren, auf 1500 Zeichen begrenzen (bei sehr langen PDFs)
|
||||
// Fließtext: HTML entfernen, auf 1500 Zeichen cappen
|
||||
$plainText = html_entity_decode(strip_tags($htmlContent), ENT_HTML5 | ENT_QUOTES, 'UTF-8');
|
||||
$plainText = trim(preg_replace('/\s+/', ' ', $plainText));
|
||||
if (strlen($plainText) > 30) {
|
||||
$parts[] = mb_substr($plainText, 0, 1500);
|
||||
}
|
||||
|
||||
// PDFs nur wenn sonst kein Inhalt vorhanden, auf 800 Zeichen begrenzen
|
||||
if (empty($parts)) {
|
||||
foreach ($files as $file) {
|
||||
if (strtolower($file['extension']) !== 'pdf') continue;
|
||||
$pdfText = extract_pdf_text($userdataDir . '/' . $file['filename']);
|
||||
if (strlen($pdfText) > 50) {
|
||||
$label = $file['description'] ?: basename($file['filename']);
|
||||
$parts[] = 'Anhang "' . $label . '": ' . mb_substr($pdfText, 0, 1500);
|
||||
$parts[] = 'Anhang "' . $label . '": ' . mb_substr($pdfText, 0, 800);
|
||||
break; // ein PDF reicht
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,19 +216,17 @@ function generate_summary(string $htmlContent, array $files, string $userdataDir
|
||||
return null;
|
||||
}
|
||||
|
||||
// Kompakter Prompt: Titel + vollständiger Inhalt
|
||||
$prompt = "Fasse diesen AWO-Wiki-Beitrag in 2-3 deutschen Sätzen zusammen. Nur Zusammenfassung, kein Intro.\n"
|
||||
. "Titel: $title\n\n"
|
||||
$prompt = "Fasse in 2-3 deutschen Sätzen zusammen. Nur Zusammenfassung.\nTitel: $title\n\n"
|
||||
. implode("\n\n", $parts);
|
||||
|
||||
logmsg(" PROMPT (" . strlen($prompt) . " Zeichen):\n" . $prompt . "\n--- PROMPT END ---", 'PROMPT');
|
||||
logmsg(" PROMPT (" . strlen($prompt) . " Zeichen)", 'PROMPT');
|
||||
|
||||
$payload = json_encode([
|
||||
'contents' => [['parts' => [['text' => $prompt]]]],
|
||||
'generationConfig' => [
|
||||
'maxOutputTokens' => 300,
|
||||
'maxOutputTokens' => 150,
|
||||
'temperature' => 0.2,
|
||||
'thinkingConfig' => ['thinkingBudget' => 0], // kein Thinking = alle Token für Antwort
|
||||
'thinkingConfig' => ['thinkingBudget' => 0],
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -279,7 +294,7 @@ if ($opts['summaries-only']) {
|
||||
if (!$apiKey) { fwrite(STDERR, "--summaries-only benötigt --api-key\n"); exit(2); }
|
||||
|
||||
$sumSql = "SELECT p.id as post_id, p.legacy_collection_id, pv.id as version_id, pv.title,
|
||||
pv.content
|
||||
pv.teaser, pv.content
|
||||
FROM knowledgecenter_posts p
|
||||
JOIN knowledgecenter_post_versions pv ON pv.post_id = p.id AND pv.version_number = 1
|
||||
WHERE (pv.summary IS NULL OR pv.summary = '')
|
||||
@@ -302,7 +317,8 @@ if ($opts['summaries-only']) {
|
||||
$title = $sumRow['title'];
|
||||
|
||||
$files = get_article_files($collId);
|
||||
$summary = generate_summary($sumRow['content'], $files, $userdataDir, $apiKey, $title) ?? '';
|
||||
$teaser = $sumRow['teaser'] ?? '';
|
||||
$summary = generate_summary($sumRow['content'], $files, $userdataDir, $apiKey, $title, $teaser) ?? '';
|
||||
|
||||
if ($summary === '') {
|
||||
logmsg(" [FAIL] Post $postId \"$title\": leere Summary", 'WARN');
|
||||
@@ -314,7 +330,7 @@ if ($opts['summaries-only']) {
|
||||
logmsg("[OK ] Post $postId \"$title\": " . mb_substr($summary, 0, 80), 'OK');
|
||||
$sumStats['ok']++;
|
||||
}
|
||||
usleep(200000);
|
||||
sleep(4); // 15 RPM Free-Tier: mind. 4 Sek. zwischen Requests
|
||||
}
|
||||
|
||||
logmsg(sprintf("Summaries fertig. ok=%d fail=%d", $sumStats['ok'], $sumStats['fail']), 'HEAD');
|
||||
@@ -380,7 +396,7 @@ while ($row = mysqli_fetch_assoc($result)) {
|
||||
$generated = generate_summary($fullContent, $files, $userdataDir, $apiKey, $title);
|
||||
$summary = $generated ?? '';
|
||||
logmsg(" Summary: " . (strlen($summary) > 0 ? '"' . mb_substr($summary, 0, 100) . '"' : '(leer)'));
|
||||
usleep(200000); // 200ms Pause für API rate limiting
|
||||
sleep(4); // 15 RPM Free-Tier: mind. 4 Sek. zwischen Requests
|
||||
}
|
||||
|
||||
// HTML-Entities dekodieren für saubere UTF-8 Speicherung
|
||||
|
||||
Reference in New Issue
Block a user