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

@@ -38,12 +38,14 @@ class HelpdeskImportService
if ($parsed === null) {
$skipped++;
$progress && $progress($processed, $total, $articleUrl, 'skipped');
continue;
}
if ($dryRun) {
$imported++;
$progress && $progress($processed, $total, $articleUrl, 'dry-run');
continue;
}
@@ -52,18 +54,16 @@ class HelpdeskImportService
$categoryId = $this->resolveCategoryId($meta['category_external_id'] ?? null, $categoryMap);
$subcategoryId = $this->resolveCategoryId($meta['subcategory_external_id'] ?? null, $categoryMap);
$result = Article::withoutEvents(function () use ($title, $content, $articleUrl, $sourceArticleId, $categoryId, $subcategoryId) {
return Article::query()->updateOrCreate(
['source' => 'internettoday_helpdesk', 'source_article_id' => $sourceArticleId],
[
'title' => $title,
'content' => $content,
'source_url' => $articleUrl,
'category_id' => $categoryId,
'subcategory_id' => $subcategoryId,
]
);
});
$result = Article::query()->updateOrCreate(
['source' => 'internettoday_helpdesk', 'source_article_id' => $sourceArticleId],
[
'title' => $title,
'content' => $content,
'source_url' => $articleUrl,
'category_id' => $categoryId,
'subcategory_id' => $subcategoryId,
]
);
if ($result->wasRecentlyCreated) {
$imported++;
@@ -92,11 +92,12 @@ class HelpdeskImportService
private function extractCategories(string $html): array
{
if (!preg_match('/const\s+categories\s*=\s*(\[.*?\]);/s', $html, $matches)) {
if (! preg_match('/const\s+categories\s*=\s*(\[.*?\]);/s', $html, $matches)) {
return [];
}
$decoded = json_decode($matches[1], true);
return is_array($decoded) ? $decoded : [];
}
@@ -104,12 +105,12 @@ class HelpdeskImportService
{
$map = [];
foreach ($categories as $category) {
if (!isset($category['id'], $category['title'], $category['slug'])) {
if (! isset($category['id'], $category['title'], $category['slug'])) {
continue;
}
$parentId = null;
if (!$dryRun) {
if (! $dryRun) {
$model = Category::query()->updateOrCreate(
['external_id' => (int) $category['id']],
['name' => (string) $category['title'], 'slug' => (string) $category['slug'], 'parent_id' => null]
@@ -120,11 +121,11 @@ class HelpdeskImportService
$map[(int) $category['id']] = $parentId;
foreach (($category['children'] ?? []) as $child) {
if (!isset($child['id'], $child['title'], $child['slug'])) {
if (! isset($child['id'], $child['title'], $child['slug'])) {
continue;
}
if (!$dryRun && $parentId !== null) {
if (! $dryRun && $parentId !== null) {
$childModel = Category::query()->updateOrCreate(
['external_id' => (int) $child['id']],
['name' => (string) $child['title'], 'slug' => (string) $child['slug'], 'parent_id' => $parentId]
@@ -143,7 +144,7 @@ class HelpdeskImportService
{
$sections = [];
foreach ($categories as $category) {
if (!isset($category['id'], $category['slug'])) {
if (! isset($category['id'], $category['slug'])) {
continue;
}
@@ -154,7 +155,7 @@ class HelpdeskImportService
];
foreach (($category['children'] ?? []) as $child) {
if (!isset($child['id'], $child['slug'])) {
if (! isset($child['id'], $child['slug'])) {
continue;
}
@@ -186,7 +187,7 @@ class HelpdeskImportService
preg_match_all('/https:\/\/www\.internettoday\.nl\/helpdesk\/(\d+)-[a-z0-9\-]+/i', $html, $matches);
foreach (($matches[0] ?? []) as $match) {
$url = strtolower($match);
if (!isset($result[$url])) {
if (! isset($result[$url])) {
$result[$url] = [
'category_external_id' => $source['category_external_id'],
'subcategory_external_id' => $source['subcategory_external_id'],
@@ -206,7 +207,7 @@ class HelpdeskImportService
return null;
}
if (!preg_match('/<h1[^>]*>(.*?)<\/h1>/is', $html, $titleMatch)) {
if (! preg_match('/<h1[^>]*>(.*?)<\/h1>/is', $html, $titleMatch)) {
return null;
}
@@ -215,7 +216,7 @@ class HelpdeskImportService
return null;
}
if (!preg_match('/<div\s+class="main_1_column">\s*(<p.*?<\/p>)\s*<\/div>/is', $html, $contentMatch)) {
if (! preg_match('/<div\s+class="main_1_column">\s*(<p.*?<\/p>)\s*<\/div>/is', $html, $contentMatch)) {
return null;
}
@@ -228,7 +229,7 @@ class HelpdeskImportService
return null;
}
if (!preg_match('/\/helpdesk\/(\d+)-/', $url, $idMatch)) {
if (! preg_match('/\/helpdesk\/(\d+)-/', $url, $idMatch)) {
return null;
}
@@ -248,6 +249,7 @@ class HelpdeskImportService
{
$decoded = html_entity_decode(strip_tags($value), ENT_QUOTES | ENT_HTML5, 'UTF-8');
$decoded = preg_replace('/\s+/', ' ', $decoded) ?? $decoded;
return trim($decoded);
}
}