From 4f1e20bee2b08ac08b7535c5b96c664fe3c75fb2 Mon Sep 17 00:00:00 2001 From: Swapnil Devesh Date: Tue, 20 Oct 2026 18:36:17 +0530 Subject: [PATCH] Add FreeLLMAPI enhanced app --- FreeLLMAPI/FreeLLMAPI.php | 139 +++++++++++++++++++++++++++++++++ FreeLLMAPI/app.json | 10 +++ FreeLLMAPI/config.blade.php | 15 ++++ FreeLLMAPI/freellmapi.svg | 1 + FreeLLMAPI/livestats.blade.php | 10 +++ 5 files changed, 175 insertions(+) create mode 100644 FreeLLMAPI/FreeLLMAPI.php create mode 100644 FreeLLMAPI/app.json create mode 100644 FreeLLMAPI/config.blade.php create mode 100644 FreeLLMAPI/freellmapi.svg create mode 100644 FreeLLMAPI/livestats.blade.php diff --git a/FreeLLMAPI/FreeLLMAPI.php b/FreeLLMAPI/FreeLLMAPI.php new file mode 100644 index 0000000000..22cb222ce2 --- /dev/null +++ b/FreeLLMAPI/FreeLLMAPI.php @@ -0,0 +1,139 @@ +providers(); + echo "Successfully communicated with the API"; + } catch (\Throwable $e) { + echo $e->getMessage(); + } + } + + public function livestats() + { + $data = [ + "providers" => null, + "attention" => null, + ]; + try { + $providers = $this->providers(); + $data["providers"] = count($providers); + $data["attention"] = 0; + foreach ($providers as $provider) { + if ($provider->status !== "healthy") { + $data["attention"]++; + } + } + return parent::getLiveStats("active", $data); + } catch (\Throwable $e) { + return parent::getLiveStats("inactive", $data); + } + } + + private function providers() + { + $result = $this->request("providers"); + if (!isset($result->providers) || !is_array($result->providers)) { + throw new \RuntimeException("Failed: Invalid provider response"); + } + foreach ($result->providers as $provider) { + if ( + !is_object($provider) || + !isset($provider->name, $provider->status) || + !is_string($provider->name) || + !in_array( + $provider->status, + ["healthy", "rate_limited", "invalid", "unknown"], + true + ) + ) { + throw new \RuntimeException("Failed: Invalid provider status"); + } + } + return $result->providers; + } + + private function request($endpoint) + { + $key = trim($this->config->api_key ?? ""); + if ($key === "") { + throw new \RuntimeException( + "Failed: A unified API key is required" + ); + } + $url = $this->url($endpoint); + try { + $response = parent::execute($url, [ + "headers" => [ + "Accept" => "application/json", + "Authorization" => "Bearer " . $key, + ], + "timeout" => 5, + "connect_timeout" => 3, + "allow_redirects" => false, + ]); + } catch (\Throwable $e) { + throw new \RuntimeException( + "Failed: Could not connect to FreeLLMAPI" + ); + } + if ($response === null) { + throw new \RuntimeException( + "Failed: Could not connect to FreeLLMAPI" + ); + } + $code = $response->getStatusCode(); + if ($code === 401 || $code === 403) { + throw new \RuntimeException( + "Failed: Invalid unified API key (client-profile keys are not supported)" + ); + } + if ($code !== 200) { + throw new \RuntimeException( + "Failed: FreeLLMAPI returned HTTP " . + $code . + " for " . + $endpoint + ); + } + $result = json_decode((string) $response->getBody()); + if (!is_object($result)) { + throw new \RuntimeException( + "Failed: FreeLLMAPI returned invalid JSON" + ); + } + return $result; + } + + public function url($endpoint) + { + $base = rtrim(trim($this->config->url ?? ""), "/"); + $parts = parse_url($base); + if ( + !$parts || + !in_array( + strtolower($parts["scheme"] ?? ""), + ["http", "https"], + true + ) || + empty($parts["host"]) || + isset($parts["user"]) || + isset($parts["pass"]) || + isset($parts["query"]) || + isset($parts["fragment"]) + ) { + throw new \RuntimeException( + "Failed: Specify the FreeLLMAPI base HTTP or HTTPS URL" + ); + } + $base = preg_replace('~/v1$~', "", $base); + return $base . "/v1/" . $endpoint; + } +} diff --git a/FreeLLMAPI/app.json b/FreeLLMAPI/app.json new file mode 100644 index 0000000000..9bfcecfa22 --- /dev/null +++ b/FreeLLMAPI/app.json @@ -0,0 +1,10 @@ +{ + "appid": "3f0af0d4dccdfd367e649a61d17291787e6a1aef", + "name": "FreeLLMAPI", + "website": "https://github.com/tashfeenahmed/freellmapi", + "license": "MIT License", + "description": "FreeLLMAPI aggregates free LLM providers behind one OpenAI-compatible API with automatic routing and failover.", + "enhanced": true, + "tile_background": "dark", + "icon": "freellmapi.svg" +} diff --git a/FreeLLMAPI/config.blade.php b/FreeLLMAPI/config.blade.php new file mode 100644 index 0000000000..8f0a6bc61d --- /dev/null +++ b/FreeLLMAPI/config.blade.php @@ -0,0 +1,15 @@ +

{{ __('app.apps.config') }} ({{ __('app.optional') }}) @include('items.enable')

+
+
+ + {!! Form::text('config[override_url]', isset($item) ? $item->getconfig()->override_url ?? null : null, ['placeholder' => __('app.apps.override'), 'id' => 'override_url', 'class' => 'form-control']) !!} +
+
+ + {!! Form::input('password', 'config[api_key]', isset($item) ? $item->getconfig()->api_key ?? null : null, ['placeholder' => 'Unified API key', 'data-config' => 'api_key', 'class' => 'form-control config-item', 'autocomplete' => 'off']) !!} + Use the unified key from FreeLLMAPI Settings. +
+
+ +
+
diff --git a/FreeLLMAPI/freellmapi.svg b/FreeLLMAPI/freellmapi.svg new file mode 100644 index 0000000000..9ab47eb637 --- /dev/null +++ b/FreeLLMAPI/freellmapi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/FreeLLMAPI/livestats.blade.php b/FreeLLMAPI/livestats.blade.php new file mode 100644 index 0000000000..fc9a89bf97 --- /dev/null +++ b/FreeLLMAPI/livestats.blade.php @@ -0,0 +1,10 @@ +