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,108 @@
<?php
namespace App\Services\Llm;
use App\Exceptions\OllamaUnavailableException;
use App\Services\AppSettingsService;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Throwable;
class OllamaClient implements LlmClientInterface
{
public function __construct(private readonly AppSettingsService $settings) {}
public function embed(string $text): array
{
$baseUrl = $this->baseUrl();
try {
$response = Http::timeout($this->timeout())
->post($baseUrl.'/api/embeddings', [
'model' => $this->model('embedding', (string) config('services.llm.embedding_model', 'nomic-embed-text')),
'prompt' => $text,
])
->throw()
->json();
} catch (Throwable $e) {
throw $this->mapException($e, 'embedding');
}
$embedding = $response['embedding'] ?? [];
if (! is_array($embedding) || $embedding === []) {
throw new OllamaUnavailableException('ollama', 'embedding', 'No embedding in response');
}
return $embedding;
}
public function generate(string $prompt, array $options = []): string
{
$baseUrl = $this->baseUrl();
$expectJson = (bool) ($options['expect_json'] ?? false);
$task = (string) ($options['task'] ?? 'chat');
$payload = [
'model' => $this->model($task, (string) config('services.llm.chat_model', 'llama3')),
'prompt' => $prompt,
'stream' => false,
];
if ($expectJson) {
$payload['format'] = 'json';
}
try {
$response = Http::timeout($this->timeout())
->post($baseUrl.'/api/generate', $payload)
->throw()
->json();
} catch (Throwable $e) {
throw $this->mapException($e, 'generation');
}
return (string) ($response['response'] ?? '');
}
private function baseUrl(): string
{
$instance = $this->settings->activeProviderInstance();
return rtrim((string) ($instance['base_url'] ?? $this->settings->get('llm.providers.ollama.base_url', (string) config('services.llm.base_url'))), '/');
}
private function timeout(): int
{
return (int) $this->settings->get('llm.timeout', (string) config('services.llm.timeout', 30));
}
private function model(string $task, string $fallback): string
{
$instance = $this->settings->activeProviderInstance();
$chatModel = (string) ($instance['chat_model'] ?? $this->settings->get('llm.providers.ollama.chat_model', $fallback));
$embeddingModel = (string) ($instance['embedding_model'] ?? $this->settings->get('llm.providers.ollama.embedding_model', $fallback));
if ($task === 'chat') {
return $chatModel;
}
$configured = trim((string) $this->settings->get('llm.models.'.$task, ''));
if ($configured !== '') {
return $configured;
}
return $task === 'embedding' ? $embeddingModel : $chatModel;
}
private function mapException(Throwable $e, string $operation): OllamaUnavailableException
{
if ($e instanceof RequestException) {
$body = $e->response?->body();
$snippet = $body ? mb_substr($body, 0, 280) : null;
return new OllamaUnavailableException('ollama', $operation, $e->getMessage(), $e, $snippet);
}
return new OllamaUnavailableException('ollama', $operation, $e->getMessage(), $e);
}
}