Add admin views for quick replies, settings, and ticket details

- Created `quick-replies.blade.php` for managing quick replies.
- Added `settings.blade.php` for admin settings management.
- Implemented `ticket-show.blade.php` to display ticket details.
- Introduced `timeline-card.blade.php` component for displaying timeline information.

Enhance quick reply management functionality

- Developed `quick-reply-manager.blade.php` for creating and editing quick replies.
- Integrated Livewire for dynamic interaction and validation.

Implement settings page for AI configuration

- Created `settings-page.blade.php` for managing AI settings, including prompts and provider instances.
- Added functionality for managing models and embeddings.

Add ticket show functionality with real-time updates

- Implemented ticket details view with processing status and tool call logs.
- Added support for displaying article suggestions and error messages.

Create unit tests for AI classifier and domain info tool

- Added `AIClassifierServiceTest.php` to validate AI classifier functionality.
- Implemented `DomainInfoToolTest.php` for domain parameter validation.
- Created `OxxaClientTest.php` to test API interactions and password hashing.
This commit is contained in:
SitiWeb
2026-04-30 01:50:21 +02:00
parent 01aa115a49
commit f939133fe0
103 changed files with 4721 additions and 245 deletions

View File

@@ -3,7 +3,8 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration {
return new class extends Migration
{
public function up(): void
{
DB::statement('CREATE EXTENSION IF NOT EXISTS vector');
@@ -13,4 +14,4 @@ return new class extends Migration {
{
DB::statement('DROP EXTENSION IF EXISTS vector');
}
};
};

View File

@@ -5,7 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
return new class extends Migration
{
public function up(): void
{
Schema::create('articles', function (Blueprint $table) {
@@ -24,4 +25,4 @@ return new class extends Migration {
{
Schema::dropIfExists('articles');
}
};
};

View File

@@ -5,7 +5,8 @@ use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
return new class extends Migration
{
public function up(): void
{
Schema::create('tickets', function (Blueprint $table) {
@@ -23,4 +24,4 @@ return new class extends Migration {
{
Schema::dropIfExists('tickets');
}
};
};

View File

@@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
return new class extends Migration
{
public function up(): void
{
Schema::create('embedding_cache', function (Blueprint $table) {
@@ -20,4 +21,4 @@ return new class extends Migration {
{
Schema::dropIfExists('embedding_cache');
}
};
};

View File

@@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
return new class extends Migration
{
public function up(): void
{
Schema::create('ai_decisions', function (Blueprint $table) {
@@ -22,4 +23,4 @@ return new class extends Migration {
{
Schema::dropIfExists('ai_decisions');
}
};
};

View File

@@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
return new class extends Migration
{
public function up(): void
{
Schema::create('feedback', function (Blueprint $table) {
@@ -21,4 +22,4 @@ return new class extends Migration {
{
Schema::dropIfExists('feedback');
}
};
};

View File

@@ -4,7 +4,8 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
return new class extends Migration
{
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {

View File

@@ -0,0 +1,44 @@
<?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::table('tickets', function (Blueprint $table) {
$table->string('status')->default('queued')->after('embedding');
$table->foreignId('best_article_id')->nullable()->after('status')->constrained('articles')->nullOnDelete();
$table->float('confidence')->nullable()->after('best_article_id');
$table->text('explanation')->nullable()->after('confidence');
$table->json('result_payload')->nullable()->after('explanation');
$table->text('error_message')->nullable()->after('result_payload');
$table->timestamp('processed_at')->nullable()->after('error_message');
$table->index('status');
});
Schema::create('ticket_processing_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('ticket_id')->constrained()->cascadeOnDelete();
$table->string('step', 100);
$table->string('status', 30)->default('info');
$table->text('message')->nullable();
$table->json('context')->nullable();
$table->timestamps();
$table->index(['ticket_id', 'created_at']);
});
}
public function down(): void
{
Schema::dropIfExists('ticket_processing_logs');
Schema::table('tickets', function (Blueprint $table) {
$table->dropConstrainedForeignId('best_article_id');
$table->dropColumn(['status', 'confidence', 'explanation', 'result_payload', 'error_message', 'processed_at']);
});
}
};

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::table('tickets', function (Blueprint $table) {
$table->text('normalized_message')->nullable()->after('message');
$table->json('redaction_report')->nullable()->after('normalized_message');
});
}
public function down(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->dropColumn(['normalized_message', 'redaction_report']);
});
}
};

View File

@@ -0,0 +1,22 @@
<?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::table('tickets', function (Blueprint $table) {
$table->text('support_reply')->nullable()->after('explanation');
});
}
public function down(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->dropColumn('support_reply');
});
}
};

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('settings', function (Blueprint $table) {
$table->id();
$table->string('key')->unique();
$table->longText('value')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('settings');
}
};

View File

@@ -0,0 +1,35 @@
<?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::table('articles', function (Blueprint $table) {
$table->string('status')->default('published')->after('content');
$table->boolean('is_ai_draft')->default(false)->after('status');
$table->foreignId('source_ticket_id')->nullable()->after('subcategory_id')->constrained('tickets')->nullOnDelete();
});
Schema::table('tickets', function (Blueprint $table) {
$table->boolean('needs_article_draft')->default(false)->after('support_reply');
$table->foreignId('draft_article_id')->nullable()->after('needs_article_draft')->constrained('articles')->nullOnDelete();
});
}
public function down(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->dropConstrainedForeignId('draft_article_id');
$table->dropColumn('needs_article_draft');
});
Schema::table('articles', function (Blueprint $table) {
$table->dropConstrainedForeignId('source_ticket_id');
$table->dropColumn(['status', 'is_ai_draft']);
});
}
};

View File

@@ -0,0 +1,31 @@
<?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('article_chunks', function (Blueprint $table) {
$table->id();
$table->foreignId('article_id')->constrained()->cascadeOnDelete();
$table->unsignedInteger('chunk_index');
$table->text('content');
$table->timestamps();
$table->unique(['article_id', 'chunk_index']);
});
$dimension = (int) config('services.embedding.dimension', 768);
DB::statement("ALTER TABLE article_chunks ADD COLUMN embedding vector({$dimension})");
DB::statement('CREATE INDEX article_chunks_embedding_cosine_idx ON article_chunks USING ivfflat (embedding vector_cosine_ops)');
DB::statement('CREATE INDEX article_chunks_article_id_idx ON article_chunks(article_id)');
}
public function down(): void
{
Schema::dropIfExists('article_chunks');
}
};

View File

@@ -0,0 +1,52 @@
<?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::table('embedding_cache', function (Blueprint $table) {
$table->string('provider_instance_id')->nullable()->after('id');
$table->string('embedding_model')->nullable()->after('provider_instance_id');
$table->dropUnique(['text_hash']);
$table->unique(['provider_instance_id', 'embedding_model', 'text_hash'], 'embedding_cache_model_text_unique');
});
Schema::table('article_chunks', function (Blueprint $table) {
$table->string('embedding_provider_instance_id')->nullable()->after('embedding');
$table->string('embedding_model')->nullable()->after('embedding_provider_instance_id');
$table->timestamp('embedded_at')->nullable()->after('embedding_model');
$table->index(['embedding_provider_instance_id', 'embedding_model'], 'article_chunks_embedding_model_idx');
});
Schema::table('tickets', function (Blueprint $table) {
$table->string('embedding_provider_instance_id')->nullable()->after('embedding');
$table->string('embedding_model')->nullable()->after('embedding_provider_instance_id');
$table->timestamp('embedded_at')->nullable()->after('embedding_model');
});
DB::table('embedding_cache')->truncate();
}
public function down(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->dropColumn(['embedding_provider_instance_id', 'embedding_model', 'embedded_at']);
});
Schema::table('article_chunks', function (Blueprint $table) {
$table->dropIndex('article_chunks_embedding_model_idx');
$table->dropColumn(['embedding_provider_instance_id', 'embedding_model', 'embedded_at']);
});
Schema::table('embedding_cache', function (Blueprint $table) {
$table->dropUnique('embedding_cache_model_text_unique');
$table->unique('text_hash');
$table->dropColumn(['provider_instance_id', 'embedding_model']);
});
}
};

View File

@@ -0,0 +1,49 @@
<?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::table('articles', function (Blueprint $table) {
$table->text('note')->nullable()->after('content');
$table->json('allowed_actions')->nullable()->after('note');
});
Schema::table('tickets', function (Blueprint $table) {
$table->text('api_credentials')->nullable()->after('result_payload');
});
Schema::create('ticket_tool_calls', function (Blueprint $table) {
$table->id();
$table->foreignId('ticket_id')->constrained()->cascadeOnDelete();
$table->foreignId('article_id')->nullable()->constrained('articles')->nullOnDelete();
$table->string('action', 100);
$table->string('status', 30)->default('pending');
$table->json('parameters')->nullable();
$table->json('response')->nullable();
$table->text('error')->nullable();
$table->timestamp('executed_at')->nullable();
$table->timestamps();
$table->index(['ticket_id', 'created_at']);
$table->index(['action', 'status']);
});
}
public function down(): void
{
Schema::dropIfExists('ticket_tool_calls');
Schema::table('tickets', function (Blueprint $table) {
$table->dropColumn('api_credentials');
});
Schema::table('articles', function (Blueprint $table) {
$table->dropColumn(['note', 'allowed_actions']);
});
}
};

View File

@@ -0,0 +1,36 @@
<?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('quick_replies', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->index('is_active');
});
Schema::create('article_quick_reply', function (Blueprint $table) {
$table->id();
$table->foreignId('article_id')->constrained()->cascadeOnDelete();
$table->foreignId('quick_reply_id')->constrained()->cascadeOnDelete();
$table->timestamps();
$table->unique(['article_id', 'quick_reply_id']);
});
}
public function down(): void
{
Schema::dropIfExists('article_quick_reply');
Schema::dropIfExists('quick_replies');
}
};

View File

@@ -21,4 +21,4 @@ class ArticleSeeder extends Seeder
Article::query()->create($article);
}
}
}
}

View File

@@ -12,4 +12,4 @@ class DatabaseSeeder extends Seeder
ArticleSeeder::class,
]);
}
}
}