Updating translation endpoint so its reachable
Homelab Main / deploy (push) Successful in 1m24s Details

This commit is contained in:
juvdiaz 2026-05-29 12:05:30 -06:00
parent 4f65136e74
commit 6e78988a6a
5 changed files with 133 additions and 19 deletions

View File

@ -38,6 +38,8 @@ RUN usermod -u 1000 apache && \
chown -R apache:apache /run/apache2 /var/log/apache2 /tmp/website-lang /var/www/localhost/htdocs/db chown -R apache:apache /run/apache2 /var/log/apache2 /tmp/website-lang /var/www/localhost/htdocs/db
ENV WEBSITE_LANG_WRITE_DIR=/var/www/localhost/htdocs/db/lang ENV WEBSITE_LANG_WRITE_DIR=/var/www/localhost/htdocs/db/lang
ENV OLLAMA_HOST=http://192.168.100.68:11434
ENV OLLAMA_MODEL=llama3.2:3b
USER apache USER apache

View File

@ -39,8 +39,7 @@
</div> </div>
<script> <script>
const OLLAMA_HOST = 'http://192.168.100.68:11434'; const TRANSLATE_URL = '/translate.php';
const OLLAMA_MODEL = 'llama3.2:3b';
const SAVE_URL = '/save_lang.php'; const SAVE_URL = '/save_lang.php';
const CURRENT_LANG = '<?php echo $lang; ?>'; const CURRENT_LANG = '<?php echo $lang; ?>';
const STATIC_LANGS = <?php echo json_encode(array_values($availableLangs)); ?>; const STATIC_LANGS = <?php echo json_encode(array_values($availableLangs)); ?>;

114
apps/website/translate.php Normal file
View File

@ -0,0 +1,114 @@
<?php
header('Content-Type: application/json');
function translate_response(int $status, array $body): never {
http_response_code($status);
echo json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
function clean_translate_text(string $value, int $maxLength = 4000): string {
$value = strip_tags($value);
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value) ?? '';
$value = trim($value);
if (strlen($value) > $maxLength) {
$value = substr($value, 0, $maxLength);
}
return $value;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
translate_response(405, ['error' => 'Method not allowed']);
}
if ((int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > 131072) {
translate_response(413, ['error' => 'Request too large']);
}
$body = json_decode(file_get_contents('php://input'), true);
if (!is_array($body) || !isset($body['targetLang'], $body['texts']) || !is_array($body['texts'])) {
translate_response(400, ['error' => 'Missing target language or texts']);
}
$targetLang = preg_replace('/[^a-z]/', '', strtolower((string) $body['targetLang']));
if (strlen($targetLang) < 2 || strlen($targetLang) > 5) {
translate_response(400, ['error' => 'Invalid target language']);
}
$targetName = clean_translate_text((string) ($body['targetName'] ?? strtoupper($targetLang)), 80);
$texts = [];
$totalLength = 0;
foreach ($body['texts'] as $text) {
if (!is_string($text) && !is_numeric($text)) {
continue;
}
$cleanText = clean_translate_text((string) $text);
$totalLength += strlen($cleanText);
$texts[] = $cleanText;
}
if (!$texts || count($texts) > 240 || $totalLength > 80000) {
translate_response(400, ['error' => 'Invalid translation batch']);
}
$prompt = "Translate each item to {$targetName}.\n"
. "Return ONLY a valid JSON array of translated strings in the same order, no explanations, no markdown.\n"
. "Example input: [\"Hello\", \"How are you\"]\n"
. "Example output: [\"Bonjour\", \"Comment allez-vous\"]\n\n"
. 'Input: ' . json_encode($texts, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ollamaHost = rtrim(getenv('OLLAMA_HOST') ?: 'http://192.168.100.68:11434', '/');
$ollamaModel = getenv('OLLAMA_MODEL') ?: 'llama3.2:3b';
$ollamaPayload = json_encode([
'model' => $ollamaModel,
'prompt' => $prompt,
'stream' => false,
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
if ($ollamaPayload === false) {
translate_response(500, ['error' => 'Could not encode Ollama request']);
}
$curl = curl_init($ollamaHost . '/api/generate');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => $ollamaPayload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 130,
]);
$rawResponse = curl_exec($curl);
$curlError = curl_error($curl);
$statusCode = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($rawResponse === false || $statusCode < 200 || $statusCode >= 300) {
translate_response(502, ['error' => 'Ollama request failed', 'status' => $statusCode, 'detail' => $curlError]);
}
$ollamaResponse = json_decode($rawResponse, true);
if (!is_array($ollamaResponse) || !isset($ollamaResponse['response']) || !is_string($ollamaResponse['response'])) {
translate_response(502, ['error' => 'Invalid Ollama response']);
}
$translationJson = trim(str_replace(['```json', '```'], '', $ollamaResponse['response']));
$translations = json_decode($translationJson, true);
if (!is_array($translations) || count($translations) !== count($texts)) {
translate_response(502, ['error' => 'Unexpected translation response']);
}
$cleanTranslations = [];
foreach ($translations as $translation) {
if (!is_string($translation) && !is_numeric($translation)) {
translate_response(502, ['error' => 'Unexpected translation item']);
}
$cleanTranslations[] = clean_translate_text((string) $translation);
}
echo json_encode([
'translations' => $cleanTranslations,
'model' => $ollamaModel,
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

View File

@ -19,6 +19,7 @@ const prompt = document.getElementById('translate-prompt');
const btn = document.getElementById('translate-btn'); const btn = document.getElementById('translate-btn');
const actionSpan = document.getElementById('translate-action'); const actionSpan = document.getElementById('translate-action');
const detectedSpan = document.getElementById('detected-lang-name'); const detectedSpan = document.getElementById('detected-lang-name');
let lastTranslationModel = 'Ollama';
function showBadge(msg) { function showBadge(msg) {
if (badge) { if (badge) {
@ -33,31 +34,25 @@ function hidePrompt() {
async function translateBatch(texts, targetLang) { async function translateBatch(texts, targetLang) {
const name = LANG_NAMES[targetLang] || targetLang; const name = LANG_NAMES[targetLang] || targetLang;
const prompt = `Translate each item to ${name}. const response = await fetch(TRANSLATE_URL, {
Return ONLY a valid JSON array of translated strings in the same order, no explanations, no markdown.
Example input: ["Hello", "How are you"]
Example output: ["Bonjour", "Comment allez-vous"]
Input: ${JSON.stringify(texts)}`;
const response = await fetch(`${OLLAMA_HOST}/api/generate`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
model: OLLAMA_MODEL, targetLang,
prompt, targetName: name,
stream: false texts
}), }),
signal: AbortSignal.timeout(120000) signal: AbortSignal.timeout(120000)
}); });
if (!response.ok) throw new Error(`HTTP ${response.status}`); if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json(); const data = await response.json();
const raw = data.response.trim().replace(/```json|```/g, '').trim(); const result = data.translations;
const result = JSON.parse(raw); if (data.model) lastTranslationModel = `Ollama / ${data.model}`;
if (!Array.isArray(result) || result.length !== texts.length) { const resultCount = Array.isArray(result) ? result.length : 0;
throw new Error(`Unexpected response: got ${result.length}, expected ${texts.length}`); if (!Array.isArray(result) || resultCount !== texts.length) {
throw new Error(`Unexpected response: got ${resultCount}, expected ${texts.length}`);
} }
return result; return result;
} }
@ -142,7 +137,7 @@ async function doTranslation() {
} }
await saveLang(browserLang, allTranslations); await saveLang(browserLang, allTranslations);
showBadge(`Translated by Ollama / ${OLLAMA_MODEL}`); showBadge(`Translated by ${lastTranslationModel}`);
} }

View File

@ -68,6 +68,10 @@ spec:
value: /var/www/localhost/htdocs/db/lang value: /var/www/localhost/htdocs/db/lang
- name: WEBSITE_IDEAS_WRITE_DIR - name: WEBSITE_IDEAS_WRITE_DIR
value: /var/www/localhost/htdocs/db/ideas value: /var/www/localhost/htdocs/db/ideas
- name: OLLAMA_HOST
value: http://192.168.100.68:11434
- name: OLLAMA_MODEL
value: llama3.2:3b
ports: ports:
- containerPort: 8080 - containerPort: 8080
name: http name: http