From 55dca1ecd93ece82b7daff601e8a89ae38fc78a9 Mon Sep 17 00:00:00 2001 From: "Sebastian BURGIN-FIX (ext)" Date: Tue, 21 Jul 2026 14:18:54 +0200 Subject: [PATCH 1/4] wip --- app/Actions/ViewDataAction.php | 29 ++ app/Enums/AiModelCategoryEnum.php | 20 ++ app/Enums/AiModelLicenseEnum.php | 29 ++ app/Http/Controllers/Ai/AiIndexController.php | 17 ++ .../AiLlm/AiLlmIndexController.php | 22 ++ .../Controllers/Sitemap/SitemapController.php | 2 + app/Models/AiModel.php | 29 ++ app/Security/Presets/MyCspPreset.php | 2 +- app/Support/CloudinaryUrl.php | 7 +- composer.lock | 284 +++++++++--------- config/database.php | 3 +- ...26_07_21_134448_create_ai_models_table.php | 40 +++ .../seeders/Codebar/AiModelsTableSeeder.php | 233 ++++++++++++++ database/seeders/Codebar/PagesTableSeeder.php | 48 +++ database/seeders/CodebarSeeder.php | 2 + lang/de_CH.json | 1 + lang/de_CH/components.php | 75 +++++ lang/en_CH.json | 1 + lang/en_CH/components.php | 75 +++++ package-lock.json | 238 +++++++-------- resources/views/app/ai-llm/index.blade.php | 65 ++++ resources/views/app/ai/index.blade.php | 11 + resources/views/app/start/index.blade.php | 9 + .../components/ai-llm/archive-row.blade.php | 6 + .../components/ai-llm/archive-table.blade.php | 12 + .../views/components/ai-llm/card.blade.php | 5 + .../components/ai-llm/infra-row.blade.php | 9 + .../components/ai-llm/model-row.blade.php | 28 ++ .../_partials/_navigation_desktop.blade.php | 4 + .../_partials/_navigation_mobile.blade.php | 6 + routes/web.php | 8 + tests/Feature/Controllers/RouteStatusTest.php | 4 + 32 files changed, 1049 insertions(+), 275 deletions(-) create mode 100644 app/Enums/AiModelCategoryEnum.php create mode 100644 app/Enums/AiModelLicenseEnum.php create mode 100644 app/Http/Controllers/Ai/AiIndexController.php create mode 100644 app/Http/Controllers/AiLlm/AiLlmIndexController.php create mode 100644 app/Models/AiModel.php create mode 100644 database/migrations/2026_07_21_134448_create_ai_models_table.php create mode 100644 database/seeders/Codebar/AiModelsTableSeeder.php create mode 100644 resources/views/app/ai-llm/index.blade.php create mode 100644 resources/views/app/ai/index.blade.php create mode 100644 resources/views/components/ai-llm/archive-row.blade.php create mode 100644 resources/views/components/ai-llm/archive-table.blade.php create mode 100644 resources/views/components/ai-llm/card.blade.php create mode 100644 resources/views/components/ai-llm/infra-row.blade.php create mode 100644 resources/views/components/ai-llm/model-row.blade.php diff --git a/app/Actions/ViewDataAction.php b/app/Actions/ViewDataAction.php index 403b1adf..aaed39f0 100644 --- a/app/Actions/ViewDataAction.php +++ b/app/Actions/ViewDataAction.php @@ -3,7 +3,9 @@ namespace App\Actions; use App\DTO\ContactDTO; +use App\Enums\AiModelCategoryEnum; use App\Enums\ContactSectionEnum; +use App\Models\AiModel; use App\Models\Configuration; use App\Models\Contact; use App\Models\News; @@ -62,6 +64,33 @@ public function technologies(string $locale): Collection }); } + public function aiModelGroups(): Collection + { + return Cache::rememberForever('ai_models_active', function () { + $categoryOrder = array_column(AiModelCategoryEnum::cases(), 'value'); + + return AiModel::whereNull('archived_at') + ->orderBy('order') + ->get() + ->sortBy(fn (AiModel $model) => array_search($model->category->value, $categoryOrder)) + ->groupBy(fn (AiModel $model) => $model->category->value); + }); + } + + public function aiModelArchive(): Collection + { + return Cache::rememberForever('ai_models_archived', function () { + $categoryOrder = array_column(AiModelCategoryEnum::cases(), 'value'); + + return AiModel::whereNotNull('archived_at') + ->with('replacedBy') + ->orderBy('order') + ->get() + ->sortBy(fn (AiModel $model) => array_search($model->category->value, $categoryOrder)) + ->groupBy(fn (AiModel $model) => $model->category->value); + }); + } + public function openSource(string $locale): Collection { $key = Str::slug("open_source_published_{$locale}"); diff --git a/app/Enums/AiModelCategoryEnum.php b/app/Enums/AiModelCategoryEnum.php new file mode 100644 index 00000000..18e88a76 --- /dev/null +++ b/app/Enums/AiModelCategoryEnum.php @@ -0,0 +1,20 @@ +value.'.title', locale: $locale); + } + + public function description(string $locale): string + { + return __('components.ai_llm.categories.'.$this->value.'.description', locale: $locale); + } +} diff --git a/app/Enums/AiModelLicenseEnum.php b/app/Enums/AiModelLicenseEnum.php new file mode 100644 index 00000000..ca0e2f5e --- /dev/null +++ b/app/Enums/AiModelLicenseEnum.php @@ -0,0 +1,29 @@ +key().'.label', locale: $locale); + } + + public function tooltip(string $locale): string + { + return __('components.ai_llm.licenses.'.$this->key().'.tooltip', locale: $locale); + } + + private function key(): string + { + return match ($this) { + self::MIT => 'mit', + self::APACHE_2_0 => 'apache', + self::GEMMA => 'gemma', + }; + } +} diff --git a/app/Http/Controllers/Ai/AiIndexController.php b/app/Http/Controllers/Ai/AiIndexController.php new file mode 100644 index 00000000..9a4ccf32 --- /dev/null +++ b/app/Http/Controllers/Ai/AiIndexController.php @@ -0,0 +1,17 @@ +with([ + 'page' => (new PageAction(locale: null, routeName: 'ai.index'))->default(), + ]); + } +} diff --git a/app/Http/Controllers/AiLlm/AiLlmIndexController.php b/app/Http/Controllers/AiLlm/AiLlmIndexController.php new file mode 100644 index 00000000..f11be3b7 --- /dev/null +++ b/app/Http/Controllers/AiLlm/AiLlmIndexController.php @@ -0,0 +1,22 @@ +with([ + 'page' => (new PageAction(locale: null, routeName: 'ai.llm.index'))->default(), + 'groups' => $viewData->aiModelGroups(), + 'archive' => $viewData->aiModelArchive(), + ]); + } +} diff --git a/app/Http/Controllers/Sitemap/SitemapController.php b/app/Http/Controllers/Sitemap/SitemapController.php index 42453516..f2926151 100644 --- a/app/Http/Controllers/Sitemap/SitemapController.php +++ b/app/Http/Controllers/Sitemap/SitemapController.php @@ -28,6 +28,8 @@ class SitemapController extends Controller 'media.index', 'legal.imprint.index', 'legal.privacy.index', + 'ai.index', + 'ai.llm.index', ]; protected const array DEFAULT_LOCALES = [ diff --git a/app/Models/AiModel.php b/app/Models/AiModel.php new file mode 100644 index 00000000..c638b85a --- /dev/null +++ b/app/Models/AiModel.php @@ -0,0 +1,29 @@ + AiModelCategoryEnum::class, + 'license' => AiModelLicenseEnum::class, + 'role' => 'json', + 'in_evaluation' => 'boolean', + 'archived_at' => 'date', + ]; + + public function replacedBy(): BelongsTo + { + return $this->belongsTo(AiModel::class, 'replaced_by_id'); + } + + public function localizedRole(string $locale): ?string + { + return $this->role[substr($locale, 0, 2)] ?? null; + } +} diff --git a/app/Security/Presets/MyCspPreset.php b/app/Security/Presets/MyCspPreset.php index 9f442eb8..de20ef22 100644 --- a/app/Security/Presets/MyCspPreset.php +++ b/app/Security/Presets/MyCspPreset.php @@ -12,7 +12,7 @@ class MyCspPreset implements Preset { public function configure(Policy $policy): void { - $cdnHost = parse_url((string) env('AWS_CDN_ENDPOINT', ''), PHP_URL_HOST); + $cdnHost = parse_url((string) config('filesystems.disks.s3.cdn_endpoint', ''), PHP_URL_HOST); $scriptSources = array_filter([ Keyword::SELF, diff --git a/app/Support/CloudinaryUrl.php b/app/Support/CloudinaryUrl.php index 8a5ec9b3..8e1ae47d 100644 --- a/app/Support/CloudinaryUrl.php +++ b/app/Support/CloudinaryUrl.php @@ -4,9 +4,9 @@ class CloudinaryUrl { - private const CLOUDINARY_HOST = 'res.cloudinary.com'; + private const string CLOUDINARY_HOST = 'res.cloudinary.com'; - private const UPLOAD_MARKER = '/image/upload/'; + private const string UPLOAD_MARKER = '/image/upload/'; public static function src(string $url, int $width): string { @@ -46,8 +46,7 @@ private static function stripExistingTransforms(string $path): string $segments = explode('/', $path); if ( - isset($segments[0]) - && preg_match('/^[a-z0-9_,.-]+$/', $segments[0]) + preg_match('/^[a-z0-9_,.-]+$/', $segments[0]) && preg_match('/[whcfq]_/', $segments[0]) ) { array_shift($segments); diff --git a/composer.lock b/composer.lock index 23968a9b..517a8319 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.386.2", + "version": "3.388.10", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "accda84af79689415782908dfa2b4a0fe434e0a1" + "reference": "25ea501f0540489bff6989cab9e7db1d12008aed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/accda84af79689415782908dfa2b4a0fe434e0a1", - "reference": "accda84af79689415782908dfa2b4a0fe434e0a1", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/25ea501f0540489bff6989cab9e7db1d12008aed", + "reference": "25ea501f0540489bff6989cab9e7db1d12008aed", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.386.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.388.10" }, - "time": "2026-06-29T18:07:20+00:00" + "time": "2026-07-20T18:06:03+00:00" }, { "name": "brick/math", @@ -1067,22 +1067,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.13.1", + "version": "7.15.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "55901a76dfd2006a0cc012b9e3c5b487f796478d" + "reference": "61443dfb33c62f308ee8add20f45b4d6e4bf8d2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/55901a76dfd2006a0cc012b9e3c5b487f796478d", - "reference": "55901a76dfd2006a0cc012b9e3c5b487f796478d", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/61443dfb33c62f308ee8add20f45b4d6e4bf8d2f", + "reference": "61443dfb33c62f308ee8add20f45b4d6e4bf8d2f", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.5", - "guzzlehttp/psr7": "^2.12.3", + "guzzlehttp/promises": "^2.5.1", + "guzzlehttp/psr7": "^2.13", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.5 || ^3.0", @@ -1094,8 +1094,8 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", - "guzzlehttp/test-server": "^0.6", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", "php-http/message-factory": "^1.1", "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" @@ -1175,7 +1175,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.13.1" + "source": "https://github.com/guzzle/guzzle/tree/7.15.1" }, "funding": [ { @@ -1191,20 +1191,20 @@ "type": "tidelift" } ], - "time": "2026-06-29T20:14:18+00:00" + "time": "2026-07-18T11:23:11+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.5.0", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "4360e982f87f5f258bf872d094647791db2f4c8e" + "reference": "9ad1e4fc607446a055b95870c7f668e93b5cff29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/4360e982f87f5f258bf872d094647791db2f4c8e", - "reference": "4360e982f87f5f258bf872d094647791db2f4c8e", + "url": "https://api.github.com/repos/guzzle/promises/zipball/9ad1e4fc607446a055b95870c7f668e93b5cff29", + "reference": "9ad1e4fc607446a055b95870c7f668e93b5cff29", "shasum": "" }, "require": { @@ -1259,7 +1259,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.5.0" + "source": "https://github.com/guzzle/promises/tree/2.5.1" }, "funding": [ { @@ -1275,20 +1275,20 @@ "type": "tidelift" } ], - "time": "2026-06-02T12:23:43+00:00" + "time": "2026-07-08T15:48:39+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.12.3", + "version": "2.13.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d" + "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/7ec62dc3f44aa218487dbed81a9bf9bc647be55d", - "reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/dad89620b7a6edb60c15858442eb2e408b45d8f4", + "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4", "shasum": "" }, "require": { @@ -1378,7 +1378,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.12.3" + "source": "https://github.com/guzzle/psr7/tree/2.13.0" }, "funding": [ { @@ -1394,20 +1394,20 @@ "type": "tidelift" } ], - "time": "2026-06-23T15:21:08+00:00" + "time": "2026-07-16T22:23:49+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.8", + "version": "v1.0.10", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "9c19128923b05a5d7355e5d2318d7808b7e33bbd" + "reference": "f6c24c21f42b990e9a58912b332d0874df6ba839" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/9c19128923b05a5d7355e5d2318d7808b7e33bbd", - "reference": "9c19128923b05a5d7355e5d2318d7808b7e33bbd", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/f6c24c21f42b990e9a58912b332d0874df6ba839", + "reference": "f6c24c21f42b990e9a58912b332d0874df6ba839", "shasum": "" }, "require": { @@ -1464,7 +1464,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.8" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.10" }, "funding": [ { @@ -1480,20 +1480,20 @@ "type": "tidelift" } ], - "time": "2026-06-23T13:02:23+00:00" + "time": "2026-07-17T13:53:03+00:00" }, { "name": "laravel/framework", - "version": "v12.62.0", + "version": "v12.64.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "f7e61eb1e0e06a38996802b769bce9127aec227c" + "reference": "727a8ea2949c23ca8b5316b86a00984b6017b7a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/f7e61eb1e0e06a38996802b769bce9127aec227c", - "reference": "f7e61eb1e0e06a38996802b769bce9127aec227c", + "url": "https://api.github.com/repos/laravel/framework/zipball/727a8ea2949c23ca8b5316b86a00984b6017b7a0", + "reference": "727a8ea2949c23ca8b5316b86a00984b6017b7a0", "shasum": "" }, "require": { @@ -1702,7 +1702,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-06-09T13:50:13+00:00" + "time": "2026-07-14T14:25:37+00:00" }, { "name": "laravel/prompts", @@ -1892,16 +1892,16 @@ }, { "name": "league/commonmark", - "version": "2.8.2", + "version": "2.8.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "59fb075d2101740c337c7216e3f32b36c204218b" + "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", - "reference": "59fb075d2101740c337c7216e3f32b36c204218b", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/1902f60f984235023acbe03db6ad614a37b3c3e7", + "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7", "shasum": "" }, "require": { @@ -1923,8 +1923,8 @@ "github/gfm": "0.29.0", "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", - "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "phpstan/phpstan": "^2.0.0", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", "scrutinizer/ocular": "^1.8.1", "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", @@ -1995,7 +1995,7 @@ "type": "tidelift" } ], - "time": "2026-03-19T13:16:38+00:00" + "time": "2026-07-12T15:29:16+00:00" }, { "name": "league/config", @@ -2081,16 +2081,16 @@ }, { "name": "league/flysystem", - "version": "3.35.1", + "version": "3.35.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "f23af6c5aafd958a7593029a271d77baf5ed793c" + "reference": "b277b5dc3d56650b68904117124e79c851e12376" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f23af6c5aafd958a7593029a271d77baf5ed793c", - "reference": "f23af6c5aafd958a7593029a271d77baf5ed793c", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/b277b5dc3d56650b68904117124e79c851e12376", + "reference": "b277b5dc3d56650b68904117124e79c851e12376", "shasum": "" }, "require": { @@ -2158,22 +2158,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.35.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.35.2" }, - "time": "2026-06-25T06:52:23+00:00" + "time": "2026-07-06T14:42:07+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.35.1", + "version": "3.35.2", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "3f93d3f4bd5c12a5dfb34a7267c40b1bc4587b94" + "reference": "8475ef9adfc6498b85469e2abec6fe3118cd08c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/3f93d3f4bd5c12a5dfb34a7267c40b1bc4587b94", - "reference": "3f93d3f4bd5c12a5dfb34a7267c40b1bc4587b94", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/8475ef9adfc6498b85469e2abec6fe3118cd08c4", + "reference": "8475ef9adfc6498b85469e2abec6fe3118cd08c4", "shasum": "" }, "require": { @@ -2213,9 +2213,9 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.35.1" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.35.2" }, - "time": "2026-06-25T06:51:08+00:00" + "time": "2026-07-01T23:25:49+00:00" }, { "name": "league/flysystem-local", @@ -2268,16 +2268,16 @@ }, { "name": "league/mime-type-detection", - "version": "1.16.0", + "version": "1.17.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76", "shasum": "" }, "require": { @@ -2287,7 +2287,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0" }, "type": "library", "autoload": { @@ -2308,7 +2308,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0" }, "funding": [ { @@ -2320,7 +2320,7 @@ "type": "tidelift" } ], - "time": "2024-09-21T08:32:55+00:00" + "time": "2026-07-09T11:49:27+00:00" }, { "name": "league/uri", @@ -2671,16 +2671,16 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.9.1", + "version": "2.9.2", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9c208ba27ae7d90853c288b3795d6702eb251d34" + "reference": "2157c5e50e813ec6a96c1eed3be7f64a20fb32a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9c208ba27ae7d90853c288b3795d6702eb251d34", - "reference": "9c208ba27ae7d90853c288b3795d6702eb251d34", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/2157c5e50e813ec6a96c1eed3be7f64a20fb32a8", + "reference": "2157c5e50e813ec6a96c1eed3be7f64a20fb32a8", "shasum": "" }, "require": { @@ -2731,22 +2731,22 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.9.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.9.2" }, - "time": "2026-06-11T10:43:56+00:00" + "time": "2026-07-06T18:56:19+00:00" }, { "name": "nesbot/carbon", - "version": "3.13.0", + "version": "3.13.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "40f6618f052df16b545f626fbf9a878e6497d16a" + "reference": "2937ad3d1d2c506fd2bc97d571438a95641f44e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/40f6618f052df16b545f626fbf9a878e6497d16a", - "reference": "40f6618f052df16b545f626fbf9a878e6497d16a", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/2937ad3d1d2c506fd2bc97d571438a95641f44e2", + "reference": "2937ad3d1d2c506fd2bc97d571438a95641f44e2", "shasum": "" }, "require": { @@ -2838,7 +2838,7 @@ "type": "tidelift" } ], - "time": "2026-06-18T13:49:15+00:00" + "time": "2026-07-09T18:23:49+00:00" }, { "name": "nette/schema", @@ -2909,16 +2909,16 @@ }, { "name": "nette/utils", - "version": "v4.1.4", + "version": "v4.1.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7" + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7", - "reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7", + "url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0", "shasum": "" }, "require": { @@ -2938,7 +2938,7 @@ }, "suggest": { "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-iconv": "to use Strings::chr(), ord() and reverse()", "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", @@ -2994,9 +2994,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.4" + "source": "https://github.com/nette/utils/tree/v4.1.5" }, - "time": "2026-05-11T20:49:54+00:00" + "time": "2026-07-17T23:02:45+00:00" }, { "name": "nicmart/tree", @@ -3054,20 +3054,19 @@ }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -3106,9 +3105,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "nunomaduro/termwind", @@ -8264,16 +8263,16 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.3", + "version": "v5.6.4", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "955e7815d677a3eaa7075231212f2110983adecc" + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", - "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/416df702837983f8d5ff48c9c3fee4f5f57b980b", + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b", "shasum": "" }, "require": { @@ -8332,7 +8331,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.4" }, "funding": [ { @@ -8344,7 +8343,7 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:49:13+00:00" + "time": "2026-07-06T19:11:50+00:00" }, { "name": "voku/portable-ascii", @@ -9279,23 +9278,23 @@ }, { "name": "nunomaduro/collision", - "version": "v8.9.4", + "version": "v8.9.5", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "716af8f95a470e9094cfca09ed897b023be191a5" + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/716af8f95a470e9094cfca09ed897b023be191a5", - "reference": "716af8f95a470e9094cfca09ed897b023be191a5", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/fb53eacd509a1d303858e2d20cfebf2d630254ec", + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec", "shasum": "" }, "require": { "filp/whoops": "^2.18.4", "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", - "symfony/console": "^7.4.8 || ^8.0.8" + "symfony/console": "^7.4.14 || ^8.1.1" }, "conflict": { "laravel/framework": "<11.48.0 || >=14.0.0", @@ -9303,12 +9302,12 @@ }, "require-dev": { "brianium/paratest": "^7.8.5", - "larastan/larastan": "^3.9.6", - "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.5.0", - "laravel/pint": "^1.29.1", - "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.2.1", - "pestphp/pest": "^3.8.5 || ^4.4.3 || ^5.0.0", - "sebastian/environment": "^7.2.1 || ^8.0.4 || ^9.3.0" + "larastan/larastan": "^3.10.0", + "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.20.0", + "laravel/pint": "^1.29.3", + "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.3.5", + "pestphp/pest": "^3.8.5 || ^4.7.5 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.1.2 || ^9.3.2" }, "type": "library", "extra": { @@ -9371,42 +9370,42 @@ "type": "patreon" } ], - "time": "2026-04-21T14:04:20+00:00" + "time": "2026-07-15T19:09:14+00:00" }, { "name": "pestphp/pest", - "version": "v3.8.6", + "version": "v3.8.7", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "8871a6f5ef1de8e7c8dee2a270991449a7b6af73" + "reference": "f108313b52e8c28dc7121ce34303f817a3790202" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/8871a6f5ef1de8e7c8dee2a270991449a7b6af73", - "reference": "8871a6f5ef1de8e7c8dee2a270991449a7b6af73", + "url": "https://api.github.com/repos/pestphp/pest/zipball/f108313b52e8c28dc7121ce34303f817a3790202", + "reference": "f108313b52e8c28dc7121ce34303f817a3790202", "shasum": "" }, "require": { "brianium/paratest": "^7.8.5", - "nunomaduro/collision": "^8.9.1", + "nunomaduro/collision": "^8.9.4", "nunomaduro/termwind": "^2.4.0", "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.1.1", "pestphp/pest-plugin-mutate": "^3.0.5", "php": "^8.2.0", - "phpunit/phpunit": "^11.5.50" + "phpunit/phpunit": "^11.5.56" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.50", + "phpunit/phpunit": ">11.5.56", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { "pestphp/pest-dev-tools": "^3.4.0", "pestphp/pest-plugin-type-coverage": "^3.6.1", - "symfony/process": "^7.4.5" + "symfony/process": "^7.4.13" }, "bin": [ "bin/pest" @@ -9471,7 +9470,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v3.8.6" + "source": "https://github.com/pestphp/pest/tree/v3.8.7" }, "funding": [ { @@ -9483,7 +9482,7 @@ "type": "github" } ], - "time": "2026-03-10T21:04:33+00:00" + "time": "2026-07-06T17:04:21+00:00" }, { "name": "pestphp/pest-plugin", @@ -10380,16 +10379,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.2", + "version": "2.3.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", "shasum": "" }, "require": { @@ -10421,9 +10420,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.3" }, - "time": "2026-01-25T14:56:51+00:00" + "time": "2026-07-08T07:01:06+00:00" }, { "name": "phpstan/phpstan", @@ -10832,31 +10831,31 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.50", + "version": "11.5.56", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f83edffa6967c3db468d48a695ec7bcb02e9256", + "reference": "5f83edffa6967c3db468d48a695ec7bcb02e9256", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-file-iterator": "^5.1.1", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", @@ -10868,6 +10867,7 @@ "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" @@ -10913,31 +10913,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.56" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2026-01-27T05:59:18+00:00" + "time": "2026-07-06T14:52:39+00:00" }, { "name": "sebastian/cli-parser", diff --git a/config/database.php b/config/database.php index 0b337404..5451a1b5 100644 --- a/config/database.php +++ b/config/database.php @@ -1,6 +1,7 @@ true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + (class_exists(Mysql::class) ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], diff --git a/database/migrations/2026_07_21_134448_create_ai_models_table.php b/database/migrations/2026_07_21_134448_create_ai_models_table.php new file mode 100644 index 00000000..8a8021eb --- /dev/null +++ b/database/migrations/2026_07_21_134448_create_ai_models_table.php @@ -0,0 +1,40 @@ +id(); + $table->string('category'); + $table->integer('order'); + $table->string('name'); + $table->string('provider')->nullable(); + $table->string('ram')->nullable(); + $table->string('license')->nullable(); + $table->json('role')->nullable(); + $table->boolean('in_evaluation')->default(false); + $table->string('link_label')->nullable(); + $table->string('link_url')->nullable(); + $table->date('archived_at')->nullable(); + $table->foreignId('replaced_by_id')->nullable()->constrained('ai_models')->nullOnDelete(); + $table->timestamps(); + $table->unique('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('ai_models'); + } +}; diff --git a/database/seeders/Codebar/AiModelsTableSeeder.php b/database/seeders/Codebar/AiModelsTableSeeder.php new file mode 100644 index 00000000..5802cd9f --- /dev/null +++ b/database/seeders/Codebar/AiModelsTableSeeder.php @@ -0,0 +1,233 @@ +active(); + $this->archived(); + } + + private function active(): void + { + $models = [ + // Reasoning & Coding + [ + 'category' => AiModelCategoryEnum::REASONING_CODING, + 'order' => 1, + 'name' => 'deepseek-v4:flash', + 'provider' => 'DeepSeek AI (CN)', + 'ram' => '~102 GB RAM', + 'license' => 'MIT', + 'role' => [ + 'de' => 'Flaggschiff: Analysen, Buchungslogik, komplexes Coding', + 'en' => 'Flagship: analysis, booking logic, complex coding', + ], + 'link_label' => 'Hugging Face', + 'link_url' => 'https://huggingface.co/unsloth', + ], + [ + 'category' => AiModelCategoryEnum::REASONING_CODING, + 'order' => 2, + 'name' => 'kimi-linear:48b', + 'provider' => 'Moonshot AI (CN)', + 'ram' => '30 GB RAM', + 'license' => 'MIT', + 'role' => [ + 'de' => 'Alltags-Sprinter, lange Dokumente & Kontextverständnis', + 'en' => 'Everyday sprinter, long documents & context understanding', + ], + 'link_label' => 'Hugging Face', + 'link_url' => 'https://huggingface.co/moonshotai', + ], + [ + 'category' => AiModelCategoryEnum::REASONING_CODING, + 'order' => 3, + 'name' => 'qwen3-coder:30b', + 'provider' => 'Alibaba / Qwen (CN)', + 'ram' => '18 GB RAM', + 'license' => 'Apache-2.0', + 'role' => [ + 'de' => 'Coding-Spezialist für schnelle Iterationen & Agenten-Tasks', + 'en' => 'Coding specialist for fast iterations & agent tasks', + ], + 'link_label' => 'Ollama', + 'link_url' => 'https://ollama.com/library/qwen3-coder', + ], + [ + 'category' => AiModelCategoryEnum::REASONING_CODING, + 'order' => 4, + 'name' => 'qwen3.6:35b', + 'provider' => 'Alibaba / Qwen (CN)', + 'ram' => '23 GB RAM', + 'license' => 'Apache-2.0', + 'role' => [ + 'de' => 'Reserve-Mittelklasse', + 'en' => 'Mid-range reserve', + ], + 'link_label' => 'Ollama', + 'link_url' => 'https://ollama.com/library/qwen3.6', + ], + + // Vision & Dokumente + [ + 'category' => AiModelCategoryEnum::VISION_DOCUMENTS, + 'order' => 1, + 'name' => 'qwen3-vl:32b', + 'provider' => 'Alibaba / Qwen (CN)', + 'ram' => '20 GB RAM', + 'license' => 'Apache-2.0', + 'role' => [ + 'de' => 'Detailliertes Bildverständnis: Screenshots, Diagramme, Belege', + 'en' => 'Detailed image understanding: screenshots, diagrams, receipts', + ], + 'link_label' => 'Ollama', + 'link_url' => 'https://ollama.com/library/qwen3-vl', + ], + [ + 'category' => AiModelCategoryEnum::VISION_DOCUMENTS, + 'order' => 2, + 'name' => 'qwen3-vl:8b', + 'provider' => 'Alibaba / Qwen (CN)', + 'ram' => '6,1 GB RAM', + 'license' => 'Apache-2.0', + 'role' => [ + 'de' => 'Schnelle Vision-Variante für einfache Bildaufgaben', + 'en' => 'Fast vision variant for simple image tasks', + ], + 'link_label' => 'Ollama', + 'link_url' => 'https://ollama.com/library/qwen3-vl', + ], + [ + 'category' => AiModelCategoryEnum::VISION_DOCUMENTS, + 'order' => 3, + 'name' => 'gemma4:31b', + 'provider' => 'Google', + 'ram' => '19 GB RAM', + 'license' => 'Gemma', + 'role' => [ + 'de' => 'Bild-Input + stilsichere Texte', + 'en' => 'Image input + polished writing', + ], + 'link_label' => 'Ollama', + 'link_url' => 'https://ollama.com/library/gemma4', + ], + [ + 'category' => AiModelCategoryEnum::VISION_DOCUMENTS, + 'order' => 4, + 'name' => 'deepseek-ocr', + 'provider' => 'DeepSeek AI (CN)', + 'ram' => '6,7 GB RAM', + 'license' => 'MIT', + 'role' => [ + 'de' => 'PDF/Scan → Markdown', + 'en' => 'PDF/scan → Markdown', + ], + 'link_label' => 'Ollama', + 'link_url' => 'https://ollama.com/library/deepseek-ocr', + ], + + // Retrieval & Suche + [ + 'category' => AiModelCategoryEnum::RETRIEVAL_SEARCH, + 'order' => 1, + 'name' => 'qwen3-embedding:8b', + 'provider' => 'Alibaba / Qwen (CN)', + 'ram' => '4,7 GB RAM', + 'license' => 'Apache-2.0', + 'role' => [ + 'de' => 'Vektoren für Ähnlichkeitssuche (4096 Dimensionen)', + 'en' => 'Vectors for similarity search (4096 dimensions)', + ], + 'link_label' => 'Ollama', + 'link_url' => 'https://ollama.com/library/qwen3-embedding', + ], + ]; + + foreach ($models as $model) { + AiModel::updateOrCreate( + ['name' => $model['name']], + array_merge(['in_evaluation' => false, 'archived_at' => null, 'replaced_by_id' => null], $model) + ); + } + } + + private function archived(): void + { + $archivedAt = '2026-07-01'; + + $models = [ + [ + 'category' => AiModelCategoryEnum::REASONING_CODING, + 'order' => 1, + 'name' => 'qwen3.5:122b', + 'replaced_by' => 'deepseek-v4:flash', + ], + [ + 'category' => AiModelCategoryEnum::REASONING_CODING, + 'order' => 2, + 'name' => 'qwen3.6:35b-a3b', + 'replaced_by' => 'kimi-linear:48b', + ], + [ + 'category' => AiModelCategoryEnum::REASONING_CODING, + 'order' => 3, + 'name' => 'qwen3.6:27b', + 'replaced_by' => 'qwen3.6:35b', + ], + [ + 'category' => AiModelCategoryEnum::REASONING_CODING, + 'order' => 4, + 'name' => 'qwen3.5:9b', + 'replaced_by' => 'kimi-linear:48b', + ], + [ + 'category' => AiModelCategoryEnum::REASONING_CODING, + 'order' => 5, + 'name' => 'qwen3.5:4b', + 'replaced_by' => 'kimi-linear:48b', + ], + [ + 'category' => AiModelCategoryEnum::VISION_DOCUMENTS, + 'order' => 1, + 'name' => 'gemma4:12b', + 'replaced_by' => 'qwen3-vl:8b', + ], + [ + 'category' => AiModelCategoryEnum::VISION_DOCUMENTS, + 'order' => 2, + 'name' => 'gemma4:e4b', + 'replaced_by' => 'qwen3-vl:8b', + ], + [ + 'category' => AiModelCategoryEnum::RETRIEVAL_SEARCH, + 'order' => 1, + 'name' => 'qwen3-embedding:4b', + 'replaced_by' => 'qwen3-embedding:8b', + ], + [ + 'category' => AiModelCategoryEnum::RETRIEVAL_SEARCH, + 'order' => 2, + 'name' => 'qwen3-reranker:8b', + 'replaced_by' => null, + ], + ]; + + foreach ($models as $model) { + $replacedByName = Arr::pull($model, 'replaced_by'); + $replacedById = $replacedByName ? AiModel::where('name', $replacedByName)->value('id') : null; + + AiModel::updateOrCreate( + ['name' => $model['name']], + array_merge(['archived_at' => $archivedAt, 'replaced_by_id' => $replacedById], $model) + ); + } + } +} diff --git a/database/seeders/Codebar/PagesTableSeeder.php b/database/seeders/Codebar/PagesTableSeeder.php index 42a10ca1..2ceeca45 100644 --- a/database/seeders/Codebar/PagesTableSeeder.php +++ b/database/seeders/Codebar/PagesTableSeeder.php @@ -128,6 +128,30 @@ private function enCH() 'description' => 'Download official codebar logos and brand assets for press and partner use.', ] ); + + Page::updateOrCreate( + [ + 'key' => 'ai.index', + 'locale' => $locale, + ], + [ + 'robots' => 'index,follow', + 'title' => 'AI at codebar', + 'description' => 'How we use artificial intelligence in our own work — from the local models we run to the infrastructure behind them.', + ] + ); + + Page::updateOrCreate( + [ + 'key' => 'ai.llm.index', + 'locale' => $locale, + ], + [ + 'robots' => 'index,follow', + 'title' => 'Our local LLMs in action', + 'description' => 'These are the local open-source models we currently rely on — all of them run on our own infrastructure, in our office.', + ] + ); } private function deCH() @@ -241,5 +265,29 @@ private function deCH() 'description' => 'Offizielle codebar-Logos und Markenassets für Presse und Partner.', ] ); + + Page::updateOrCreate( + [ + 'key' => 'ai.index', + 'locale' => $locale, + ], + [ + 'robots' => 'index,follow', + 'title' => 'KI bei codebar', + 'description' => 'Wie wir künstliche Intelligenz in unserer eigenen Arbeit einsetzen — von den lokalen Modellen, die wir betreiben, bis zur Infrastruktur dahinter.', + ] + ); + + Page::updateOrCreate( + [ + 'key' => 'ai.llm.index', + 'locale' => $locale, + ], + [ + 'robots' => 'index,follow', + 'title' => 'Unsere lokalen LLMs im Einsatz', + 'description' => 'Auf diese lokalen Open-Source-Modelle setzen wir aktuell — sie laufen alle auf unserer eigenen Infrastruktur, im hauseigenen Bürokeller.', + ] + ); } } diff --git a/database/seeders/CodebarSeeder.php b/database/seeders/CodebarSeeder.php index 563f8bcc..c7c57a06 100644 --- a/database/seeders/CodebarSeeder.php +++ b/database/seeders/CodebarSeeder.php @@ -2,6 +2,7 @@ namespace Database\Seeders; +use Database\Seeders\Codebar\AiModelsTableSeeder; use Database\Seeders\Codebar\ConfigurationsTableSeeder; use Database\Seeders\Codebar\ContactsTableSeeder; use Database\Seeders\Codebar\OpenSourceTableSeeder; @@ -23,6 +24,7 @@ public function run(): void $this->call(ContactsTableSeeder::class); // $this->call(OpenSourceTableSeeder::class); $this->call(TechnologiesTableSeeder::class); + $this->call(AiModelsTableSeeder::class); if (app()->isLocal()) { Artisan::call(ClearCommand::class); diff --git a/lang/de_CH.json b/lang/de_CH.json index dd1c5d4b..0403f2ed 100644 --- a/lang/de_CH.json +++ b/lang/de_CH.json @@ -1,6 +1,7 @@ { "+41 61 515 60 90": "+41 61 515 60 90", "About us": "Über uns", + "AI": "KI", "Author": "Autor", "Author: :name": "Autor: :name", "Board of directors": "Verwaltungsrat", diff --git a/lang/de_CH/components.php b/lang/de_CH/components.php index 798c402e..524a067c 100644 --- a/lang/de_CH/components.php +++ b/lang/de_CH/components.php @@ -34,4 +34,79 @@ 'title' => 'Interessiert?', 'teaser' => 'Lassen Sie uns sprechen.', ], + 'ai' => [ + 'title' => 'KI bei codebar', + 'intro' => 'Wie wir künstliche Intelligenz in unserer eigenen Arbeit einsetzen — von den lokalen Modellen, die wir betreiben, bis zur Infrastruktur dahinter.', + 'llm_teaser' => 'Die lokalen Open-Source-Sprachmodelle, auf die wir setzen, und die Infrastruktur, die sie betreibt.', + 'more_info' => 'Mehr erfahren', + ], + 'ai_llm' => [ + 'title' => 'Unsere lokalen LLMs im Einsatz', + 'intro' => 'Auf diese lokalen Open-Source-Modelle setzen wir aktuell. Wir versuchen, so viel wie möglich unserer KI-Arbeit mit lokalen Modellen abzubilden — alle laufen auf unserer eigenen Infrastruktur, im hauseigenen Bürokeller.', + 'categories' => [ + 'reasoning_coding' => [ + 'title' => 'Reasoning & Coding', + 'description' => 'Die Denker: komplexe Analysen, Geschäftslogik und Unterstützung beim Programmieren.', + ], + 'vision_documents' => [ + 'title' => 'Vision & Dokumente', + 'description' => 'Die Augen: Bilder verstehen und gescannte Dokumente in verwertbaren Text verwandeln.', + ], + 'retrieval_search' => [ + 'title' => 'Retrieval & Suche', + 'description' => 'Das Gedächtnis: findet in grossen Datenbeständen die passenden Inhalte.', + ], + ], + 'tooltips' => [ + 'provider' => 'Wer dieses Modell entwickelt', + 'ram' => 'Arbeitsspeicher, den das Modell auf unserem Server benötigt', + 'status' => 'Dieses Modell testen wir derzeit', + 'link' => 'Woher wir das Modell beziehen', + ], + 'status_label' => 'in Evaluation', + 'licenses' => [ + 'mit' => [ + 'label' => 'MIT', + 'tooltip' => 'MIT-Lizenz: sehr freizügige Open-Source-Lizenz — freie Nutzung, auch kommerziell', + ], + 'apache' => [ + 'label' => 'Apache 2.0', + 'tooltip' => 'Apache 2.0: Open Source mit Patentschutz — freie Nutzung, auch kommerziell', + ], + 'gemma' => [ + 'label' => 'Gemma-Lizenz', + 'tooltip' => 'Googles eigene Lizenz mit Nutzungsbedingungen', + ], + ], + 'infrastructure' => [ + 'title' => 'Unsere Infrastruktur', + 'intro' => 'Unsere Modelle laufen vollständig auf eigener, lokaler Infrastruktur — bei uns im Büro, nicht in der Cloud. Alle Daten bleiben im Haus.', + 'items' => [ + 'hardware' => [ + 'label' => 'Hardware', + 'text' => 'MacBook Pro 16" M5 Max, 128 GB RAM.', + ], + 'management' => [ + 'label' => 'Modellverwaltung', + 'text' => 'LiteLLM Studio für Verwaltung und Autorisierung, Ollama betreibt die Modelle.', + ], + 'access' => [ + 'label' => 'Zugang & Sicherheit', + 'text' => 'Cloudinary-Tunnel auf das lokale MacBook.', + ], + 'power' => [ + 'label' => 'Strom', + 'text' => 'USV Ubiquiti UniFi.', + ], + ], + ], + 'archive' => [ + 'title' => 'Archiv', + 'intro' => 'Modelle, die wir inzwischen abgelöst haben — der Vollständigkeit halber.', + 'columns' => [ + 'model' => 'Altes Modell', + 'replaced_by' => 'Abgelöst durch', + ], + ], + ], ]; diff --git a/lang/en_CH.json b/lang/en_CH.json index a1a1a49e..89c88a2c 100644 --- a/lang/en_CH.json +++ b/lang/en_CH.json @@ -1,6 +1,7 @@ { "+41 61 515 60 90": "+41 61 515 60 90", "About us": "About us", + "AI": "AI", "Author": "Author", "Author: :name": "Author: :name", "Board of directors": "Board of directors", diff --git a/lang/en_CH/components.php b/lang/en_CH/components.php index 4c44a69b..45d797a7 100644 --- a/lang/en_CH/components.php +++ b/lang/en_CH/components.php @@ -32,4 +32,79 @@ 'title' => 'Interested?', 'teaser' => 'Let\'s talk.', ], + 'ai' => [ + 'title' => 'AI at codebar', + 'intro' => 'How we use artificial intelligence in our own work — from the local models we run to the infrastructure behind them.', + 'llm_teaser' => 'The local open-source language models we rely on and the infrastructure that runs them.', + 'more_info' => 'More information', + ], + 'ai_llm' => [ + 'title' => 'Our local LLMs in action', + 'intro' => 'These are the local open-source models we currently rely on. We aim to cover as much of our AI work as possible with local models — all of them run on our own infrastructure, in our very own office basement.', + 'categories' => [ + 'reasoning_coding' => [ + 'title' => 'Reasoning & coding', + 'description' => 'The thinkers: complex analysis, business logic and help with programming.', + ], + 'vision_documents' => [ + 'title' => 'Vision & documents', + 'description' => 'The eyes: understanding images and turning scanned documents into usable text.', + ], + 'retrieval_search' => [ + 'title' => 'Retrieval & search', + 'description' => 'The memory: finds the right content in large data sets.', + ], + ], + 'tooltips' => [ + 'provider' => 'Who develops this model', + 'ram' => 'Memory the model needs on our server', + 'status' => 'We are currently evaluating this model', + 'link' => 'Where we get the model from', + ], + 'status_label' => 'evaluating', + 'licenses' => [ + 'mit' => [ + 'label' => 'MIT', + 'tooltip' => 'MIT license: very permissive open source — free to use, including commercially', + ], + 'apache' => [ + 'label' => 'Apache 2.0', + 'tooltip' => 'Apache 2.0: open source with patent grant — free to use, including commercially', + ], + 'gemma' => [ + 'label' => 'Gemma license', + 'tooltip' => 'Google\'s own license with usage terms', + ], + ], + 'infrastructure' => [ + 'title' => 'Our infrastructure', + 'intro' => 'Our models run entirely on our own local infrastructure — in our office, not in the cloud. All data stays in-house.', + 'items' => [ + 'hardware' => [ + 'label' => 'Hardware', + 'text' => 'MacBook Pro 16" M5 Max, 128 GB RAM.', + ], + 'management' => [ + 'label' => 'Model management', + 'text' => 'LiteLLM Studio for management and authorization, Ollama runs the models.', + ], + 'access' => [ + 'label' => 'Access & security', + 'text' => 'Cloudinary tunnel to the local MacBook.', + ], + 'power' => [ + 'label' => 'Power', + 'text' => 'UPS Ubiquiti UniFi.', + ], + ], + ], + 'archive' => [ + 'title' => 'Archive', + 'intro' => 'Models we have since replaced — kept here for the record.', + 'columns' => [ + 'model' => 'Old model', + 'replaced_by' => 'Replaced by', + ], + ], + ], ]; diff --git a/package-lock.json b/package-lock.json index 871b031b..da2e635b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -852,47 +852,47 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", - "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "5.21.6", + "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.2" + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", - "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-x64": "4.3.2", - "@tailwindcss/oxide-freebsd-x64": "4.3.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-x64-musl": "4.3.2", - "@tailwindcss/oxide-wasm32-wasi": "4.3.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", - "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], @@ -906,9 +906,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", - "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], @@ -922,9 +922,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", - "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], @@ -938,9 +938,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", - "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], @@ -954,9 +954,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", - "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], @@ -970,9 +970,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", - "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], @@ -986,9 +986,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", - "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], @@ -1002,9 +1002,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", - "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], @@ -1018,9 +1018,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", - "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], @@ -1034,9 +1034,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", - "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -1063,9 +1063,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", - "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], @@ -1079,9 +1079,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", - "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -1095,16 +1095,16 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.2.tgz", - "integrity": "sha512-rjVWYCa7Ngbi5AarT6k8TkxUG3Wl1QKzHdIZVsjZSzf36Jmo2IKZt/NHRAwly8oDkbBOH0YTu+CHuf9jPxMc+g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "postcss": "^8.5.15", - "tailwindcss": "4.3.2" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/typography": { @@ -1120,14 +1120,14 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", - "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "tailwindcss": "4.3.2" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" @@ -1210,9 +1210,9 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz", - "integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==", + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", "funding": [ { "type": "opencollective", @@ -1229,8 +1229,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.4", - "caniuse-lite": "^1.0.30001799", + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -1258,9 +1258,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.40", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", - "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "version": "2.10.44", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.44.tgz", + "integrity": "sha512-T3ghW+sl/ZJ8w1v/yQx3qvJ9040DWoLBz8JT/CILbAKcFyG9b2MRe75v6W5uXjv6uH1lumK2Kv46y2zSkcej0Q==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -1270,9 +1270,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "funding": [ { "type": "opencollective", @@ -1289,10 +1289,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -1323,9 +1323,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001800", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", - "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "funding": [ { "type": "opencollective", @@ -1422,14 +1422,14 @@ "license": "MIT" }, "node_modules/concurrently": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.3.tgz", - "integrity": "sha512-ihjs0E2SxvDgq/MK418hX6YycQgKhsqxpbZuZbHo0yKfqDWdymWMjWYIpCIzqDDLLKClHlXev8whW/8WXmJ0BA==", + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", "license": "MIT", "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", - "shell-quote": "1.8.4", + "shell-quote": "1.9.0", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" @@ -1507,9 +1507,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.382", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.382.tgz", - "integrity": "sha512-8ETaWbV6SZOrno+G93Ffd9ENsMtetqdnqj4nlfxFW90Sm5GgnuV28Kf62hqQVD6VUgzm7qFQKsTsAPmeUiU3Ug==", + "version": "1.5.394", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.394.tgz", + "integrity": "sha512-Wmt2Gm0o8JWBuGgmc4XZ0u9s1RaCRqhxP47phplmfg04+qypTUurpeJGP45A7Fhv7jdrrVH44PLlR9qXo37cVQ==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -1519,9 +1519,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", - "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "version": "5.24.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", + "integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -2181,9 +2181,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -2199,9 +2199,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "license": "MIT", "engines": { "node": ">=18" @@ -2214,9 +2214,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { "node": ">=12" @@ -2226,9 +2226,9 @@ } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.21.tgz", + "integrity": "sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ==", "funding": [ { "type": "opencollective", @@ -2245,7 +2245,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2344,9 +2344,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", - "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -2427,9 +2427,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", - "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "license": "MIT" }, "node_modules/tapable": { @@ -2446,9 +2446,9 @@ } }, "node_modules/terser": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", - "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "devOptional": true, "license": "BSD-2-Clause", "dependencies": { diff --git a/resources/views/app/ai-llm/index.blade.php b/resources/views/app/ai-llm/index.blade.php new file mode 100644 index 00000000..b28bde12 --- /dev/null +++ b/resources/views/app/ai-llm/index.blade.php @@ -0,0 +1,65 @@ +@php + $locale = app()->getLocale(); +@endphp + + + +

{{ __('components.ai_llm.intro') }}

+ + @foreach ($groups as $category => $models) + @php + $categoryEnum = \App\Enums\AiModelCategoryEnum::from($category); + @endphp + + +

{{ $categoryEnum->description($locale) }}

+ + @foreach ($models as $model) + + @endforeach +
+ @endforeach + +
+ +

{{ __('components.ai_llm.infrastructure.intro') }}

+ + + + +
+ + +
+
+ + + + +
+ + + +

{{ __('components.ai_llm.archive.intro') }}

+ + @foreach ($archive as $category => $models) + @php + $categoryEnum = \App\Enums\AiModelCategoryEnum::from($category); + @endphp +
+ + +
+ @endforeach +
+
diff --git a/resources/views/app/ai/index.blade.php b/resources/views/app/ai/index.blade.php new file mode 100644 index 00000000..7f10f66d --- /dev/null +++ b/resources/views/app/ai/index.blade.php @@ -0,0 +1,11 @@ + + +

{{ __('components.ai.intro') }}

+ +
+ +

{{ __('components.ai.llm_teaser') }}

+ +
+
diff --git a/resources/views/app/start/index.blade.php b/resources/views/app/start/index.blade.php index 3ca54a8d..a17104e9 100644 --- a/resources/views/app/start/index.blade.php +++ b/resources/views/app/start/index.blade.php @@ -4,6 +4,15 @@ + + + + + + @if($configuration?->section_news) diff --git a/resources/views/components/ai-llm/archive-row.blade.php b/resources/views/components/ai-llm/archive-row.blade.php new file mode 100644 index 00000000..25151e9c --- /dev/null +++ b/resources/views/components/ai-llm/archive-row.blade.php @@ -0,0 +1,6 @@ +@props(['model']) + +
+
{{ $model->name }}
+
{{ $model->replacedBy?->name }}
+
diff --git a/resources/views/components/ai-llm/archive-table.blade.php b/resources/views/components/ai-llm/archive-table.blade.php new file mode 100644 index 00000000..aaacd181 --- /dev/null +++ b/resources/views/components/ai-llm/archive-table.blade.php @@ -0,0 +1,12 @@ +@props(['models']) + +
+
+
{{ __('components.ai_llm.archive.columns.model') }}
+
{{ __('components.ai_llm.archive.columns.replaced_by') }}
+
+ + @foreach ($models as $model) + + @endforeach +
diff --git a/resources/views/components/ai-llm/card.blade.php b/resources/views/components/ai-llm/card.blade.php new file mode 100644 index 00000000..0b27c314 --- /dev/null +++ b/resources/views/components/ai-llm/card.blade.php @@ -0,0 +1,5 @@ +@props(['id' => null]) + +
+ {{ $slot }} +
diff --git a/resources/views/components/ai-llm/infra-row.blade.php b/resources/views/components/ai-llm/infra-row.blade.php new file mode 100644 index 00000000..49368b6b --- /dev/null +++ b/resources/views/components/ai-llm/infra-row.blade.php @@ -0,0 +1,9 @@ +@props(['label', 'text']) + +
+
{{ $label }}
+
+ {{ $text }} + {{ $slot }} +
+
diff --git a/resources/views/components/ai-llm/model-row.blade.php b/resources/views/components/ai-llm/model-row.blade.php new file mode 100644 index 00000000..e58b80a5 --- /dev/null +++ b/resources/views/components/ai-llm/model-row.blade.php @@ -0,0 +1,28 @@ +@props(['model']) + +@php + $locale = app()->getLocale(); +@endphp + +
+
+
{{ $model->name }}
+
{{ $model->localizedRole($locale) }}
+
+ + + + @if($model->in_evaluation) + + @endif +
+
+
+ +
+
diff --git a/resources/views/layouts/_partials/_navigation_desktop.blade.php b/resources/views/layouts/_partials/_navigation_desktop.blade.php index a9e5a21d..a353bd3b 100644 --- a/resources/views/layouts/_partials/_navigation_desktop.blade.php +++ b/resources/views/layouts/_partials/_navigation_desktop.blade.php @@ -36,6 +36,10 @@ | @endif + + + | + diff --git a/resources/views/layouts/_partials/_navigation_mobile.blade.php b/resources/views/layouts/_partials/_navigation_mobile.blade.php index e0556e16..d425ce8c 100644 --- a/resources/views/layouts/_partials/_navigation_mobile.blade.php +++ b/resources/views/layouts/_partials/_navigation_mobile.blade.php @@ -65,6 +65,12 @@ class="block text-base hover:text-brand hover:font-semibold transition"> @endif + + + {{ __('AI') }} + +
name('open-source.index'); Route::get('open-source-contributions/{locale}/{openSource}', OpenSoruceShowController::class)->name('open-source.show'); + Route::get('ai', AiIndexController::class)->name('ai.index'); + Route::get('ai/llm', AiLlmIndexController::class)->name('ai.llm.index'); + Route::get('legal/privacy', PrivacyIndexController::class)->name('legal.privacy.index'); Route::get('legal/imprint', ImprintIndexController::class)->name('legal.imprint.index'); Route::get('legal/terms', TermsIndexController::class)->name('legal.terms.index'); @@ -76,6 +81,9 @@ Route::get('open-source-beitraege', OpenSourceIndexController::class)->name('open-source.index'); Route::get('open-source-beitraege/{locale}/{openSource}', OpenSoruceShowController::class)->name('open-source.show'); + Route::get('ki', AiIndexController::class)->name('ai.index'); + Route::get('ki/llm', AiLlmIndexController::class)->name('ai.llm.index'); + Route::get('rechtlichtes/datenschutz', PrivacyIndexController::class)->name('legal.privacy.index'); Route::get('rechtlichtes/impressum', ImprintIndexController::class)->name('legal.imprint.index'); Route::get('rechtlichtes/geschaeftsbedingungen', TermsIndexController::class)->name('legal.terms.index'); diff --git a/tests/Feature/Controllers/RouteStatusTest.php b/tests/Feature/Controllers/RouteStatusTest.php index fda1ac92..1c35647a 100644 --- a/tests/Feature/Controllers/RouteStatusTest.php +++ b/tests/Feature/Controllers/RouteStatusTest.php @@ -20,6 +20,8 @@ [LocaleEnum::EN->value, 'legal.privacy.index'], [LocaleEnum::EN->value, 'media.index'], [LocaleEnum::EN->value, 'contact.index'], + [LocaleEnum::EN->value, 'ai.index'], + [LocaleEnum::EN->value, 'ai.llm.index'], // DE-CH [LocaleEnum::DE->value, 'start.index'], @@ -33,6 +35,8 @@ [LocaleEnum::DE->value, 'legal.privacy.index'], [LocaleEnum::DE->value, 'media.index'], [LocaleEnum::DE->value, 'contact.index'], + [LocaleEnum::DE->value, 'ai.index'], + [LocaleEnum::DE->value, 'ai.llm.index'], ]; }); From 0225c28f9ea4b3c531838aea022703f92c9499fd Mon Sep 17 00:00:00 2001 From: "Sebastian BURGIN-FIX (ext)" Date: Tue, 21 Jul 2026 14:25:48 +0200 Subject: [PATCH 2/4] wip --- app/Enums/AiModelLicenseEnum.php | 29 ------------------- app/Models/AiModel.php | 22 ++++++++++++-- ...26_07_21_134448_create_ai_models_table.php | 1 - .../seeders/Codebar/AiModelsTableSeeder.php | 2 +- lang/de_CH/components.php | 2 -- lang/en_CH/components.php | 2 -- .../components/ai-llm/model-row.blade.php | 5 +--- 7 files changed, 21 insertions(+), 42 deletions(-) delete mode 100644 app/Enums/AiModelLicenseEnum.php diff --git a/app/Enums/AiModelLicenseEnum.php b/app/Enums/AiModelLicenseEnum.php deleted file mode 100644 index ca0e2f5e..00000000 --- a/app/Enums/AiModelLicenseEnum.php +++ /dev/null @@ -1,29 +0,0 @@ -key().'.label', locale: $locale); - } - - public function tooltip(string $locale): string - { - return __('components.ai_llm.licenses.'.$this->key().'.tooltip', locale: $locale); - } - - private function key(): string - { - return match ($this) { - self::MIT => 'mit', - self::APACHE_2_0 => 'apache', - self::GEMMA => 'gemma', - }; - } -} diff --git a/app/Models/AiModel.php b/app/Models/AiModel.php index c638b85a..0ca18b94 100644 --- a/app/Models/AiModel.php +++ b/app/Models/AiModel.php @@ -3,7 +3,6 @@ namespace App\Models; use App\Enums\AiModelCategoryEnum; -use App\Enums\AiModelLicenseEnum; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -11,9 +10,7 @@ class AiModel extends Model { protected $casts = [ 'category' => AiModelCategoryEnum::class, - 'license' => AiModelLicenseEnum::class, 'role' => 'json', - 'in_evaluation' => 'boolean', 'archived_at' => 'date', ]; @@ -26,4 +23,23 @@ public function localizedRole(string $locale): ?string { return $this->role[substr($locale, 0, 2)] ?? null; } + + public function licenseLabel(string $locale): ?string + { + return $this->license ? __('components.ai_llm.licenses.'.$this->licenseKey().'.label', locale: $locale) : null; + } + + public function licenseTooltip(string $locale): ?string + { + return $this->license ? __('components.ai_llm.licenses.'.$this->licenseKey().'.tooltip', locale: $locale) : null; + } + + private function licenseKey(): string + { + return match ($this->license) { + 'MIT' => 'mit', + 'Apache-2.0' => 'apache', + 'Gemma' => 'gemma', + }; + } } diff --git a/database/migrations/2026_07_21_134448_create_ai_models_table.php b/database/migrations/2026_07_21_134448_create_ai_models_table.php index 8a8021eb..17429e11 100644 --- a/database/migrations/2026_07_21_134448_create_ai_models_table.php +++ b/database/migrations/2026_07_21_134448_create_ai_models_table.php @@ -20,7 +20,6 @@ public function up(): void $table->string('ram')->nullable(); $table->string('license')->nullable(); $table->json('role')->nullable(); - $table->boolean('in_evaluation')->default(false); $table->string('link_label')->nullable(); $table->string('link_url')->nullable(); $table->date('archived_at')->nullable(); diff --git a/database/seeders/Codebar/AiModelsTableSeeder.php b/database/seeders/Codebar/AiModelsTableSeeder.php index 5802cd9f..2d6ec343 100644 --- a/database/seeders/Codebar/AiModelsTableSeeder.php +++ b/database/seeders/Codebar/AiModelsTableSeeder.php @@ -154,7 +154,7 @@ private function active(): void foreach ($models as $model) { AiModel::updateOrCreate( ['name' => $model['name']], - array_merge(['in_evaluation' => false, 'archived_at' => null, 'replaced_by_id' => null], $model) + array_merge(['archived_at' => null, 'replaced_by_id' => null], $model) ); } } diff --git a/lang/de_CH/components.php b/lang/de_CH/components.php index 524a067c..2a7916bb 100644 --- a/lang/de_CH/components.php +++ b/lang/de_CH/components.php @@ -60,10 +60,8 @@ 'tooltips' => [ 'provider' => 'Wer dieses Modell entwickelt', 'ram' => 'Arbeitsspeicher, den das Modell auf unserem Server benötigt', - 'status' => 'Dieses Modell testen wir derzeit', 'link' => 'Woher wir das Modell beziehen', ], - 'status_label' => 'in Evaluation', 'licenses' => [ 'mit' => [ 'label' => 'MIT', diff --git a/lang/en_CH/components.php b/lang/en_CH/components.php index 45d797a7..def29e18 100644 --- a/lang/en_CH/components.php +++ b/lang/en_CH/components.php @@ -58,10 +58,8 @@ 'tooltips' => [ 'provider' => 'Who develops this model', 'ram' => 'Memory the model needs on our server', - 'status' => 'We are currently evaluating this model', 'link' => 'Where we get the model from', ], - 'status_label' => 'evaluating', 'licenses' => [ 'mit' => [ 'label' => 'MIT', diff --git a/resources/views/components/ai-llm/model-row.blade.php b/resources/views/components/ai-llm/model-row.blade.php index e58b80a5..0cc8585b 100644 --- a/resources/views/components/ai-llm/model-row.blade.php +++ b/resources/views/components/ai-llm/model-row.blade.php @@ -11,10 +11,7 @@
- - @if($model->in_evaluation) - - @endif +
From f9177a5ab536347dbb51bd69b070d861b4252959 Mon Sep 17 00:00:00 2001 From: "Sebastian BURGIN-FIX (ext)" Date: Tue, 21 Jul 2026 14:55:00 +0200 Subject: [PATCH 3/4] wip --- app/Actions/ViewDataAction.php | 32 +++++----- app/Enums/AiModelCategoryEnum.php | 8 +-- .../{AiLlm => Ai}/AiLlmIndexController.php | 4 +- app/Models/AiModel.php | 19 +++--- database/seeders/Codebar/PagesTableSeeder.php | 2 +- lang/de_CH/components.php | 6 +- lang/en_CH/components.php | 6 +- resources/views/app/ai/index.blade.php | 6 +- .../app/{ai-llm => ai/llm}/index.blade.php | 60 +++++++++---------- resources/views/app/start/index.blade.php | 2 +- resources/views/components/a-badge.blade.php | 11 +++- .../components/ai-llm/archive-row.blade.php | 4 +- .../components/ai-llm/archive-table.blade.php | 2 +- .../views/components/ai-llm/card.blade.php | 2 +- .../components/ai-llm/model-row.blade.php | 11 ++-- .../components/badge-transparent.blade.php | 6 -- .../_partials/_navigation_mobile.blade.php | 11 ++-- routes/web.php | 2 +- .../Controllers/SitemapControllerTest.php | 20 +++++++ 19 files changed, 119 insertions(+), 95 deletions(-) rename app/Http/Controllers/{AiLlm => Ai}/AiLlmIndexController.php (84%) rename resources/views/app/{ai-llm => ai/llm}/index.blade.php (59%) delete mode 100644 resources/views/components/badge-transparent.blade.php diff --git a/app/Actions/ViewDataAction.php b/app/Actions/ViewDataAction.php index aaed39f0..30ef8453 100644 --- a/app/Actions/ViewDataAction.php +++ b/app/Actions/ViewDataAction.php @@ -67,30 +67,32 @@ public function technologies(string $locale): Collection public function aiModelGroups(): Collection { return Cache::rememberForever('ai_models_active', function () { - $categoryOrder = array_column(AiModelCategoryEnum::cases(), 'value'); - - return AiModel::whereNull('archived_at') - ->orderBy('order') - ->get() - ->sortBy(fn (AiModel $model) => array_search($model->category->value, $categoryOrder)) - ->groupBy(fn (AiModel $model) => $model->category->value); + return $this->groupAiModelsByCategory( + AiModel::whereNull('archived_at')->orderBy('order')->get() + ); }); } public function aiModelArchive(): Collection { return Cache::rememberForever('ai_models_archived', function () { - $categoryOrder = array_column(AiModelCategoryEnum::cases(), 'value'); - - return AiModel::whereNotNull('archived_at') - ->with('replacedBy') - ->orderBy('order') - ->get() - ->sortBy(fn (AiModel $model) => array_search($model->category->value, $categoryOrder)) - ->groupBy(fn (AiModel $model) => $model->category->value); + return $this->groupAiModelsByCategory( + AiModel::whereNotNull('archived_at')->with('replacedBy')->orderBy('order')->get() + ); }); } + private function groupAiModelsByCategory(Collection $models): Collection + { + return collect(AiModelCategoryEnum::cases()) + ->map(fn (AiModelCategoryEnum $category) => [ + 'category' => $category, + 'models' => $models->where('category', $category)->values(), + ]) + ->filter(fn (array $group) => $group['models']->isNotEmpty()) + ->values(); + } + public function openSource(string $locale): Collection { $key = Str::slug("open_source_published_{$locale}"); diff --git a/app/Enums/AiModelCategoryEnum.php b/app/Enums/AiModelCategoryEnum.php index 18e88a76..36d0370c 100644 --- a/app/Enums/AiModelCategoryEnum.php +++ b/app/Enums/AiModelCategoryEnum.php @@ -8,13 +8,13 @@ enum AiModelCategoryEnum: string case VISION_DOCUMENTS = 'vision_documents'; case RETRIEVAL_SEARCH = 'retrieval_search'; - public function title(string $locale): string + public function title(): string { - return __('components.ai_llm.categories.'.$this->value.'.title', locale: $locale); + return __('components.ai_llm.categories.'.$this->value.'.title'); } - public function description(string $locale): string + public function description(): string { - return __('components.ai_llm.categories.'.$this->value.'.description', locale: $locale); + return __('components.ai_llm.categories.'.$this->value.'.description'); } } diff --git a/app/Http/Controllers/AiLlm/AiLlmIndexController.php b/app/Http/Controllers/Ai/AiLlmIndexController.php similarity index 84% rename from app/Http/Controllers/AiLlm/AiLlmIndexController.php rename to app/Http/Controllers/Ai/AiLlmIndexController.php index f11be3b7..10a10d98 100644 --- a/app/Http/Controllers/AiLlm/AiLlmIndexController.php +++ b/app/Http/Controllers/Ai/AiLlmIndexController.php @@ -1,6 +1,6 @@ with([ + return view('app.ai.llm.index')->with([ 'page' => (new PageAction(locale: null, routeName: 'ai.llm.index'))->default(), 'groups' => $viewData->aiModelGroups(), 'archive' => $viewData->aiModelArchive(), diff --git a/app/Models/AiModel.php b/app/Models/AiModel.php index 0ca18b94..0eb689f9 100644 --- a/app/Models/AiModel.php +++ b/app/Models/AiModel.php @@ -19,27 +19,32 @@ public function replacedBy(): BelongsTo return $this->belongsTo(AiModel::class, 'replaced_by_id'); } - public function localizedRole(string $locale): ?string + public function localizedRole(): ?string { - return $this->role[substr($locale, 0, 2)] ?? null; + return $this->role[substr(app()->getLocale(), 0, 2)] ?? null; } - public function licenseLabel(string $locale): ?string + public function licenseLabel(): ?string { - return $this->license ? __('components.ai_llm.licenses.'.$this->licenseKey().'.label', locale: $locale) : null; + $key = $this->licenseKey(); + + return $key ? __('components.ai_llm.licenses.'.$key.'.label') : $this->license; } - public function licenseTooltip(string $locale): ?string + public function licenseTooltip(): ?string { - return $this->license ? __('components.ai_llm.licenses.'.$this->licenseKey().'.tooltip', locale: $locale) : null; + $key = $this->licenseKey(); + + return $key ? __('components.ai_llm.licenses.'.$key.'.tooltip') : null; } - private function licenseKey(): string + private function licenseKey(): ?string { return match ($this->license) { 'MIT' => 'mit', 'Apache-2.0' => 'apache', 'Gemma' => 'gemma', + default => null, }; } } diff --git a/database/seeders/Codebar/PagesTableSeeder.php b/database/seeders/Codebar/PagesTableSeeder.php index 2ceeca45..e2bb115d 100644 --- a/database/seeders/Codebar/PagesTableSeeder.php +++ b/database/seeders/Codebar/PagesTableSeeder.php @@ -149,7 +149,7 @@ private function enCH() [ 'robots' => 'index,follow', 'title' => 'Our local LLMs in action', - 'description' => 'These are the local open-source models we currently rely on — all of them run on our own infrastructure, in our office.', + 'description' => 'These are the local open-source models we currently rely on — all of them run on our own infrastructure, in our very own office basement.', ] ); } diff --git a/lang/de_CH/components.php b/lang/de_CH/components.php index 2a7916bb..9b08c942 100644 --- a/lang/de_CH/components.php +++ b/lang/de_CH/components.php @@ -78,7 +78,7 @@ ], 'infrastructure' => [ 'title' => 'Unsere Infrastruktur', - 'intro' => 'Unsere Modelle laufen vollständig auf eigener, lokaler Infrastruktur — bei uns im Büro, nicht in der Cloud. Alle Daten bleiben im Haus.', + 'intro' => 'Hier laufen unsere lokalen Modelle.', 'items' => [ 'hardware' => [ 'label' => 'Hardware', @@ -86,11 +86,11 @@ ], 'management' => [ 'label' => 'Modellverwaltung', - 'text' => 'LiteLLM Studio für Verwaltung und Autorisierung, Ollama betreibt die Modelle.', + 'text' => 'LiteLLM für Verwaltung und Autorisierung, Ollama betreibt die Modelle.', ], 'access' => [ 'label' => 'Zugang & Sicherheit', - 'text' => 'Cloudinary-Tunnel auf das lokale MacBook.', + 'text' => 'Cloudflare Tunnel auf das lokale MacBook.', ], 'power' => [ 'label' => 'Strom', diff --git a/lang/en_CH/components.php b/lang/en_CH/components.php index def29e18..97c73806 100644 --- a/lang/en_CH/components.php +++ b/lang/en_CH/components.php @@ -76,7 +76,7 @@ ], 'infrastructure' => [ 'title' => 'Our infrastructure', - 'intro' => 'Our models run entirely on our own local infrastructure — in our office, not in the cloud. All data stays in-house.', + 'intro' => 'This is where our local models run.', 'items' => [ 'hardware' => [ 'label' => 'Hardware', @@ -84,11 +84,11 @@ ], 'management' => [ 'label' => 'Model management', - 'text' => 'LiteLLM Studio for management and authorization, Ollama runs the models.', + 'text' => 'LiteLLM for management and authorization, Ollama runs the models.', ], 'access' => [ 'label' => 'Access & security', - 'text' => 'Cloudinary tunnel to the local MacBook.', + 'text' => 'Cloudflare Tunnel to the local MacBook.', ], 'power' => [ 'label' => 'Power', diff --git a/resources/views/app/ai/index.blade.php b/resources/views/app/ai/index.blade.php index 7f10f66d..eea9e361 100644 --- a/resources/views/app/ai/index.blade.php +++ b/resources/views/app/ai/index.blade.php @@ -2,10 +2,10 @@

{{ __('components.ai.intro') }}

-
+

{{ __('components.ai.llm_teaser') }}

-
+ class-attributes="mt-2 inline-block text-base"/> + diff --git a/resources/views/app/ai-llm/index.blade.php b/resources/views/app/ai/llm/index.blade.php similarity index 59% rename from resources/views/app/ai-llm/index.blade.php rename to resources/views/app/ai/llm/index.blade.php index b28bde12..5496a96d 100644 --- a/resources/views/app/ai-llm/index.blade.php +++ b/resources/views/app/ai/llm/index.blade.php @@ -1,26 +1,21 @@ -@php - $locale = app()->getLocale(); -@endphp -

{{ __('components.ai_llm.intro') }}

- @foreach ($groups as $category => $models) - @php - $categoryEnum = \App\Enums\AiModelCategoryEnum::from($category); - @endphp - - -

{{ $categoryEnum->description($locale) }}

- - @foreach ($models as $model) - - @endforeach -
+ @foreach ($groups as $group) + + + +

{{ $group['category']->description() }}

+ + @foreach ($group['models'] as $model) + + @endforeach +
+
@endforeach -
+

{{ __('components.ai_llm.infrastructure.intro') }}

@@ -46,20 +41,19 @@ -
- - - -

{{ __('components.ai_llm.archive.intro') }}

- - @foreach ($archive as $category => $models) - @php - $categoryEnum = \App\Enums\AiModelCategoryEnum::from($category); - @endphp -
- - -
- @endforeach -
+ + + + + +

{{ __('components.ai_llm.archive.intro') }}

+ + @foreach ($archive as $group) +
+ + +
+ @endforeach +
+
diff --git a/resources/views/app/start/index.blade.php b/resources/views/app/start/index.blade.php index a17104e9..45a282b8 100644 --- a/resources/views/app/start/index.blade.php +++ b/resources/views/app/start/index.blade.php @@ -9,7 +9,7 @@ + :teaser="__('components.ai.llm_teaser')"/> diff --git a/resources/views/components/a-badge.blade.php b/resources/views/components/a-badge.blade.php index e826d26a..3391c796 100644 --- a/resources/views/components/a-badge.blade.php +++ b/resources/views/components/a-badge.blade.php @@ -1,7 +1,14 @@ -@props(['href','label','target' => '_self','classAttributes' => "", 'title' => null]) +@props(['href','label','target' => '_self','classAttributes' => "", 'title' => null, 'variant' => 'default']) + +@php + $variantClasses = match ($variant) { + 'brand' => 'bg-brand text-white hover:bg-brand-strong', + default => 'bg-gray-400/10 text-gray-600 hover:bg-gray-400/20 hover:text-gray-800 hover:font-semibold ring-1 ring-gray-400/20 ring-inset', + }; +@endphp
merge(['class' => $classAttributes.' inline-flex items-center rounded-md bg-gray-400/10 px-2 py-1 text-sm font-medium text-gray-600 hover:bg-gray-400/20 hover:text-gray-800 hover:font-semibold ring-1 ring-gray-400/20 ring-inset cursor-pointer']) }}> + {{ $attributes->merge(['class' => $classAttributes.' '.$variantClasses.' inline-flex items-center rounded-md px-2 py-1 text-sm font-medium cursor-pointer']) }}> {{ $label }} {{ $slot }} diff --git a/resources/views/components/ai-llm/archive-row.blade.php b/resources/views/components/ai-llm/archive-row.blade.php index 25151e9c..981dd554 100644 --- a/resources/views/components/ai-llm/archive-row.blade.php +++ b/resources/views/components/ai-llm/archive-row.blade.php @@ -2,5 +2,7 @@
{{ $model->name }}
-
{{ $model->replacedBy?->name }}
+
+ {{ __('components.ai_llm.archive.columns.replaced_by') }}: {{ $model->replacedBy?->name ?? '—' }} +
diff --git a/resources/views/components/ai-llm/archive-table.blade.php b/resources/views/components/ai-llm/archive-table.blade.php index aaacd181..620558d5 100644 --- a/resources/views/components/ai-llm/archive-table.blade.php +++ b/resources/views/components/ai-llm/archive-table.blade.php @@ -1,7 +1,7 @@ @props(['models'])
-
+ diff --git a/resources/views/components/ai-llm/card.blade.php b/resources/views/components/ai-llm/card.blade.php index 0b27c314..70b3e45f 100644 --- a/resources/views/components/ai-llm/card.blade.php +++ b/resources/views/components/ai-llm/card.blade.php @@ -1,5 +1,5 @@ @props(['id' => null]) -
+
{{ $slot }}
diff --git a/resources/views/components/ai-llm/model-row.blade.php b/resources/views/components/ai-llm/model-row.blade.php index 0cc8585b..62ad5546 100644 --- a/resources/views/components/ai-llm/model-row.blade.php +++ b/resources/views/components/ai-llm/model-row.blade.php @@ -1,17 +1,13 @@ @props(['model']) -@php - $locale = app()->getLocale(); -@endphp -
{{ $model->name }}
-
{{ $model->localizedRole($locale) }}
+
{{ $model->localizedRole() }}
- +
@@ -20,6 +16,7 @@ :label="$model->link_label.' ↗'" target="_blank" :title="__('components.ai_llm.tooltips.link')" - class-attributes="!bg-brand !text-white !ring-0 hover:!bg-brand-strong hover:!font-medium text-xs whitespace-nowrap"/> + variant="brand" + class-attributes="text-xs whitespace-nowrap"/>
diff --git a/resources/views/components/badge-transparent.blade.php b/resources/views/components/badge-transparent.blade.php deleted file mode 100644 index 7aa1806c..00000000 --- a/resources/views/components/badge-transparent.blade.php +++ /dev/null @@ -1,6 +0,0 @@ -@props(['label','title' => '','classAttributes' => ""]) - - - {{ $label }} - diff --git a/resources/views/layouts/_partials/_navigation_mobile.blade.php b/resources/views/layouts/_partials/_navigation_mobile.blade.php index d425ce8c..128dcbe9 100644 --- a/resources/views/layouts/_partials/_navigation_mobile.blade.php +++ b/resources/views/layouts/_partials/_navigation_mobile.blade.php @@ -4,7 +4,7 @@ - {{ __('News') }} + {{ __('Home') }} @if ($configuration?->section_news) @@ -67,7 +67,7 @@ class="block text-base hover:text-brand hover:font-semibold transition"> + class="block py-3 text-center bg-zinc-50/50 hover:text-brand hover:font-semibold transition"> {{ __('AI') }} @@ -82,9 +82,12 @@ class="block text-center bg-zinc-50/50 hover:text-brand hover:font-semibold tran class="block text-base hover:text-brand hover:font-semibold transition"> +41 61 515 60 90 - key === '_paperflakes' ? 'info@paperflakes.ch' : 'info@codebar.ch'; + @endphp + - info@paperflakes.ch + {{ $contactEmail }}
diff --git a/routes/web.php b/routes/web.php index a6b7de94..cf447501 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,7 +3,7 @@ use App\Enums\LocaleEnum; use App\Http\Controllers\AboutUs\AboutUsIndexController; use App\Http\Controllers\Ai\AiIndexController; -use App\Http\Controllers\AiLlm\AiLlmIndexController; +use App\Http\Controllers\Ai\AiLlmIndexController; use App\Http\Controllers\Contact\ContactIndexController; use App\Http\Controllers\Entry\EntryIndexController; use App\Http\Controllers\Jobs\JobsIndexController; diff --git a/tests/Feature/Controllers/SitemapControllerTest.php b/tests/Feature/Controllers/SitemapControllerTest.php index d84793b4..b395d206 100644 --- a/tests/Feature/Controllers/SitemapControllerTest.php +++ b/tests/Feature/Controllers/SitemapControllerTest.php @@ -51,3 +51,23 @@ expect($content)->toContain('rechtlichtes/datenschutz'); expect($content)->toContain('medien'); })->group('sitemap'); + +it('includes the AI pages in the sitemap', function () { + $this->seed([ + ConfigurationsTableSeeder::class, + PagesTableSeeder::class, + ]); + + Cache::flush(); + + $response = get('sitemap.xml'); + + $response->assertOk(); + + $content = $response->getContent(); + + expect($content)->toContain('/ai<'); + expect($content)->toContain('/ai/llm'); + expect($content)->toContain('/ki<'); + expect($content)->toContain('/ki/llm'); +})->group('sitemap'); From 86e6aec2ed9a2e63b02d00f37adf8b5382c26b97 Mon Sep 17 00:00:00 2001 From: "Sebastian BURGIN-FIX (ext)" Date: Tue, 21 Jul 2026 14:57:43 +0200 Subject: [PATCH 4/4] wip --- database/seeders/Codebar/AiModelsTableSeeder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/seeders/Codebar/AiModelsTableSeeder.php b/database/seeders/Codebar/AiModelsTableSeeder.php index 2d6ec343..164895e8 100644 --- a/database/seeders/Codebar/AiModelsTableSeeder.php +++ b/database/seeders/Codebar/AiModelsTableSeeder.php @@ -143,8 +143,8 @@ private function active(): void 'ram' => '4,7 GB RAM', 'license' => 'Apache-2.0', 'role' => [ - 'de' => 'Vektoren für Ähnlichkeitssuche (4096 Dimensionen)', - 'en' => 'Vectors for similarity search (4096 dimensions)', + 'de' => 'Vektoren für Ähnlichkeitssuche', + 'en' => 'Vectors for similarity search', ], 'link_label' => 'Ollama', 'link_url' => 'https://ollama.com/library/qwen3-embedding',