~firefoxphpai-chat
12 items • Download ./*

..
ajax.php
ana-ai-chat.png
atom-one-dark.min.css
emoji.json
favicon.ico
highlight.min.js
index.php
note.txt
readme.txt
setup.php
setup_and_launch.py
worker.php


ai-chatworker.php
20 KB• 9•  8 hours ago•  DownloadRawClose
8 hours ago•  9

{}
<?php
// worker.php - Background CLI queue processor
include('./setup.php');

$queueFile = __DIR__ . '/queue.json';
$chatDir = __DIR__ . '/chats/';
$indexFile = __DIR__ . '/chats.json';

// Prevent multiple concurrent workers from overlapping
$lockFile = __DIR__ . '/worker.lock';
$fp = fopen($lockFile, 'c+');
if (!flock($fp, LOCK_EX | LOCK_NB)) {
    exit("Worker already running.\n");
}

while (true) {
    if (!file_exists($queueFile)) {
        usleep(500000); // Sleep 0.5s if no queue file
        continue;
    }

    $queueData = json_decode(file_get_contents($queueFile), true);
    if (!is_array($queueData) || empty($queueData)) {
        usleep(500000);
        continue;
    }

    // Find first pending job and store its unique job_id
    $targetJobId = null;
	$job = null;
    foreach ($queueData as $j) {
        if ($j['status'] === 'pending') {
            $targetJobId = $j['job_id'];
			$job = $j;
            break;
        }
    }

    if ($targetJobId === null) {
        break;
    }

    // Helper function to update job status safely by job_id
    function updateJobStatus(&$queueArr, $jobId, $newStatus) {
        foreach ($queueArr as &$j) {
            if ($j['job_id'] === $jobId) {
                $j['status'] = $newStatus;
                break;
            }
        }
    }

    workerLog("found job to process {$job['type']} - {$job['chat_id']}");

    // Mark job as processing
    $queueData = json_decode(file_get_contents($queueFile), true) ?: [];
    updateJobStatus($queueData, $targetJobId, 'processing');
    file_put_contents($queueFile, json_encode($queueData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX);

    $chatId = $job['chat_id'];
    $type = $job['type'];
    $payload = $job['payload'];

    $jobSuccess = false;

    try {
        switch($type) {
            case "ai_chat":
                // 1. Handle File Uploads
                $attachmentPaths = array();
                $isImageUpload = false;
                $fileContextContent = "";
                $base64Images = array(); // Array to store multiple images
				
				$deleteuploads=array(); // cleanup

                if (!isset($fileContextContent)) $fileContextContent = "";
                for($i=0;$i<count($payload['attachments']);$i++) {
                    $targetFile = $payload['attachments'][0];
                    $fileName = preg_replace('/[^a-zA-Z0-9\.\-_]/', '_', $targetFile);

                    if (file_exists($targetFile)) {
                        $attachmentPaths[] = './chats/' . $chatId . '/uploads/' . basename($targetFile);
                        $mimeType = mime_content_type($targetFile);

                        if (strpos($mimeType, 'image/') === 0) {
                            $isImageUpload = true;
                            $imgBinary = file_get_contents($targetFile);
                            // Push the newly encoded string into our array
                            $base64Images[] = base64_encode($imgBinary);
                        } else {
                            $fileContextContent .= "\n\n[Attached File Content (" . $fileName . ")]:\n" . file_get_contents($targetFile);
                        }
						array_push($deleteuploads,$targetFile);
                    }
                }

                // Process Ollama Chat Request
                $fullPromptForAI = $payload['prompt'] . $fileContextContent;

                //
                $backendUrl = 'http://'.$BACKEND_IP.':'.$BACKEND_AI_PORT.'/api/chat';
                $actualModel = $payload['model'] ?? 'gemma4-uncensored';

                // Load existing history
                $filePath = $chatDir . $chatId . '.json';
                $messagesPayload = [];
                if (file_exists($filePath)) {
                    $oldHistory = json_decode(file_get_contents($filePath), true) ?: [];
                    foreach ($oldHistory as $msg) {
                        if (empty($msg['is_image']) && isset($msg['role'], $msg['content'])) {
                            $messagesPayload[] = [
                                "role" => $msg['role'],
                                "content" => $msg['content']
                            ];
                        }
                    }
                }

                // Build user message structure for Ollama (supports multi-modal images array if vision model is used)
                $currentUserMessage = [
                    "role" => "user",
                    "content" => $fullPromptForAI
                ];
                if ($isImageUpload && !empty($base64Images)) {
                    // Assign the entire accumulated array directly
                    $currentUserMessage["images"] = $base64Images;
                }
                $messagesPayload[] = $currentUserMessage;

                //--updateChatStatusWorker($chatId, 'idle');

                $options=array(
                    "num_ctx" => 65536,
                    "num_predict" => 4096
                );

                $ch = curl_init($backendUrl);
                curl_setopt($ch, CURLOPT_TIMEOUT, $curl_timeout);
                curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $curl_connout);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
                    "model" => $actualModel,
                    "messages" => $messagesPayload,
                    "stream" => false,
                    "options" => $options
                ]));
                curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);

                $apiResult = curl_exec($ch);
                $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                curl_close($ch);
				
				if($cleanup_uploads) {
					for($i=0;$i<count($deleteuploads);$i++) {
						if($deleteuploads[$i]<>"") {
							if(file_exists($deleteuploads[$i])) {
								unlink($deleteuploads[$i]);
							}
						}
					}
				}

                // Reset status to idle when done
                updateChatStatusWorker($chatId, 'idle');

                if ($apiResult && $httpCode === 200) {
                    $data = json_decode($apiResult, true);
                    $output = $data['message']['content'] ?? "No response generated.";
                    saveChatHistory($chatId, $payload['prompt'], $output, false, $payload['attachments'] ?? [], $payload['is_upload'] ?? false);
                    $jobSuccess = true;
                    
                    workerLog("job completed ai_chat - {$chatId}");
                } else {
                    saveChatHistory($chatId, $payload['prompt'], "System Error: Failed to reach Ollama backend.", false, [], false);
                }

                // DEBUG: Log all Ollama responses to a file to inspect what happened
                //file_put_contents(__DIR__ . '/ollama_debug.log', "Model: {$actualModel} | HTTP: {$httpCode} | Error: {$curlError} | Result: {$apiResult}\n", FILE_APPEND);
                break;

            case "generate_image":
                $prompt = $payload['prompt'];
                $steps = $payload['steps'];
                $width = $payload['width'];
                $height = $payload['height'];
                $attachments = $payload['attachments'] ?? [];
                $isUpload = $payload['is_upload'] ?? false;

				$deleteuploads=array(); // cleanup
                $initImagesBase64 = [];
                if ($isUpload && !empty($attachments)) {
                    foreach ($attachments as $attPath) {
                        $localFile = __DIR__ . '/' . ltrim($attPath, './');
                        if (file_exists($localFile)) {
                            $mimeType = mime_content_type($localFile);
                            $imgBinary = file_get_contents($localFile);
                            $initImagesBase64[] = 'data:' . $mimeType . ';base64,' . base64_encode($imgBinary);
							array_push($deleteuploads,$localFile);
						}
                    }
                }

                $endpoint = '/sdapi/v1/txt2img';
                $postData = [
                    "prompt" => $prompt,
                    "steps" => (int)$steps,
                    "width" => (int)$width,
                    "height" => (int)$height
                ];

                if (!empty($initImagesBase64)) {
                    $endpoint = '/sdapi/v1/img2img';
                    $postData["init_images"] = $initImagesBase64;
                    $postData["denoising_strength"] = 0.75;
                }

                $ch = curl_init('http://'.$BACKEND_IP.':'.$BACKEND_IMG_PORT.$endpoint);
                curl_setopt($ch, CURLOPT_TIMEOUT, $curl_timeout);
                curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $curl_connout);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
                curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);

                $apiResult = curl_exec($ch);
                $curlError = curl_error($ch);
                curl_close($ch);
				
				if($cleanup_uploads) {
					for($i=0;$i<count($deleteuploads);$i++) {
						if($deleteuploads[$i]<>"") {
							if(file_exists($deleteuploads[$i])) {
								unlink($deleteuploads[$i]);
							}
						}
					}
				}

                // Reset status to idle when done
                updateChatStatusWorker($chatId, 'idle');

                if ($apiResult) {
                    $data = json_decode($apiResult, true);
                    if (isset($data['images'][0])) {
                        $imgBinary = base64_decode($data['images'][0]);
                        $genDir = __DIR__ . "/chats/{$chatId}/generated/";
                        $prevDir = __DIR__ . "/chats/{$chatId}/generated_prev/";
                        if (!is_dir($genDir)) mkdir($genDir, 0777, true);
                        if (!is_dir($prevDir)) mkdir($prevDir, 0777, true);

                        $filename = 'img-' . time();
                        $pngPath = $genDir . $filename . '.png';
                        $jpgPath = $prevDir . $filename . '.jpg';

                        file_put_contents($pngPath, $imgBinary);
                        if (function_exists('imagecreatefromstring')) {
                            $srcImg = @imagecreatefromstring($imgBinary);
                            if ($srcImg) {
                                imagejpeg($srcImg, $jpgPath, 75);
                                imagedestroy($srcImg);
                            }
                        }

                        $imgPathRelative = './chats/' . $chatId . '/generated/' . $filename . '.png';
                        saveChatHistory($chatId, $prompt, $imgPathRelative, true, $attachments, $isUpload);
                        $jobSuccess = true;
                        workerLog("job completed generate_image - {$chatId}");
                    } else {
						workerLog("generate_image apiResult no image0? {$apiResult}");
					}
                } else{
                    workerLog("generate_image invalid apiResult? {$apiResult}");
                }
                break;

            case "upscale_image":
                $msgIndex = $payload['message_index'];
                $pngRelativePath = $payload['png_path'];
                $localPng = __DIR__ . '/' . ltrim($pngRelativePath, './');
                $filePath = __DIR__ . "/chats/{$chatId}.json";

                // Start precise sub-second timer
                $tstart = microtime(true);
                
                if (file_exists($localPng) && file_exists($filePath)) {
                    $imgData = file_get_contents($localPng);
                    $base64Image = 'data:image/png;base64,' . base64_encode($imgData);
                
                    // Optional: Quick dynamic upscaler check or default fallback
                    $upscalerName = "R-ESRGAN 4x+"; 
                
                    $payloadData = [
                        "resize_mode" => 0,
                        "show_extras_results" => true,
                        "gfpgan_visibility" => 0,
                        "codeformer_visibility" => 0,
                        "codeformer_weight" => 0,
                        "upscaling_resize" => 2.0,
                        "upscaling_crop" => true,
                        "upscaler_1" => $upscalerName,
                        "upscaler_2" => "None",
                        "extras_upscaler_2_visibility" => 0,
                        "upscale_first" => false,
                        "image" => $base64Image
                    ];
                
                    $ch = curl_init('http://'.$BACKEND_IP.':'.$BACKEND_IMG_PORT.'/sdapi/v1/extra-single-image');
                    curl_setopt($ch, CURLOPT_TIMEOUT, $curl_timeout);
                    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $curl_connout);
                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                    curl_setopt($ch, CURLOPT_POST, true);
                    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payloadData));
                    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
                
                    $apiResult = curl_exec($ch);
                    curl_close($ch);

                    // Stop precise timer
                    $tend = microtime(true);
                    $duration = $tend - $tstart;

                    // STRICT CHECK: If it completed in under 3 seconds, it's a false/unprocessed return!
                    if ($duration < 3.0) {
                        $response = array("status" => "error", "message" => "Upscale failed: Backend responded too fast (" . round($duration, 2) . "s), likely unprocessed.");
                        break;
                    }
                
                    if ($apiResult) {
                        $data = json_decode($apiResult, true);
                        if (isset($data['image']) && !empty($data['image'])) {
                            $upscaledBinary = base64_decode($data['image']);
                
                            $genDir = __DIR__ . "/chats/{$chatId}/generated/";
                            $prevDir = __DIR__ . "/chats/{$chatId}/generated_prev/";
                            if (!is_dir($genDir)) mkdir($genDir, 0777, true);
                            if (!is_dir($prevDir)) mkdir($prevDir, 0777, true);
                
                            $origFilename = pathinfo($localPng, PATHINFO_FILENAME);
                            $baseFilename = str_replace('-upscaled', '', $origFilename);
                            $filename = $baseFilename . "-upscaled";
                
                            $newPngPath = $genDir . $filename . ".png";
                            $newJpgPath = $prevDir . $filename . ".jpg";
                
                            file_put_contents($newPngPath, $upscaledBinary);
                
                            if (function_exists('imagecreatefromstring')) {
                                $srcImg = @imagecreatefromstring($upscaledBinary);
                                if ($srcImg) {
                                    imagejpeg($srcImg, $newJpgPath, 75);
                                    imagedestroy($srcImg);
                                }
                            }
                
                            // Cleanup old file versions if needed
                            $localJpgOld = str_replace(['/generated/', '.png'], ['/generated_prev/', '.jpg'], $localPng);
                            if (file_exists($localPng) && $localPng !== $newPngPath) @unlink($localPng);
                            if (file_exists($localJpgOld) && $localJpgOld !== $newJpgPath) @unlink($localJpgOld);
                
                            // Update message ledger
                            $messages = json_decode(file_get_contents($filePath), true) ?: [];
                            if (isset($messages[$msgIndex])) {
                                $messages[$msgIndex]['content'] = './chats/' . $chatId . '/generated/' . $filename . '.png';
                                $messages[$msgIndex]['is_upscaled'] = true;
                                $messages[$msgIndex]['time'] = time();
                
                                file_put_contents($filePath, json_encode($messages, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), LOCK_EX);
                                $jobSuccess = true;

                                workerLog("job completed upload_image - {$chatId}");
                            }
                        }
                    }
                }
                break;
        }
    } catch(Exception $e) {
        $jobSuccess = false;
        workerLog("job failed {$type} - {$chatId} [" . get_class($e) . "]: {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}");
    }

    // Re-read queue, update final status by job_id, and save with LOCK_EX
    $queueData = json_decode(file_get_contents($queueFile), true) ?: [];
    updateJobStatus($queueData, $targetJobId, $jobSuccess ? 'completed' : 'error');
    file_put_contents($queueFile, json_encode($queueData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX);

    updateChatStatusWorker($chatId, 'idle');
}

flock($fp, LOCK_UN);
fclose($fp);

function saveChatHistory($chatId, $userText, $aiText, $isImage, $attachmentPaths, $isUpload) {
    global $chatDir, $indexFile;
    $filePath = $chatDir . $chatId . '.json';

    $history = [];
    if (file_exists($filePath)) {
        $history = json_decode(file_get_contents($filePath), true) ?: [];
    }

    // Append user message with optional attachment & upload flags
    $pos=count($history);
    $history[] = [
        "role" => "user",
        "content" => $userText,
        "is_image" => false,
        "attachment" => $attachmentPaths,
        "is_upload" => $isUpload,
        "time" => time()
    ];
    // pos length is now index match

    if(count($attachmentPaths)>0) {
        $history[$pos]['attachment'] = $attachmentPaths;
    }
    if($isUpload) {
        $history[$pos]['is_upload'] = $isUpload;
    }

    // Append assistant response
    $history[] = [
        "role" => "assistant",
        "content" => $aiText,
        "is_image" => $isImage,
        "time" => time()
    ];

    file_put_contents($filePath, json_encode($history, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));

    // Update central index
    $index = [];
    if (file_exists($indexFile)) {
        $index = json_decode(file_get_contents($indexFile), true) ?: [];
    }

    $found = false;
    foreach ($index as &$item) {
        if ($item['id'] === $chatId) {
            $found = true;
            // Auto-update title from first user prompt if it's still default
            if ($item['title'] === 'New Chat' || empty($item['title'])) {
                $item['title'] = strlen($userText) > 25 ? substr($userText, 0, 25) . '...' : $userText;
            }
            break;
        }
    }

    if (!$found) {
        $title = strlen($userText) > 25 ? substr($userText, 0, 25) . '...' : $userText;
        $index[] = ["id" => $chatId, "title" => $title, "time" => time()];
    }

    file_put_contents($indexFile, json_encode($index, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
}

function updateChatStatusWorker($chatId, $status) {
    global $indexFile;
    if (!file_exists($indexFile)) return;
    $index = json_decode(file_get_contents($indexFile), true) ?: [];
    foreach ($index as &$item) {
        if ($item['id'] === $chatId) {
            $item['status'] = $status;
            $item['time'] = time();
            break;
        }
    }
    file_put_contents($indexFile, json_encode($index, JSON_PRETTY_PRINT));
}

function workerLog($data) {
    $ts=time();
    file_put_contents(__DIR__ . '/worker.log', "{$ts}: {$data}\n", FILE_APPEND);
}
?>

Top
©twily.info 2013 - 2026
twily at twily dot info



3 816 606 visits
... ^ v