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

@@ -0,0 +1,199 @@
<?php
namespace App\Livewire\Admin;
use App\Services\AppSettingsService;
use App\Services\ArticleEmbeddingMaintenanceService;
use App\Services\LlmModelCatalogService;
use Illuminate\Support\Str;
use Livewire\Component;
class SettingsPage extends Component
{
public string $activeTab = 'process';
public string $tone_addressing = 'je';
public string $activeProviderInstanceId = 'ollama_default';
public int $llm_timeout = 300;
public array $promptValues = [];
public array $providerInstances = [];
public array $modelValues = [];
public array $availableModels = [];
public array $embeddingStats = [];
public ?string $modelLoadError = null;
public array $processSteps = [];
public array $providerDefinitions = [];
public array $modelTasks = [];
public function mount(AppSettingsService $settings): void
{
$all = $settings->all();
$providerSettings = $settings->providerSettings();
$this->tone_addressing = (string) ($all['tone_addressing'] ?? 'je');
$this->activeProviderInstanceId = (string) ($providerSettings['active_instance_id'] ?? 'ollama_default');
$this->llm_timeout = (int) ($all['llm.timeout'] ?? 300);
$this->promptValues = $settings->promptValues();
$this->providerInstances = $providerSettings['instances'] ?? $settings->defaultProviderInstances();
$this->modelValues = $settings->modelSettings();
$this->processSteps = $settings->processSteps();
$this->providerDefinitions = $settings->providerDefinitions();
$this->modelTasks = $settings->modelTasks();
$this->refreshEmbeddingStats();
$this->loadModels();
}
public function setTab(string $tab): void
{
if (in_array($tab, ['process', 'providers', 'models', 'embeddings'], true)) {
$this->activeTab = $tab;
if ($tab === 'embeddings') {
$this->refreshEmbeddingStats();
}
}
}
public function refreshEmbeddingStats(): void
{
$this->embeddingStats = app(ArticleEmbeddingMaintenanceService::class)->stats();
}
public function reindexMissingEmbeddings(): void
{
$count = app(ArticleEmbeddingMaintenanceService::class)->dispatchReindex(false);
$this->refreshEmbeddingStats();
session()->flash('saved', "{$count} artikelen zonder chunks zijn in de queue geplaatst.");
}
public function reindexAllEmbeddings(): void
{
$count = app(ArticleEmbeddingMaintenanceService::class)->dispatchReindex(true);
$this->refreshEmbeddingStats();
session()->flash('saved', "{$count} artikelen zijn in de queue geplaatst voor volledige herindex.");
}
public function addProviderInstance(): void
{
$id = 'provider_'.Str::uuid()->toString();
$this->providerInstances[] = [
'id' => $id,
'name' => 'Nieuwe provider',
'type' => 'lmstudio',
'base_url' => 'http://localhost:1234',
'chat_model' => '',
'embedding_model' => '',
];
$this->activeProviderInstanceId = $id;
$this->availableModels = [];
$this->modelLoadError = null;
}
public function removeProviderInstance(string $id): void
{
if (count($this->providerInstances) <= 1) {
return;
}
$this->providerInstances = array_values(array_filter(
$this->providerInstances,
fn (array $instance) => ($instance['id'] ?? null) !== $id
));
if ($this->activeProviderInstanceId === $id) {
$this->activeProviderInstanceId = (string) ($this->providerInstances[0]['id'] ?? '');
}
$this->loadModels();
}
public function setActiveProviderInstance(string $id): void
{
$ids = array_column($this->providerInstances, 'id');
if (in_array($id, $ids, true)) {
$this->activeProviderInstanceId = $id;
$this->loadModels();
}
}
public function loadModels(bool $refresh = false): void
{
$instance = $this->activeProviderInstance();
if ($instance === null) {
$this->availableModels = [];
return;
}
try {
$this->availableModels = app(LlmModelCatalogService::class)->modelsFor($instance, $refresh);
$this->modelLoadError = null;
} catch (\Throwable $e) {
$this->availableModels = [];
$this->modelLoadError = $e->getMessage();
}
}
public function refreshModels(): void
{
$this->loadModels(true);
}
public function save(AppSettingsService $settings): void
{
$this->validate([
'tone_addressing' => ['required', 'in:je,u'],
'activeProviderInstanceId' => ['required', 'string'],
'llm_timeout' => ['required', 'integer', 'min:5', 'max:600'],
'promptValues' => ['array'],
'promptValues.*' => ['required', 'string', 'min:10'],
'providerInstances' => ['array', 'min:1'],
'providerInstances.*.id' => ['required', 'string'],
'providerInstances.*.name' => ['required', 'string', 'min:1'],
'providerInstances.*.type' => ['required', 'in:ollama,lmstudio'],
'providerInstances.*.base_url' => ['required', 'url'],
'providerInstances.*.chat_model' => ['nullable', 'string'],
'providerInstances.*.embedding_model' => ['nullable', 'string'],
'modelValues' => ['array'],
'modelValues.*' => ['required', 'string', 'min:1'],
]);
$settings->saveStructuredSettings(
promptValues: $this->promptValues,
providerInstances: $this->providerInstances,
activeProviderInstanceId: $this->activeProviderInstanceId,
modelValues: $this->modelValues,
timeout: $this->llm_timeout,
tone: $this->tone_addressing,
);
session()->flash('saved', 'Settings opgeslagen.');
$this->refreshEmbeddingStats();
}
private function activeProviderInstance(): ?array
{
foreach ($this->providerInstances as $instance) {
if (($instance['id'] ?? null) === $this->activeProviderInstanceId) {
return $instance;
}
}
return $this->providerInstances[0] ?? null;
}
public function render()
{
return view('livewire.admin.settings-page');
}
}