diff --git a/.env.example b/.env.example index 219bbb8c..33db8751 100644 --- a/.env.example +++ b/.env.example @@ -65,3 +65,8 @@ VITE_APP_NAME="${APP_NAME}" OPENAI_API_KEY= OPENAI_ORGANIZATION= + + +REVERB_APP_ID=SOMETHING +REVERB_APP_KEY=SOMETHING +REVERB_APP_SECRET=SOMETHING \ No newline at end of file diff --git a/README.md b/README.md index fd5c3eff..6ac6b6fb 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,8 @@ CREATE EXTENSION vector; And just thinking how to make some of those flows work in a Laravel environment [![LaraChain Full Demo](https://img.youtube.com/vi/cz7d6d3pk4o/0.jpg)](https://www.youtube.com/watch?v=cz7d6d3pk4o) + + +## Make sure to setup Reverb + +Per the Laravel docs https://laravel.com/docs/11.x/reverb \ No newline at end of file diff --git a/app/Domains/Documents/Transformers/PdfTransformer.php b/app/Domains/Documents/Transformers/PdfTransformer.php index 6ef7bfc3..a6a71817 100644 --- a/app/Domains/Documents/Transformers/PdfTransformer.php +++ b/app/Domains/Documents/Transformers/PdfTransformer.php @@ -2,6 +2,8 @@ namespace App\Domains\Documents\Transformers; +use App\Domains\Collections\CollectionStatusEnum; +use App\Events\CollectionStatusEvent; use App\Jobs\SummarizeDataJob; use App\Jobs\SummarizeDocumentJob; use App\Jobs\VectorlizeDataJob; @@ -48,6 +50,8 @@ public function handle(Document $document): Document new SummarizeDataJob($DocumentChunk), //Tagging ]; + + CollectionStatusEvent::dispatch($document->collection, CollectionStatusEnum::PROCESSING); } $batch = Bus::batch($chunks) diff --git a/app/Domains/Messages/RoleEnum.php b/app/Domains/Messages/RoleEnum.php new file mode 100644 index 00000000..becb1a25 --- /dev/null +++ b/app/Domains/Messages/RoleEnum.php @@ -0,0 +1,10 @@ + $input]); + + /** @var EmbeddingsResponseDto $embedding */ + $embedding = LlmDriverFacade::driver( + $chat->chatable->getDriver() + )->embedData($input); + + $results = DocumentChunk::query() + ->join('documents', 'documents.id', '=', 'document_chunks.document_id') + ->selectRaw( + 'document_chunks.embedding <-> ? as distance, document_chunks.content, document_chunks.embedding as embedding, document_chunks.id as id', + [$embedding->embedding] + ) + ->where('documents.collection_id', $chat->chatable->id) + ->limit(5) + ->orderByRaw('distance') + ->get(); + + $content = []; + + foreach ($results as $result) { + $content[] = reduce_text_size($result->content); + } + + $content = implode(' ', $content); + + $content = 'This is data from the search results when entering the users prompt please use this for context and only this: '.$content; + + $chat->addInput( + message: $content, + role: RoleEnum::Assistant, + systemPrompt: $chat->chatable->systemPrompt(), + show_in_thread: false + ); + + $chat->addInput($input, RoleEnum::User, $chat->chatable->systemPrompt()); + + $latestMessagesArray = $chat->getChatResponse(); + + /** @var CompletionResponse $response */ + $response = LlmDriverFacade::driver( + $chat->chatable->getDriver() + )->chat($latestMessagesArray); + + $chat->addInput($response->content, RoleEnum::Assistant); + + return $response->content; + } +} diff --git a/app/Events/ChatUpdatedEvent.php b/app/Events/ChatUpdatedEvent.php new file mode 100644 index 00000000..b4ee1910 --- /dev/null +++ b/app/Events/ChatUpdatedEvent.php @@ -0,0 +1,44 @@ + + */ + public function broadcastOn(): array + { + return [ + new PrivateChannel('collection.chat.'.$this->collection->id.'.'.$this->chat->id), + ]; + } + + /** + * The event's broadcast name. + */ + public function broadcastAs(): string + { + return 'status'; + } +} diff --git a/app/Http/Controllers/ChatController.php b/app/Http/Controllers/ChatController.php index e52d6355..5ea2bd99 100644 --- a/app/Http/Controllers/ChatController.php +++ b/app/Http/Controllers/ChatController.php @@ -2,10 +2,13 @@ namespace App\Http\Controllers; +use App\Events\ChatUpdatedEvent; use App\Http\Resources\ChatResource; use App\Http\Resources\CollectionResource; +use App\Http\Resources\MessageResource; use App\Models\Chat; use App\Models\Collection; +use Facades\App\Domains\Messages\SearchOrSummarizeChatRepo; class ChatController extends Controller { @@ -27,11 +30,24 @@ public function storeCollectionChat(Collection $collection) public function showCollectionChat(Collection $collection, Chat $chat) { - return inertia('Collection/Chat', [ 'collection' => new CollectionResource($collection), 'chat' => new ChatResource($chat), + 'system_prompt' => $collection->systemPrompt(), + 'messages' => MessageResource::collection($chat->latest_messages), ]); + } + + public function chat(Chat $chat) + { + $validated = request()->validate([ + 'input' => 'required|string', + ]); + + $response = SearchOrSummarizeChatRepo::search($chat, $validated['input']); + + ChatUpdatedEvent::dispatch($chat->chatable, $chat); + return response()->json(['message' => $response]); } } diff --git a/app/Http/Controllers/CollectionController.php b/app/Http/Controllers/CollectionController.php index a3a22d0d..d79565d4 100644 --- a/app/Http/Controllers/CollectionController.php +++ b/app/Http/Controllers/CollectionController.php @@ -30,6 +30,7 @@ public function store() $validated = request()->validate([ 'name' => 'required', 'description' => 'required', + 'driver' => 'required', ]); $validated['team_id'] = auth()->user()->current_team_id; diff --git a/app/Http/Resources/ChatResource.php b/app/Http/Resources/ChatResource.php index a15090ac..f190fd66 100644 --- a/app/Http/Resources/ChatResource.php +++ b/app/Http/Resources/ChatResource.php @@ -14,6 +14,9 @@ class ChatResource extends JsonResource */ public function toArray(Request $request): array { - return parent::toArray($request); + return [ + 'id' => $this->id, + 'user_id' => new UserResource($this->user), + ]; } } diff --git a/app/Http/Resources/MessageResource.php b/app/Http/Resources/MessageResource.php new file mode 100644 index 00000000..6a66fa6c --- /dev/null +++ b/app/Http/Resources/MessageResource.php @@ -0,0 +1,26 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'from_ai' => $this->from_ai, + 'initials' => ($this->from_ai) ? 'Ai' : 'You', + 'type' => 'text', //@TODO + 'body' => $this->body, + 'diff_for_humans' => $this->created_at->diffForHumans(), + ]; + } +} diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php new file mode 100644 index 00000000..3b6b127a --- /dev/null +++ b/app/Http/Resources/UserResource.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/app/LlmDriver/BaseClient.php b/app/LlmDriver/BaseClient.php index 35308ea7..3ca6a678 100644 --- a/app/LlmDriver/BaseClient.php +++ b/app/LlmDriver/BaseClient.php @@ -2,6 +2,7 @@ namespace App\LlmDriver; +use App\LlmDriver\Requests\MessageInDto; use App\LlmDriver\Responses\CompletionResponse; use App\LlmDriver\Responses\EmbeddingsResponseDto; use Illuminate\Support\Facades\Log; @@ -12,19 +13,43 @@ abstract class BaseClient public function embedData(string $data): EmbeddingsResponseDto { - + if (! app()->environment('testing')) { + sleep(2); + } Log::info('LlmDriver::MockClient::embedData'); $data = get_fixture('embedding_response.json'); - return new EmbeddingsResponseDto( - data_get($data, 'data.0.embedding'), - 1000, - ); + return EmbeddingsResponseDto::from([ + 'embedding' => data_get($data, 'data.0.embedding'), + 'token_count' => 1000, + ]); + } + + /** + * @param MessageInDto[] $messages + */ + public function chat(array $messages): CompletionResponse + { + if (! app()->environment('testing')) { + sleep(2); + } + + Log::info('LlmDriver::MockClient::completion'); + + $data = <<<'EOD' + Voluptate irure cillum dolor anim officia reprehenderit dolor. Eiusmod veniam nostrud consectetur incididunt proident id. Anim adipisicing pariatur amet duis Lorem sunt veniam veniam est. Deserunt ea aliquip cillum pariatur consectetur. Dolor in reprehenderit adipisicing consectetur cupidatat ad cupidatat reprehenderit. Nostrud mollit voluptate aliqua anim pariatur excepteur eiusmod velit quis exercitation tempor quis excepteur. +EOD; + + return new CompletionResponse($data); } public function completion(string $prompt): CompletionResponse { + if (! app()->environment('testing')) { + sleep(2); + } + Log::info('LlmDriver::MockClient::completion'); $data = <<<'EOD' diff --git a/app/LlmDriver/OpenAiClient.php b/app/LlmDriver/OpenAiClient.php index 092bc6f7..f83c7579 100644 --- a/app/LlmDriver/OpenAiClient.php +++ b/app/LlmDriver/OpenAiClient.php @@ -2,6 +2,7 @@ namespace App\LlmDriver; +use App\LlmDriver\Requests\MessageInDto; use App\LlmDriver\Responses\CompletionResponse; use App\LlmDriver\Responses\EmbeddingsResponseDto; use OpenAI\Laravel\Facades\OpenAI; @@ -10,6 +11,27 @@ class OpenAiClient extends BaseClient { protected string $driver = 'openai'; + /** + * @param MessageInDto[] $messages + */ + public function chat(array $messages): CompletionResponse + { + $response = OpenAI::chat()->create([ + 'model' => $this->getConfig('openai')['completion_model'], + 'messages' => collect($messages)->map(function ($message) { + return $message->toArray(); + })->toArray(), + ]); + + $results = null; + + foreach ($response->choices as $result) { + $results = $result->message->content; + } + + return new CompletionResponse($results); + } + public function embedData(string $data): EmbeddingsResponseDto { @@ -24,10 +46,10 @@ public function embedData(string $data): EmbeddingsResponseDto $results = $embedding->embedding; // [0.018990106880664825, -0.0073809814639389515, ...] } - return new EmbeddingsResponseDto( - $results, - $response->usage->totalTokens, - ); + return EmbeddingsResponseDto::from([ + 'embedding' => $results, + 'token_count' => $response->usage->totalTokens, + ]); } public function completion(string $prompt, int $temperature = 0): CompletionResponse diff --git a/app/LlmDriver/Requests/MessageInDto.php b/app/LlmDriver/Requests/MessageInDto.php new file mode 100644 index 00000000..c4a8bc14 --- /dev/null +++ b/app/LlmDriver/Requests/MessageInDto.php @@ -0,0 +1,14 @@ +messages()->count() == 0) { + + $this->messages()->create( + [ + 'body' => $systemPrompt, + 'in_out' => false, + 'role' => RoleEnum::System, + 'created_at' => now(), + 'updated_at' => now(), + 'chat_id' => $this->id, + 'is_chat_ignored' => true, + ]); + } + } + /* ----------------------------------------------------------------- | Methods | ----------------------------------------------------------------- @@ -21,17 +44,25 @@ class Chat extends Model /** * Save the input message of the user */ - public function addInput(string $message, bool $in = true, bool $isChatIgnored = false): Message + public function addInput(string $message, + RoleEnum $role = RoleEnum::User, + ?string $systemPrompt = null, + bool $show_in_thread = true): Message { + if ($systemPrompt) { + $this->createSystemMessageIfNeeded($systemPrompt); + } + $message = $this->messages()->create( [ 'body' => $message, - 'in_out' => $in, + 'role' => $role, + 'in_out' => ($role === RoleEnum::User) ? true : false, 'created_at' => now(), 'updated_at' => now(), 'chat_id' => $this->id, - 'is_chat_ignored' => $isChatIgnored, + 'is_chat_ignored' => ! $show_in_thread, ]); return $message; @@ -44,7 +75,7 @@ public function addOutput(string $request): Message { $message = $this->getAiResponse($request); - return $this->addInput($message, false); + return $this->addInput($message, RoleEnum::Assistant); } /** @@ -60,28 +91,22 @@ public function getAiResponse($input) return $this->getOpenAiImage(); } - return $this->getOpenAiChat(); + return $this->getChatResponse(); } - /** - * Get response chat from OpenAI - */ - public function getOpenAiChat(int $limit = 5): string + public function getChatResponse(int $limit = 5): array { $latestMessages = $this->messages()->latest()->limit($limit)->get()->sortBy('id'); - /** - * Reverse the messages to preserve the order for OpenAI - */ $latestMessagesArray = []; + foreach ($latestMessages as $message) { - $latestMessagesArray[] = [ - 'role' => $message->in_out ? 'user' : 'assistant', 'content' => $message->compressed_body]; + $latestMessagesArray[] = MessageInDto::from([ + 'role' => $message->role->value, 'content' => $message->compressed_body, + ]); } - $response = OpenAI::chat()->create(['model' => 'gpt-3.5-turbo', 'messages' => $latestMessagesArray]); - - return $response->choices[0]->message->content; + return $latestMessagesArray; } @@ -96,15 +121,15 @@ public function getOpenAiImage(): string return ''; } - public function chatable() + public function chatable(): MorphTo { return $this->morphTo(); } - /* ----------------------------------------------------------------- - | Relationships - | ----------------------------------------------------------------- - */ + public function latest_messages(): HasMany + { + return $this->hasMany(Message::class)->where('is_chat_ignored', false)->oldest(); + } /** * Chat has many messages. diff --git a/app/Models/Collection.php b/app/Models/Collection.php index 87dbbc7e..d2a7aa4b 100644 --- a/app/Models/Collection.php +++ b/app/Models/Collection.php @@ -34,6 +34,11 @@ public function team(): BelongsTo return $this->belongsTo(Team::class); } + public function getDriver(): string + { + return $this->driver; + } + public function documents(): HasMany { return $this->hasMany(Document::class); @@ -43,4 +48,15 @@ public function chats(): MorphMany { return $this->morphMany(Chat::class, 'chatable'); } + + public function systemPrompt(): string + { + $systemPrompt = config('llmlarahub.collection.system_prompt'); + $prompt = <<description} +EOD; + + return $prompt; + } } diff --git a/app/Models/Message.php b/app/Models/Message.php index 7f4ab54e..9c7d0274 100644 --- a/app/Models/Message.php +++ b/app/Models/Message.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Domains\Messages\RoleEnum; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -12,7 +13,14 @@ class Message extends Model public $fillable = [ 'body', + 'role', 'in_out', + 'is_chat_ignored', + ]; + + protected $casts = [ + 'role' => RoleEnum::class, + 'in_out' => 'boolean', ]; /** @@ -20,7 +28,7 @@ class Message extends Model */ public function getFromUserAttribute(): bool { - return $this->in_out == true; + return $this->role === RoleEnum::User; } /** @@ -28,7 +36,7 @@ public function getFromUserAttribute(): bool */ public function getFromAiAttribute(): bool { - return ! $this->from_user; + return $this->role !== RoleEnum::User; } /** diff --git a/app/helpers.php b/app/helpers.php index a9858969..e5501a99 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,5 +1,6 @@ handle($text); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 461aafd3..13d0951e 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -9,6 +9,7 @@ web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', + channels: __DIR__.'/../routes/channels.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { diff --git a/composer.json b/composer.json index 85d26f95..193d6583 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "laravel/jetstream": "^5.0", "laravel/pennant": "^1.7", "laravel/pulse": "^1.0@beta", + "laravel/reverb": "@beta", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.9", "openai-php/laravel": "^0.8.1", diff --git a/composer.lock b/composer.lock index 655327f1..99fb3b20 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9a90413fe67b3ed0110c6e6a26ef662f", + "content-hash": "1313d049706577645a0e754579b74eaa", "packages": [ { "name": "amphp/amp", @@ -1222,6 +1222,122 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "clue/redis-protocol", + "version": "v0.3.1", + "source": { + "type": "git", + "url": "https://github.com/clue/php-redis-protocol.git", + "reference": "271b8009887209d930f613ad3b9518f94bd6b51c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/php-redis-protocol/zipball/271b8009887209d930f613ad3b9518f94bd6b51c", + "reference": "271b8009887209d930f613ad3b9518f94bd6b51c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "autoload": { + "psr-0": { + "Clue\\Redis\\Protocol": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@lueck.tv" + } + ], + "description": "A streaming redis wire protocol parser and serializer implementation in PHP", + "homepage": "https://github.com/clue/php-redis-protocol", + "keywords": [ + "parser", + "protocol", + "redis", + "serializer", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/php-redis-protocol/issues", + "source": "https://github.com/clue/php-redis-protocol/tree/v0.3.1" + }, + "time": "2017-06-06T16:07:10+00:00" + }, + { + "name": "clue/redis-react", + "version": "v2.7.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-redis.git", + "reference": "2283690f249e8d93342dd63b5285732d2654e077" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-redis/zipball/2283690f249e8d93342dd63b5285732d2654e077", + "reference": "2283690f249e8d93342dd63b5285732d2654e077", + "shasum": "" + }, + "require": { + "clue/redis-protocol": "0.3.*", + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3", + "react/event-loop": "^1.2", + "react/promise": "^3 || ^2.0 || ^1.1", + "react/promise-timer": "^1.9", + "react/socket": "^1.12" + }, + "require-dev": { + "clue/block-react": "^1.5", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\Redis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Async Redis client implementation, built on top of ReactPHP.", + "homepage": "https://github.com/clue/reactphp-redis", + "keywords": [ + "async", + "client", + "database", + "reactphp", + "redis" + ], + "support": { + "issues": "https://github.com/clue/reactphp-redis/issues", + "source": "https://github.com/clue/reactphp-redis/tree/v2.7.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2024-01-05T15:54:20+00:00" + }, { "name": "dasprid/enum", "version": "1.0.5", @@ -1786,6 +1902,53 @@ ], "time": "2023-10-06T06:47:41+00:00" }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -3095,6 +3258,87 @@ }, "time": "2024-03-11T13:19:11+00:00" }, + { + "name": "laravel/reverb", + "version": "v1.0.0-beta4", + "source": { + "type": "git", + "url": "https://github.com/laravel/reverb.git", + "reference": "7237ff17a249128218c614a6e0f9cf0a8aca91a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/reverb/zipball/7237ff17a249128218c614a6e0f9cf0a8aca91a9", + "reference": "7237ff17a249128218c614a6e0f9cf0a8aca91a9", + "shasum": "" + }, + "require": { + "clue/redis-react": "^2.6", + "guzzlehttp/psr7": "^2.6", + "illuminate/console": "^10.47|^11.0", + "illuminate/contracts": "^10.47|^11.0", + "illuminate/http": "^10.47|^11.0", + "illuminate/support": "^10.47|^11.0", + "laravel/prompts": "^0.1.15", + "php": "^8.2", + "pusher/pusher-php-server": "^7.2", + "ratchet/rfc6455": "^0.3.1", + "react/promise-timer": "^1.10", + "react/socket": "^1.14", + "symfony/http-foundation": "^6.3|^7.0" + }, + "require-dev": { + "orchestra/testbench": "^8.0|^9.0", + "pestphp/pest": "^2.0", + "phpstan/phpstan": "^1.10", + "ratchet/pawl": "^0.4.1", + "react/async": "^4.0", + "react/http": "^1.9" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Reverb\\ApplicationManagerServiceProvider", + "Laravel\\Reverb\\ReverbServiceProvider" + ], + "aliases": { + "Output": "Laravel\\Reverb\\Output" + } + } + }, + "autoload": { + "psr-4": { + "Laravel\\Reverb\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Joe Dixon", + "email": "joe@laravel.com" + } + ], + "description": "Laravel Reverb provides a real-time WebSocket communication backend for Laravel applications.", + "keywords": [ + "WebSockets", + "laravel", + "real-time", + "websocket" + ], + "support": { + "issues": "https://github.com/laravel/reverb/issues", + "source": "https://github.com/laravel/reverb/tree/v1.0.0-beta4" + }, + "time": "2024-03-18T17:36:49+00:00" + }, { "name": "laravel/sanctum", "version": "v4.0.0", @@ -4802,6 +5046,142 @@ }, "time": "2022-06-14T06:56:20+00:00" }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "paragonie/sodium_compat", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/e592a3e06d1fa0d43988c7c7d9948ca836f644b6", + "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6", + "shasum": "" + }, + "require": { + "paragonie/random_compat": ">=1", + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9" + }, + "suggest": { + "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", + "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "support": { + "issues": "https://github.com/paragonie/sodium_compat/issues", + "source": "https://github.com/paragonie/sodium_compat/tree/v1.20.0" + }, + "time": "2023-04-30T00:54:53+00:00" + }, { "name": "php-http/discovery", "version": "1.19.2", @@ -5712,6 +6092,67 @@ }, "time": "2023-12-20T15:28:09+00:00" }, + { + "name": "pusher/pusher-php-server", + "version": "7.2.4", + "source": { + "type": "git", + "url": "https://github.com/pusher/pusher-http-php.git", + "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/de2f72296808f9cafa6a4462b15a768ff130cddb", + "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.2", + "paragonie/sodium_compat": "^1.6", + "php": "^7.3|^8.0", + "psr/log": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "overtrue/phplint": "^2.3", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Pusher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Library for interacting with the Pusher REST API", + "keywords": [ + "events", + "messaging", + "php-pusher-server", + "publish", + "push", + "pusher", + "real time", + "real-time", + "realtime", + "rest", + "trigger" + ], + "support": { + "issues": "https://github.com/pusher/pusher-http-php/issues", + "source": "https://github.com/pusher/pusher-http-php/tree/7.2.4" + }, + "time": "2023-12-15T10:58:53+00:00" + }, { "name": "ralouphie/getallheaders", "version": "3.0.3", @@ -5937,6 +6378,593 @@ ], "time": "2023-11-08T05:53:05+00:00" }, + { + "name": "ratchet/rfc6455", + "version": "v0.3.1", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/RFC6455.git", + "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/7c964514e93456a52a99a20fcfa0de242a43ccdb", + "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^2 || ^1.7", + "php": ">=5.4.2" + }, + "require-dev": { + "phpunit/phpunit": "^5.7", + "react/socket": "^1.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ratchet\\RFC6455\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + }, + { + "name": "Matt Bonneau", + "role": "Developer" + } + ], + "description": "RFC6455 WebSocket protocol handler", + "homepage": "http://socketo.me", + "keywords": [ + "WebSockets", + "rfc6455", + "websocket" + ], + "support": { + "chat": "https://gitter.im/reactphp/reactphp", + "issues": "https://github.com/ratchetphp/RFC6455/issues", + "source": "https://github.com/ratchetphp/RFC6455/tree/v0.3.1" + }, + "time": "2021-12-09T23:20:49+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/dns", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "c134600642fa615b46b41237ef243daa65bb64ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/c134600642fa615b46b41237ef243daa65bb64ec", + "reference": "c134600642fa615b46b41237ef243daa65bb64ec", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.0 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4 || ^3 || ^2", + "react/promise-timer": "^1.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.12.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-29T12:41:06+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/promise", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-16T16:21:57+00:00" + }, + { + "name": "react/promise-timer", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise-timer.git", + "reference": "4cb85c1c2125390748e3908120bb82feb99fe766" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/4cb85c1c2125390748e3908120bb82feb99fe766", + "reference": "4cb85c1c2125390748e3908120bb82feb99fe766", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/event-loop": "^1.2", + "react/promise": "^3.0 || ^2.7.0 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\Timer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", + "homepage": "https://github.com/reactphp/promise-timer", + "keywords": [ + "async", + "event-loop", + "promise", + "reactphp", + "timeout", + "timer" + ], + "support": { + "issues": "https://github.com/reactphp/promise-timer/issues", + "source": "https://github.com/reactphp/promise-timer/tree/v1.10.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-07-20T15:40:28+00:00" + }, + { + "name": "react/socket", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.11", + "react/event-loop": "^1.2", + "react/promise": "^3 || ^2.6 || ^1.2.1", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4 || ^3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.15.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-12-15T11:02:10+00:00" + }, + { + "name": "react/stream", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-06-16T10:52:11+00:00" + }, { "name": "revolt/event-loop", "version": "v1.0.6", @@ -12358,7 +13386,8 @@ "aliases": [], "minimum-stability": "beta", "stability-flags": { - "laravel/pulse": 10 + "laravel/pulse": 10, + "laravel/reverb": 10 }, "prefer-stable": true, "prefer-lowest": false, diff --git a/config/broadcasting.php b/config/broadcasting.php new file mode 100644 index 00000000..ebc3fb9c --- /dev/null +++ b/config/broadcasting.php @@ -0,0 +1,82 @@ + env('BROADCAST_CONNECTION', 'null'), + + /* + |-------------------------------------------------------------------------- + | Broadcast Connections + |-------------------------------------------------------------------------- + | + | Here you may define all of the broadcast connections that will be used + | to broadcast events to other systems or over WebSockets. Samples of + | each available type of connection are provided inside this array. + | + */ + + 'connections' => [ + + 'reverb' => [ + 'driver' => 'reverb', + 'key' => env('REVERB_APP_KEY'), + 'secret' => env('REVERB_APP_SECRET'), + 'app_id' => env('REVERB_APP_ID'), + 'options' => [ + 'host' => env('REVERB_HOST'), + 'port' => env('REVERB_PORT', 443), + 'scheme' => env('REVERB_SCHEME', 'https'), + 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https'), + 'encrypted' => true, + 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/config/llmdriver.php b/config/llmdriver.php index a6f0c1f6..5c090d18 100644 --- a/config/llmdriver.php +++ b/config/llmdriver.php @@ -12,6 +12,7 @@ 'api_url' => env('OPENAI_API_URL', 'https://api.openai.com/v1/engines/davinci-codex/completions'), 'embedding_model' => env('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-large'), 'completion_model' => env('OPENAI_COMPLETION_MODEL', 'gpt-4-turbo-preview'), + 'chat_model' => env('OPENAICHAT_MODEL', 'gpt-4-turbo-preview'), ], 'azure' => [ diff --git a/config/llmlarahub.php b/config/llmlarahub.php new file mode 100644 index 00000000..3509c552 --- /dev/null +++ b/config/llmlarahub.php @@ -0,0 +1,8 @@ + [ + 'system_prompt' => 'This is a collection of data the user has imported that they will + ask questions about. The description they gave for this collection is', + ], +]; diff --git a/config/reverb.php b/config/reverb.php new file mode 100644 index 00000000..a17d7d43 --- /dev/null +++ b/config/reverb.php @@ -0,0 +1,82 @@ + env('REVERB_SERVER', 'reverb'), + + /* + |-------------------------------------------------------------------------- + | Reverb Servers + |-------------------------------------------------------------------------- + | + | Here you may define details for each of the supported Reverb servers. + | Each server has its own configuration options that are defined in + | the array below. You should ensure all the options are present. + | + */ + + 'servers' => [ + + 'reverb' => [ + 'host' => env('REVERB_SERVER_HOST', '0.0.0.0'), + 'port' => env('REVERB_SERVER_PORT', 8080), + 'hostname' => env('REVERB_HOST'), + 'options' => [ + 'tls' => [], + ], + 'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000), + 'scaling' => [ + 'enabled' => env('REVERB_SCALING_ENABLED', false), + 'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'), + ], + 'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Reverb Applications + |-------------------------------------------------------------------------- + | + | Here you may define how Reverb applications are managed. If you choose + | to use the "config" provider, you may define an array of apps which + | your server will support, including their connection credentials. + | + */ + + 'apps' => [ + + 'provider' => 'config', + + 'apps' => [ + [ + 'key' => env('REVERB_APP_KEY'), + 'secret' => env('REVERB_APP_SECRET'), + 'app_id' => env('REVERB_APP_ID'), + 'options' => [ + 'host' => env('REVERB_HOST'), + 'port' => env('REVERB_PORT', 443), + 'scheme' => env('REVERB_SCHEME', 'https'), + 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', + ], + 'allowed_origins' => ['*'], + 'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60), + 'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000), + ], + ], + + ], + +]; diff --git a/database/factories/MessageFactory.php b/database/factories/MessageFactory.php index 7d7547be..60e22bb2 100644 --- a/database/factories/MessageFactory.php +++ b/database/factories/MessageFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Domains\Messages\RoleEnum; use App\Models\Chat; use Illuminate\Database\Eloquent\Factories\Factory; @@ -20,6 +21,7 @@ public function definition(): array return [ 'body' => $this->faker->paragraphs(3, true), 'in_out' => $this->faker->boolean, + 'role' => RoleEnum::User, 'chat_id' => Chat::factory(), ]; } diff --git a/database/migrations/2024_03_27_132847_add_role_to_messages.php b/database/migrations/2024_03_27_132847_add_role_to_messages.php new file mode 100644 index 00000000..153d5ab0 --- /dev/null +++ b/database/migrations/2024_03_27_132847_add_role_to_messages.php @@ -0,0 +1,28 @@ +string('role')->default('user'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('messages', function (Blueprint $table) { + // + }); + } +}; diff --git a/package-lock.json b/package-lock.json index cf745760..f616eb7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "@formkit/auto-animate": "^0.8.1", "@headlessui/vue": "^1.7.19", "@heroicons/vue": "^2.1.1", "apexcharts": "^3.47.0", @@ -22,8 +23,10 @@ "autoprefixer": "^10.4.16", "axios": "^1.6.4", "chokidar": "^3.6.0", + "laravel-echo": "^1.16.0", "laravel-vite-plugin": "^1.0", "postcss": "^8.4.32", + "pusher-js": "^8.4.0-rc2", "tailwindcss": "^3.4.0", "vite": "^5.0", "vue": "^3.3.13" @@ -420,6 +423,11 @@ "node": ">=12" } }, + "node_modules/@formkit/auto-animate": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.8.1.tgz", + "integrity": "sha512-0/Z2cuNXWVVIG/l0SpcHAWFhGdvLJ8DRvEfRWvmojtmRWfEy+LWNwgDazbZqY0qQYtkHcoEK3jBLkhiZaB/4Ig==" + }, "node_modules/@headlessui/vue": { "version": "1.7.19", "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.19.tgz", @@ -1762,6 +1770,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/laravel-echo": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.16.0.tgz", + "integrity": "sha512-BJGUa4tcKvYmTkzTmcBGMHiO2tq+k7Do5wPmLbRswWfzKwyfZEUR+J5iwBTPEfLLwNPZlA9Kjo6R/NV6pmyIpg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/laravel-vite-plugin": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.0.2.tgz", @@ -2240,6 +2257,15 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, + "node_modules/pusher-js": { + "version": "8.4.0-rc2", + "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0-rc2.tgz", + "integrity": "sha512-d87GjOEEl9QgO5BWmViSqW0LOzPvybvX6WA9zLUstNdB57jVJuR27zHkRnrav2a3+zAMlHbP2Og8wug+rG8T+g==", + "dev": true, + "dependencies": { + "tweetnacl": "^1.0.3" + } + }, "node_modules/qs": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", @@ -2761,6 +2787,12 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", diff --git a/package.json b/package.json index a061a3b1..d5b76210 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,16 @@ "autoprefixer": "^10.4.16", "axios": "^1.6.4", "chokidar": "^3.6.0", + "laravel-echo": "^1.16.0", "laravel-vite-plugin": "^1.0", "postcss": "^8.4.32", + "pusher-js": "^8.4.0-rc2", "tailwindcss": "^3.4.0", "vite": "^5.0", "vue": "^3.3.13" }, "dependencies": { + "@formkit/auto-animate": "^0.8.1", "@headlessui/vue": "^1.7.19", "@heroicons/vue": "^2.1.1", "apexcharts": "^3.47.0", diff --git a/resources/js/Components/SecondaryLink.vue b/resources/js/Components/SecondaryLink.vue new file mode 100644 index 00000000..58e04f2a --- /dev/null +++ b/resources/js/Components/SecondaryLink.vue @@ -0,0 +1,21 @@ + + + diff --git a/resources/js/Pages/Chat/ChatBaloon.vue b/resources/js/Pages/Chat/ChatBaloon.vue index f1889257..0be58fd3 100644 --- a/resources/js/Pages/Chat/ChatBaloon.vue +++ b/resources/js/Pages/Chat/ChatBaloon.vue @@ -12,14 +12,7 @@ const props = defineProps({ :class="message.from_ai ? 'flex-row-reverse' : 'flex-row'">
- - + :class="message.from_ai ? 'bg-gray-300/10 rounded-tr-none border-indigo-500' : 'bg-black/20 rounded-tl-none flex-row-reverse'">
-import {useForm, usePage} from "@inertiajs/vue3"; +import {router, useForm, usePage} from "@inertiajs/vue3"; import SampleQuestions from "./SampleQuestions.vue"; import { ChevronDoubleDownIcon, ChevronRightIcon} from "@heroicons/vue/20/solid"; import {computed, inject, onUnmounted, ref, watch} from "vue"; +import axios from "axios"; +import { useToast } from "vue-toastification"; + +const toast = useToast(); const props = defineProps({ loading: { type: Boolean, - default: true + default: false }, - assistant: Object, chat: Object, }) @@ -23,55 +26,25 @@ const form = useForm({ const getting_results = ref(false) - -let echoChannel = ref(null); // keep track of the Echo channel - -watch(() => props.chat?.id, (newVal, oldVal) => { - if (newVal !== undefined && newVal !== oldVal) { // check if the id has a value and it's different from the previous one - if(props.chat?.id) { - echoChannel.value = Echo.private(`chat.user.${usePage().props.user.id}`) - .listen('.complete', (event) => { - getting_results.value = false; - }); - } else { - console.log("No chat id yet") - } - - } -}, { immediate: true }); // { immediate: true } ensures that the watcher checks the initial value - -onUnmounted(() => { - if(echoChannel.value) { - echoChannel.value.stopListening('.stream'); // stop listening when component is unmounted - } -}); - -const starterQuestions = computed(() => { - if(!props.chat?.id) { - return usePage().props.assistants.default.starter_questions; - } - - return usePage().props.assistants[props.chat.assistant.assistant_type]['starter_questions']; -}) - - const save = () => { getting_results.value = true - form.post(route('assistants.converse', { - assistant: props.assistant.id + let message = form.input + form.reset(); + axios.post(route('chats.messages.create', { + chat: props.chat.id }), { - onSuccess: (data) => { - console.log(data) - emits('chatSubmitted', form.input) - form.reset(); - }, - onError: (error) => { - console.log("Error") - console.log(error) - errors.value = error - } - }) - + input: message + }).then(response => { + getting_results.value = false + console.log(response.data.message) + router.reload({ + preserveScroll: true + }); + }).catch(error => { + getting_results.value = false + toast.error('An error occurred. Please try again.') + console.log(error) + }); } const setQuestion = (question) => { @@ -91,40 +64,41 @@ const setQuestion = (question) => {
- - - + v-if="getting_results" + class="mt-2"> + + + + + + + -
-
- -
diff --git a/resources/js/Pages/Chat/ChatMessage.vue b/resources/js/Pages/Chat/ChatMessage.vue index ecc266e4..e62802a8 100644 --- a/resources/js/Pages/Chat/ChatMessage.vue +++ b/resources/js/Pages/Chat/ChatMessage.vue @@ -10,79 +10,24 @@ const props = defineProps({ type: Boolean, default: true }, - assistant: Object, + messages: Object, chat: Object, }) const toast = useToast(); -const chatType = ref('threaded') - -const eventSource = ref({}) - -const waiting_on_run = ref(false) - -const eventData = ref("") - - -const messages = ref([]) - -const getMessages = () => { - axios.get(route("assistants.messages", { - assistant: props.assistant.id - })).then(data => { - console.log(data.data); - messages.value = data.data.messages; - }).catch(error => { - console.log("Error getting messages") - console.log(error) - }) -} - -const messagesComputed = computed(() => { - if(messages.value.length === 0) { - return props.chat?.messages; - } - - return messages.value; -}) - -let echoChannel = ref(null); // keep track of the Echo channel - -watch(() => props.chat?.id, (newVal, oldVal) => { - if (newVal !== undefined && newVal !== oldVal) { // check if the id has a value and it's different from the previous one - if(props.chat?.id) { - echoChannel.value = Echo.private(`chat.user.${usePage().props.user.id}`) - .listen('.threaded', (event) => { - getMessages(); - }).listen('.complete', (event) => { - getMessages(); - }); - } else { - console.log("No chat id yet") - } - - } -}, { immediate: true }); // { immediate: true } ensures that the watcher checks the initial value - -onUnmounted(() => { - if(echoChannel.value) { - echoChannel.value.stopListening('.stream'); // stop listening when component is unmounted - } -}); -