Build Laravel 13 ticket assistant with Docker, Livewire admin, and helpdesk scraper command

This commit is contained in:
SitiWeb
2026-04-29 13:11:39 +02:00
parent 141a1a3c9b
commit 3c4572bb12
58 changed files with 9377 additions and 455 deletions

View File

@@ -0,0 +1,16 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration {
public function up(): void
{
DB::statement('CREATE EXTENSION IF NOT EXISTS vector');
}
public function down(): void
{
DB::statement('DROP EXTENSION IF EXISTS vector');
}
};

View File

@@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->timestamps();
});
$dimension = (int) config('services.embedding.dimension', 768);
DB::statement("ALTER TABLE articles ADD COLUMN embedding vector({$dimension})");
DB::statement('CREATE INDEX articles_embedding_cosine_idx ON articles USING ivfflat (embedding vector_cosine_ops)');
}
public function down(): void
{
Schema::dropIfExists('articles');
}
};

View File

@@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('tickets', function (Blueprint $table) {
$table->id();
$table->text('message');
$table->timestamps();
});
$dimension = (int) config('services.embedding.dimension', 768);
DB::statement("ALTER TABLE tickets ADD COLUMN embedding vector({$dimension})");
DB::statement('CREATE INDEX tickets_embedding_cosine_idx ON tickets USING ivfflat (embedding vector_cosine_ops)');
}
public function down(): void
{
Schema::dropIfExists('tickets');
}
};

View File

@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('embedding_cache', function (Blueprint $table) {
$table->id();
$table->string('text_hash', 64)->unique();
$table->longText('text');
$table->json('embedding');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('embedding_cache');
}
};

View File

@@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('ai_decisions', function (Blueprint $table) {
$table->id();
$table->foreignId('ticket_id')->constrained()->cascadeOnDelete();
$table->foreignId('article_id')->nullable()->constrained()->nullOnDelete();
$table->float('confidence')->default(0);
$table->text('explanation')->nullable();
$table->json('raw_response')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('ai_decisions');
}
};

View File

@@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('feedback', function (Blueprint $table) {
$table->id();
$table->foreignId('ticket_id')->constrained()->cascadeOnDelete();
$table->foreignId('article_id')->nullable()->constrained()->nullOnDelete();
$table->boolean('is_correct');
$table->text('notes')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('feedback');
}
};

View File

@@ -0,0 +1,24 @@
<?php
namespace Database\Seeders;
use App\Models\Article;
use Illuminate\Database\Seeder;
class ArticleSeeder extends Seeder
{
public function run(): void
{
$articles = [
['title' => 'Password Reset Instructions', 'content' => 'Use the Forgot Password link on login. A reset email arrives within 2 minutes. Check spam folder if not received.'],
['title' => 'Refund Policy', 'content' => 'Refund requests are accepted within 14 days for annual plans and 7 days for monthly plans.'],
['title' => 'Two-Factor Authentication Setup', 'content' => 'Enable 2FA from Account Security. Scan the QR code in your authenticator app and confirm with OTP.'],
['title' => 'Subscription Upgrade Guide', 'content' => 'Go to Billing, click Change Plan, choose Pro or Enterprise, and confirm immediate prorated billing.'],
['title' => 'Webhook Delivery Troubleshooting', 'content' => 'Verify endpoint HTTPS, 2xx response, and signature validation. Retry logs are available in Developer Settings.'],
];
foreach ($articles as $article) {
Article::query()->create($article);
}
}
}

View File

@@ -2,24 +2,14 @@
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents;
/**
* Seed the application's database.
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
$this->call([
ArticleSeeder::class,
]);
}
}
}