feat: Enhance Support Reply Service with tone instructions and article details

- Added tone instruction retrieval to SupportReplyService.
- Improved user feedback when no relevant article is found.
- Included article URL and tone instruction in LLM prompt.
- Updated response format to include source information.
- Enhanced article management UI with search functionality and editing capabilities.
- Introduced a new API endpoint for nearest articles based on vector search.
- Added confidence badge component to display article confidence levels.
- Implemented tests for article searching, editing, and nearest article API.
- Removed obsolete .htaccess file.
This commit is contained in:
your name
2026-05-13 22:25:45 +02:00
parent c94d3f85e8
commit 9244899f9b
22 changed files with 813 additions and 123 deletions

View File

@@ -0,0 +1,59 @@
<?php
namespace Tests\Feature;
use App\Livewire\Admin\ArticleManager;
use App\Models\Article;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
use Tests\TestCase;
class ArticleManagerTest extends TestCase
{
use RefreshDatabase;
public function test_it_can_search_articles(): void
{
Article::withoutEvents(function (): void {
Article::query()->create([
'title' => 'DNS instellen',
'content' => 'Managed DNS handleiding',
]);
Article::query()->create([
'title' => 'E-mail wachtwoord wijzigen',
'content' => 'Mailbox instellingen',
]);
});
Livewire::test(ArticleManager::class)
->set('search', 'Managed DNS')
->assertSee('DNS instellen')
->assertDontSee('E-mail wachtwoord wijzigen');
}
public function test_it_can_edit_article_title_and_content(): void
{
Queue::fake();
config(['services.embedding.queue_embeddings' => true]);
$article = Article::withoutEvents(fn () => Article::query()->create([
'title' => 'Oude titel',
'content' => 'Oude inhoud',
]));
Livewire::test(ArticleManager::class)
->call('startEdit', $article->id)
->assertSet('editingArticleId', $article->id)
->set('editTitle', 'Nieuwe titel')
->set('editContent', 'Nieuwe inhoud voor het artikel')
->call('saveEdit')
->assertSet('editingArticleId', null);
$article->refresh();
$this->assertSame('Nieuwe titel', $article->title);
$this->assertSame('Nieuwe inhoud voor het artikel', $article->content);
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Tests\Feature;
use App\DTOs\ArticleCandidateDTO;
use App\Repositories\Contracts\ArticleRepositoryInterface;
use App\Services\EmbeddingService;
use Mockery;
use Tests\Fakes\FakeArticleRepository;
use Tests\TestCase;
class NearestArticleApiTest extends TestCase
{
public function test_it_returns_nearest_published_articles(): void
{
$embeddingService = Mockery::mock(EmbeddingService::class);
$embeddingService
->shouldReceive('embed')
->once()
->with('Hoe stel ik DNS in?')
->andReturn([0.1, 0.2, 0.3]);
$embeddingService
->shouldReceive('context')
->once()
->andReturn([
'provider_instance_id' => 'instance-1',
'embedding_model' => 'embed-model',
]);
$repository = new FakeArticleRepository;
$repository->candidates = [
new ArticleCandidateDTO(
articleId: 10,
title: 'DNS instellen',
content: 'Open het DNS beheer en voeg de juiste records toe.',
distance: 0.12,
sourceUrl: 'https://example.test/articles/dns'
),
];
$this->app->instance(EmbeddingService::class, $embeddingService);
$this->app->instance(ArticleRepositoryInterface::class, $repository);
$response = $this->getJson('/api/articles/nearest?query=Hoe%20stel%20ik%20DNS%20in%3F&limit=5');
$response
->assertOk()
->assertJsonPath('data.0.article_id', 10)
->assertJsonPath('data.0.title', 'DNS instellen')
->assertJsonPath('data.0.similarity', 0.88)
->assertJsonPath('data.0.content', null)
->assertJsonPath('meta.published_only', true)
->assertJsonPath('meta.embedding_model', 'embed-model');
}
public function test_it_validates_the_search_query(): void
{
$response = $this->getJson('/api/articles/nearest?query=x');
$response->assertStatus(422);
}
}

View File

@@ -19,6 +19,7 @@ class TicketShowPageTest extends TestCase
'message' => 'vraag',
'status' => 'completed',
'best_article_id' => $article->id,
'confidence' => 0.9,
'support_reply' => 'Gebruik deze stappen',
'result_payload' => [
'quick_reply' => ['id' => 1, 'title' => 'DNS Quick'],
@@ -39,5 +40,23 @@ class TicketShowPageTest extends TestCase
$response->assertOk();
$response->assertSee('Snelantwoord gebruikt', false);
$response->assertSee('Toolcalls', false);
$response->assertSee('haalt drempel', false);
}
public function test_ticket_show_marks_confidence_below_threshold(): void
{
$article = Article::query()->create(['title' => 'DNS', 'content' => 'x']);
$ticket = Ticket::query()->create([
'message' => 'vraag',
'status' => 'completed',
'best_article_id' => $article->id,
'confidence' => 0.25,
]);
$response = $this->get("/admin/tickets/{$ticket->id}");
$response->assertOk();
$response->assertSee('Confidence 0.25', false);
$response->assertSee('onder drempel 0.45', false);
}
}