200 lines
7.2 KiB
PHP
200 lines
7.2 KiB
PHP
<?php
|
|
// lib/generate_data_sync.php
|
|
// Run generate_data.sh synchronously (no-append) and return the generated JSON/description.
|
|
require __DIR__ . '/../auth.php';
|
|
mvlog_require_login();
|
|
|
|
$MVLOG_ROOT = dirname(__DIR__);
|
|
const DESCRIBE_DEFAULT_MAX_FRAMES = 32;
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
$id = strtolower(trim((string)($_POST['id'] ?? '')));
|
|
$job = trim((string)($_POST['job'] ?? ''));
|
|
$max_frames = isset($_POST['max_frames']) ? intval($_POST['max_frames']) : DESCRIBE_DEFAULT_MAX_FRAMES;
|
|
$additional_prompt = trim((string)($_POST['additional_prompt'] ?? ''));
|
|
$personality = strtolower(trim((string)($_POST['personality'] ?? 'auto')));
|
|
if (!preg_match('/^[a-z0-9_-]+$/', $personality)) $personality = 'auto';
|
|
|
|
function mvlog_article_id_is_valid(string $id): bool {
|
|
return (bool)preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $id);
|
|
}
|
|
|
|
function mvlog_legacy_job_is_valid(string $job): bool {
|
|
return (bool)preg_match('/^[0-9]{8}[-_][A-Za-z0-9._-]+$/', $job);
|
|
}
|
|
|
|
function mvlog_read_article_id(string $jobdir): string {
|
|
$path = $jobdir . '/.mvlog-id';
|
|
if (!is_file($path)) return '';
|
|
$value = strtolower(trim((string)@file_get_contents($path)));
|
|
return mvlog_article_id_is_valid($value) ? $value : '';
|
|
}
|
|
|
|
function mvlog_find_jobdir_by_id(string $mvlogRoot, string $articleId): ?string {
|
|
if (!mvlog_article_id_is_valid($articleId)) return null;
|
|
|
|
$cacheFile = $mvlogRoot . '/cache/articles-index.json';
|
|
if (is_file($cacheFile)) {
|
|
$cache = json_decode((string)@file_get_contents($cacheFile), true);
|
|
if (is_array($cache) && is_array($cache['by_id'] ?? null)) {
|
|
$name = (string)($cache['by_id'][$articleId] ?? '');
|
|
if ($name !== '') {
|
|
$candidate = $mvlogRoot . '/in-dir/' . basename($name);
|
|
if (is_dir($candidate)) return $candidate;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (glob($mvlogRoot . '/in-dir/*', GLOB_ONLYDIR) ?: [] as $dir) {
|
|
if (mvlog_read_article_id($dir) === $articleId) return $dir;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
$jobdir = null;
|
|
if ($id !== '' && mvlog_article_id_is_valid($id)) {
|
|
$jobdir = mvlog_find_jobdir_by_id($MVLOG_ROOT, $id);
|
|
}
|
|
if ($jobdir === null && $job !== '' && mvlog_legacy_job_is_valid($job)) {
|
|
$candidate = $MVLOG_ROOT . '/in-dir/' . basename($job);
|
|
if (is_dir($candidate)) $jobdir = $candidate;
|
|
}
|
|
|
|
if ($jobdir === null || !is_dir($jobdir)) {
|
|
http_response_code(404);
|
|
echo json_encode(['status'=>'error','message'=>'job not found']);
|
|
exit;
|
|
}
|
|
|
|
$describe_prompt_file = $jobdir . '/.mvlog-describe-prompt.txt';
|
|
if ($additional_prompt !== '') {
|
|
@file_put_contents($describe_prompt_file, $additional_prompt . PHP_EOL);
|
|
@chmod($describe_prompt_file, 0664);
|
|
@chown($describe_prompt_file, 'www-data');
|
|
@chgrp($describe_prompt_file, 'www-data');
|
|
}
|
|
|
|
// Read existing data.txt for Title and Location (case-insensitive aliases)
|
|
$data_file = $jobdir . '/data.txt';
|
|
$title = '';
|
|
$location = '';
|
|
if (is_file($data_file)) {
|
|
$lines = file($data_file, FILE_IGNORE_NEW_LINES);
|
|
$i = 0;
|
|
while ($i < count($lines)) {
|
|
$raw = $lines[$i];
|
|
$line = trim($raw);
|
|
if ($line === '' || str_starts_with($line, '#')) { $i++; continue; }
|
|
if (!str_contains($line, ':')) { $i++; continue; }
|
|
[$key, $value] = array_map('trim', explode(':', $line, 2));
|
|
$low = strtolower($key);
|
|
if ($value === '|' && in_array($low, ['description','desc','synopsis'], true)) {
|
|
// Skip indented block
|
|
$i++;
|
|
while ($i < count($lines)) {
|
|
$next = $lines[$i];
|
|
if (trim($next) !== '' && $next[0] !== ' ' && $next[0] !== "\t") break;
|
|
$i++;
|
|
}
|
|
continue;
|
|
}
|
|
if (in_array($low, ['title','name'], true) && $title === '') $title = $value;
|
|
if (in_array($low, ['place','location','where'], true) && $location === '') $location = $value;
|
|
$i++;
|
|
}
|
|
}
|
|
|
|
// Run generator synchronously but in no-append mode so it doesn't modify data.txt directly.
|
|
$script = $MVLOG_ROOT . '/bin/generate_data.sh';
|
|
if (!is_file($script)) {
|
|
http_response_code(500);
|
|
echo json_encode(['status' => 'error', 'message' => 'generate_data.sh not found']);
|
|
exit;
|
|
}
|
|
|
|
// Build command and execute. Pass optional additional prompt and chosen personality.
|
|
$prompt_arg = $additional_prompt !== '' ? $additional_prompt : 'no-append';
|
|
$cmd = escapeshellcmd($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' ' . escapeshellarg($prompt_arg) . ' ' . escapeshellarg($personality) . ' 2>&1';
|
|
|
|
// Allow long-running
|
|
@set_time_limit(0);
|
|
|
|
$gfile = dirname($jobdir) . '/' . basename($jobdir) . '.json';
|
|
$gfileExistedBefore = is_file($gfile);
|
|
$gfileMtimeBefore = $gfileExistedBefore ? @filemtime($gfile) : null;
|
|
|
|
exec($cmd, $out_lines, $rc);
|
|
$log = implode("\n", $out_lines);
|
|
clearstatcache(true, $gfile);
|
|
|
|
if ($rc !== 0) {
|
|
http_response_code(500);
|
|
echo json_encode([
|
|
'status' => 'error',
|
|
'message' => 'generator failed',
|
|
'log' => $log,
|
|
'rc' => $rc,
|
|
'output_file' => basename($gfile),
|
|
'stale_output_present' => is_file($gfile),
|
|
], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
if (!is_file($gfile)) {
|
|
http_response_code(500);
|
|
echo json_encode([
|
|
'status' => 'error',
|
|
'message' => 'no gemini output',
|
|
'log' => $log,
|
|
'rc' => $rc,
|
|
], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
// If gemini_generated.json exists, parse and return
|
|
if (is_file($gfile)) {
|
|
$raw = @file_get_contents($gfile);
|
|
$json_data = @json_decode($raw, true);
|
|
|
|
// Handle case where Gemini returns an array of candidates or a single object.
|
|
// We will always take the first valid description object we can find.
|
|
$json = null;
|
|
if (is_array($json_data) && isset($json_data[0]) && is_array($json_data[0])) {
|
|
$json = $json_data[0]; // It's an array of descriptions, take the first.
|
|
} elseif (is_array($json_data)) {
|
|
$json = $json_data; // It's already a single description object.
|
|
}
|
|
|
|
$description = '';
|
|
$teaser = '';
|
|
$quote_da = '';
|
|
$personality = '';
|
|
$tags = [];
|
|
if (is_array($json)) {
|
|
$description = isset($json['description']) ? (string)$json['description'] : '';
|
|
$teaser = isset($json['teaser']) ? (string)$json['teaser'] : '';
|
|
$quote_da = isset($json['quote_da']) ? (string)$json['quote_da'] : '';
|
|
$quote_da = trim($quote_da, "\"“”");
|
|
$personality = isset($json['personality']) ? (string)$json['personality'] : '';
|
|
$tags = isset($json['tags']) && is_array($json['tags']) ? $json['tags'] : [];
|
|
}
|
|
|
|
echo json_encode([
|
|
'status' => 'ok',
|
|
'description' => $description,
|
|
'teaser' => $teaser,
|
|
'quote_da' => $quote_da,
|
|
'personality' => $personality,
|
|
'tags' => $tags,
|
|
'raw_json' => $json_data, // Send original raw data back for reference
|
|
'log' => $log,
|
|
'rc' => $rc,
|
|
], JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
// No gemini file — return error and include log
|
|
http_response_code(500);
|
|
echo json_encode(['status'=>'error','message'=>'no gemini output','log'=>$log,'rc'=>$rc], JSON_UNESCAPED_UNICODE);
|
|
exit;
|