diff --git a/app/Events/ChatUiUpdateEvent.php b/app/Events/ChatUiUpdateEvent.php new file mode 100644 index 00000000..7b1de258 --- /dev/null +++ b/app/Events/ChatUiUpdateEvent.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 'update'; + } +} diff --git a/app/Http/Controllers/ChatController.php b/app/Http/Controllers/ChatController.php index 5ea2bd99..b0e89681 100644 --- a/app/Http/Controllers/ChatController.php +++ b/app/Http/Controllers/ChatController.php @@ -2,13 +2,15 @@ namespace App\Http\Controllers; +use App\Domains\Messages\RoleEnum; use App\Events\ChatUpdatedEvent; use App\Http\Resources\ChatResource; use App\Http\Resources\CollectionResource; use App\Http\Resources\MessageResource; +use App\LlmDriver\Requests\MessageInDto; use App\Models\Chat; use App\Models\Collection; -use Facades\App\Domains\Messages\SearchOrSummarizeChatRepo; +use Facades\App\LlmDriver\Orchestrate; class ChatController extends Controller { @@ -44,7 +46,19 @@ public function chat(Chat $chat) 'input' => 'required|string', ]); - $response = SearchOrSummarizeChatRepo::search($chat, $validated['input']); + $chat->addInput( + message: $validated['input'], + role: RoleEnum::User, + show_in_thread: true); + + $messagesArray = []; + + $messagesArray[] = MessageInDto::from([ + 'content' => $validated['input'], + 'role' => 'user', + ]); + + $response = Orchestrate::handle($messagesArray, $chat); ChatUpdatedEvent::dispatch($chat->chatable, $chat); diff --git a/app/Http/Controllers/CollectionController.php b/app/Http/Controllers/CollectionController.php index dff49201..40f5a730 100644 --- a/app/Http/Controllers/CollectionController.php +++ b/app/Http/Controllers/CollectionController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Domains\Documents\StatusEnum; use App\Domains\Documents\TypesEnum; use App\Http\Resources\ChatResource; use App\Http\Resources\CollectionResource; @@ -110,4 +111,18 @@ public function filesUpload(Collection $collection) return back(); } + + public function resetCollectionDocument(Collection $collection, Document $document) + { + $document->document_chunks()->delete(); + $document->status = StatusEnum::Running; + $document->document_chunk_count = 0; + $document->update(); + + ProcessFileJob::dispatch($document); + + request()->session()->flash('flash.banner', 'Document reset process running!'); + + return back(); + } } diff --git a/app/LlmDriver/BaseClient.php b/app/LlmDriver/BaseClient.php index 3ca6a678..3cd53617 100644 --- a/app/LlmDriver/BaseClient.php +++ b/app/LlmDriver/BaseClient.php @@ -26,6 +26,46 @@ public function embedData(string $data): EmbeddingsResponseDto ]); } + /** + * This is to get functions out of the llm + * if none are returned your system + * can error out or try another way. + * + * @param MessageInDto[] $messages + */ + public function functionPromptChat(array $messages, array $only = []): array + { + if (! app()->environment('testing')) { + sleep(2); + } + + Log::info('LlmDriver::MockClient::functionPromptChat', $messages); + + $data = get_fixture('openai_response_with_functions_summarize_collection.json'); + + $functions = []; + + foreach (data_get($data, 'choices', []) as $choice) { + foreach (data_get($choice, 'message.toolCalls', []) as $tool) { + if (data_get($tool, 'type') === 'function') { + $name = data_get($tool, 'function.name', null); + if (! in_array($name, $only)) { + $functions[] = [ + 'name' => $name, + 'arguments' => json_decode(data_get($tool, 'function.arguments', []), true), + ]; + } + } + } + } + + /** + * @TODO + * make this a dto + */ + return $functions; + } + /** * @param MessageInDto[] $messages */ @@ -37,9 +77,7 @@ public function chat(array $messages): CompletionResponse 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; + $data = fake()->sentences(3, true); return new CompletionResponse($data); } @@ -63,4 +101,9 @@ protected function getConfig(string $driver): array { return config("llmdriver.drivers.$driver"); } + + public function getFunctions(): array + { + return []; + } } diff --git a/app/LlmDriver/ClaudeClient.php b/app/LlmDriver/ClaudeClient.php index bae0197e..5c161639 100644 --- a/app/LlmDriver/ClaudeClient.php +++ b/app/LlmDriver/ClaudeClient.php @@ -38,26 +38,7 @@ public function chat(array $messages): CompletionResponse * in betwee a user row with some copy to make it work like "And the user search results had" * using the Laravel Collection library */ - $messages = collect($messages)->map(function ($item) { - if ($item->role === 'system') { - $item->role = 'assistant'; - } - - return $item->toArray(); - })->reverse()->values(); - - $messages = $messages->flatMap(function ($item, $index) use ($messages) { - if ($index > 0 && $item['role'] === 'assistant' && optional($messages->get($index + 1))['role'] === 'assistant') { - return [ - $item, - ['role' => 'user', 'content' => 'Continuation of search results'], - ]; - } - - return [$item]; - })->toArray(); - - put_fixture('claude_messages_debug.json', $messages); + $messages = $this->remapMessagesForClaude($messages); $results = $this->getClient()->post('/messages', [ 'model' => $model, @@ -136,8 +117,136 @@ protected function getClient() return Http::withHeaders([ 'x-api-key' => $api_token, + 'anthropic-beta' => 'tools-2024-04-04', 'anthropic-version' => $this->version, 'content-type' => 'application/json', ])->baseUrl($this->baseUrl); } + + /** + * This is to get functions out of the llm + * if none are returned your system + * can error out or try another way. + * + * @param MessageInDto[] $messages + */ + public function functionPromptChat(array $messages, array $only = []): array + { + $messages = $this->remapMessagesForClaude($messages); + Log::info('LlmDriver::ClaudeClient::functionPromptChat', $messages); + + $functions = $this->getFunctions(); + + $model = $this->getConfig('claude')['models']['completion_model']; + $maxTokens = $this->getConfig('claude')['max_tokens']; + + $results = $this->getClient()->post('/messages', [ + 'model' => $model, + 'system' => 'Return a markdown response.', + 'max_tokens' => $maxTokens, + 'messages' => $messages, + 'tools' => $this->getFunctions(), + ]); + + $functions = []; + + if (! $results->ok()) { + $error = $results->json()['error']['type']; + $message = $results->json()['error']['message']; + Log::error('Claude API Error ', [ + 'type' => $error, + 'message' => $message, + ]); + throw new \Exception('Claude API Error '.$message); + } + + $stop_reason = $results->json()['stop_reason']; + + if ($stop_reason === 'tool_use') { + + foreach ($results->json()['content'] as $content) { + if (data_get($content, 'type') === 'tool_use') { + $functions[] = [ + 'name' => data_get($content, 'name'), + 'arguments' => data_get($content, 'input'), + ]; + } + } + } + + /** + * @TODO + * make this a dto + */ + return $functions; + } + + /** + * @NOTE + * Since this abstraction layer is based on OpenAi + * Not much needs to happen here + * but on the others I might need to do XML? + */ + public function getFunctions(): array + { + $functions = LlmDriverFacade::getFunctions(); + + return collect($functions)->map(function ($function) { + $function = $function->toArray(); + $properties = []; + $required = []; + + foreach (data_get($function, 'parameters.properties', []) as $property) { + $name = data_get($property, 'name'); + + if (data_get($property, 'required', false)) { + $required[] = $name; + } + + $properties[$name] = [ + 'description' => data_get($property, 'description', null), + 'type' => data_get($property, 'type', 'string'), + 'enum' => data_get($property, 'enum', []), + 'default' => data_get($property, 'default', null), + ]; + } + + return [ + 'name' => data_get($function, 'name'), + 'description' => data_get($function, 'description'), + 'input_schema' => [ + 'type' => 'object', + 'properties' => $properties, + 'required' => $required, + ], + ]; + })->toArray(); + } + + /** + * @param MessageInDto[] $messages + */ + protected function remapMessagesForClaude(array $messages): array + { + $messages = collect($messages)->map(function ($item) { + if ($item->role === 'system') { + $item->role = 'assistant'; + } + + return $item->toArray(); + })->reverse()->values(); + + $messages = $messages->flatMap(function ($item, $index) use ($messages) { + if ($index > 0 && $item['role'] === 'assistant' && optional($messages->get($index + 1))['role'] === 'assistant') { + return [ + $item, + ['role' => 'user', 'content' => 'Continuation of search results'], + ]; + } + + return [$item]; + })->toArray(); + + return $messages; + } } diff --git a/app/LlmDriver/Functions/ArgumentCaster.php b/app/LlmDriver/Functions/ArgumentCaster.php index d6dc732b..a3b2a97d 100644 --- a/app/LlmDriver/Functions/ArgumentCaster.php +++ b/app/LlmDriver/Functions/ArgumentCaster.php @@ -1,22 +1,15 @@ $data - * @return array + * @param MessageInDto[] $messageArray */ - abstract public function handle(FunctionCallDto $functionCallDto): array; + abstract public function handle( + array $messageArray, + Chat $chat, + FunctionCallDto $functionCallDto): FunctionResponse; public function getFunction(): FunctionDto { @@ -20,10 +28,12 @@ public function getFunction(): FunctionDto [ 'name' => $this->getName(), 'description' => $this->getDescription(), - 'parameters' => $this->getParameters(), + 'parameters' => [ + 'type' => $this->type, + 'properties' => $this->getProperties(), + ], ] ); - } protected function getName(): string @@ -31,16 +41,13 @@ protected function getName(): string return $this->name; } - /** - * @return ParameterDto[] - */ - protected function getParameters(): array - { - return []; - } - protected function getDescription(): string { - return $this->name; + return $this->description; } + + /** + * @return PropertyDto[] + */ + abstract protected function getProperties(): array; } diff --git a/app/LlmDriver/Functions/ParametersDto.php b/app/LlmDriver/Functions/ParametersDto.php index 3225a25f..57827861 100644 --- a/app/LlmDriver/Functions/ParametersDto.php +++ b/app/LlmDriver/Functions/ParametersDto.php @@ -5,12 +5,12 @@ class ParametersDto extends \Spatie\LaravelData\Data { /** - * @param ParameterDto[] $parameters + * @param PropertyDto[] $properties * @return void */ public function __construct( public string $type = 'object', - public array $parameters = [], + public array $properties = [], ) { } } diff --git a/app/LlmDriver/Functions/ParameterDto.php b/app/LlmDriver/Functions/PropertyDto.php similarity index 85% rename from app/LlmDriver/Functions/ParameterDto.php rename to app/LlmDriver/Functions/PropertyDto.php index c7b29d8c..2dde30c3 100644 --- a/app/LlmDriver/Functions/ParameterDto.php +++ b/app/LlmDriver/Functions/PropertyDto.php @@ -2,7 +2,7 @@ namespace App\LlmDriver\Functions; -class ParameterDto extends \Spatie\LaravelData\Data +class PropertyDto extends \Spatie\LaravelData\Data { public function __construct( public string $name, diff --git a/app/LlmDriver/Functions/SearchAndSummarize.php b/app/LlmDriver/Functions/SearchAndSummarize.php index 2fe5e129..6c13f609 100644 --- a/app/LlmDriver/Functions/SearchAndSummarize.php +++ b/app/LlmDriver/Functions/SearchAndSummarize.php @@ -2,29 +2,38 @@ namespace App\LlmDriver\Functions; +use App\LlmDriver\Responses\FunctionResponse; +use App\Models\Chat; + class SearchAndSummarize extends FunctionContract { protected string $name = 'search_and_summarize'; - protected string $dscription = 'Used to embed users prompt, search database and return summarized results.'; + protected string $description = 'Used to embed users prompt, search database and return summarized results.'; - public function handle(FunctionCallDto $functionCallDto): array + public function handle( + array $messageArray, + Chat $chat, + FunctionCallDto $functionCallDto): FunctionResponse { - - return []; + return FunctionResponse::from( + [ + 'content' => '', + ] + ); } /** - * @return ParameterDto[] + * @return PropertyDto[] */ - protected function getParameters(): array + protected function getProperties(): array { return [ - new ParameterDto( + new PropertyDto( name: 'prompt', - description: 'The prompt to search for in the database.', + description: 'This is the prompt the user is using to search the database and may or may not assist the results.', type: 'string', - required: true, + required: false, ), ]; } diff --git a/app/LlmDriver/Functions/SummarizeCollection.php b/app/LlmDriver/Functions/SummarizeCollection.php new file mode 100644 index 00000000..74bbc94b --- /dev/null +++ b/app/LlmDriver/Functions/SummarizeCollection.php @@ -0,0 +1,70 @@ +chatable->documents as $document) { + foreach ($document->document_chunks as $chunk) { + $summary->add($chunk->summary); + } + } + + $summary = $summary->implode('\n'); + + $prompt = 'Can you summarize all of this content for me from a collection of documents I uploaded what follows is the content: '.$summary; + + $messagesArray = []; + + $messagesArray[] = MessageInDto::from([ + 'content' => $prompt, + 'role' => 'user', + ]); + + $results = LlmDriverFacade::driver($chat->getDriver())->chat($messagesArray); + + $chat->addInput( + message: $results->content, + role: RoleEnum::Assistant, + show_in_thread: true); + + return FunctionResponse::from([ + 'content' => $results->content, + ]); + } + + /** + * @return PropertyDto[] + */ + protected function getProperties(): array + { + return [ + new PropertyDto( + name: 'prompt', + description: 'The prompt the user is using the search for.', + type: 'string', + required: true, + ), + ]; + } +} diff --git a/app/LlmDriver/LlmDriverClient.php b/app/LlmDriver/LlmDriverClient.php index 228c3241..75f8fadf 100644 --- a/app/LlmDriver/LlmDriverClient.php +++ b/app/LlmDriver/LlmDriverClient.php @@ -2,6 +2,9 @@ namespace App\LlmDriver; +use App\LlmDriver\Functions\SearchAndSummarize; +use App\LlmDriver\Functions\SummarizeCollection; + class LlmDriverClient { protected $drivers = []; @@ -46,4 +49,12 @@ protected function getDefaultDriver() { return 'mock'; } + + public function getFunctions(): array + { + return [ + (new SearchAndSummarize())->getFunction(), + (new SummarizeCollection())->getFunction(), + ]; + } } diff --git a/app/LlmDriver/LlmServiceProvider.php b/app/LlmDriver/LlmServiceProvider.php new file mode 100644 index 00000000..d511c904 --- /dev/null +++ b/app/LlmDriver/LlmServiceProvider.php @@ -0,0 +1,37 @@ +app->bind('llm_driver', function () { + return new LlmDriverClient(); + }); + + $this->app->bind('summarize_collection', function () { + return new SummarizeCollection(); + }); + + $this->app->bind('search_and_summarize', function () { + return new SearchAndSummarize(); + }); + + } +} diff --git a/app/LlmDriver/OllamaClient.php b/app/LlmDriver/OllamaClient.php index 1ea2b693..f304e9ab 100644 --- a/app/LlmDriver/OllamaClient.php +++ b/app/LlmDriver/OllamaClient.php @@ -30,6 +30,36 @@ public function embedData(string $prompt): EmbeddingsResponseDto ]); } + /** + * This is to get functions out of the llm + * if none are returned your system + * can error out or try another way. + * + * @param MessageInDto[] $messages + */ + public function functionPromptChat(array $messages, array $only = []): array + { + Log::info('LlmDriver::OllmaClient::functionPromptChat', $messages); + + $functions = $this->getFunctions(); + + $response = $this->getClient()->post('/chat', [ + 'model' => $this->getConfig('ollama')['models']['completion_model'], + 'messages' => collect($messages)->map(function ($message) { + return $message->toArray(); + })->toArray(), + 'stream' => false, + ]); + + $functions = []; + + /** + * @TODO + * make this a dto + */ + return $functions; + } + /** * @param MessageInDto[] $messages * diff --git a/app/LlmDriver/OpenAiClient.php b/app/LlmDriver/OpenAiClient.php index f83c7579..5841dea9 100644 --- a/app/LlmDriver/OpenAiClient.php +++ b/app/LlmDriver/OpenAiClient.php @@ -5,6 +5,7 @@ use App\LlmDriver\Requests\MessageInDto; use App\LlmDriver\Responses\CompletionResponse; use App\LlmDriver\Responses\EmbeddingsResponseDto; +use Illuminate\Support\Facades\Log; use OpenAI\Laravel\Facades\OpenAI; class OpenAiClient extends BaseClient @@ -16,8 +17,9 @@ class OpenAiClient extends BaseClient */ public function chat(array $messages): CompletionResponse { + $response = OpenAI::chat()->create([ - 'model' => $this->getConfig('openai')['completion_model'], + 'model' => $this->getConfig('openai')['chat_model'], 'messages' => collect($messages)->map(function ($message) { return $message->toArray(); })->toArray(), @@ -69,4 +71,94 @@ public function completion(string $prompt, int $temperature = 0): CompletionResp return new CompletionResponse($results); } + + /** + * This is to get functions out of the llm + * if none are returned your system + * can error out or try another way. + * + * @param MessageInDto[] $messages + */ + public function functionPromptChat(array $messages, array $only = []): array + { + + Log::info('LlmDriver::OpenAiClient::functionPromptChat', $messages); + + $functions = $this->getFunctions(); + + $response = OpenAI::chat()->create([ + 'model' => $this->getConfig('openai')['chat_model'], + 'messages' => collect($messages)->map(function ($message) { + return $message->toArray(); + })->toArray(), + 'tool_choice' => 'auto', + 'tools' => $functions, + ]); + + $functions = []; + foreach ($response->choices as $result) { + foreach (data_get($result, 'message.toolCalls', []) as $tool) { + if (data_get($tool, 'type') === 'function') { + $name = data_get($tool, 'function.name', null); + if (! in_array($name, $only)) { + $functions[] = [ + 'name' => $name, + 'arguments' => json_decode(data_get($tool, 'function.arguments', []), true), + ]; + } + } + } + } + + /** + * @TODO + * make this a dto + */ + return $functions; + } + + /** + * @NOTE + * Since this abstraction layer is based on OpenAi + * Not much needs to happen here + * but on the others I might need to do XML? + */ + public function getFunctions(): array + { + $functions = LlmDriverFacade::getFunctions(); + + return collect($functions)->map(function ($function) { + $function = $function->toArray(); + $properties = []; + $required = []; + + foreach (data_get($function, 'parameters.properties', []) as $property) { + $name = data_get($property, 'name'); + + if (data_get($property, 'required', false)) { + $required[] = $name; + } + + $properties[$name] = [ + 'description' => data_get($property, 'description', null), + 'type' => data_get($property, 'type', 'string'), + 'enum' => data_get($property, 'enum', []), + 'default' => data_get($property, 'default', null), + ]; + } + + return [ + 'type' => 'function', + 'function' => [ + 'name' => data_get($function, 'name'), + 'description' => data_get($function, 'description'), + 'parameters' => [ + 'type' => 'object', + 'properties' => $properties, + ], + 'required' => $required, + ], + ]; + })->toArray(); + } } diff --git a/app/LlmDriver/Orchestrate.php b/app/LlmDriver/Orchestrate.php new file mode 100644 index 00000000..37d9f6df --- /dev/null +++ b/app/LlmDriver/Orchestrate.php @@ -0,0 +1,129 @@ +chatable->getDriver()) + ->functionPromptChat($messagesArray); + + if (! empty($functions)) { + /** + * @TODO + * We will deal with multi functions shortly + * @TODO + * When should messages be made + * which class should make them + * In this case I will assume the user of this class + * save the Users input as a Message already + */ + foreach ($functions as $function) { + $functionName = data_get($function, 'name', null); + + if (is_null($functionName)) { + throw new \Exception('Function name is required'); + } + + ChatUiUpdateEvent::dispatch( + $chat->chatable, + $chat, + sprintf('We are running the agent %s back shortly', + str($functionName)->headline()->toString() + ) + ); + + $functionClass = app()->make($functionName); + + $arguments = data_get($function, 'arguments'); + + $arguments = is_array($arguments) ? json_encode($arguments) : ''; + + $functionDto = FunctionCallDto::from([ + 'arguments' => $arguments, + 'function_name' => $functionName, + ]); + + /** @var FunctionResponse $response */ + $response = $functionClass->handle($messagesArray, $chat, $functionDto); + + $chat->addInput( + message: $response->content, + role: RoleEnum::Assistant, + show_in_thread: false); + + $messagesArray = Arr::wrap(MessageInDto::from([ + 'role' => 'assistant', + 'content' => $response->content, + ])); + + ChatUiUpdateEvent::dispatch( + $chat->chatable, + $chat, + 'The Agent has completed the task going to the final step now'); + + $this->response = $response->content; + $this->requiresFollowup = $response->requires_follow_up_prompt; + } + + /** + * @NOTE the function might return the results of a table + * or csv file or image info etc. + * This prompt should consider the initial prompt and the output of the function(s) + */ + if ($this->requiresFollowup) { + $results = LlmDriverFacade::driver($chat->chatable->getDriver()) + ->chat($messagesArray); + + $chat->addInput( + message: $results->content, + role: RoleEnum::Assistant, + show_in_thread: true); + + /** + * Could just show this in the ui + */ + ChatUiUpdateEvent::dispatch( + $chat->chatable, + $chat, + 'Functions and Agents have completed their tasks, results will appear shortly'); + + $this->response = $results->content; + } + + return $this->response; + } else { + /** + * @NOTE + * this assumes way too much + */ + $message = collect($messagesArray)->first( + function ($message) { + return $message->role === 'user'; + } + )->content; + + return SearchOrSummarizeChatRepo::search($chat, $message); + } + } +} diff --git a/app/LlmDriver/Responses/FunctionResponse.php b/app/LlmDriver/Responses/FunctionResponse.php new file mode 100644 index 00000000..a140223e --- /dev/null +++ b/app/LlmDriver/Responses/FunctionResponse.php @@ -0,0 +1,17 @@ +chatable->getDriver(); + } + + public function getEmbeddingDriver(): string + { + return $this->chatable->getEmbeddingDriver(); + } protected function createSystemMessageIfNeeded(string $systemPrompt): void { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e6b8a28b..efee12eb 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,7 +2,6 @@ namespace App\Providers; -use App\LlmDriver\LlmDriverClient; use App\Models\User; use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; @@ -26,9 +25,5 @@ public function boot(): void return $user->isAdmin(); }); - $this->app->bind('llm_driver', function () { - return new LlmDriverClient(); - }); - } } diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 4b3883c0..51a15e98 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -5,4 +5,5 @@ App\Providers\FortifyServiceProvider::class, App\Providers\HorizonServiceProvider::class, App\Providers\JetstreamServiceProvider::class, + App\LlmDriver\LlmServiceProvider::class, ]; diff --git a/composer.json b/composer.json index 193d6583..8883b071 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "php": "^8.2", "ankane/pgvector": "^0.1.3", "archtechx/enums": "^0.3.2", + "fakerphp/faker": "^1.23", "inertiajs/inertia-laravel": "^1.0", "laravel/framework": "^11.0", "laravel/horizon": "^5.23", @@ -24,10 +25,10 @@ "spatie/laravel-markdown": "^2.3", "tightenco/ziggy": "^2.0", "voku/stop-words": "^2.0", - "wamania/php-stemmer": "^3.0" + "wamania/php-stemmer": "^3.0", + "yethee/tiktoken": "^0.3.0" }, "require-dev": { - "fakerphp/faker": "^1.23", "larastan/larastan": "^2.9", "laravel/pint": "^1.13", "laravel/sail": "^1.26", diff --git a/composer.lock b/composer.lock index 99fb3b20..a6c1f4a8 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": "1313d049706577645a0e754579b74eaa", + "content-hash": "5a7b8196ee2ce364300f100ffe0212a0", "packages": [ { "name": "amphp/amp", @@ -1949,6 +1949,69 @@ }, "time": "2023-08-08T05:53:35+00:00" }, + { + "name": "fakerphp/faker", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + }, + "time": "2024-01-02T13:46:09+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -10562,72 +10625,58 @@ "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, "time": "2022-06-03T18:03:27+00:00" - } - ], - "packages-dev": [ + }, { - "name": "fakerphp/faker", - "version": "v1.23.1", + "name": "yethee/tiktoken", + "version": "0.3.0", "source": { "type": "git", - "url": "https://github.com/FakerPHP/Faker.git", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + "url": "https://github.com/yethee/tiktoken-php.git", + "reference": "c84f066dcb0cff60685537c71bf795967775446a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "url": "https://api.github.com/repos/yethee/tiktoken-php/zipball/c84f066dcb0cff60685537c71bf795967775446a", + "reference": "c84f066dcb0cff60685537c71bf795967775446a", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "conflict": { - "fzaninotto/faker": "*" + "php": "^8.1", + "symfony/service-contracts": "^2.5 || ^3.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "doctrine/persistence": "^1.3 || ^2.0", - "ext-intl": "*", - "phpunit/phpunit": "^9.5.26", - "symfony/phpunit-bridge": "^5.4.16" - }, - "suggest": { - "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", - "ext-curl": "Required by Faker\\Provider\\Image to download images.", - "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", - "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", - "ext-mbstring": "Required for multibyte Unicode string functionality." + "doctrine/coding-standard": "^12.0", + "phpunit/phpunit": "^10.3", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "5.19.0" }, "type": "library", "autoload": { "psr-4": { - "Faker\\": "src/Faker/" + "Yethee\\Tiktoken\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", + "description": "PHP version of tiktoken", "keywords": [ - "data", - "faker", - "fixtures" + "bpe", + "decode", + "encode", + "openai", + "tiktoken", + "tokenizer" ], "support": { - "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + "issues": "https://github.com/yethee/tiktoken-php/issues", + "source": "https://github.com/yethee/tiktoken-php/tree/0.3.0" }, - "time": "2024-01-02T13:46:09+00:00" - }, + "time": "2024-01-10T10:34:57+00:00" + } + ], + "packages-dev": [ { "name": "filp/whoops", "version": "2.15.4", diff --git a/phpunit.xml b/phpunit.xml index 236e4e5a..da2f836c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -19,6 +19,7 @@ + diff --git a/resources/js/Pages/Chat/ChatBaloon.vue b/resources/js/Pages/Chat/ChatBaloon.vue index ca0a3c65..01986fe0 100644 --- a/resources/js/Pages/Chat/ChatBaloon.vue +++ b/resources/js/Pages/Chat/ChatBaloon.vue @@ -38,18 +38,16 @@ const props = defineProps({ -
+
- -
diff --git a/resources/js/Pages/Chat/ChatInputThreaded.vue b/resources/js/Pages/Chat/ChatInputThreaded.vue index 5ae21572..99ac42f5 100644 --- a/resources/js/Pages/Chat/ChatInputThreaded.vue +++ b/resources/js/Pages/Chat/ChatInputThreaded.vue @@ -56,7 +56,24 @@ const setQuestion = (question) => {