Kaynağa Gözat

Добавлены чаты и сохранение сообщений

Artem Kastrov 2 ay önce
ebeveyn
işleme
e0815f3cbc

+ 27 - 2
app/Http/Controllers/ChatController.php

@@ -2,10 +2,35 @@
 
 namespace App\Http\Controllers;
 
+use App\Models\Chat;
+use Illuminate\Http\Request;
+
 class ChatController extends Controller
 {
-    public function __invoke()
+    public function index(?Chat $chat = null)
+    {
+        $chat?->load('messages');
+
+        return inertia("Chat", [
+            'chat' => $chat?->toResource(),
+            "chats" => fn() => Chat::all(),
+        ]);
+    }
+
+    public function message(Request $request)
     {
-        return inertia("Chat");
+        $chat = $request->filled('uuid') ? Chat::find($request->input('uuid')) : Chat::create();
+        $chat->messages()->create(['text' => $request->input('message')]);
+
+        //TODO: Send To LLM
+
+        return redirect()->route('chats.view', ['chat' => $chat->id]);
+    }
+
+    public function destroy(Chat $chat)
+    {
+        $chat->delete();
+
+        return redirect()->route('chats.index');
     }
 }

+ 22 - 0
app/Http/Resources/ChatResource.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class ChatResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return array_merge(parent::toArray($request), [
+            'title' => $this->title ?? 'Новый чат',
+            'messages' => MessageResource::collection($this->whenLoaded('messages')),
+        ]);
+    }
+}

+ 21 - 0
app/Http/Resources/MessageResource.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class MessageResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return array_merge(parent::toArray($request), [
+            'text' => str($this->text)->replace("\n", '<br>')
+        ]);
+    }
+}

+ 19 - 0
app/Models/Chat.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Concerns\HasUuids;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+
+class Chat extends Model
+{
+    use HasUuids;
+
+    protected $fillable = ['text'];
+
+    public function messages(): HasMany
+    {
+        return $this->hasMany(Message::class)->orderBy('created_at');
+    }
+}

+ 17 - 0
app/Models/Message.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Concerns\HasUuids;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+class Message extends Model
+{
+    protected $fillable = ['text'];
+
+    public function chat(): BelongsTo
+    {
+        return $this->belongsTo(Chat::class);
+    }
+}

+ 3 - 0
app/Providers/AppServiceProvider.php

@@ -6,6 +6,7 @@ use Carbon\CarbonImmutable;
 //use Illuminate\Support\Facades\Data;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\Relation;
+use Illuminate\Http\Resources\Json\JsonResource;
 use Illuminate\Support\ServiceProvider;
 
 class AppServiceProvider extends ServiceProvider
@@ -40,5 +41,7 @@ class AppServiceProvider extends ServiceProvider
         Relation::enforceMorphMap([
             // TODO
         ]);
+
+        JsonResource::withoutWrapping();
     }
 }

+ 1 - 0
composer.json

@@ -13,6 +13,7 @@
         "laravel/octane": "^2.3",
         "laravel/tinker": "^2.9",
         "sentry/sentry-laravel": "^4.3",
+        "spatie/laravel-ignition": "^2.9",
         "tightenco/ziggy": "^2.6"
     },
     "require-dev": {

+ 382 - 1
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": "0b9fbd465e5845a34a59951ed8c676b8",
+    "content-hash": "ea8fc5c025ffad90fab4e4967609f464",
     "packages": [
         {
             "name": "brick/math",
@@ -3922,6 +3922,387 @@
             ],
             "time": "2025-10-20T12:57:51+00:00"
         },
+        {
+            "name": "spatie/backtrace",
+            "version": "1.8.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/backtrace.git",
+                "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110",
+                "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.3 || ^8.0"
+            },
+            "require-dev": {
+                "ext-json": "*",
+                "laravel/serializable-closure": "^1.3 || ^2.0",
+                "phpunit/phpunit": "^9.3 || ^11.4.3",
+                "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6",
+                "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Spatie\\Backtrace\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Freek Van de Herten",
+                    "email": "freek@spatie.be",
+                    "homepage": "https://spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A better backtrace",
+            "homepage": "https://github.com/spatie/backtrace",
+            "keywords": [
+                "Backtrace",
+                "spatie"
+            ],
+            "support": {
+                "issues": "https://github.com/spatie/backtrace/issues",
+                "source": "https://github.com/spatie/backtrace/tree/1.8.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/sponsors/spatie",
+                    "type": "github"
+                },
+                {
+                    "url": "https://spatie.be/open-source/support-us",
+                    "type": "other"
+                }
+            ],
+            "time": "2025-08-26T08:22:30+00:00"
+        },
+        {
+            "name": "spatie/error-solutions",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/error-solutions.git",
+                "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/error-solutions/zipball/e495d7178ca524f2dd0fe6a1d99a1e608e1c9936",
+                "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.0"
+            },
+            "require-dev": {
+                "illuminate/broadcasting": "^10.0|^11.0|^12.0",
+                "illuminate/cache": "^10.0|^11.0|^12.0",
+                "illuminate/support": "^10.0|^11.0|^12.0",
+                "livewire/livewire": "^2.11|^3.5.20",
+                "openai-php/client": "^0.10.1",
+                "orchestra/testbench": "8.22.3|^9.0|^10.0",
+                "pestphp/pest": "^2.20|^3.0",
+                "phpstan/phpstan": "^2.1",
+                "psr/simple-cache": "^3.0",
+                "psr/simple-cache-implementation": "^3.0",
+                "spatie/ray": "^1.28",
+                "symfony/cache": "^5.4|^6.0|^7.0",
+                "symfony/process": "^5.4|^6.0|^7.0",
+                "vlucas/phpdotenv": "^5.5"
+            },
+            "suggest": {
+                "openai-php/client": "Require get solutions from OpenAI",
+                "simple-cache-implementation": "To cache solutions from OpenAI"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Spatie\\Ignition\\": "legacy/ignition",
+                    "Spatie\\ErrorSolutions\\": "src",
+                    "Spatie\\LaravelIgnition\\": "legacy/laravel-ignition"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ruben Van Assche",
+                    "email": "ruben@spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "This is my package error-solutions",
+            "homepage": "https://github.com/spatie/error-solutions",
+            "keywords": [
+                "error-solutions",
+                "spatie"
+            ],
+            "support": {
+                "issues": "https://github.com/spatie/error-solutions/issues",
+                "source": "https://github.com/spatie/error-solutions/tree/1.1.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/Spatie",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-14T12:29:50+00:00"
+        },
+        {
+            "name": "spatie/flare-client-php",
+            "version": "1.10.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/flare-client-php.git",
+                "reference": "bf1716eb98bd689451b071548ae9e70738dce62f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/bf1716eb98bd689451b071548ae9e70738dce62f",
+                "reference": "bf1716eb98bd689451b071548ae9e70738dce62f",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0",
+                "php": "^8.0",
+                "spatie/backtrace": "^1.6.1",
+                "symfony/http-foundation": "^5.2|^6.0|^7.0",
+                "symfony/mime": "^5.2|^6.0|^7.0",
+                "symfony/process": "^5.2|^6.0|^7.0",
+                "symfony/var-dumper": "^5.2|^6.0|^7.0"
+            },
+            "require-dev": {
+                "dms/phpunit-arraysubset-asserts": "^0.5.0",
+                "pestphp/pest": "^1.20|^2.0",
+                "phpstan/extension-installer": "^1.1",
+                "phpstan/phpstan-deprecation-rules": "^1.0",
+                "phpstan/phpstan-phpunit": "^1.0",
+                "spatie/pest-plugin-snapshots": "^1.0|^2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/helpers.php"
+                ],
+                "psr-4": {
+                    "Spatie\\FlareClient\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Send PHP errors to Flare",
+            "homepage": "https://github.com/spatie/flare-client-php",
+            "keywords": [
+                "exception",
+                "flare",
+                "reporting",
+                "spatie"
+            ],
+            "support": {
+                "issues": "https://github.com/spatie/flare-client-php/issues",
+                "source": "https://github.com/spatie/flare-client-php/tree/1.10.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/spatie",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-14T13:42:06+00:00"
+        },
+        {
+            "name": "spatie/ignition",
+            "version": "1.15.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/ignition.git",
+                "reference": "31f314153020aee5af3537e507fef892ffbf8c85"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/ignition/zipball/31f314153020aee5af3537e507fef892ffbf8c85",
+                "reference": "31f314153020aee5af3537e507fef892ffbf8c85",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "php": "^8.0",
+                "spatie/error-solutions": "^1.0",
+                "spatie/flare-client-php": "^1.7",
+                "symfony/console": "^5.4|^6.0|^7.0",
+                "symfony/var-dumper": "^5.4|^6.0|^7.0"
+            },
+            "require-dev": {
+                "illuminate/cache": "^9.52|^10.0|^11.0|^12.0",
+                "mockery/mockery": "^1.4",
+                "pestphp/pest": "^1.20|^2.0",
+                "phpstan/extension-installer": "^1.1",
+                "phpstan/phpstan-deprecation-rules": "^1.0",
+                "phpstan/phpstan-phpunit": "^1.0",
+                "psr/simple-cache-implementation": "*",
+                "symfony/cache": "^5.4|^6.0|^7.0",
+                "symfony/process": "^5.4|^6.0|^7.0",
+                "vlucas/phpdotenv": "^5.5"
+            },
+            "suggest": {
+                "openai-php/client": "Require get solutions from OpenAI",
+                "simple-cache-implementation": "To cache solutions from OpenAI"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.5.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Spatie\\Ignition\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Spatie",
+                    "email": "info@spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A beautiful error page for PHP applications.",
+            "homepage": "https://flareapp.io/ignition",
+            "keywords": [
+                "error",
+                "flare",
+                "laravel",
+                "page"
+            ],
+            "support": {
+                "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction",
+                "forum": "https://twitter.com/flareappio",
+                "issues": "https://github.com/spatie/ignition/issues",
+                "source": "https://github.com/spatie/ignition"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/spatie",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-21T14:31:39+00:00"
+        },
+        {
+            "name": "spatie/laravel-ignition",
+            "version": "2.9.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/laravel-ignition.git",
+                "reference": "1baee07216d6748ebd3a65ba97381b051838707a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1baee07216d6748ebd3a65ba97381b051838707a",
+                "reference": "1baee07216d6748ebd3a65ba97381b051838707a",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "illuminate/support": "^10.0|^11.0|^12.0",
+                "php": "^8.1",
+                "spatie/ignition": "^1.15",
+                "symfony/console": "^6.2.3|^7.0",
+                "symfony/var-dumper": "^6.2.3|^7.0"
+            },
+            "require-dev": {
+                "livewire/livewire": "^2.11|^3.3.5",
+                "mockery/mockery": "^1.5.1",
+                "openai-php/client": "^0.8.1|^0.10",
+                "orchestra/testbench": "8.22.3|^9.0|^10.0",
+                "pestphp/pest": "^2.34|^3.7",
+                "phpstan/extension-installer": "^1.3.1",
+                "phpstan/phpstan-deprecation-rules": "^1.1.1|^2.0",
+                "phpstan/phpstan-phpunit": "^1.3.16|^2.0",
+                "vlucas/phpdotenv": "^5.5"
+            },
+            "suggest": {
+                "openai-php/client": "Require get solutions from OpenAI",
+                "psr/simple-cache-implementation": "Needed to cache solutions from OpenAI"
+            },
+            "type": "library",
+            "extra": {
+                "laravel": {
+                    "aliases": {
+                        "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare"
+                    },
+                    "providers": [
+                        "Spatie\\LaravelIgnition\\IgnitionServiceProvider"
+                    ]
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/helpers.php"
+                ],
+                "psr-4": {
+                    "Spatie\\LaravelIgnition\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Spatie",
+                    "email": "info@spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A beautiful error page for Laravel applications.",
+            "homepage": "https://flareapp.io/ignition",
+            "keywords": [
+                "error",
+                "flare",
+                "laravel",
+                "page"
+            ],
+            "support": {
+                "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction",
+                "forum": "https://twitter.com/flareappio",
+                "issues": "https://github.com/spatie/laravel-ignition/issues",
+                "source": "https://github.com/spatie/laravel-ignition"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/spatie",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-02-20T13:13:55+00:00"
+        },
         {
             "name": "symfony/clock",
             "version": "v7.3.0",

+ 29 - 0
database/migrations/2025_11_09_131804_create_chats_table.php

@@ -0,0 +1,29 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('chats', function (Blueprint $table) {
+            $table->uuid('id')->primary();
+            $table->string('title')->nullable();
+
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('chats');
+    }
+};

+ 33 - 0
database/migrations/2025_11_09_131811_create_messages_table.php

@@ -0,0 +1,33 @@
+<?php
+
+use App\Models\Chat;
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('messages', function (Blueprint $table) {
+            $table->id();
+
+            $table->foreignIdFor(Chat::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
+
+            $table->longText('text')->nullable();
+            $table->string('from')->default('user');
+
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('messages');
+    }
+};

+ 2 - 0
docker-compose.services.yml

@@ -8,6 +8,8 @@ services:
       POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
     volumes:
       - pgsql-data:/var/lib/postgresql/data:rw
+    ports:
+      - 5432:5432
     healthcheck:
       test: pg_isready -d ${DB_DATABASE:-app} -U ${DB_USERNAME:-laravel}
       interval: 2s

+ 1 - 1
docker-compose.yml

@@ -13,7 +13,7 @@ x-app: &app
 services:
     application:
         <<: *app
-        command: php /app/artisan octane:start --watch --caddyfile=Caddyfile --server=frankenphp --admin-port=9019 --host=0.0.0.0 --port=9000 --workers=4 --max-requests=100
+        command: php /app/artisan octane:start --watch --caddyfile=Caddyfile --server=frankenphp --admin-port=9019 --host=0.0.0.0 --port=9000 --workers=1 --max-requests=1
         healthcheck:
             test: php artisan octane:status
             interval: 2s

+ 44 - 29
resources/js/Components/AppSidebar.vue

@@ -27,6 +27,15 @@ import {
     EmptyMedia,
     EmptyTitle,
 } from '@/Packages/Shadcn/Components/ui/empty'
+import {router} from "@inertiajs/vue3";
+
+
+defineProps({
+    chats: {
+        type: Array,
+        default: () => [],
+    }
+})
 
 const items = [{title: "Нет чатов", url: "#"}];
 </script>
@@ -36,39 +45,45 @@ const items = [{title: "Нет чатов", url: "#"}];
         <SidebarContent>
             <SidebarGroup>
                 <SidebarGroupLabel>Application</SidebarGroupLabel>
-                <SidebarGroupAction title="New Chat" class="rounded-full cursor-pointer">
+                <SidebarGroupAction title="New Chat" class="rounded-full cursor-pointer" @click="() => router.visit(route('chats.index'))">
                     <Plus/>
                     <span class="sr-only">New Chat</span>
                 </SidebarGroupAction>
                 <SidebarGroupContent>
-                    <Empty class="border border-dashed">
-                        <EmptyHeader>
-                            <EmptyMedia variant="icon">
-                                <MessageSquareOff/>
-                            </EmptyMedia>
-                            <EmptyTitle>Нет чатов</EmptyTitle>
-                            <EmptyDescription>Список чатов пока пуст</EmptyDescription>
-                        </EmptyHeader>
-                    </Empty>
-                    <!--                    <SidebarMenu>-->
-                    <!--                        <SidebarMenuItem v-for="item in items" :key="item.title">-->
-                    <!--                            <SidebarMenuButton asChild class="cursor-pointer">-->
-                    <!--                                <span>{{ item.title }}</span>-->
-                    <!--                            </SidebarMenuButton>-->
-                    <!--                            <DropdownMenu>-->
-                    <!--                                <DropdownMenuTrigger asChild>-->
-                    <!--                                    <SidebarMenuAction class="rounded-full cursor-pointer">-->
-                    <!--                                        <MoreHorizontal/>-->
-                    <!--                                    </SidebarMenuAction>-->
-                    <!--                                </DropdownMenuTrigger>-->
-                    <!--                                <DropdownMenuContent side="top" align="start">-->
-                    <!--                                    <DropdownMenuItem class="text-primary cursor-pointer">-->
-                    <!--                                        <Trash /> <span>Удалить чат</span>-->
-                    <!--                                    </DropdownMenuItem>-->
-                    <!--                                </DropdownMenuContent>-->
-                    <!--                            </DropdownMenu>-->
-                    <!--                        </SidebarMenuItem>-->
-                    <!--                    </SidebarMenu>-->
+                    <template v-if="chats.length === 0">
+                        <Empty class="border border-dashed">
+                            <EmptyHeader>
+                                <EmptyMedia variant="icon">
+                                    <MessageSquareOff/>
+                                </EmptyMedia>
+                                <EmptyTitle>Нет чатов</EmptyTitle>
+                                <EmptyDescription>Список чатов пока пуст</EmptyDescription>
+                            </EmptyHeader>
+                        </Empty>
+                    </template>
+                    <template v-else>
+                        <SidebarMenu>
+                            <SidebarMenuItem v-for="item in chats" :key="item.title">
+                                <SidebarMenuButton asChild class="cursor-pointer" @click="() => router.visit(route('chats.view', item.id))">
+                                    <span>{{ item.title ?? 'Новый чат' }}</span>
+                                </SidebarMenuButton>
+                                <DropdownMenu>
+                                    <DropdownMenuTrigger asChild>
+                                        <SidebarMenuAction class="rounded-full cursor-pointer">
+                                            <MoreHorizontal/>
+                                        </SidebarMenuAction>
+                                    </DropdownMenuTrigger>
+                                    <DropdownMenuContent side="right" align="center">
+                                        <DropdownMenuItem class="text-primary cursor-pointer" @click="() => router.delete(route('chats.destroy', item.id))">
+                                            <Trash/>
+                                            <span>Удалить чат</span>
+                                        </DropdownMenuItem>
+                                    </DropdownMenuContent>
+                                </DropdownMenu>
+                            </SidebarMenuItem>
+                        </SidebarMenu>
+                    </template>
+
                 </SidebarGroupContent>
             </SidebarGroup>
         </SidebarContent>

+ 6 - 1
resources/js/Layouts/AppLayout.vue

@@ -8,6 +8,11 @@ defineOptions({ inheritAttrs: false })
 defineProps({
     pageClass: {
         default: () => null,
+    },
+
+    chats: {
+        type: Array,
+        default: () => [],
     }
 })
 
@@ -17,7 +22,7 @@ defineProps({
     <Head/>
 
     <SidebarProvider>
-        <AppSidebar/>
+        <AppSidebar :chats="chats" />
 
         <main class="container mx-auto px-2 md:px-0 md:pr-2 py-2" :class="pageClass">
             <slot/>

+ 25 - 45
resources/js/Pages/Chat.vue

@@ -1,32 +1,26 @@
 <script setup lang="ts">
 import AppLayout from "@/Layouts/AppLayout.vue"
-import {ArrowUpIcon} from "lucide-vue-next"
-import {
-    DropdownMenu,
-    DropdownMenuContent,
-    DropdownMenuItem,
-    DropdownMenuTrigger
-} from "@/Packages/Shadcn/Components/ui/dropdown-menu"
+import {ArrowUpIcon, Loader2} from "lucide-vue-next"
 import {
     InputGroup,
     InputGroupAddon,
     InputGroupButton,
-    InputGroupText,
     InputGroupTextarea
 } from "@/Packages/Shadcn/Components/ui/input-group"
-import {Separator} from "@/Packages/Shadcn/Components/ui/separator"
-import {nextTick, reactive, ref} from "vue";
+import {computed, nextTick, watch} from "vue";
 import {useForm} from "@inertiajs/vue3";
 
-const messages = reactive<Array<string>>([])
+const { chat } = defineProps(['chats', 'chat'])
 const form = useForm({
+    uuid: chat?.id ?? null,
     message: null
 })
 
 const submit = function () {
-    messages.push({from: 'user', text: form.message})
-    messages.push({from: 'ai', text: form.message})
-    form.reset()
+    if (!form.message) return
+    form.post(route('chats.message'), {
+        onSuccess: () => form.reset()
+    })
 }
 
 const handleEnter = function (e) {
@@ -40,17 +34,19 @@ const handleEnter = function (e) {
     } else submit()
 }
 
+const messages = computed(() => chat?.messages ?? [])
+
+watch(() => chat?.id, () => form.defaults('uuid', chat?.id ?? null))
 </script>
 
 <template>
-    <AppLayout page-class="!py-0">
+    <AppLayout page-class="!py-0" :chats="chats">
         <div class="max-w-4xl mx-auto flex flex-col h-full">
-            <div class="overflow-y-auto duration-500" :class="{'grow mt-2': messages.length !== 0}">
+            <div class="overflow-y-auto duration-500 space-y-4" :class="{'grow mt-2': messages.length !== 0}">
                 <div v-for="message in messages">
                     <div class="flex" v-if="message.from === 'user'">
-                        <div class="ml-auto border bg-card text-card-foreground p-2 rounded-lg max-w-[75%]">
-                            {{ message.text }}
-                        </div>
+                        <div class="ml-auto border bg-card text-card-foreground p-2 rounded-lg max-w-[75%]"
+                             v-html="message.text"></div>
                     </div>
                     <div v-else>{{ message.text }}</div>
                 </div>
@@ -62,33 +58,17 @@ const handleEnter = function (e) {
                     <InputGroupTextarea placeholder="Ask, Search or Chat..." v-model="form.message"
                                         @keydown.enter.prevent="handleEnter"/>
                     <InputGroupAddon align="block-end">
-                        <!--                <InputGroupButton-->
-                        <!--                    variant="outline"-->
-                        <!--                    class="rounded-full"-->
-                        <!--                    size="icon-xs"-->
-                        <!--                >-->
-                        <!--                    <PlusIcon class="size-4"/>-->
-                        <!--                </InputGroupButton>-->
-                        <DropdownMenu>
-                            <DropdownMenuTrigger as-child>
-                                <InputGroupButton variant="ghost">
-                                    Модель 1
-                                </InputGroupButton>
-                            </DropdownMenuTrigger>
-                            <DropdownMenuContent side="top" align="start">
-                                <DropdownMenuItem>Модель 1</DropdownMenuItem>
-                                <DropdownMenuItem>Модель 2</DropdownMenuItem>
-                                <DropdownMenuItem>Модель 3</DropdownMenuItem>
-                            </DropdownMenuContent>
-                        </DropdownMenu>
-                        <InputGroupText class="ml-auto">
-                            123 токенов
-                        </InputGroupText>
-                        <Separator orientation="vertical" class="!h-4"/>
-                        <InputGroupButton class="cursor-pointer" variant="default" size="icon-xs"
+                        <InputGroupButton class="ml-auto cursor-pointer" variant="default"
+                                          :disabled="!form.message" :loading="true"
                                           @click="() => submit()">
-                            <ArrowUpIcon class="size-4"/>
-                            <span class="sr-only">Отправить</span>
+                            <template v-if="!form.processing">
+                                <ArrowUpIcon class="size-5"/>
+                                <span>Отправить</span>
+                            </template>
+                            <template v-else>
+                                <Loader2 class="w-4 h-4 animate-spin"/>
+                                Отправка
+                            </template>
                         </InputGroupButton>
                     </InputGroupAddon>
                 </InputGroup>

+ 9 - 1
routes/web.php

@@ -4,6 +4,14 @@ use App\Http\Controllers\ChatController;
 use Illuminate\Support\Facades\Http;
 use Illuminate\Support\Facades\Route;
 
-Route::get('/', ChatController::class);
+Route::get('/', fn () => redirect()->route('chats.index'));
+
+Route::prefix('chats')->name('chats.')->group(function () {
+    Route::get('/', [ChatController::class, 'index'])->name('index');
+    Route::get('/{chat}', [ChatController::class, 'index'])->name('view');
+    Route::delete('/{chat}', [ChatController::class, 'destroy'])->name('destroy');
+    Route::post('/message', [ChatController::class, 'message'])->name('message');
+});
+
 
 Route::get('/metrics', static fn() => Http::get('http://127.0.0.1:9019/metrics'));