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("
");
		}

		$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;
	}
}