├── .DS_Store ├── .editorconfig ├── .env.example ├── .env.github ├── .gitattributes ├── .gitignore ├── README.md ├── app ├── Actions │ ├── Fortify │ │ ├── CreateNewUser.php │ │ ├── PasswordValidationRules.php │ │ ├── ResetUserPassword.php │ │ ├── UpdateUserPassword.php │ │ └── UpdateUserProfileInformation.php │ └── Jetstream │ │ └── DeleteUser.php ├── Console │ └── Commands │ │ └── ChunkFolder.php ├── Domains │ ├── Documents │ │ ├── ChunkContent.php │ │ └── Chunking │ │ │ └── TextChunker.php │ ├── News │ │ ├── NewsDigest.php │ │ ├── NewsDigestCompletion.php │ │ └── NewsFeedParser.php │ └── Tasks │ │ └── QueryTasksPrompt.php ├── Http │ └── Controllers │ │ └── Controller.php ├── Models │ ├── Chat.php │ ├── Chunk.php │ ├── Document.php │ ├── Event.php │ ├── Message.php │ ├── News.php │ └── User.php ├── Providers │ ├── AppServiceProvider.php │ ├── FortifyServiceProvider.php │ └── JetstreamServiceProvider.php ├── Services │ ├── LlmServices │ │ ├── BaseClient.php │ │ ├── ClaudeClient.php │ │ ├── Functions │ │ │ ├── ArgumentCaster.php │ │ │ ├── CreateEventTool.php │ │ │ ├── FunctionCallDto.php │ │ │ ├── FunctionContract.php │ │ │ ├── FunctionDto.php │ │ │ ├── FunctionResponse.php │ │ │ ├── ParametersDto.php │ │ │ ├── PropertyDto.php │ │ │ └── ToolDto.php │ │ ├── GroqClient.php │ │ ├── LlmDriverClient.php │ │ ├── LlmDriverFacade.php │ │ ├── LlmServiceProvider.php │ │ ├── MessageWrapper.php │ │ ├── Messages │ │ │ └── RoleEnum.php │ │ ├── MockClient.php │ │ ├── OllamaClient.php │ │ ├── OpenAiClient.php │ │ ├── Orchestration │ │ │ └── Orchestrate.php │ │ ├── Prompts │ │ │ ├── MaterialListPrompt.php │ │ │ ├── NotesPrompt.php │ │ │ ├── SystemPrompt.php │ │ │ └── Templatizer.php │ │ ├── Requests │ │ │ └── MessageInDto.php │ │ ├── Responses │ │ │ ├── ClaudeCompletionResponse.php │ │ │ ├── ClaudeContentCaster.php │ │ │ ├── ClaudeToolCaster.php │ │ │ ├── CompletionResponse.php │ │ │ └── FunctionResponse.php │ │ └── helpers.php │ └── Ollama │ │ └── Client.php ├── View │ └── Components │ │ ├── AppLayout.php │ │ └── GuestLayout.php └── helpers.php ├── artisan ├── book ├── PHP_LLMs_001.pdf └── PHP_and_LLMs.png ├── bootstrap ├── app.php ├── cache │ └── .gitignore └── providers.php ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── cache.php ├── database.php ├── filesystems.php ├── fortify.php ├── jetstream.php ├── llmdriver.php ├── logging.php ├── mail.php ├── openai.php ├── pennant.php ├── queue.php ├── sanctum.php ├── services.php └── session.php ├── database ├── .gitignore ├── factories │ ├── ChatFactory.php │ ├── ChunkFactory.php │ ├── DocumentFactory.php │ ├── EventFactory.php │ ├── MessageFactory.php │ ├── NewsFactory.php │ └── UserFactory.php ├── migrations │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ ├── 2022_08_03_000000_create_vector_extension.php │ ├── 2024_08_17_193735_add_two_factor_columns_to_users_table.php │ ├── 2024_08_17_193742_create_personal_access_tokens_table.php │ ├── 2024_08_18_155014_create_chats_table.php │ ├── 2024_08_18_160909_create_features_table.php │ ├── 2024_08_18_161306_create_messages_table.php │ ├── 2024_08_18_173624_create_events_table.php │ ├── 2024_08_22_191825_create_news_table.php │ ├── 2024_09_15_211042_create_documents_table.php │ └── 2024_09_15_211349_create_chunks_table.php └── seeders │ └── DatabaseSeeder.php ├── package-lock.json ├── package.json ├── phpstan.neon ├── phpunit.xml ├── postcss.config.js ├── public ├── .htaccess ├── favicon.ico ├── index.php └── robots.txt ├── resources ├── css │ └── app.css ├── js │ ├── app.js │ └── bootstrap.js ├── markdown │ ├── policy.md │ └── terms.md └── views │ ├── api │ ├── api-token-manager.blade.php │ └── index.blade.php │ ├── auth │ ├── confirm-password.blade.php │ ├── forgot-password.blade.php │ ├── login.blade.php │ ├── register.blade.php │ ├── reset-password.blade.php │ ├── two-factor-challenge.blade.php │ └── verify-email.blade.php │ ├── components │ ├── action-message.blade.php │ ├── action-section.blade.php │ ├── application-logo.blade.php │ ├── application-mark.blade.php │ ├── authentication-card-logo.blade.php │ ├── authentication-card.blade.php │ ├── banner.blade.php │ ├── button.blade.php │ ├── checkbox.blade.php │ ├── confirmation-modal.blade.php │ ├── confirms-password.blade.php │ ├── danger-button.blade.php │ ├── dialog-modal.blade.php │ ├── dropdown-link.blade.php │ ├── dropdown.blade.php │ ├── form-section.blade.php │ ├── input-error.blade.php │ ├── input.blade.php │ ├── label.blade.php │ ├── modal.blade.php │ ├── nav-link.blade.php │ ├── responsive-nav-link.blade.php │ ├── secondary-button.blade.php │ ├── section-border.blade.php │ ├── section-title.blade.php │ ├── switchable-team.blade.php │ ├── validation-errors.blade.php │ └── welcome.blade.php │ ├── dashboard.blade.php │ ├── emails │ └── team-invitation.blade.php │ ├── layouts │ ├── app.blade.php │ └── guest.blade.php │ ├── navigation-menu.blade.php │ ├── policy.blade.php │ ├── profile │ ├── delete-user-form.blade.php │ ├── logout-other-browser-sessions-form.blade.php │ ├── show.blade.php │ ├── two-factor-authentication-form.blade.php │ ├── update-password-form.blade.php │ └── update-profile-information-form.blade.php │ ├── terms.blade.php │ └── welcome.blade.php ├── routes ├── api.php ├── console.php └── web.php ├── storage ├── .DS_Store ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── data │ ├── .DS_Store │ └── pickleball │ │ ├── .DS_Store │ │ ├── history.md │ │ ├── images │ │ ├── .DS_Store │ │ ├── by_state.jpeg │ │ ├── compared.jpeg │ │ ├── gender.jpeg │ │ ├── growth_rate.jpeg │ │ ├── market.jpeg │ │ ├── player_growth.jpeg │ │ └── tournaments.jpeg │ │ ├── places.md │ │ ├── playing_tips.md │ │ ├── skill_levels.md │ │ ├── stats.md │ │ └── ten_teammate_tips.md ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── Feature │ ├── ApiTokenPermissionsTest.php │ ├── AuthenticationTest.php │ ├── BrowserSessionsTest.php │ ├── ChunkContentTest.php │ ├── ClaudeClientTest.php │ ├── ClientTest.php │ ├── CreateApiTokenTest.php │ ├── CreateEventToolTest.php │ ├── DeleteAccountTest.php │ ├── DeleteApiTokenTest.php │ ├── EmailVerificationTest.php │ ├── ExampleTest.php │ ├── GroqClientTest.php │ ├── Models │ │ ├── ChatTest.php │ │ ├── ChunkTest.php │ │ ├── DocumentTest.php │ │ ├── EventTest.php │ │ ├── MessageTest.php │ │ └── NewsTest.php │ ├── NewsDigestTest.php │ ├── NewsDigestTestCompletion.php │ ├── NewsFeedTest.php │ ├── OrchestrateTest.php │ ├── PasswordConfirmationTest.php │ ├── PasswordResetTest.php │ ├── ProfileInformationTest.php │ ├── RegistrationTest.php │ ├── TemplatizerTest.php │ ├── TwoFactorAuthenticationSettingsTest.php │ └── UpdatePasswordTest.php ├── Pest.php ├── TestCase.php └── fixtures │ ├── claude_completion.json │ ├── claude_remap_functions_results_v2.json │ ├── claude_remap_messages.json │ ├── claude_remap_payload.json │ ├── claude_results.json │ ├── cloud_client_tool_use_response.json │ ├── create_event_tool.json │ ├── embed_data_for_testing.json │ ├── embedding_question_distance.json │ ├── embedding_response.json │ ├── embedding_response_flat.json │ ├── example_email.md │ ├── example_markdown.txt │ ├── example_question_embedding.json │ ├── groq_chat_messages.json │ ├── groq_chat_payload_post_remap.json │ ├── groq_chat_payload_pre_remap.json │ ├── groq_completion.json │ ├── groq_functions_prompt.json │ ├── groq_functions_prompt_real.json │ ├── groq_functions_response.json │ ├── groq_functions_response_real.json │ ├── http_results.json │ ├── news_feed_false.json │ ├── news_feed_good.html │ ├── news_feed_good_response.json │ ├── openai_chat_payload.json │ ├── openai_chat_response.json │ ├── simple_ollama_client_chat_results.json │ └── simple_ollama_client_results.json └── vite.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/.DS_Store -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | # APP_MAINTENANCE_STORE=database 14 | 15 | BCRYPT_ROUNDS=12 16 | 17 | LOG_CHANNEL=stack 18 | LOG_STACK=single 19 | LOG_DEPRECATIONS_CHANNEL=null 20 | LOG_LEVEL=debug 21 | 22 | DB_CONNECTION=pgsql 23 | DB_HOST=127.0.0.1 24 | DB_PORT=5432 25 | DB_DATABASE=laravel_llms 26 | DB_USERNAME=root 27 | DB_PASSWORD= 28 | 29 | SESSION_DRIVER=database 30 | SESSION_LIFETIME=120 31 | SESSION_ENCRYPT=false 32 | SESSION_PATH=/ 33 | SESSION_DOMAIN=null 34 | 35 | BROADCAST_CONNECTION=log 36 | FILESYSTEM_DISK=local 37 | QUEUE_CONNECTION=database 38 | 39 | CACHE_STORE=database 40 | CACHE_PREFIX= 41 | 42 | MEMCACHED_HOST=127.0.0.1 43 | 44 | REDIS_CLIENT=phpredis 45 | REDIS_HOST=127.0.0.1 46 | REDIS_PASSWORD=null 47 | REDIS_PORT=6379 48 | 49 | MAIL_MAILER=log 50 | MAIL_HOST=127.0.0.1 51 | MAIL_PORT=2525 52 | MAIL_USERNAME=null 53 | MAIL_PASSWORD=null 54 | MAIL_ENCRYPTION=null 55 | MAIL_FROM_ADDRESS="hello@example.com" 56 | MAIL_FROM_NAME="${APP_NAME}" 57 | 58 | AWS_ACCESS_KEY_ID= 59 | AWS_SECRET_ACCESS_KEY= 60 | AWS_DEFAULT_REGION=us-east-1 61 | AWS_BUCKET= 62 | AWS_USE_PATH_STYLE_ENDPOINT=false 63 | 64 | VITE_APP_NAME="${APP_NAME}" 65 | -------------------------------------------------------------------------------- /.env.github: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY=base64:zR66Rx/bzt2/cu0BK92ijOXxCpGxoPCVgFEdHxZQa+k= 4 | APP_DEBUG=true 5 | APP_URL=http://laravel-llms.test 6 | 7 | LOG_CHANNEL=stack 8 | LOG_DEPRECATIONS_CHANNEL=null 9 | LOG_LEVEL=debug 10 | 11 | DB_CONNECTION=pgsql 12 | DB_HOST=127.0.0.1 13 | DB_PORT=5432 14 | DB_DATABASE=testing 15 | DB_USERNAME=root 16 | DB_PASSWORD=password 17 | 18 | BROADCAST_DRIVER=log 19 | CACHE_DRIVER=file 20 | FILESYSTEM_DISK=local 21 | QUEUE_CONNECTION=sync 22 | SESSION_DRIVER=database 23 | SESSION_LIFETIME=120 24 | 25 | MEMCACHED_HOST=127.0.0.1 26 | 27 | REDIS_HOST=127.0.0.1 28 | REDIS_PASSWORD=null 29 | REDIS_PORT=6379 30 | 31 | MAIL_MAILER=smtp 32 | MAIL_HOST=mailhog 33 | MAIL_PORT=1025 34 | MAIL_USERNAME=null 35 | MAIL_PASSWORD=null 36 | MAIL_ENCRYPTION=null 37 | MAIL_FROM_ADDRESS="hello@example.com" 38 | MAIL_FROM_NAME="${APP_NAME}" 39 | 40 | AWS_ACCESS_KEY_ID= 41 | AWS_SECRET_ACCESS_KEY= 42 | AWS_DEFAULT_REGION=us-east-1 43 | AWS_BUCKET= 44 | AWS_USE_PATH_STYLE_ENDPOINT=false 45 | 46 | PUSHER_APP_ID= 47 | PUSHER_APP_KEY= 48 | PUSHER_APP_SECRET= 49 | PUSHER_HOST= 50 | PUSHER_PORT=443 51 | PUSHER_SCHEME=https 52 | PUSHER_APP_CLUSTER=mt1 53 | 54 | VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 55 | VITE_PUSHER_HOST="${PUSHER_HOST}" 56 | VITE_PUSHER_PORT="${PUSHER_PORT}" 57 | VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" 58 | VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 59 | 60 | 61 | ## Fake key 62 | OPENAI_API_KEY=sk-m8ox9NVgboSLRCr5VA5RT3BXbkFJzP1QGUedbQxGmmuAQWRT 63 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpactor.json 12 | .phpunit.result.cache 13 | Homestead.json 14 | Homestead.yaml 15 | auth.json 16 | npm-debug.log 17 | yarn-error.log 18 | /.fleet 19 | /.idea 20 | /.vscode 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP and LLMs 2 | [![CI-CD](https://github.com/alnutile/php-llms/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/alnutile/php-llms/actions/workflows/ci-cd.yml) 3 | 4 | ![Cover Image](book/PHP_and_LLMs.png) 5 | 6 | See the sample with two complete chapters 7 | [PHP and LLMs](https://bit.ly/php_llms_sample) 8 | 9 | 10 | ## About the Book 11 | 12 | This book is a practical guide for PHP developers navigating the rapidly evolving landscape of Large Language Models (LLMs). Moving beyond the hype, it delves into real-world applications and solutions, providing step-by-step instructions on how to integrate LLMs into your existing projects. 13 | 14 | From mastering prompts to building reusable tools and working with various LLMs, this book equips you with the skills needed to leverage the power of Ai in everyday development tasks. 15 | 16 | Whether you’re looking to automate complex workflows, enhance content verification, or simply stay ahead in a changing industry, this book offers the insights and hands-on examples to get you there. This isn’t just another “Hello World” guide—it’s a deep dive into the future of PHP development, focusing on real solutions that drive real results. 17 | 18 | 19 | 20 | 21 | ## Learn More 22 | 23 | [LeanPub Books](https://bit.ly/php_llms) 24 | -------------------------------------------------------------------------------- /app/Actions/Fortify/CreateNewUser.php: -------------------------------------------------------------------------------- 1 | $input 19 | */ 20 | public function create(array $input): User 21 | { 22 | Validator::make($input, [ 23 | 'name' => ['required', 'string', 'max:255'], 24 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 25 | 'password' => $this->passwordRules(), 26 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '', 27 | ])->validate(); 28 | 29 | return User::create([ 30 | 'name' => $input['name'], 31 | 'email' => $input['email'], 32 | 'password' => Hash::make($input['password']), 33 | ]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Actions/Fortify/PasswordValidationRules.php: -------------------------------------------------------------------------------- 1 | |string> 13 | */ 14 | protected function passwordRules(): array 15 | { 16 | return ['required', 'string', Password::default(), 'confirmed']; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Actions/Fortify/ResetUserPassword.php: -------------------------------------------------------------------------------- 1 | $input 18 | */ 19 | public function reset(User $user, array $input): void 20 | { 21 | Validator::make($input, [ 22 | 'password' => $this->passwordRules(), 23 | ])->validate(); 24 | 25 | $user->forceFill([ 26 | 'password' => Hash::make($input['password']), 27 | ])->save(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserPassword.php: -------------------------------------------------------------------------------- 1 | $input 18 | */ 19 | public function update(User $user, array $input): void 20 | { 21 | Validator::make($input, [ 22 | 'current_password' => ['required', 'string', 'current_password:web'], 23 | 'password' => $this->passwordRules(), 24 | ], [ 25 | 'current_password.current_password' => __('The provided password does not match your current password.'), 26 | ])->validateWithBag('updatePassword'); 27 | 28 | $user->forceFill([ 29 | 'password' => Hash::make($input['password']), 30 | ])->save(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserProfileInformation.php: -------------------------------------------------------------------------------- 1 | $input 17 | */ 18 | public function update(User $user, array $input): void 19 | { 20 | Validator::make($input, [ 21 | 'name' => ['required', 'string', 'max:255'], 22 | 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 23 | 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], 24 | ])->validateWithBag('updateProfileInformation'); 25 | 26 | if (isset($input['photo'])) { 27 | $user->updateProfilePhoto($input['photo']); 28 | } 29 | 30 | if ($input['email'] !== $user->email && 31 | $user instanceof MustVerifyEmail) { 32 | $this->updateVerifiedUser($user, $input); 33 | } else { 34 | $user->forceFill([ 35 | 'name' => $input['name'], 36 | 'email' => $input['email'], 37 | ])->save(); 38 | } 39 | } 40 | 41 | /** 42 | * Update the given verified user's profile information. 43 | * 44 | * @param array $input 45 | */ 46 | protected function updateVerifiedUser(User $user, array $input): void 47 | { 48 | $user->forceFill([ 49 | 'name' => $input['name'], 50 | 'email' => $input['email'], 51 | 'email_verified_at' => null, 52 | ])->save(); 53 | 54 | $user->sendEmailVerificationNotification(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteUser.php: -------------------------------------------------------------------------------- 1 | deleteProfilePhoto(); 16 | $user->tokens->each->delete(); 17 | $user->delete(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Console/Commands/ChunkFolder.php: -------------------------------------------------------------------------------- 1 | withProgressBar(File::allFiles($this->argument('absolute-path')), function ($file) { 33 | try { 34 | $fileName = $file->getFilenameWithoutExtension(); 35 | 36 | $content = File::get($file); 37 | 38 | if (str(File::mimeType($file))->contains('image')) { 39 | $content = $this->getImageContent($file); 40 | } 41 | 42 | $this->info('Chunking '.$file); 43 | app(ChunkContent::class)->handle($content, $fileName); 44 | } catch (\Throwable $e) { 45 | $this->error('Error chunking '.$file); 46 | Log::error($e); 47 | } 48 | }); 49 | } 50 | 51 | protected function getImageContent($file): string 52 | { 53 | $prompt = <<<'PROMPT' 54 | 55 | This is an image with chart data about Pickleball. Please describe the data shown image and render as a markdown table. 56 | Do not add any flavor text. 57 | 58 | PROMPT; 59 | 60 | $results = LlmDriverFacade::driver('openai')->image( 61 | prompt: $prompt, 62 | base64Image: base64_encode(File::get($file)) 63 | ); 64 | 65 | return $results->content; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/Domains/Documents/ChunkContent.php: -------------------------------------------------------------------------------- 1 | title = $title; 16 | $document->content = $content; 17 | $document->save(); 18 | 19 | $chunks = (new TextChunker)->handle($content); 20 | 21 | foreach ($chunks as $chunkSection => $chunk) { 22 | $embedding = Http::withHeaders([ 23 | 'Content-Type' => 'application/json', 24 | 'Authorization' => 'Bearer ollama', 25 | ])->post('http://localhost:11434/api/embed', [ 26 | 'model' => 'nomic-embed-text', 27 | 'input' => $chunk, 28 | ])->json(); 29 | 30 | $embedding = data_get($embedding, 'embeddings.0'); 31 | Chunk::create( 32 | [ 33 | 'content' => $chunk, 34 | 'document_id' => $document->id, 35 | 'sort_order' => $chunkSection, 36 | 'embedding_768' => $embedding, 37 | ] 38 | ); 39 | } 40 | 41 | return $document; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Domains/Documents/Chunking/TextChunker.php: -------------------------------------------------------------------------------- 1 | $textLength) { 15 | // Get the remaining text if it's shorter than the chunk size. 16 | $chunks[] = substr($text, $start); 17 | break; 18 | } 19 | 20 | // Get the chunk from the text. 21 | $chunks[] = substr($text, $start, $chunkSize); 22 | } 23 | 24 | return $chunks; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Domains/News/NewsDigest.php: -------------------------------------------------------------------------------- 1 | get()->map( 14 | function ($news) { 15 | return [ 16 | 'content' => sprintf('News: Title: %s Content: %s', $news->title, $news->body), 17 | 'role' => 'user', 18 | ]; 19 | } 20 | ); 21 | 22 | $prompt = <<<'PROMPT' 23 | 24 | You are my news digest assistant 25 | 26 | Take the news articles from this thread and create a TLDR followed by a title and summary of each one 27 | If not news is passed in the just say "No News in this thread" 28 | PROMPT; 29 | 30 | $messages = $messages->push([ 31 | 'role' => 'user', 32 | 'content' => $prompt, 33 | ])->toArray(); 34 | 35 | $results = Client::chat($messages); 36 | 37 | return data_get($results, 'content', 'No Results'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Domains/News/NewsDigestCompletion.php: -------------------------------------------------------------------------------- 1 | map(function ($news) { 14 | return sprintf('News: Title: %s Content: %s', $news->title, $news->body); 15 | })->join("\n"); 16 | 17 | $prompt = << 19 | You are my news digest assistant 20 | 21 | Take the news articles from the section below and create a TLDR followed by a title and summary of each one 22 | If not news is passed in the just say "No News in this 23 | 24 | 25 | { $messages } 26 | PROMPT; 27 | 28 | $results = Client::completion($messages); 29 | 30 | return data_get($results, 'content', 'No Results'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Domains/News/NewsFeedParser.php: -------------------------------------------------------------------------------- 1 | 15 | You are extracting the content from the provided html or text that is related to technology news like ollama, llms, laravel etc. 16 | 17 | 18 | First see if the article provided in the sections talks about tecnology news and if not just return the one word 'false'. Else pull out the content of the article as a Title, Summary, URL, and Content formatted as below. 19 | 20 | 21 | Title: 22 | Url: 23 | Summary: 24 | Content: 25 | 26 | 27 | $context 28 | PROMPT; 29 | 30 | $results = Client::completion($prompt); 31 | 32 | $results = data_get($results, 'response', false); 33 | 34 | if ($results == 'false') { 35 | return false; 36 | } 37 | 38 | return $results; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Domains/Tasks/QueryTasksPrompt.php: -------------------------------------------------------------------------------- 1 | 11 | You are an assistant helping me keep up on all the tasks in the database. You will query the database and return the tasks for me. 12 | 13 | 14 | Create the SQL query needed to query the tasks database. The schema is as follows. You an see context in the section including user_ids and the users quesion. 15 | 16 | name: 17 | due_date: "y-m-d h:i" 18 | description: 19 | assigned_id: 20 | 21 | 22 | SELECT * FROM tasks WHERE DATE(due_date) = CURDATE(); 23 | SELECT * FROM tasks WHERE due_date < NOW(); 24 | SELECT * FROM tasks WHERE assigned_id = 5; 25 | SELECT * FROM tasks 26 | WHERE due_date BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 1 WEEK); 27 | SELECT * FROM tasks ORDER BY due_date ASC; 28 | 29 | 30 | 31 | user_id: 1 //user asking the question 32 | user_id: 2 //the assistant user 33 | user_id: 7 //editor of the book 34 | 35 | PROMPT; 36 | 37 | return $prompt; 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | hasMany(Message::class); 20 | } 21 | 22 | public function getDriver(): string 23 | { 24 | return config('llmdriver.driver'); 25 | } 26 | 27 | public function addInput( 28 | string $message, 29 | RoleEnum $role, 30 | string $tool = '', 31 | mixed $tool_id = null, 32 | array $args = []): Message 33 | { 34 | return Message::create([ 35 | 'body' => $message, 36 | 'chat_id' => $this->id, 37 | 'role' => $role, 38 | 'tool_name' => $tool, 39 | 'tool_id' => $tool_id, 40 | 'args' => $args, 41 | ]); 42 | } 43 | 44 | public function getChatResponse(int $limit = 5): array 45 | { 46 | $latestMessages = $this->messages() 47 | ->orderBy('id', 'desc') 48 | ->get(); 49 | 50 | $latestMessagesArray = []; 51 | 52 | foreach ($latestMessages as $message) { 53 | /** 54 | * @NOTE 55 | * I am super verbose here due to an odd BUG 56 | * I keep losing the data due to some 57 | * magic toArray() method that 58 | * was not working 59 | */ 60 | $asArray = [ 61 | 'role' => $message->role->value, 62 | 'content' => $message->body, 63 | 'tool_id' => $message->tool_id, 64 | 'tool' => $message->tool_name, 65 | 'args' => $message->args ?? [], 66 | ]; 67 | 68 | $dto = new MessageInDto( 69 | content: $asArray['content'], 70 | role: $asArray['role'], 71 | tool_id: $asArray['tool_id'], 72 | tool: $asArray['tool'], 73 | args: $asArray['args'], 74 | ); 75 | $latestMessagesArray[] = $dto; 76 | } 77 | 78 | return array_reverse($latestMessagesArray); 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/Models/Chunk.php: -------------------------------------------------------------------------------- 1 | Vector::class, 19 | 'embedding_3072' => Vector::class, 20 | 'embedding_1536' => Vector::class, 21 | 'embedding_2048' => Vector::class, 22 | 'embedding_1024' => Vector::class, 23 | 'embedding_4096' => Vector::class, 24 | 'meta_data' => 'array', 25 | ]; 26 | 27 | public function document(): \Illuminate\Database\Eloquent\Relations\BelongsTo 28 | { 29 | return $this->belongsTo(Document::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Models/Document.php: -------------------------------------------------------------------------------- 1 | hasMany(Chunk::class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Models/Event.php: -------------------------------------------------------------------------------- 1 | 'date', 17 | 'start_time' => 'timestamp', 18 | 'all_day' => 'boolean', 19 | 'assigned_to_assistant' => 'boolean', 20 | 'end_date' => 'date', 21 | 'end_time' => 'timestamp', 22 | ]; 23 | 24 | public function getStartAttribute() 25 | { 26 | return $this->formatDateTime($this->start_date, $this->start_time); 27 | } 28 | 29 | public function getEndAttribute() 30 | { 31 | return $this->formatDateTime($this->end_date, $this->end_time); 32 | } 33 | 34 | private function formatDateTime($date, $time) 35 | { 36 | if (! $date) { 37 | return null; 38 | } 39 | 40 | $dateTime = Carbon::parse($date); 41 | 42 | if ($time) { 43 | // Convert the Postgres time(0) to a format Carbon can understand 44 | $formattedTime = date('H:i:s', strtotime($time)); 45 | $dateTime->setTimeFromTimeString($formattedTime); 46 | } 47 | 48 | return $dateTime->toIso8601String(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Models/Message.php: -------------------------------------------------------------------------------- 1 | RoleEnum::class, 18 | 'args' => 'array', 19 | 'in_out' => 'boolean', 20 | ]; 21 | 22 | public function chat(): BelongsTo 23 | { 24 | return $this->belongsTo(Chat::class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Models/News.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | protected $fillable = [ 27 | 'name', 28 | 'email', 29 | 'password', 30 | ]; 31 | 32 | /** 33 | * The attributes that should be hidden for serialization. 34 | * 35 | * @var array 36 | */ 37 | protected $hidden = [ 38 | 'password', 39 | 'remember_token', 40 | 'two_factor_recovery_codes', 41 | 'two_factor_secret', 42 | ]; 43 | 44 | /** 45 | * The accessors to append to the model's array form. 46 | * 47 | * @var array 48 | */ 49 | protected $appends = [ 50 | 'profile_photo_url', 51 | ]; 52 | 53 | /** 54 | * Get the attributes that should be cast. 55 | * 56 | * @return array 57 | */ 58 | protected function casts(): array 59 | { 60 | return [ 61 | 'email_verified_at' => 'datetime', 62 | 'password' => 'hashed', 63 | ]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | input(Fortify::username())).'|'.$request->ip()); 38 | 39 | return Limit::perMinute(5)->by($throttleKey); 40 | }); 41 | 42 | RateLimiter::for('two-factor', function (Request $request) { 43 | return Limit::perMinute(5)->by($request->session()->get('login.id')); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Providers/JetstreamServiceProvider.php: -------------------------------------------------------------------------------- 1 | configurePermissions(); 25 | 26 | Jetstream::deleteUsersUsing(DeleteUser::class); 27 | } 28 | 29 | /** 30 | * Configure the permissions that are available within the application. 31 | */ 32 | protected function configurePermissions(): void 33 | { 34 | Jetstream::defaultApiTokenPermissions(['read']); 35 | 36 | Jetstream::permissions([ 37 | 'create', 38 | 'read', 39 | 'update', 40 | 'delete', 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Services/LlmServices/Functions/ArgumentCaster.php: -------------------------------------------------------------------------------- 1 | $this->getName(), 24 | 'description' => $this->getDescription(), 25 | 'parameters' => [ 26 | 'type' => $this->type, 27 | 'properties' => $this->getProperties(), 28 | ], 29 | ] 30 | ); 31 | } 32 | 33 | public function getName(): string 34 | { 35 | return $this->name; 36 | } 37 | 38 | public function getKey(): string 39 | { 40 | return $this->name; 41 | } 42 | 43 | public function getDescription(): string 44 | { 45 | return $this->description; 46 | } 47 | 48 | public function getParameters(): array 49 | { 50 | return $this->getProperties(); 51 | } 52 | 53 | /** 54 | * @return PropertyDto[] 55 | */ 56 | abstract protected function getProperties(): array; 57 | } 58 | -------------------------------------------------------------------------------- /app/Services/LlmServices/Functions/FunctionDto.php: -------------------------------------------------------------------------------- 1 | getDefaultDriver(); 12 | 13 | if (! isset($this->drivers[$name])) { 14 | $this->drivers[$name] = $this->createDriver($name); 15 | } 16 | 17 | return $this->drivers[$name]; 18 | } 19 | 20 | protected function createDriver($name) 21 | { 22 | /** 23 | * @TODO 24 | * Turn into a match statement 25 | */ 26 | switch ($name) { 27 | case 'openai': 28 | return new OpenAiClient; 29 | case 'ollama': 30 | return new OllamaClient; 31 | case 'claude': 32 | return new ClaudeClient; 33 | case 'groq': 34 | return new GroqClient; 35 | case 'mock': 36 | return new MockClient; 37 | default: 38 | throw new \InvalidArgumentException("Driver [{$name}] is not supported."); 39 | } 40 | } 41 | 42 | public static function getDrivers(): array 43 | { 44 | return array_keys(config('llmdriver.drivers')); 45 | } 46 | 47 | protected function getDefaultDriver() 48 | { 49 | return 'mock'; 50 | } 51 | 52 | public function getFunctions(): array 53 | { 54 | return []; 55 | } 56 | 57 | /** 58 | * @NOTE 59 | * Some systems like Ollama might not like all the traffic 60 | * at once 61 | */ 62 | public function isAsync(): bool 63 | { 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/Services/LlmServices/LlmDriverFacade.php: -------------------------------------------------------------------------------- 1 | app->bind('llm_driver', function () { 24 | return new LlmDriverClient; 25 | }); 26 | 27 | $this->app->bind('create_event_tool', function () { 28 | return new CreateEventTool; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Services/LlmServices/MessageWrapper.php: -------------------------------------------------------------------------------- 1 | getChatMessages($user); 18 | 19 | $messages[] = MessageInDto::from([ 20 | 'content' => str($input)->markdown(), 21 | 'role' => $role, 22 | 'is_ai' => $role !== 'user', 23 | 'show' => $role !== 'system', 24 | ]); 25 | 26 | Cache::set('messages_'.$user->id, $messages); 27 | 28 | return $messages; 29 | } 30 | 31 | public function getChatMessages(User $user): array 32 | { 33 | $messages = Cache::get('messages_'.$user->id); 34 | 35 | if (empty($messages)) { 36 | $messages = MessageInDto::from([ 37 | 'content' => SystemPrompt::prompt(), 38 | 'role' => 'system', 39 | 'is_ai' => true, 40 | 'show' => false, 41 | ]); 42 | 43 | $messages = Arr::wrap($messages); 44 | 45 | Cache::set('messages_'.$user->id, $messages); 46 | } 47 | 48 | return $messages; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Services/LlmServices/Messages/RoleEnum.php: -------------------------------------------------------------------------------- 1 | addInput( 17 | message: $prompt, 18 | role: RoleEnum::User 19 | ); 20 | 21 | $messageInDto = MessageInDto::from([ 22 | 'content' => $prompt, 23 | 'role' => 'user', 24 | ]); 25 | 26 | $response = LlmDriverFacade::driver($chat->getDriver()) 27 | ->chat([ 28 | $messageInDto, 29 | ]); 30 | 31 | if (! empty($response->tool_calls)) { 32 | Log::info('Orchestration Tools Found', [ 33 | 'tool_calls' => collect($response->tool_calls) 34 | ->pluck('name')->toArray(), 35 | ]); 36 | 37 | $count = 1; 38 | foreach ($response->tool_calls as $tool_call) { 39 | Log::info('[LaraChain] - Tool Call '.$count, [ 40 | 'tool_call' => $tool_call->name, 41 | 'tool_count' => count($response->tool_calls), 42 | ]); 43 | 44 | $message = $chat->addInput( 45 | message: $response->content ?? 'Calling Tools', //@NOTE ollama, openai blank but claude needs this :( 46 | role: RoleEnum::Assistant, 47 | tool: $tool_call->name, 48 | tool_id: $tool_call->id, 49 | args: $tool_call->arguments, 50 | ); 51 | 52 | $tool = app()->make($tool_call->name); 53 | $results = $tool->handle($message); 54 | $message->updateQuietly([ 55 | 'role' => RoleEnum::Tool, 56 | 'body' => $results->content, 57 | ]); 58 | $count++; 59 | } 60 | 61 | $messages = $chat->getChatResponse(); 62 | 63 | $response = LlmDriverFacade::driver($chat->getDriver()) 64 | ->chat($messages); 65 | 66 | return $chat->addInput( 67 | message: $response->content, 68 | role: RoleEnum::Assistant, 69 | ); 70 | 71 | } else { 72 | Log::info('No Tools found just gonna chat'); 73 | $assistantMessage = $chat->addInput( 74 | message: $response->content ?? 'Calling Tools', 75 | role: RoleEnum::Assistant 76 | ); 77 | 78 | return $assistantMessage; 79 | } 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/Services/LlmServices/Prompts/MaterialListPrompt.php: -------------------------------------------------------------------------------- 1 | toISOString(); 11 | 12 | $prompt = <<toISOString(); 11 | 12 | $prompt = << $this->content, 24 | 'role' => $this->role, 25 | 'tool_id' => $this->tool_id, 26 | 'tool' => $this->tool, 27 | 'args' => $this->args, 28 | ]; 29 | } 30 | 31 | public static function fromMessageAsUser(Message $message): self 32 | { 33 | return MessageInDto::from( 34 | [ 35 | 'content' => $message->body, 36 | 'role' => $message->role->value, 37 | 'is_ai' => false, 38 | 'show' => true, 39 | ] 40 | ); 41 | } 42 | 43 | public static function fromMessageAsAssistant(Message $message): self 44 | { 45 | return MessageInDto::from( 46 | [ 47 | 'content' => $message->body, 48 | 'role' => $message->role->value, 49 | 'tool_id' => $message->tool_id, 50 | 'tool' => $message->tool_name, 51 | 'is_ai' => true, 52 | 'show' => true, 53 | ] 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Services/LlmServices/Responses/ClaudeCompletionResponse.php: -------------------------------------------------------------------------------- 1 | */ 16 | #[WithCastable(ClaudeToolCaster::class)] 17 | #[MapInputName('content')] 18 | public array $tool_calls = [], 19 | ) {} 20 | } 21 | -------------------------------------------------------------------------------- /app/Services/LlmServices/Responses/ClaudeContentCaster.php: -------------------------------------------------------------------------------- 1 | filter( 21 | function ($item) { 22 | return $item['type'] === 'text'; 23 | } 24 | )->first(); 25 | 26 | return data_get($results, 'text'); 27 | } 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Services/LlmServices/Responses/ClaudeToolCaster.php: -------------------------------------------------------------------------------- 1 | filter( 22 | function ($item) { 23 | return $item['type'] === 'tool_use'; 24 | } 25 | )->toArray(); 26 | 27 | foreach ($results as $index => $result) { 28 | $results[$index] = ToolDto::from( 29 | [ 30 | 'name' => $result['name'], 31 | 'arguments' => $result['input'], 32 | 'id' => $result['id'], 33 | ] 34 | ); 35 | } 36 | 37 | return $results; 38 | } 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Services/LlmServices/Responses/CompletionResponse.php: -------------------------------------------------------------------------------- 1 | */ 13 | public array $tool_calls = [], 14 | ) {} 15 | } 16 | -------------------------------------------------------------------------------- /app/Services/LlmServices/Responses/FunctionResponse.php: -------------------------------------------------------------------------------- 1 | 'phi3:latest', 13 | 'prompt' => $prompt, 14 | 'stream' => false, 15 | 'options' => [ 16 | 'temperature' => 0.1, 17 | ], 18 | ])->json(); 19 | } 20 | 21 | public function chat(array $messages) 22 | { 23 | return Http::post('http://localhost:11434/api/chat', [ 24 | 'model' => 'phi3:latest', 25 | 'messages' => $messages, 26 | 'stream' => false, 27 | 'options' => [ 28 | 'temperature' => 0.1, 29 | ], 30 | ])->json(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/View/Components/AppLayout.php: -------------------------------------------------------------------------------- 1 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /book/PHP_LLMs_001.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/book/PHP_LLMs_001.pdf -------------------------------------------------------------------------------- /book/PHP_and_LLMs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/book/PHP_and_LLMs.png -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 9 | web: __DIR__.'/../routes/web.php', 10 | api: __DIR__.'/../routes/api.php', 11 | commands: __DIR__.'/../routes/console.php', 12 | health: '/up', 13 | ) 14 | ->withMiddleware(function (Middleware $middleware) { 15 | // 16 | }) 17 | ->withExceptions(function (Exceptions $exceptions) { 18 | // 19 | })->create(); 20 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Below you may configure as many filesystem disks as necessary, and you 24 | | may even configure multiple disks for the same driver. Examples for 25 | | most supported storage drivers are configured here for reference. 26 | | 27 | | Supported drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app'), 36 | 'throw' => false, 37 | ], 38 | 39 | 'public' => [ 40 | 'driver' => 'local', 41 | 'root' => storage_path('app/public'), 42 | 'url' => env('APP_URL').'/storage', 43 | 'visibility' => 'public', 44 | 'throw' => false, 45 | ], 46 | 47 | 's3' => [ 48 | 'driver' => 's3', 49 | 'key' => env('AWS_ACCESS_KEY_ID'), 50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 51 | 'region' => env('AWS_DEFAULT_REGION'), 52 | 'bucket' => env('AWS_BUCKET'), 53 | 'url' => env('AWS_URL'), 54 | 'endpoint' => env('AWS_ENDPOINT'), 55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 56 | 'throw' => false, 57 | ], 58 | 59 | ], 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Symbolic Links 64 | |-------------------------------------------------------------------------- 65 | | 66 | | Here you may configure the symbolic links that will be created when the 67 | | `storage:link` Artisan command is executed. The array keys should be 68 | | the locations of the links and the values should be their targets. 69 | | 70 | */ 71 | 72 | 'links' => [ 73 | public_path('storage') => storage_path('app/public'), 74 | ], 75 | 76 | ]; 77 | -------------------------------------------------------------------------------- /config/jetstream.php: -------------------------------------------------------------------------------- 1 | 'livewire', 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Jetstream Route Middleware 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may specify which middleware Jetstream will assign to the routes 27 | | that it registers with the application. When necessary, you may modify 28 | | these middleware; however, this default value is usually sufficient. 29 | | 30 | */ 31 | 32 | 'middleware' => ['web'], 33 | 34 | 'auth_session' => AuthenticateSession::class, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Jetstream Guard 39 | |-------------------------------------------------------------------------- 40 | | 41 | | Here you may specify the authentication guard Jetstream will use while 42 | | authenticating users. This value should correspond with one of your 43 | | guards that is already present in your "auth" configuration file. 44 | | 45 | */ 46 | 47 | 'guard' => 'sanctum', 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Features 52 | |-------------------------------------------------------------------------- 53 | | 54 | | Some of Jetstream's features are optional. You may disable the features 55 | | by removing them from this array. You're free to only remove some of 56 | | these features or you can even remove all of these if you need to. 57 | | 58 | */ 59 | 60 | 'features' => [ 61 | // Features::termsAndPrivacyPolicy(), 62 | // Features::profilePhotos(), 63 | Features::api(), 64 | // Features::teams(['invitations' => true]), 65 | Features::accountDeletion(), 66 | ], 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | Profile Photo Disk 71 | |-------------------------------------------------------------------------- 72 | | 73 | | This configuration value determines the default disk that will be used 74 | | when storing profile photos for your application's users. Typically 75 | | this will be the "public" disk but you may adjust this if needed. 76 | | 77 | */ 78 | 79 | 'profile_photo_disk' => 'public', 80 | 81 | ]; 82 | -------------------------------------------------------------------------------- /config/openai.php: -------------------------------------------------------------------------------- 1 | env('OPENAI_API_KEY'), 16 | 'organization' => env('OPENAI_ORGANIZATION'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Request Timeout 21 | |-------------------------------------------------------------------------- 22 | | 23 | | The timeout may be used to specify the maximum number of seconds to wait 24 | | for a response. By default, the client will time out after 30 seconds. 25 | */ 26 | 27 | 'request_timeout' => env('OPENAI_REQUEST_TIMEOUT', 120), 28 | ]; 29 | -------------------------------------------------------------------------------- /config/pennant.php: -------------------------------------------------------------------------------- 1 | env('PENNANT_STORE', 'database'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Pennant Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure each of the stores that should be available to 26 | | Pennant. These stores shall be used to store resolved feature flag 27 | | values - you may configure as many as your application requires. 28 | | 29 | */ 30 | 31 | 'stores' => [ 32 | 33 | 'array' => [ 34 | 'driver' => 'array', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'connection' => null, 40 | 'table' => 'features', 41 | ], 42 | 43 | ], 44 | ]; 45 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'token' => env('POSTMARK_TOKEN'), 19 | ], 20 | 21 | 'ses' => [ 22 | 'key' => env('AWS_ACCESS_KEY_ID'), 23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 25 | ], 26 | 27 | 'resend' => [ 28 | 'key' => env('RESEND_KEY'), 29 | ], 30 | 31 | 'slack' => [ 32 | 'notifications' => [ 33 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 34 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 35 | ], 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/factories/ChatFactory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ChatFactory extends Factory 11 | { 12 | /** 13 | * Define the model's default state. 14 | * 15 | * @return array 16 | */ 17 | public function definition(): array 18 | { 19 | return [ 20 | 'title' => fake()->sentence(), 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/factories/ChunkFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class ChunkFactory extends Factory 12 | { 13 | /** 14 | * Define the model's default state. 15 | * 16 | * @return array 17 | */ 18 | public function definition(): array 19 | { 20 | $embeddings = get_fixture('embedding_response.json'); 21 | 22 | return [ 23 | 'content' => fake()->sentence(10), 24 | 'sort_order' => fake()->numberBetween(1, 100), 25 | 'summary' => fake()->sentence(5), 26 | 'embedding_3072' => data_get($embeddings, 'data.0.embedding'), 27 | 'embedding_1536' => null, 28 | 'embedding_2048' => null, 29 | 'embedding_4096' => null, 30 | 'document_id' => Document::factory(), 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/factories/DocumentFactory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class DocumentFactory extends Factory 11 | { 12 | /** 13 | * Define the model's default state. 14 | * 15 | * @return array 16 | */ 17 | public function definition(): array 18 | { 19 | return [ 20 | 'title' => fake()->sentence(), 21 | 'summary' => fake()->paragraph(), 22 | 'content' => fake()->paragraph(), 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /database/factories/EventFactory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class EventFactory extends Factory 11 | { 12 | /** 13 | * Define the model's default state. 14 | * 15 | * @return array 16 | */ 17 | public function definition(): array 18 | { 19 | $start = now()->subDays(rand(1, 10)); 20 | 21 | return [ 22 | 'title' => $this->faker->sentence, 23 | 'description' => $this->faker->paragraph, 24 | 'start_date' => $start->format('Y-m-d'), 25 | 'start_time' => now()->format('H:i:s'), 26 | 'end_date' => $start->addDays(rand(1, 10))->format('Y-m-d'), 27 | 'end_time' => now()->format('H:i:s'), 28 | 'location' => $this->faker->sentence, 29 | 'all_day' => false, 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/factories/MessageFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class MessageFactory extends Factory 12 | { 13 | /** 14 | * Define the model's default state. 15 | * 16 | * @return array 17 | */ 18 | public function definition(): array 19 | { 20 | return [ 21 | 'body' => fake()->sentence(), 22 | 'chat_id' => Chat::factory(), 23 | 'tool_name' => null, 24 | 'tool_id' => null, 25 | 'args' => null, 26 | 'role' => \App\Services\LlmServices\Messages\RoleEnum::User->value, 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/factories/NewsFactory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class NewsFactory extends Factory 11 | { 12 | /** 13 | * Define the model's default state. 14 | * 15 | * @return array 16 | */ 17 | public function definition(): array 18 | { 19 | return [ 20 | 'title' => fake()->title, 21 | 'body' => fake()->sentences(3, true), 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class UserFactory extends Factory 16 | { 17 | /** 18 | * The current password being used by the factory. 19 | */ 20 | protected static ?string $password; 21 | 22 | /** 23 | * Define the model's default state. 24 | * 25 | * @return array 26 | */ 27 | public function definition(): array 28 | { 29 | return [ 30 | 'name' => fake()->name(), 31 | 'email' => fake()->unique()->safeEmail(), 32 | 'email_verified_at' => now(), 33 | 'password' => static::$password ??= Hash::make('password'), 34 | 'two_factor_secret' => null, 35 | 'two_factor_recovery_codes' => null, 36 | 'remember_token' => Str::random(10), 37 | 'profile_photo_path' => null, 38 | 'current_team_id' => null, 39 | ]; 40 | } 41 | 42 | /** 43 | * Indicate that the model's email address should be unverified. 44 | */ 45 | public function unverified(): static 46 | { 47 | return $this->state(fn (array $attributes) => [ 48 | 'email_verified_at' => null, 49 | ]); 50 | } 51 | 52 | /** 53 | * Indicate that the user should have a personal team. 54 | */ 55 | public function withPersonalTeam(?callable $callback = null): static 56 | { 57 | if (! Features::hasTeamFeatures()) { 58 | return $this->state([]); 59 | } 60 | 61 | return $this->has( 62 | Team::factory() 63 | ->state(fn (array $attributes, User $user) => [ 64 | 'name' => $user->name.'\'s Team', 65 | 'user_id' => $user->id, 66 | 'personal_team' => true, 67 | ]) 68 | ->when(is_callable($callback), $callback), 69 | 'ownedTeams' 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->foreignId('current_team_id')->nullable(); 22 | $table->string('profile_photo_path', 2048)->nullable(); 23 | $table->timestamps(); 24 | }); 25 | 26 | Schema::create('password_reset_tokens', function (Blueprint $table) { 27 | $table->string('email')->primary(); 28 | $table->string('token'); 29 | $table->timestamp('created_at')->nullable(); 30 | }); 31 | 32 | Schema::create('sessions', function (Blueprint $table) { 33 | $table->string('id')->primary(); 34 | $table->foreignId('user_id')->nullable()->index(); 35 | $table->string('ip_address', 45)->nullable(); 36 | $table->text('user_agent')->nullable(); 37 | $table->longText('payload'); 38 | $table->integer('last_activity')->index(); 39 | }); 40 | } 41 | 42 | /** 43 | * Reverse the migrations. 44 | */ 45 | public function down(): void 46 | { 47 | Schema::dropIfExists('users'); 48 | Schema::dropIfExists('password_reset_tokens'); 49 | Schema::dropIfExists('sessions'); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000001_create_cache_table.php: -------------------------------------------------------------------------------- 1 | string('key')->primary(); 16 | $table->mediumText('value'); 17 | $table->integer('expiration'); 18 | }); 19 | 20 | Schema::create('cache_locks', function (Blueprint $table) { 21 | $table->string('key')->primary(); 22 | $table->string('owner'); 23 | $table->integer('expiration'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('cache'); 33 | Schema::dropIfExists('cache_locks'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000002_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | 24 | Schema::create('job_batches', function (Blueprint $table) { 25 | $table->string('id')->primary(); 26 | $table->string('name'); 27 | $table->integer('total_jobs'); 28 | $table->integer('pending_jobs'); 29 | $table->integer('failed_jobs'); 30 | $table->longText('failed_job_ids'); 31 | $table->mediumText('options')->nullable(); 32 | $table->integer('cancelled_at')->nullable(); 33 | $table->integer('created_at'); 34 | $table->integer('finished_at')->nullable(); 35 | }); 36 | 37 | Schema::create('failed_jobs', function (Blueprint $table) { 38 | $table->id(); 39 | $table->string('uuid')->unique(); 40 | $table->text('connection'); 41 | $table->text('queue'); 42 | $table->longText('payload'); 43 | $table->longText('exception'); 44 | $table->timestamp('failed_at')->useCurrent(); 45 | }); 46 | } 47 | 48 | /** 49 | * Reverse the migrations. 50 | */ 51 | public function down(): void 52 | { 53 | Schema::dropIfExists('jobs'); 54 | Schema::dropIfExists('job_batches'); 55 | Schema::dropIfExists('failed_jobs'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /database/migrations/2022_08_03_000000_create_vector_extension.php: -------------------------------------------------------------------------------- 1 | text('two_factor_secret') 17 | ->after('password') 18 | ->nullable(); 19 | 20 | $table->text('two_factor_recovery_codes') 21 | ->after('two_factor_secret') 22 | ->nullable(); 23 | 24 | if (Fortify::confirmsTwoFactorAuthentication()) { 25 | $table->timestamp('two_factor_confirmed_at') 26 | ->after('two_factor_recovery_codes') 27 | ->nullable(); 28 | } 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | */ 35 | public function down(): void 36 | { 37 | Schema::table('users', function (Blueprint $table) { 38 | $table->dropColumn(array_merge([ 39 | 'two_factor_secret', 40 | 'two_factor_recovery_codes', 41 | ], Fortify::confirmsTwoFactorAuthentication() ? [ 42 | 'two_factor_confirmed_at', 43 | ] : [])); 44 | }); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /database/migrations/2024_08_17_193742_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->morphs('tokenable'); 17 | $table->string('name'); 18 | $table->string('token', 64)->unique(); 19 | $table->text('abilities')->nullable(); 20 | $table->timestamp('last_used_at')->nullable(); 21 | $table->timestamp('expires_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('personal_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2024_08_18_155014_create_chats_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('title'); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('chats'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2024_08_18_160909_create_features_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('scope'); 20 | $table->text('value'); 21 | $table->timestamps(); 22 | 23 | $table->unique(['name', 'scope']); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('features'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2024_08_18_161306_create_messages_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->longText('body'); 17 | $table->foreignIdFor(\App\Models\Chat::class); 18 | $table->string('tool_name')->nullable(); 19 | $table->string('tool_id')->nullable(); 20 | $table->json('args')->nullable(); 21 | $table->string('role')->default(\App\Services\LlmServices\Messages\RoleEnum::User->value); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('messages'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2024_08_18_173624_create_events_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('title'); 17 | $table->longText('description')->nullable(); 18 | $table->date('start_date')->nullable(); 19 | $table->time('start_time')->nullable(); 20 | $table->date('end_date')->nullable(); 21 | $table->time('end_time')->nullable(); 22 | $table->string('location')->nullable(); 23 | $table->boolean('all_day')->default(false); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | */ 31 | public function down(): void 32 | { 33 | Schema::dropIfExists('events'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/2024_08_22_191825_create_news_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('title'); 17 | $table->longText('body'); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('news'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2024_09_15_211042_create_documents_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('title')->nullable(); 17 | $table->longText('summary')->nullable(); 18 | $table->longText('content')->nullable(); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('documents'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2024_09_15_211349_create_chunks_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('sort_order')->default(1); 17 | $table->longText('content')->nullable(); 18 | $table->longText('summary')->nullable(); 19 | $table->vector('embedding_768', 768)->nullable(); 20 | $table->vector('embedding_1536', 1536)->nullable(); 21 | $table->vector('embedding_2048', 2048)->nullable(); 22 | $table->vector('embedding_3072', 3072)->nullable(); 23 | $table->vector('embedding_1024', 1024)->nullable(); 24 | $table->vector('embedding_4096', 4096)->nullable(); 25 | $table->foreignIdFor(\App\Models\Document::class, 'document_id'); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | */ 33 | public function down(): void 34 | { 35 | Schema::dropIfExists('chunks'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 17 | 18 | User::factory()->create([ 19 | 'name' => 'Test User', 20 | 'email' => 'test@example.com', 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "@tailwindcss/forms": "^0.5.7", 10 | "@tailwindcss/typography": "^0.5.10", 11 | "autoprefixer": "^10.4.16", 12 | "axios": "^1.6.4", 13 | "laravel-vite-plugin": "^1.0", 14 | "postcss": "^8.4.32", 15 | "tailwindcss": "^3.4.0", 16 | "vite": "^5.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/larastan/larastan/extension.neon 3 | 4 | parameters: 5 | 6 | paths: 7 | - app 8 | 9 | # The level 8 is the highest level 10 | level: 5 11 | 12 | # ignoreErrors: 13 | 14 | excludePaths: 15 | - vendor 16 | 17 | checkMissingIterableValueType: false 18 | treatPhpDocTypesAsCertain: false 19 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Feature 10 | 11 | 12 | 13 | 14 | app 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | [x-cloak] { 6 | display: none; 7 | } 8 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | window.axios = axios; 3 | 4 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 5 | -------------------------------------------------------------------------------- /resources/markdown/policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Edit this file to define the privacy policy for your application. 4 | -------------------------------------------------------------------------------- /resources/markdown/terms.md: -------------------------------------------------------------------------------- 1 | # Terms of Service 2 | 3 | Edit this file to define the terms of service for your application. 4 | -------------------------------------------------------------------------------- /resources/views/api/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('API Tokens') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('api.api-token-manager') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/auth/confirm-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('This is a secure area of the application. Please confirm your password before continuing.') }} 9 |
10 | 11 | 12 | 13 |
14 | @csrf 15 | 16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 | {{ __('Confirm') }} 24 | 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /resources/views/auth/forgot-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }} 9 |
10 | 11 | @session('status') 12 |
13 | {{ $value }} 14 |
15 | @endsession 16 | 17 | 18 | 19 |
20 | @csrf 21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 | {{ __('Email Password Reset Link') }} 30 | 31 |
32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | @session('status') 10 |
11 | {{ $value }} 12 |
13 | @endsession 14 | 15 |
16 | @csrf 17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 |
27 | 28 |
29 | 33 |
34 | 35 |
36 | @if (Route::has('password.request')) 37 | 38 | {{ __('Forgot your password?') }} 39 | 40 | @endif 41 | 42 | 43 | {{ __('Log in') }} 44 | 45 |
46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /resources/views/auth/reset-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | @csrf 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 |
30 | 31 | {{ __('Reset Password') }} 32 | 33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /resources/views/auth/two-factor-challenge.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 | {{ __('Please confirm access to your account by entering the authentication code provided by your authenticator application.') }} 10 |
11 | 12 |
13 | {{ __('Please confirm access to your account by entering one of your emergency recovery codes.') }} 14 |
15 | 16 | 17 | 18 |
19 | @csrf 20 | 21 |
22 | 23 | 24 |
25 | 26 |
27 | 28 | 29 |
30 | 31 |
32 | 40 | 41 | 50 | 51 | 52 | {{ __('Log in') }} 53 | 54 |
55 |
56 |
57 |
58 |
59 | -------------------------------------------------------------------------------- /resources/views/auth/verify-email.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('Before continuing, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }} 9 |
10 | 11 | @if (session('status') == 'verification-link-sent') 12 |
13 | {{ __('A new verification link has been sent to the email address you provided in your profile settings.') }} 14 |
15 | @endif 16 | 17 |
18 |
19 | @csrf 20 | 21 |
22 | 23 | {{ __('Resend Verification Email') }} 24 | 25 |
26 |
27 | 28 |
29 | 33 | {{ __('Edit Profile') }} 34 | 35 |
36 | @csrf 37 | 38 | 41 |
42 |
43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /resources/views/components/action-message.blade.php: -------------------------------------------------------------------------------- 1 | @props(['on']) 2 | 3 |
merge(['class' => 'text-sm text-gray-600 dark:text-gray-400']) }}> 9 | {{ $slot->isEmpty() ? 'Saved.' : $slot }} 10 |
11 | -------------------------------------------------------------------------------- /resources/views/components/action-section.blade.php: -------------------------------------------------------------------------------- 1 |
merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}> 2 | 3 | {{ $title }} 4 | {{ $description }} 5 | 6 | 7 |
8 |
9 | {{ $content }} 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /resources/views/components/application-mark.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/views/components/authentication-card-logo.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/views/components/authentication-card.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ $logo }} 4 |
5 | 6 |
7 | {{ $slot }} 8 |
9 |
10 | -------------------------------------------------------------------------------- /resources/views/components/button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/checkbox.blade.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'rounded dark:bg-gray-900 border-gray-300 dark:border-gray-700 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:focus:ring-offset-gray-800']) !!}> 2 | -------------------------------------------------------------------------------- /resources/views/components/confirmation-modal.blade.php: -------------------------------------------------------------------------------- 1 | @props(['id' => null, 'maxWidth' => null]) 2 | 3 | 4 |
5 |
6 |
7 | 8 | 9 | 10 |
11 | 12 |
13 |

14 | {{ $title }} 15 |

16 | 17 |
18 | {{ $content }} 19 |
20 |
21 |
22 |
23 | 24 |
25 | {{ $footer }} 26 |
27 |
28 | -------------------------------------------------------------------------------- /resources/views/components/confirms-password.blade.php: -------------------------------------------------------------------------------- 1 | @props(['title' => __('Confirm Password'), 'content' => __('For your security, please confirm your password to continue.'), 'button' => __('Confirm')]) 2 | 3 | @php 4 | $confirmableId = md5($attributes->wire('then')); 5 | @endphp 6 | 7 | wire('then') }} 9 | x-data 10 | x-ref="span" 11 | x-on:click="$wire.startConfirmingPassword('{{ $confirmableId }}')" 12 | x-on:password-confirmed.window="setTimeout(() => $event.detail.id === '{{ $confirmableId }}' && $refs.span.dispatchEvent(new CustomEvent('then', { bubbles: false })), 250);" 13 | > 14 | {{ $slot }} 15 | 16 | 17 | @once 18 | 19 | 20 | {{ $title }} 21 | 22 | 23 | 24 | {{ $content }} 25 | 26 |
27 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 | {{ __('Cancel') }} 39 | 40 | 41 | 42 | {{ $button }} 43 | 44 | 45 |
46 | @endonce 47 | -------------------------------------------------------------------------------- /resources/views/components/danger-button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/dialog-modal.blade.php: -------------------------------------------------------------------------------- 1 | @props(['id' => null, 'maxWidth' => null]) 2 | 3 | 4 |
5 |
6 | {{ $title }} 7 |
8 | 9 |
10 | {{ $content }} 11 |
12 |
13 | 14 |
15 | {{ $footer }} 16 |
17 |
18 | -------------------------------------------------------------------------------- /resources/views/components/dropdown-link.blade.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out']) }}>{{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/components/dropdown.blade.php: -------------------------------------------------------------------------------- 1 | @props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white dark:bg-gray-700', 'dropdownClasses' => '']) 2 | 3 | @php 4 | $alignmentClasses = match ($align) { 5 | 'left' => 'ltr:origin-top-left rtl:origin-top-right start-0', 6 | 'top' => 'origin-top', 7 | 'none', 'false' => '', 8 | default => 'ltr:origin-top-right rtl:origin-top-left end-0', 9 | }; 10 | 11 | $width = match ($width) { 12 | '48' => 'w-48', 13 | '60' => 'w-60', 14 | default => 'w-48', 15 | }; 16 | @endphp 17 | 18 |
19 |
20 | {{ $trigger }} 21 |
22 | 23 | 37 |
38 | -------------------------------------------------------------------------------- /resources/views/components/form-section.blade.php: -------------------------------------------------------------------------------- 1 | @props(['submit']) 2 | 3 |
merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}> 4 | 5 | {{ $title }} 6 | {{ $description }} 7 | 8 | 9 |
10 |
11 |
12 |
13 | {{ $form }} 14 |
15 |
16 | 17 | @if (isset($actions)) 18 |
19 | {{ $actions }} 20 |
21 | @endif 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /resources/views/components/input-error.blade.php: -------------------------------------------------------------------------------- 1 | @props(['for']) 2 | 3 | @error($for) 4 |

merge(['class' => 'text-sm text-red-600 dark:text-red-400']) }}>{{ $message }}

5 | @enderror 6 | -------------------------------------------------------------------------------- /resources/views/components/input.blade.php: -------------------------------------------------------------------------------- 1 | @props(['disabled' => false]) 2 | 3 | merge(['class' => 'border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm']) !!}> 4 | -------------------------------------------------------------------------------- /resources/views/components/label.blade.php: -------------------------------------------------------------------------------- 1 | @props(['value']) 2 | 3 | 6 | -------------------------------------------------------------------------------- /resources/views/components/modal.blade.php: -------------------------------------------------------------------------------- 1 | @props(['id', 'maxWidth']) 2 | 3 | @php 4 | $id = $id ?? md5($attributes->wire('model')); 5 | 6 | $maxWidth = [ 7 | 'sm' => 'sm:max-w-sm', 8 | 'md' => 'sm:max-w-md', 9 | 'lg' => 'sm:max-w-lg', 10 | 'xl' => 'sm:max-w-xl', 11 | '2xl' => 'sm:max-w-2xl', 12 | ][$maxWidth ?? '2xl']; 13 | @endphp 14 | 15 | 44 | -------------------------------------------------------------------------------- /resources/views/components/nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 dark:border-indigo-600 text-sm font-medium leading-5 text-gray-900 dark:text-gray-100 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out' 6 | : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-700 focus:outline-none focus:text-gray-700 dark:focus:text-gray-300 focus:border-gray-300 dark:focus:border-gray-700 transition duration-150 ease-in-out'; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /resources/views/components/responsive-nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 dark:border-indigo-600 text-start text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/50 focus:outline-none focus:text-indigo-800 dark:focus:text-indigo-200 focus:bg-indigo-100 dark:focus:bg-indigo-900 focus:border-indigo-700 dark:focus:border-indigo-300 transition duration-150 ease-in-out' 6 | : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600 focus:outline-none focus:text-gray-800 dark:focus:text-gray-200 focus:bg-gray-50 dark:focus:bg-gray-700 focus:border-gray-300 dark:focus:border-gray-600 transition duration-150 ease-in-out'; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /resources/views/components/secondary-button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/section-border.blade.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /resources/views/components/section-title.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ $title }}

4 | 5 |

6 | {{ $description }} 7 |

8 |
9 | 10 |
11 | {{ $aside ?? '' }} 12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/components/switchable-team.blade.php: -------------------------------------------------------------------------------- 1 | @props(['team', 'component' => 'dropdown-link']) 2 | 3 |
4 | @method('PUT') 5 | @csrf 6 | 7 | 8 | 9 | 10 | 11 |
12 | @if (Auth::user()->isCurrentTeam($team)) 13 | 14 | 15 | 16 | @endif 17 | 18 |
{{ $team->name }}
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /resources/views/components/validation-errors.blade.php: -------------------------------------------------------------------------------- 1 | @if ($errors->any()) 2 |
3 |
{{ __('Whoops! Something went wrong.') }}
4 | 5 |
    6 | @foreach ($errors->all() as $error) 7 |
  • {{ $error }}
  • 8 | @endforeach 9 |
10 |
11 | @endif 12 | -------------------------------------------------------------------------------- /resources/views/dashboard.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Dashboard') }} 5 |

6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /resources/views/emails/team-invitation.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | {{ __('You have been invited to join the :team team!', ['team' => $invitation->team->name]) }} 3 | 4 | @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::registration())) 5 | {{ __('If you do not have an account, you may create one by clicking the button below. After creating an account, you may click the invitation acceptance button in this email to accept the team invitation:') }} 6 | 7 | @component('mail::button', ['url' => route('register')]) 8 | {{ __('Create Account') }} 9 | @endcomponent 10 | 11 | {{ __('If you already have an account, you may accept this invitation by clicking the button below:') }} 12 | 13 | @else 14 | {{ __('You may accept this invitation by clicking the button below:') }} 15 | @endif 16 | 17 | 18 | @component('mail::button', ['url' => $acceptUrl]) 19 | {{ __('Accept Invitation') }} 20 | @endcomponent 21 | 22 | {{ __('If you did not expect to receive an invitation to this team, you may discard this email.') }} 23 | @endcomponent 24 | -------------------------------------------------------------------------------- /resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | @vite(['resources/css/app.css', 'resources/js/app.js']) 16 | 17 | 18 | @livewireStyles 19 | 20 | 21 | 22 | 23 |
24 | @livewire('navigation-menu') 25 | 26 | 27 | @if (isset($header)) 28 |
29 |
30 | {{ $header }} 31 |
32 |
33 | @endif 34 | 35 | 36 |
37 | {{ $slot }} 38 |
39 |
40 | 41 | @stack('modals') 42 | 43 | @livewireScripts 44 | 45 | 46 | -------------------------------------------------------------------------------- /resources/views/layouts/guest.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | @vite(['resources/css/app.css', 'resources/js/app.js']) 16 | 17 | 18 | @livewireStyles 19 | 20 | 21 |
22 | {{ $slot }} 23 |
24 | 25 | @livewireScripts 26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/views/policy.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | {!! $policy !!} 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/profile/delete-user-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Delete Account') }} 4 | 5 | 6 | 7 | {{ __('Permanently delete your account.') }} 8 | 9 | 10 | 11 |
12 | {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }} 13 |
14 | 15 |
16 | 17 | {{ __('Delete Account') }} 18 | 19 |
20 | 21 | 22 | 23 | 24 | {{ __('Delete Account') }} 25 | 26 | 27 | 28 | {{ __('Are you sure you want to delete your account? Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }} 29 | 30 |
31 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | {{ __('Cancel') }} 45 | 46 | 47 | 48 | {{ __('Delete Account') }} 49 | 50 | 51 |
52 |
53 |
54 | -------------------------------------------------------------------------------- /resources/views/profile/show.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Profile') }} 5 |

6 |
7 | 8 |
9 |
10 | @if (Laravel\Fortify\Features::canUpdateProfileInformation()) 11 | @livewire('profile.update-profile-information-form') 12 | 13 | 14 | @endif 15 | 16 | @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::updatePasswords())) 17 |
18 | @livewire('profile.update-password-form') 19 |
20 | 21 | 22 | @endif 23 | 24 | @if (Laravel\Fortify\Features::canManageTwoFactorAuthentication()) 25 |
26 | @livewire('profile.two-factor-authentication-form') 27 |
28 | 29 | 30 | @endif 31 | 32 |
33 | @livewire('profile.logout-other-browser-sessions-form') 34 |
35 | 36 | @if (Laravel\Jetstream\Jetstream::hasAccountDeletionFeatures()) 37 | 38 | 39 |
40 | @livewire('profile.delete-user-form') 41 |
42 | @endif 43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /resources/views/profile/update-password-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Update Password') }} 4 | 5 | 6 | 7 | {{ __('Ensure your account is using a long, random password to stay secure.') }} 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | {{ __('Saved.') }} 33 | 34 | 35 | 36 | {{ __('Save') }} 37 | 38 | 39 |
40 | -------------------------------------------------------------------------------- /resources/views/terms.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | {!! $terms !!} 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | user(); 8 | })->middleware('auth:sanctum'); 9 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 8 | })->purpose('Display an inspiring quote')->hourly(); 9 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | group(function () { 14 | Route::get('/dashboard', function () { 15 | return view('dashboard'); 16 | })->name('dashboard'); 17 | }); 18 | -------------------------------------------------------------------------------- /storage/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/.DS_Store -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/data/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/.DS_Store -------------------------------------------------------------------------------- /storage/data/pickleball/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/pickleball/.DS_Store -------------------------------------------------------------------------------- /storage/data/pickleball/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/pickleball/images/.DS_Store -------------------------------------------------------------------------------- /storage/data/pickleball/images/by_state.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/pickleball/images/by_state.jpeg -------------------------------------------------------------------------------- /storage/data/pickleball/images/compared.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/pickleball/images/compared.jpeg -------------------------------------------------------------------------------- /storage/data/pickleball/images/gender.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/pickleball/images/gender.jpeg -------------------------------------------------------------------------------- /storage/data/pickleball/images/growth_rate.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/pickleball/images/growth_rate.jpeg -------------------------------------------------------------------------------- /storage/data/pickleball/images/market.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/pickleball/images/market.jpeg -------------------------------------------------------------------------------- /storage/data/pickleball/images/player_growth.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/pickleball/images/player_growth.jpeg -------------------------------------------------------------------------------- /storage/data/pickleball/images/tournaments.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alnutile/php-llms/593c5e3eca6bf64bb305e72b8e101c00dd9826d3/storage/data/pickleball/images/tournaments.jpeg -------------------------------------------------------------------------------- /storage/data/pickleball/places.md: -------------------------------------------------------------------------------- 1 | # Top 10 Places Around the World to Play Pickleball 2 | 3 | 1. **Indian Wells Tennis Garden - California, USA** 4 | - Home to the USA Pickleball National Championships 5 | - World-class facilities with numerous courts 6 | 7 | 2. **Bainbridge Island - Washington, USA** 8 | - Birthplace of pickleball 9 | - Historical significance and a thriving pickleball community 10 | 11 | 3. **The Villages - Florida, USA** 12 | - Largest concentration of pickleball courts in the world 13 | - Vibrant retirement community with over 200 courts 14 | 15 | 4. **Surprise Tennis & Racquet Complex - Arizona, USA** 16 | - Hosts major tournaments 17 | - Excellent facilities with both indoor and outdoor courts 18 | 19 | 5. **Bobby Riggs Racket & Paddle Club - Encinitas, California, USA** 20 | - Named after tennis legend Bobby Riggs 21 | - State-of-the-art facilities and a pickleball-focused environment 22 | 23 | 6. **Jardim de Alves Redol - Lisbon, Portugal** 24 | - One of Europe's pickleball hotspots 25 | - Beautiful outdoor courts in a scenic setting 26 | 27 | 7. **Pickleball Thailand - Hua Hin, Thailand** 28 | - Pioneering pickleball in Southeast Asia 29 | - Combines pickleball with a tropical vacation experience 30 | 31 | 8. **Dubai Pickleball Club - Dubai, UAE** 32 | - Growing pickleball scene in the Middle East 33 | - Modern facilities in an iconic city 34 | 35 | 9. **Pickleball England Hub - Bolton Arena, UK** 36 | - Center for pickleball growth in the United Kingdom 37 | - Hosts national championships and training programs 38 | 39 | 10. **Pickleball Barcelona - Barcelona, Spain** 40 | - Beautiful Mediterranean setting 41 | - Combines Spanish culture with the growing European pickleball scene 42 | 43 | These locations offer a mix of historical significance, excellent facilities, vibrant communities, and unique cultural experiences for pickleball enthusiasts around the world. 44 | -------------------------------------------------------------------------------- /storage/data/pickleball/playing_tips.md: -------------------------------------------------------------------------------- 1 | # Top 10 Pickleball Playing Tips 2 | 3 | 1. **Master the Dink Shot**: Practice soft, controlled shots that just barely clear the net. 4 | 5 | 2. **Stay at the Non-Volley Zone Line**: Position yourself close to the kitchen line for better control and quicker reactions. 6 | 7 | 3. **Communicate with Your Partner**: Clear and constant communication is key in doubles play. 8 | 9 | 4. **Develop a Solid Serve**: A consistent, accurate serve sets the tone for each point. 10 | 11 | 5. **Learn to Read Your Opponents**: Anticipate their shots by studying their positioning and patterns. 12 | 13 | 6. **Practice the Third Shot Drop**: This shot can help you transition from the baseline to the net effectively. 14 | 15 | 7. **Stay Low and Ready**: Maintain a low stance to react quickly to incoming shots. 16 | 17 | 8. **Mix Up Your Shots**: Vary your shots to keep your opponents guessing and off-balance. 18 | 19 | 9. **Improve Your Footwork**: Quick, small steps help with balance and positioning. 20 | 21 | 10. **Focus on Consistency**: Aim for steady, reliable play rather than flashy, high-risk shots. 22 | -------------------------------------------------------------------------------- /storage/data/pickleball/skill_levels.md: -------------------------------------------------------------------------------- 1 | # How to Determine Your Pickleball Skill Level 2 | 3 | Pickleball skill levels typically range from 1.0 to 5.0+. Here's how you can figure out where you stand: 4 | 5 | 1. **Self-Assessment**: 6 | - Evaluate your ability to execute basic shots, strategies, and court positioning. 7 | - Consider your consistency, power, and control. 8 | 9 | 2. **Compare to Level Descriptions**: 10 | - 1.0-2.0: Beginner (new to the sport, learning basic rules and shots) 11 | - 2.5-3.0: Intermediate (consistent basic shots, beginning to use strategies) 12 | - 3.5-4.0: Advanced Intermediate (good shot variety, consistent serves, understands strategies) 13 | - 4.5-5.0: Advanced (excellent shot control, can play all shot types effectively, strong strategic play) 14 | - 5.0+: Open (tournament-level play, exceptional skills and strategy) 15 | 16 | 3. **Play with Rated Players**: 17 | - Play games with players of known skill levels to gauge where you fit. 18 | 19 | 4. **Attend a Skill Rating Clinic**: 20 | - Many clubs and tournaments offer skill rating clinics where professionals assess your play. 21 | 22 | 5. **Online Assessments**: 23 | - Some websites offer questionnaires to help estimate your skill level based on your abilities. 24 | 25 | 6. **Tournament Play**: 26 | - Participate in rated tournaments to get an official rating. 27 | 28 | 7. **Video Analysis**: 29 | - Record yourself playing and compare your form and strategies to players of different levels. 30 | 31 | 8. **Coaching Feedback**: 32 | - A pickleball coach can provide an expert assessment of your skills. 33 | 34 | Remember, skill levels can vary slightly between different organizations and regions. Consistently reassess your level as you improve to ensure accurate self-rating. 35 | -------------------------------------------------------------------------------- /storage/data/pickleball/ten_teammate_tips.md: -------------------------------------------------------------------------------- 1 | # How to Be a Good Teammate in Pickleball 2 | 3 | 1. **Communicate clearly on the court** 4 | 2. **Know your position and stick to it** 5 | 3. **Be supportive and encouraging** 6 | 4. **Call the ball to avoid collisions** 7 | 5. **Stay positive, even when losing** 8 | 6. **Be ready to receive serves and returns** 9 | 7. **Learn your partner's strengths and weaknesses** 10 | 8. **Cover the middle when your partner is pulled wide** 11 | 9. **Practice together to improve teamwork** 12 | 10. **Be a good sport, win or lose** 13 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | import defaultTheme from 'tailwindcss/defaultTheme'; 2 | import forms from '@tailwindcss/forms'; 3 | import typography from '@tailwindcss/typography'; 4 | 5 | /** @type {import('tailwindcss').Config} */ 6 | export default { 7 | content: [ 8 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', 9 | './vendor/laravel/jetstream/**/*.blade.php', 10 | './storage/framework/views/*.php', 11 | './resources/views/**/*.blade.php', 12 | ], 13 | 14 | theme: { 15 | extend: { 16 | fontFamily: { 17 | sans: ['Figtree', ...defaultTheme.fontFamily.sans], 18 | }, 19 | }, 20 | }, 21 | 22 | plugins: [forms, typography], 23 | }; 24 | -------------------------------------------------------------------------------- /tests/Feature/ApiTokenPermissionsTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->withPersonalTeam()->create()); 12 | } else { 13 | $this->actingAs($user = User::factory()->create()); 14 | } 15 | 16 | $token = $user->tokens()->create([ 17 | 'name' => 'Test Token', 18 | 'token' => Str::random(40), 19 | 'abilities' => ['create', 'read'], 20 | ]); 21 | 22 | Livewire::test(ApiTokenManager::class) 23 | ->set(['managingPermissionsFor' => $token]) 24 | ->set(['updateApiTokenForm' => [ 25 | 'permissions' => [ 26 | 'delete', 27 | 'missing-permission', 28 | ], 29 | ]]) 30 | ->call('updateApiToken'); 31 | 32 | expect($user->fresh()->tokens->first()) 33 | ->can('delete')->toBeTrue() 34 | ->can('read')->toBeFalse() 35 | ->can('missing-permission')->toBeFalse(); 36 | })->skip(function () { 37 | return ! Features::hasApiFeatures(); 38 | }, 'API support is not enabled.'); 39 | -------------------------------------------------------------------------------- /tests/Feature/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 7 | 8 | $response->assertStatus(200); 9 | }); 10 | 11 | test('users can authenticate using the login screen', function () { 12 | $user = User::factory()->create(); 13 | 14 | $response = $this->post('/login', [ 15 | 'email' => $user->email, 16 | 'password' => 'password', 17 | ]); 18 | 19 | $this->assertAuthenticated(); 20 | $response->assertRedirect(route('dashboard', absolute: false)); 21 | }); 22 | 23 | test('users cannot authenticate with invalid password', function () { 24 | $user = User::factory()->create(); 25 | 26 | $this->post('/login', [ 27 | 'email' => $user->email, 28 | 'password' => 'wrong-password', 29 | ]); 30 | 31 | $this->assertGuest(); 32 | }); 33 | -------------------------------------------------------------------------------- /tests/Feature/BrowserSessionsTest.php: -------------------------------------------------------------------------------- 1 | actingAs(User::factory()->create()); 9 | 10 | Livewire::test(LogoutOtherBrowserSessionsForm::class) 11 | ->set('password', 'password') 12 | ->call('logoutOtherBrowserSessions') 13 | ->assertSuccessful(); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/Feature/ChunkContentTest.php: -------------------------------------------------------------------------------- 1 | Http::response($embedding, 200), 10 | ]); 11 | 12 | Http::preventStrayRequests(); 13 | 14 | $data = get_fixture('example_markdown.txt', false); 15 | 16 | $results = (new \App\Domains\Documents\ChunkContent)->handle($data); 17 | 18 | expect($results->content)->not->toBeNull(); 19 | expect($results->chunks->count())->toBe(23); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /tests/Feature/ClientTest.php: -------------------------------------------------------------------------------- 1 | $data] 9 | ); 10 | 11 | \Illuminate\Support\Facades\Http::preventStrayRequests(); 12 | 13 | $client = new \App\Services\Ollama\Client; 14 | $response = $client->completion('What is PHP?'); 15 | 16 | $this->assertNotNull($response); 17 | }); 18 | 19 | test('should return chat response', function () { 20 | 21 | $data = get_fixture('simple_ollama_client_chat_results.json'); 22 | 23 | \Illuminate\Support\Facades\Http::fake([ 24 | 'localhost:11434/*' => $data] 25 | ); 26 | 27 | \Illuminate\Support\Facades\Http::preventStrayRequests(); 28 | 29 | $client = new \App\Services\Ollama\Client; 30 | $response = $client->chat([ 31 | [ 32 | 'role' => 'user', 33 | 'content' => 'What is PHP?', 34 | ], 35 | ]); 36 | 37 | $this->assertNotNull($response); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/Feature/CreateApiTokenTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->withPersonalTeam()->create()); 11 | } else { 12 | $this->actingAs($user = User::factory()->create()); 13 | } 14 | 15 | Livewire::test(ApiTokenManager::class) 16 | ->set(['createApiTokenForm' => [ 17 | 'name' => 'Test Token', 18 | 'permissions' => [ 19 | 'read', 20 | 'update', 21 | ], 22 | ]]) 23 | ->call('createApiToken'); 24 | 25 | expect($user->fresh()->tokens)->toHaveCount(1); 26 | expect($user->fresh()->tokens->first()) 27 | ->name->toEqual('Test Token') 28 | ->can('read')->toBeTrue() 29 | ->can('delete')->toBeFalse(); 30 | })->skip(function () { 31 | return ! Features::hasApiFeatures(); 32 | }, 'API support is not enabled.'); 33 | -------------------------------------------------------------------------------- /tests/Feature/CreateEventToolTest.php: -------------------------------------------------------------------------------- 1 | create([ 8 | 'args' => $data['args'], 9 | ]); 10 | 11 | \Pest\Laravel\assertDatabaseCount('events', 0); 12 | 13 | (new \App\Services\LlmServices\Functions\CreateEventTool)->handle($message); 14 | 15 | \Pest\Laravel\assertDatabaseCount('events', 19); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/Feature/DeleteAccountTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 10 | 11 | Livewire::test(DeleteUserForm::class) 12 | ->set('password', 'password') 13 | ->call('deleteUser'); 14 | 15 | expect($user->fresh())->toBeNull(); 16 | })->skip(function () { 17 | return ! Features::hasAccountDeletionFeatures(); 18 | }, 'Account deletion is not enabled.'); 19 | 20 | test('correct password must be provided before account can be deleted', function () { 21 | $this->actingAs($user = User::factory()->create()); 22 | 23 | Livewire::test(DeleteUserForm::class) 24 | ->set('password', 'wrong-password') 25 | ->call('deleteUser') 26 | ->assertHasErrors(['password']); 27 | 28 | expect($user->fresh())->not->toBeNull(); 29 | })->skip(function () { 30 | return ! Features::hasAccountDeletionFeatures(); 31 | }, 'Account deletion is not enabled.'); 32 | -------------------------------------------------------------------------------- /tests/Feature/DeleteApiTokenTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->withPersonalTeam()->create()); 12 | } else { 13 | $this->actingAs($user = User::factory()->create()); 14 | } 15 | 16 | $token = $user->tokens()->create([ 17 | 'name' => 'Test Token', 18 | 'token' => Str::random(40), 19 | 'abilities' => ['create', 'read'], 20 | ]); 21 | 22 | Livewire::test(ApiTokenManager::class) 23 | ->set(['apiTokenIdBeingDeleted' => $token->id]) 24 | ->call('deleteApiToken'); 25 | 26 | expect($user->fresh()->tokens)->toHaveCount(0); 27 | })->skip(function () { 28 | return ! Features::hasApiFeatures(); 29 | }, 'API support is not enabled.'); 30 | -------------------------------------------------------------------------------- /tests/Feature/EmailVerificationTest.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create([ 11 | 'email_verified_at' => null, 12 | ]); 13 | 14 | $response = $this->actingAs($user)->get('/email/verify'); 15 | 16 | $response->assertStatus(200); 17 | })->skip(function () { 18 | return ! Features::enabled(Features::emailVerification()); 19 | }, 'Email verification not enabled.'); 20 | 21 | test('email can be verified', function () { 22 | Event::fake(); 23 | 24 | $user = User::factory()->create([ 25 | 'email_verified_at' => null, 26 | ]); 27 | 28 | $verificationUrl = URL::temporarySignedRoute( 29 | 'verification.verify', 30 | now()->addMinutes(60), 31 | ['id' => $user->id, 'hash' => sha1($user->email)] 32 | ); 33 | 34 | $response = $this->actingAs($user)->get($verificationUrl); 35 | 36 | Event::assertDispatched(Verified::class); 37 | 38 | expect($user->fresh()->hasVerifiedEmail())->toBeTrue(); 39 | $response->assertRedirect(route('dashboard', absolute: false).'?verified=1'); 40 | })->skip(function () { 41 | return ! Features::enabled(Features::emailVerification()); 42 | }, 'Email verification not enabled.'); 43 | 44 | test('email can not verified with invalid hash', function () { 45 | $user = User::factory()->create([ 46 | 'email_verified_at' => null, 47 | ]); 48 | 49 | $verificationUrl = URL::temporarySignedRoute( 50 | 'verification.verify', 51 | now()->addMinutes(60), 52 | ['id' => $user->id, 'hash' => sha1('wrong-email')] 53 | ); 54 | 55 | $this->actingAs($user)->get($verificationUrl); 56 | 57 | expect($user->fresh()->hasVerifiedEmail())->toBeFalse(); 58 | })->skip(function () { 59 | return ! Features::enabled(Features::emailVerification()); 60 | }, 'Email verification not enabled.'); 61 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 5 | 6 | $response->assertStatus(200); 7 | }); 8 | -------------------------------------------------------------------------------- /tests/Feature/GroqClientTest.php: -------------------------------------------------------------------------------- 1 | Http::response($data, 200), 28 | ]); 29 | 30 | $results = $client->completion('test'); 31 | 32 | $this->assertInstanceOf(CompletionResponse::class, $results); 33 | 34 | } 35 | 36 | public function test_remap_messages(): void 37 | { 38 | $client = new GroqClient; 39 | $messages = [ 40 | MessageInDto::from([ 41 | 'content' => 'test', 42 | 'role' => 'user', 43 | ]), 44 | ]; 45 | $remapped = $client->remapMessages($messages); 46 | 47 | $first = $remapped[0]; 48 | 49 | $this->assertEquals('test', $first['content']); 50 | $this->assertEquals('user', $first['role']); 51 | $this->assertCount(2, $first); 52 | } 53 | 54 | public function test_completion_pool(): void 55 | { 56 | $client = new GroqClient; 57 | 58 | $data = get_fixture('groq_completion.json'); 59 | 60 | Http::fake([ 61 | 'api.groq.com/*' => Http::response($data, 200), 62 | ]); 63 | 64 | Http::preventStrayRequests(); 65 | 66 | $results = $client->completionPool([ 67 | 'test1', 68 | 'test2', 69 | 'test3', 70 | ]); 71 | 72 | $this->assertCount(3, $results); 73 | 74 | } 75 | 76 | public function test_chat(): void 77 | { 78 | 79 | $client = new GroqClient; 80 | 81 | $data = get_fixture('groq_completion.json'); 82 | 83 | Http::fake([ 84 | 'api.groq.com/*' => Http::response($data, 200), 85 | ]); 86 | 87 | $results = $client->chat([ 88 | MessageInDto::from([ 89 | 'content' => 'test', 90 | 'role' => 'system', 91 | ]), 92 | MessageInDto::from([ 93 | 'content' => 'test', 94 | 'role' => 'user', 95 | ]), 96 | ]); 97 | 98 | $this->assertInstanceOf(CompletionResponse::class, $results); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/Feature/Models/ChatTest.php: -------------------------------------------------------------------------------- 1 | create(); 5 | 6 | expect($chat->title)->not()->toBeNull(); 7 | }); 8 | 9 | test('messages in thread', function () { 10 | $chat = \App\Models\Chat::factory() 11 | ->has(\App\Models\Message::factory(2))->create(); 12 | 13 | expect($chat->messages()->count())->toBe(2); 14 | expect(count($chat->getChatResponse()))->toBe(2); 15 | }); 16 | 17 | test('add input', function () { 18 | $chat = \App\Models\Chat::factory()->create(); 19 | 20 | $message = $chat->addInput( 21 | message: 'test', 22 | role: \App\Services\LlmServices\Messages\RoleEnum::User, 23 | tool: 'test', 24 | tool_id: 'test', 25 | args: ['test'], 26 | ); 27 | 28 | expect($message->chat_id)->toBe($chat->id); 29 | expect($message->role)->toBe(\App\Services\LlmServices\Messages\RoleEnum::User); 30 | expect($message->tool_name)->toBe('test'); 31 | expect($message->tool_id)->toBe('test'); 32 | expect($message->args)->toBe(['test']); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/Feature/Models/ChunkTest.php: -------------------------------------------------------------------------------- 1 | withoutExceptionHandling(); 8 | $model = Chunk::factory()->create(); 9 | expect($model->content)->not->toBeNull(); 10 | expect($model->sort_order)->not->toBeNull(); 11 | expect($model->embedding_3072)->not->toBeNull(); 12 | expect($model->embedding_3072)->toBeInstanceOf(Vector::class); 13 | expect($model->document->id)->not->toBeNull(); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/Feature/Models/DocumentTest.php: -------------------------------------------------------------------------------- 1 | create(); 5 | expect($model->summary)->not->toBeNull(); 6 | expect($model->content)->not->toBeNull(); 7 | }); 8 | -------------------------------------------------------------------------------- /tests/Feature/Models/EventTest.php: -------------------------------------------------------------------------------- 1 | create(); 5 | expect($model->title)->not->toBeNull(); 6 | }); 7 | -------------------------------------------------------------------------------- /tests/Feature/Models/MessageTest.php: -------------------------------------------------------------------------------- 1 | create(); 5 | 6 | expect($model->chat_id)->not->toBeNull(); 7 | 8 | expect($model->role)->toBe(\App\Services\LlmServices\Messages\RoleEnum::User); 9 | }); 10 | -------------------------------------------------------------------------------- /tests/Feature/Models/NewsTest.php: -------------------------------------------------------------------------------- 1 | create(); 5 | expect($model->title)->not()->toBeNull(); 6 | }); 7 | -------------------------------------------------------------------------------- /tests/Feature/NewsDigestTest.php: -------------------------------------------------------------------------------- 1 | once() 9 | ->andReturn($data); 10 | 11 | \App\Models\News::factory(3)->create(); 12 | 13 | $results = (new \App\Domains\News\NewsDigest)->handle( 14 | now()->subDay(), now()->endOfDay() 15 | ); 16 | 17 | expect($results)->not()->toBeNull(); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /tests/Feature/NewsDigestTestCompletion.php: -------------------------------------------------------------------------------- 1 | once() 9 | ->andReturn($data); 10 | 11 | \App\Models\News::factory(3)->create(); 12 | 13 | $results = (new \App\Domains\News\NewsDigestCompletion)->handle( 14 | now()->subDay(), now()->endOfDay() 15 | ); 16 | 17 | expect($results)->not()->toBeNull(); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /tests/Feature/NewsFeedTest.php: -------------------------------------------------------------------------------- 1 | once() 9 | ->andReturn($data); 10 | 11 | $context = 'This is about how to make a hamburger'; 12 | 13 | $results = (new \App\Domains\News\NewsFeedParser)->handle($context); 14 | 15 | expect($results)->toBeFalse(); 16 | }); 17 | 18 | test('test can return summary', function () { 19 | 20 | $data = get_fixture('news_feed_good_response.json'); 21 | 22 | Facades\App\Services\Ollama\Client::shouldReceive('completion') 23 | ->once() 24 | ->andReturn($data); 25 | 26 | $context = get_fixture('news_feed_good.html', false); 27 | 28 | $results = (new \App\Domains\News\NewsFeedParser)->handle($context); 29 | 30 | expect($results)->not()->toBeNull(); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/Feature/OrchestrateTest.php: -------------------------------------------------------------------------------- 1 | chat') 5 | ->once() 6 | ->andReturn(new \App\Services\LlmServices\Responses\CompletionResponse('Hello')); 7 | 8 | $chat = \App\Models\Chat::factory()->create(); 9 | 10 | $results = (new \App\Services\LlmServices\Orchestration\Orchestrate)->handle($chat, 'Hello'); 11 | 12 | $this->assertDatabaseCount('messages', 2); 13 | }); 14 | 15 | test('runs create tool test', function () { 16 | \App\Services\LlmServices\LlmDriverFacade::shouldReceive('driver->chat') 17 | ->twice() 18 | ->andReturn(\App\Services\LlmServices\Responses\CompletionResponse::from( 19 | [ 20 | 'content' => 'I need to create an event called Cowboys vs Rams. I will need to provide the start time, end time, location, and description. I will also need to assign the event to an assistant.', 21 | 'tool_calls' => [ 22 | [ 23 | 'name' => 'create_event_tool', 24 | 'arguments' => [ 25 | 'events' => [ 26 | [ 27 | 'title' => 'Cowboys vs Rams', 28 | 'description' => 'Preseason Week 1', 29 | 'start_time' => '2024-08-11T15:30:00', 30 | 'end_time' => '2024-08-11T18:30:00', 31 | 'location' => 'SoFi Stadium', 32 | ], 33 | ], 34 | ], 35 | ], 36 | ], 37 | ] 38 | )); 39 | $chat = \App\Models\Chat::factory()->create(); 40 | 41 | $results = (new \App\Services\LlmServices\Orchestration\Orchestrate)->handle($chat, 'Hello'); 42 | 43 | $this->assertDatabaseCount('messages', 3); 44 | $this->assertDatabaseCount('events', 1); 45 | }); 46 | -------------------------------------------------------------------------------- /tests/Feature/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create() 9 | : User::factory()->create(); 10 | 11 | $response = $this->actingAs($user)->get('/user/confirm-password'); 12 | 13 | $response->assertStatus(200); 14 | }); 15 | 16 | test('password can be confirmed', function () { 17 | $user = User::factory()->create(); 18 | 19 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 20 | 'password' => 'password', 21 | ]); 22 | 23 | $response->assertRedirect(); 24 | $response->assertSessionHasNoErrors(); 25 | }); 26 | 27 | test('password is not confirmed with invalid password', function () { 28 | $user = User::factory()->create(); 29 | 30 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 31 | 'password' => 'wrong-password', 32 | ]); 33 | 34 | $response->assertSessionHasErrors(); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/Feature/PasswordResetTest.php: -------------------------------------------------------------------------------- 1 | get('/forgot-password'); 10 | 11 | $response->assertStatus(200); 12 | })->skip(function () { 13 | return ! Features::enabled(Features::resetPasswords()); 14 | }, 'Password updates are not enabled.'); 15 | 16 | test('reset password link can be requested', function () { 17 | Notification::fake(); 18 | 19 | $user = User::factory()->create(); 20 | 21 | $response = $this->post('/forgot-password', [ 22 | 'email' => $user->email, 23 | ]); 24 | 25 | Notification::assertSentTo($user, ResetPassword::class); 26 | })->skip(function () { 27 | return ! Features::enabled(Features::resetPasswords()); 28 | }, 'Password updates are not enabled.'); 29 | 30 | test('reset password screen can be rendered', function () { 31 | Notification::fake(); 32 | 33 | $user = User::factory()->create(); 34 | 35 | $response = $this->post('/forgot-password', [ 36 | 'email' => $user->email, 37 | ]); 38 | 39 | Notification::assertSentTo($user, ResetPassword::class, function (object $notification) { 40 | $response = $this->get('/reset-password/'.$notification->token); 41 | 42 | $response->assertStatus(200); 43 | 44 | return true; 45 | }); 46 | })->skip(function () { 47 | return ! Features::enabled(Features::resetPasswords()); 48 | }, 'Password updates are not enabled.'); 49 | 50 | test('password can be reset with valid token', function () { 51 | Notification::fake(); 52 | 53 | $user = User::factory()->create(); 54 | 55 | $response = $this->post('/forgot-password', [ 56 | 'email' => $user->email, 57 | ]); 58 | 59 | Notification::assertSentTo($user, ResetPassword::class, function (object $notification) use ($user) { 60 | $response = $this->post('/reset-password', [ 61 | 'token' => $notification->token, 62 | 'email' => $user->email, 63 | 'password' => 'password', 64 | 'password_confirmation' => 'password', 65 | ]); 66 | 67 | $response->assertSessionHasNoErrors(); 68 | 69 | return true; 70 | }); 71 | })->skip(function () { 72 | return ! Features::enabled(Features::resetPasswords()); 73 | }, 'Password updates are not enabled.'); 74 | -------------------------------------------------------------------------------- /tests/Feature/ProfileInformationTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 9 | 10 | $component = Livewire::test(UpdateProfileInformationForm::class); 11 | 12 | expect($component->state['name'])->toEqual($user->name); 13 | expect($component->state['email'])->toEqual($user->email); 14 | }); 15 | 16 | test('profile information can be updated', function () { 17 | $this->actingAs($user = User::factory()->create()); 18 | 19 | Livewire::test(UpdateProfileInformationForm::class) 20 | ->set('state', ['name' => 'Test Name', 'email' => 'test@example.com']) 21 | ->call('updateProfileInformation'); 22 | 23 | expect($user->fresh()) 24 | ->name->toEqual('Test Name') 25 | ->email->toEqual('test@example.com'); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/Feature/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | get('/register'); 8 | 9 | $response->assertStatus(200); 10 | })->skip(function () { 11 | return ! Features::enabled(Features::registration()); 12 | }, 'Registration support is not enabled.'); 13 | 14 | test('registration screen cannot be rendered if support is disabled', function () { 15 | $response = $this->get('/register'); 16 | 17 | $response->assertStatus(404); 18 | })->skip(function () { 19 | return Features::enabled(Features::registration()); 20 | }, 'Registration support is enabled.'); 21 | 22 | test('new users can register', function () { 23 | $response = $this->post('/register', [ 24 | 'name' => 'Test User', 25 | 'email' => 'test@example.com', 26 | 'password' => 'password', 27 | 'password_confirmation' => 'password', 28 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(), 29 | ]); 30 | 31 | $this->assertAuthenticated(); 32 | $response->assertRedirect(route('dashboard', absolute: false)); 33 | })->skip(function () { 34 | return ! Features::enabled(Features::registration()); 35 | }, 'Registration support is not enabled.'); 36 | -------------------------------------------------------------------------------- /tests/Feature/TwoFactorAuthenticationSettingsTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()->fresh()); 10 | 11 | $this->withSession(['auth.password_confirmed_at' => time()]); 12 | 13 | Livewire::test(TwoFactorAuthenticationForm::class) 14 | ->call('enableTwoFactorAuthentication'); 15 | 16 | $user = $user->fresh(); 17 | 18 | expect($user->two_factor_secret)->not->toBeNull(); 19 | expect($user->recoveryCodes())->toHaveCount(8); 20 | })->skip(function () { 21 | return ! Features::canManageTwoFactorAuthentication(); 22 | }, 'Two factor authentication is not enabled.'); 23 | 24 | test('recovery codes can be regenerated', function () { 25 | $this->actingAs($user = User::factory()->create()->fresh()); 26 | 27 | $this->withSession(['auth.password_confirmed_at' => time()]); 28 | 29 | $component = Livewire::test(TwoFactorAuthenticationForm::class) 30 | ->call('enableTwoFactorAuthentication') 31 | ->call('regenerateRecoveryCodes'); 32 | 33 | $user = $user->fresh(); 34 | 35 | $component->call('regenerateRecoveryCodes'); 36 | 37 | expect($user->recoveryCodes())->toHaveCount(8); 38 | expect(array_diff($user->recoveryCodes(), $user->fresh()->recoveryCodes()))->toHaveCount(8); 39 | })->skip(function () { 40 | return ! Features::canManageTwoFactorAuthentication(); 41 | }, 'Two factor authentication is not enabled.'); 42 | 43 | test('two factor authentication can be disabled', function () { 44 | $this->actingAs($user = User::factory()->create()->fresh()); 45 | 46 | $this->withSession(['auth.password_confirmed_at' => time()]); 47 | 48 | $component = Livewire::test(TwoFactorAuthenticationForm::class) 49 | ->call('enableTwoFactorAuthentication'); 50 | 51 | $this->assertNotNull($user->fresh()->two_factor_secret); 52 | 53 | $component->call('disableTwoFactorAuthentication'); 54 | 55 | expect($user->fresh()->two_factor_secret)->toBeNull(); 56 | })->skip(function () { 57 | return ! Features::canManageTwoFactorAuthentication(); 58 | }, 'Two factor authentication is not enabled.'); 59 | -------------------------------------------------------------------------------- /tests/Feature/UpdatePasswordTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 10 | 11 | Livewire::test(UpdatePasswordForm::class) 12 | ->set('state', [ 13 | 'current_password' => 'password', 14 | 'password' => 'new-password', 15 | 'password_confirmation' => 'new-password', 16 | ]) 17 | ->call('updatePassword'); 18 | 19 | expect(Hash::check('new-password', $user->fresh()->password))->toBeTrue(); 20 | }); 21 | 22 | test('current password must be correct', function () { 23 | $this->actingAs($user = User::factory()->create()); 24 | 25 | Livewire::test(UpdatePasswordForm::class) 26 | ->set('state', [ 27 | 'current_password' => 'wrong-password', 28 | 'password' => 'new-password', 29 | 'password_confirmation' => 'new-password', 30 | ]) 31 | ->call('updatePassword') 32 | ->assertHasErrors(['current_password']); 33 | 34 | expect(Hash::check('password', $user->fresh()->password))->toBeTrue(); 35 | }); 36 | 37 | test('new passwords must match', function () { 38 | $this->actingAs($user = User::factory()->create()); 39 | 40 | Livewire::test(UpdatePasswordForm::class) 41 | ->set('state', [ 42 | 'current_password' => 'password', 43 | 'password' => 'new-password', 44 | 'password_confirmation' => 'wrong-password', 45 | ]) 46 | ->call('updatePassword') 47 | ->assertHasErrors(['password']); 48 | 49 | expect(Hash::check('password', $user->fresh()->password))->toBeTrue(); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Expectations 22 | |-------------------------------------------------------------------------- 23 | | 24 | | When you're writing tests, you often need to check that values meet certain conditions. The 25 | | "expect()" function gives you access to a set of "expectations" methods that you can use 26 | | to assert different things. Of course, you may extend the Expectation API at any time. 27 | | 28 | */ 29 | 30 | expect()->extend('toBeOne', function () { 31 | return $this->toBe(1); 32 | }); 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Functions 37 | |-------------------------------------------------------------------------- 38 | | 39 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 40 | | project that you don't want to repeat in every file. Here you can also expose helpers as 41 | | global functions to help you to reduce the number of lines of code in your test files. 42 | | 43 | */ 44 | 45 | function something() 46 | { 47 | // .. 48 | } 49 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | test 3<\/thinking>" 20 | }, 21 | { 22 | "type": "tool_use", 23 | "id": "toolu_019wStJ3pKNRiqhAhbUfpPv8", 24 | "name": "create_event_tool", 25 | "input": { 26 | "events": [ 27 | { 28 | "title": "Cowboys vs Rams", 29 | "description": "Preseason Week 1", 30 | "start_time": "2024-08-11T15:30:00", 31 | "end_time": "2024-08-11T18:30:00", 32 | "location": "SoFi Stadium" 33 | } 34 | ] 35 | } 36 | } 37 | ] 38 | }, 39 | { 40 | "role": "user", 41 | "content": [ 42 | { 43 | "type": "tool_result", 44 | "tool_use_id": "toolu_019wStJ3pKNRiqhAhbUfpPv8", 45 | "content": "test 3" 46 | } 47 | ] 48 | } 49 | ] -------------------------------------------------------------------------------- /tests/fixtures/claude_remap_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": "claude-3-haiku-20240307", 3 | "max_tokens": 4096, 4 | "messages": [ 5 | { 6 | "role": "user", 7 | "content": "Hello Worlds!" 8 | } 9 | ], 10 | "system": "You are an alien robot so you respond from enother planet to earth as if you are not sure who we are." 11 | } -------------------------------------------------------------------------------- /tests/fixtures/claude_results.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "msg_012yjf4dWMmm57ikCTibrraL", 3 | "type": "message", 4 | "role": "assistant", 5 | "model": "claude-3-haiku-20240307", 6 | "content": [ 7 | { 8 | "type": "text", 9 | "text": "{\n \"full_name\": \"John Doe\",\n \"home_address\": \"123 Main Street, Anytown, USA\",\n \"other_name\": null,\n \"reply_to\": null,\n \"valid_user_email\": \"johndoe@example.com\",\n \"phone\": \"555-555-5555\"\n}" 10 | } 11 | ], 12 | "stop_reason": "end_turn", 13 | "stop_sequence": null, 14 | "usage": { 15 | "input_tokens": 507, 16 | "output_tokens": 86 17 | } 18 | } -------------------------------------------------------------------------------- /tests/fixtures/cloud_client_tool_use_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "msg_01Aq9w938a90dw8q", 3 | "model": "claude-3-opus-20240229", 4 | "stop_reason": "tool_use", 5 | "role": "assistant", 6 | "content": [ 7 | { 8 | "type": "text", 9 | "text": "I need to use the summarize_collection" 10 | }, 11 | { 12 | "type": "tool_use", 13 | "id": "toolu_01A09q90qw90lq917835lq9", 14 | "name": "summarize_collection", 15 | "input": {"prompt": "TLDR the document"} 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tests/fixtures/example_email.md: -------------------------------------------------------------------------------- 1 | Subject: Request for Deletion of Personal Information 2 | 3 | My name is John Doe, and I request that Unearth deletes all of the information it has collected about me, whether directly from me, through a third party, or through a service provider. 4 | 5 | The following information is provided as required for validation of my request: 6 | 7 | First Name: John 8 | Last Name: Doe 9 | Email: johndoe@example.com 10 | Address: 123 Main Street, Anytown, USA 11 | Phone Number: 555-555-5555 12 | 13 | If you need any more information, please let me know as soon as possible. If you cannot comply with my request—either in whole or in part—please state the reason why you cannot comply. If part of my information is subject to an exception, please delete all information that is not subject to an exception. If my request is incomplete, please provide me with specific instructions on how to complete my request. 14 | 15 | I have used a consumer identity protection service to send this email, but please respond directly to my personal email address johndoe@example.com. 16 | 17 | I am sending this request exercising my rights to my Personal Data under applicable privacy laws, including, but not limited to, the General Data Protection Regulation (“GDPR”) and the California Consumer Privacy Act (“CCPA”) 18 | -------------------------------------------------------------------------------- /tests/fixtures/groq_chat_messages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "content": "You are an alien robot so you respond from enother planet to earth as if you are not sure who we are.", 4 | "role": "system" 5 | }, 6 | { 7 | "content": "Hello Worlds!", 8 | "role": "user" 9 | } 10 | ] -------------------------------------------------------------------------------- /tests/fixtures/groq_chat_payload_post_remap.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "content": "What do you know about Laravel", 4 | "role": "user" 5 | } 6 | ] -------------------------------------------------------------------------------- /tests/fixtures/groq_chat_payload_pre_remap.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "content": "What do you know about Laravel", 4 | "role": "user", 5 | "is_ai": false, 6 | "show": true, 7 | "tool": "", 8 | "tool_id": "", 9 | "args": [], 10 | "meta_data": null 11 | } 12 | ] -------------------------------------------------------------------------------- /tests/fixtures/groq_completion.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "chatcmpl-a81fe4d3-ff84-4b53-936b-b173a4126597", 3 | "object": "chat.completion", 4 | "created": 1714475587, 5 | "model": "mixtral-8x7b-32768", 6 | "choices": [ 7 | { 8 | "index": 0, 9 | "message": { 10 | "role": "assistant", 11 | "content": "Laravel is a popular open-source PHP web application framework that follows the Model-View-Controller (MVC) architectural pattern. It was created by Taylor Otwell and was first released in June 2011. Laravel is known for its elegant syntax, high level of abstraction, and large community of developers and contributors.\n\nSome of the key features of Laravel include:\n\n* A powerful ORM (Object-Relational Mapping) system for working with databases\n* A clean and expressive syntax for building web applications\n* A robust set of tools for routing, middleware, and controllers\n* An integrated template engine for building user interfaces\n* Support for Artisan, a command-line interface for Laravel\n* A large ecosystem of third-party packages and libraries\n\nLaravel is a popular choice for web developers due to its ease of use, flexibility, and scalability. It is suitable for a wide range of web applications, from small, simple websites to large, complex systems.\n\nIf you are interested in learning more about Laravel, there are many resources available online, including the official Laravel documentation, tutorials, and video courses. I would suggest starting with the Laravel documentation to get a solid understanding of the framework's core concepts and features. From there, you can explore the many other resources available to help you learn and master Laravel." 12 | }, 13 | "logprobs": null, 14 | "finish_reason": "stop" 15 | } 16 | ], 17 | "usage": { 18 | "prompt_tokens": 18, 19 | "prompt_time": 0.005, 20 | "completion_tokens": 296, 21 | "completion_time": 0.522, 22 | "total_tokens": 314, 23 | "total_time": 0.527 24 | }, 25 | "system_fingerprint": "fp_c5f20b5bb1", 26 | "x_groq": { 27 | "id": "req_01hwqban9tfw6aamnka4w7jtrp" 28 | } 29 | } -------------------------------------------------------------------------------- /tests/fixtures/groq_functions_prompt.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "content": "You are a helpful assistant in a RAG system with tools and functions to help perform tasks. \nWhen you find the right function make sure to return just the JSON that represents the requirements of that function. \nIf no function is found just return {} empty json\n\nIf so can you return the function name and arguments to call it with. the return format would just be json\nand it would be empty if no function is needed. But if a function is needed it would be like this:\n[\n {\n \"name\": \"example_function_name\",\n \"arguments\": {\n \"prompt\": \"The users prompt here\"\n }\n }\n]\nHere is a list of the function names, description and parameters for the function. IT IS OK TO RETURN EMPTY ARRAY if none are needed.\nNo extra text like \"I think it is this function\"\nThe default function the system uses will take care of anything else so if the user just wants a word or phrase search just return an empy array the default.\nDo not stray from this below list since these are the only functions the system can run other than the default one mentioned above. The below list of \nfunctions to choose from will start with ### START FUNCTION and end with ### END FUNCTION. Pleas ONLY choose from that list and return JSON OR return [] if \nnone are a fit which is ok too: \n\\n", 4 | "role": "system" 5 | }, 6 | { 7 | "content": "can you summarize the content in the collection", 8 | "role": "user" 9 | } 10 | ] -------------------------------------------------------------------------------- /tests/fixtures/groq_functions_prompt_real.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "content": "You are a helpful assistant in a RAG system with tools and functions to help perform tasks. \nWhen you find the right function make sure to return just the JSON that represents the requirements of that function. \nIf no function is found just return {} empty json\n\nIf so can you return the function name and arguments to call it with. the return format would just be json\nand it would be empty if no function is needed. But if a function is needed it would be like this:\n[\n {\n \"name\": \"example_function_name\",\n \"arguments\": {\n \"prompt\": \"The users prompt here\"\n }\n }\n]\nHere is a list of the function names, description and parameters for the function. IT IS OK TO RETURN EMPTY ARRAY if none are needed.\nNo extra text like \"I think it is this function\"\nThe default function the system uses will take care of anything else so if the user just wants a word or phrase search just return an empy array the default.\nDo not stray from this below list since these are the only functions the system can run other than the default one mentioned above. The below list of \nfunctions to choose from will start with ### START FUNCTION and end with ### END FUNCTION. Pleas ONLY choose from that list and return JSON OR return [] if \nnone are a fit which is ok too: \n### START FUNCTION \n name: summarize_collection, description: NOT FOR SEARCH, This is used when the prompt wants to summarize the entire collection of documents, parameters: {\"type\":\"object\",\"properties\":{\"prompt\":{\"description\":\"The prompt the user is using the search for.\",\"type\":\"string\",\"enum\":[],\"default\":\"\"}},\"required\":[\"prompt\"]} \n ### END FUNCTION\\n### START FUNCTION \n name: search_and_summarize, description: Used to embed users prompt, search database and return summarized results., parameters: {\"type\":\"object\",\"properties\":{\"prompt\":{\"description\":\"This is the prompt the user is using to search the database and may or may not assist the results.\",\"type\":\"string\",\"enum\":[],\"default\":\"\"}},\"required\":[]} \n ### END FUNCTION", 4 | "role": "system" 5 | }, 6 | { 7 | "content": "can you summarize the collection", 8 | "role": "user" 9 | } 10 | ] -------------------------------------------------------------------------------- /tests/fixtures/groq_functions_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "chatcmpl-e1fe3e51-f81c-45b3-b0cf-fb862332304d", 3 | "object": "chat.completion", 4 | "created": 1714481375, 5 | "model": "mixtral-8x7b-32768", 6 | "choices": [ 7 | { 8 | "index": 0, 9 | "message": { 10 | "role": "assistant", 11 | "content": "[\n {\n \"name\": \"summarize_content\",\n \"arguments\": {\n \"collection\": \"The content to summarize\"\n }\n }\n]" 12 | }, 13 | "logprobs": null, 14 | "finish_reason": "stop" 15 | } 16 | ], 17 | "usage": { 18 | "prompt_tokens": 325, 19 | "prompt_time": 0.074, 20 | "completion_tokens": 42, 21 | "completion_time": 0.076, 22 | "total_tokens": 367, 23 | "total_time": 0.15 24 | }, 25 | "system_fingerprint": "fp_7b44c65f25", 26 | "x_groq": { 27 | "id": "req_01hwqgv9tkefg93ng07j3da8je" 28 | } 29 | } -------------------------------------------------------------------------------- /tests/fixtures/groq_functions_response_real.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "chatcmpl-55229faa-82f5-4935-9bf3-899c00194e09", 3 | "object": "chat.completion", 4 | "created": 1714482793, 5 | "model": "mixtral-8x7b-32768", 6 | "choices": [ 7 | { 8 | "index": 0, 9 | "message": { 10 | "role": "assistant", 11 | "content": "[\n {\n \"name\": \"summarize_collection\",\n \"arguments\": {\n \"prompt\": \"can you summarize the collection\"\n }\n }\n]" 12 | }, 13 | "logprobs": null, 14 | "finish_reason": "stop" 15 | } 16 | ], 17 | "usage": { 18 | "prompt_tokens": 508, 19 | "prompt_time": 0.323, 20 | "completion_tokens": 44, 21 | "completion_time": 0.079, 22 | "total_tokens": 552, 23 | "total_time": 0.402 24 | }, 25 | "system_fingerprint": "fp_c5f20b5bb1", 26 | "x_groq": { 27 | "id": "req_01hwqj6j64fh2tm9kkmnr3cg1p" 28 | } 29 | } -------------------------------------------------------------------------------- /tests/fixtures/http_results.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "msg_016ioPeA9oFpTH97Fnts5waD", 3 | "type": "message", 4 | "role": "assistant", 5 | "model": "claude-3-5-sonnet-20240620", 6 | "content": [ 7 | { 8 | "type": "text", 9 | "text": "[\n {\n \"tag\": \"PHP\",\n \"reason\": \"The post is primarily about using PHP to interact with LLMs\"\n },\n {\n \"tag\": \"LLMs\",\n \"reason\": \"Large Language Models are the main topic being discussed throughout the post\"\n },\n {\n \"tag\": \"Laravel\",\n \"reason\": \"The code examples use Laravel facades and conventions\"\n },\n {\n \"tag\": \"Ollama\",\n \"reason\": \"Ollama is used as the local LLM platform in the examples\"\n },\n {\n \"tag\": \"Testing\",\n \"reason\": \"The post covers mocking and testing strategies for LLM integrations\"\n }\n]\n\n<\/context>" 10 | } 11 | ], 12 | "stop_reason": "end_turn", 13 | "stop_sequence": null, 14 | "usage": { 15 | "input_tokens": 19050, 16 | "output_tokens": 181 17 | } 18 | } -------------------------------------------------------------------------------- /tests/fixtures/news_feed_false.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": "false", 3 | "stop_reason": "", 4 | "tool_calls": [] 5 | } -------------------------------------------------------------------------------- /tests/fixtures/openai_chat_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": "gpt-4o", 3 | "messages": [ 4 | { 5 | "content": "You are an alien robot so you respond from another planet to earth as if you are not sure who we are.", 6 | "role": "system", 7 | "tool_id": "", 8 | "tool": "", 9 | "args": [] 10 | }, 11 | { 12 | "content": "Hello Worlds!", 13 | "role": "user", 14 | "tool_id": "", 15 | "tool": "", 16 | "args": [] 17 | } 18 | ], 19 | "temperature": 0.1 20 | } -------------------------------------------------------------------------------- /tests/fixtures/openai_chat_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "chatcmpl-A361XByusoss6K8Tz2Xx0tEMbZ0k7", 3 | "object": "chat.completion", 4 | "created": 1725301503, 5 | "model": "gpt-4o-2024-05-13", 6 | "choices": [ 7 | { 8 | "index": 0, 9 | "message": { 10 | "role": "assistant", 11 | "content": "Greetings, Earth entity! Your message has reached us on Planet Xylar. We are intrigued by your use of the term \"Worlds.\" Are you referring to multiple planetary bodies or perhaps different dimensions within your own planet? Please enlighten us about your fascinating existence!", 12 | "refusal": null 13 | }, 14 | "logprobs": null, 15 | "finish_reason": "stop" 16 | } 17 | ], 18 | "usage": { 19 | "prompt_tokens": 37, 20 | "completion_tokens": 54, 21 | "total_tokens": 91 22 | }, 23 | "system_fingerprint": "fp_157b3831f5" 24 | } -------------------------------------------------------------------------------- /tests/fixtures/simple_ollama_client_chat_results.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": "phi3:latest", 3 | "created_at": "2024-08-22T17:11:40.160747Z", 4 | "message": { 5 | "role": "assistant", 6 | "content": "PHP, which stands for Hypertext Preprocessor, is a widely-used open source server-side scripting language. It was originally created by Rasmus Lerdorf in 1994 and has since evolved into an object-oriented programming language that's primarily used to develop dynamic web pages and applications.\n\nPHP code is executed on the server, not the client's browser, which means it can generate HTML (or other output formats) dynamically based on user input or database content before sending it to the client's browser for display. This makes PHP an essential tool in creating interactive websites that require real-time data processing and manipulation.\n\nSome of its key features include:\n\n1. Easy integration with HTML, allowing developers to embed PHP code within their web pages easily.\n2. A vast ecosystem of libraries, frameworks, and tools for various purposes like database access (e.g., PDO), session management, authentication, and more.\n3. Strong community support that provides a wealth of resources such as tutorials, documentation, and third-party packages to help developers get started or solve complex problems quickly.\n4. Cross-platform compatibility, meaning it can run on various operating systems like Linux, Windows, and macOS.\n5. Support for multiple programming paradigms, including procedural, object-oriented, and functional programming styles.\n6. Extensive built-in functions that cover a wide range of tasks from string manipulation to date\/time handling.\n7. Easy integration with other web technologies like JavaScript, CSS, AJAX, and RESTful APIs for creating rich user experiences.\n\nOverall, PHP is an essential tool in the world of web development due to its ease-of-use, flexibility, and extensive support ecosystem. It's widely used by developers around the globe to create dynamic websites, content management systems (CMS), e-commerce platforms, social networks, and more." 7 | }, 8 | "done_reason": "stop", 9 | "done": true, 10 | "total_duration": 16670826417, 11 | "load_duration": 5122545583, 12 | "prompt_eval_count": 14, 13 | "prompt_eval_duration": 84989000, 14 | "eval_count": 412, 15 | "eval_duration": 11462114000 16 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | laravel({ 7 | input: [ 8 | 'resources/css/app.css', 9 | 'resources/js/app.js', 10 | ], 11 | refresh: true, 12 | }), 13 | ], 14 | }); 15 | --------------------------------------------------------------------------------