Files
decpdf.site/lib/Gemini.php
T
2026-06-04 11:13:34 +01:00

290 lines
5.7 KiB
PHP

<?php
require_once(__DIR__ . "/File.php");
require_once(__DIR__ . "/HTTPRequest.php");
class Gemini {
public $file_uri = "";
public $root = "https://generativelanguage.googleapis.com";
public $verbose = false;
private $_upload_callback = false;
private $_process_callback = false;
public $parameters = [];
public $attachments = [];
public function add_parameter($param) {
$this->parameters[$param->name] = $param;
}
public function get_parameters() {
$o = [];
foreach ($this->parameters as $name=>$param) {
$o[$name] = $param->get_object();
}
return $o;
}
public function get_required() {
$o = [];
foreach ($this->parameters as $name=>$param) {
if ($param->required) {
$o[] = $name;
}
}
return $o;
}
public static function get_key() {
Config::refresh();
$keys = Config::get("GEMINI_KEY");
if (!is_array($keys)) return $keys;
return $keys[rand(0, count($keys)-1)];
}
public function attach($name, File $file) {
$o = new stdClass;
$o->uri = null;
$o->name = $name;
$o->file = $file;
$this->attachments[$name] = $o;
}
public function upload_file($name, File $file, $key = false) {
if ($file->size() > 50000000) {
throw new Exception("File too large");
}
set_time_limit(300);
if ($this->verbose) {
print("Uploading file...\n");
flush();
}
$cb = false;
if ($this->_upload_callback) {
$cb = $this->_upload_callback;
}
if ($cb) $cb(0);
if ($key == false) {
$key = Gemini::get_key();
}
$json = "{'file': {'display_name': '" . $name . "'}}";
if ($this->verbose) {
print("Upload data: " . $json . "\n");
flush();
}
$h = new HTTPRequest();
$r = $h->post($this->root . "/upload/v1beta/files", $json, [
"x-goog-api-key: " . $key,
"Content-Type: application/json",
"X-Goog-Upload-Protocol: resumable",
"X-Goog-Upload-Command: start",
"X-Goog-Upload-Header-Content-Length: " . $file->size(),
"X-Goog-Upload-Header-Content-Type: " . $file->mime()
]);
if ($this->verbose) {
print("Request data:\n");
print_r($h);
flush();
}
if ($r != 200) {
throw new Exception("Error uploading file to Gemini", $r);
}
$url = $h->headers['x-goog-upload-url'];
$chunksize = $h->headers['x-goog-upload-chunk-granularity'];
$size = $file->size();
$s = $size;
$pos = 0;
while ($s > 0) {
$chunk = min($s, $chunksize);
$pct = round($pos / $size * 100);
if ($cb) $cb($pct);
if ($this->verbose) {
printf("***** %d%%\n", round($pct));
flush();
}
$data = $file->get_chunk($pos, $chunk);
$s -= $chunk;
$fin = $s == 0 ? ", finalize" : "";
$r = $h->post($url, $data, [
"Content-Length: " . $chunk,
"X-Goog-Upload-Offset: " . $pos,
"X-Goog-Upload-Command: upload$fin"
]);
if ($r != 200) {
throw new Exception("Error uploading chunk $pos to Gemini", $r);
}
if ($this->verbose) {
print_r($r);
flush();
}
$pos += $chunk;
}
$d = json_decode($h->body);
if ($this->verbose) {
print("Final returned body:\n");
print_r($d);
}
if ($cb) $cb(100);
return $d->file->uri;
}
public function geminiOverview() {
$cb = false;
if ($this->_process_callback) $cb = $this->_process_callback;
if ($cb) $cb("Uploading files");
if ($this->verbose) {
print("<pre>");
}
$key = Gemini::get_key();
if (count($this->attachments) == 0) {
throw new Exception("No files to upload");
}
foreach ($this->attachments as $name=>$file) {
$file->uri = $this->upload_file($name, $file->file, $key);
}
$system = [];
$system[] = "Summarize the attached PDF document.";
$parts = [];
$text = new stdClass;
$text->text = implode("\n", $system);
$parts[] = $text;
foreach ($this->attachments as $name=>$file) {
$f = new stdClass;
$f->file_data = new stdClass;
$f->file_data->mime_type = $file->file->mime();
$f->file_data->file_uri = $file->uri;
$parts[] = $f;
}
$ob = [
"contents" => [[
"parts" => $parts
]],
"generationConfig" => [
"responseMimeType" => "application/json",
"responseSchema" => [
"type" => "object",
"properties" => $this->get_parameters(),
"required" => $this->get_required(),
]
]
];
$json = json_encode($ob, JSON_PRETTY_PRINT);
if ($this->verbose) {
print("AI reuqest: " . $json . "\n");
flush();
}
set_time_limit(300);
if ($cb) $cb("Sending request");
$h = new HTTPRequest();
$r = $h->post($this->root . "/v1beta/models/" . Config::get("GEMINI_MODEL") . ":generateContent", $json, [
"x-goog-api-key: " . $key,
"Content-Type: application/json"
]);
if ($cb) $cb("Processing response");
if ($this->verbose) {
print("AI Response:\n");
print_r($h);
flush();
}
$resp = json_decode($h->body);
if ($r != 200) {
if (@$resp->error) {
throw new Exception($resp->error->message, $resp->error->code);
} else {
throw new Exception("HTTP Error $r requesting AI assistance", $r);
}
}
if (property_exists($resp, "candidates")) {
$text = $resp->candidates[0]->content->parts[0]->text;
$lines = explode("\n", $text);
$lastLine = "";
$out = [];
foreach ($lines as $line) {
if (str_starts_with($line, "* ") and !str_starts_with($lastLine, "* ")) {
$lastLine = $line;
$line = "\n" . $line;
} else if (str_starts_with($line, "1. ") and (trim($lastLine) != "")) {
$lastLine = $line;
$line = "\n" . $line;
} else {
$lastLine = $line;
}
$out[] = $line;
}
return implode("\n", $out);
}
throw new Exception("Content missing processing AI data", 0);
}
public function upload_callback(callable $cb) {
$this->_upload_callback = $cb;
}
public function process_callback(callable $cb) {
$this->_process_callback = $cb;
}
}