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:
142
app/Services/Llm/LmStudioClient.php
Normal file
142
app/Services/Llm/LmStudioClient.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?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 LmStudioClient implements LlmClientInterface
|
||||
{
|
||||
public function __construct(private readonly AppSettingsService $settings) {}
|
||||
|
||||
public function embed(string $text): array
|
||||
{
|
||||
$timeout = $this->timeout();
|
||||
$baseUrl = $this->baseUrl();
|
||||
|
||||
try {
|
||||
$response = Http::connectTimeout(5)
|
||||
->timeout($timeout)
|
||||
->withOptions(['read_timeout' => $timeout])
|
||||
->post($baseUrl.'/v1/embeddings', [
|
||||
'model' => $this->model('embedding', (string) config('services.llm.embedding_model')),
|
||||
'input' => $text,
|
||||
])
|
||||
->throw()
|
||||
->json();
|
||||
} catch (Throwable $e) {
|
||||
throw $this->mapException($e, 'embedding');
|
||||
}
|
||||
|
||||
$embedding = $response['data'][0]['embedding'] ?? [];
|
||||
if (! is_array($embedding) || $embedding === []) {
|
||||
throw new OllamaUnavailableException('lmstudio', 'embedding', 'No embedding in response');
|
||||
}
|
||||
|
||||
return $embedding;
|
||||
}
|
||||
|
||||
public function generate(string $prompt, array $options = []): string
|
||||
{
|
||||
$timeout = $this->timeout();
|
||||
$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')),
|
||||
'messages' => [
|
||||
['role' => 'user', 'content' => $prompt],
|
||||
],
|
||||
'temperature' => 0.1,
|
||||
];
|
||||
|
||||
if ($expectJson) {
|
||||
// OpenAI-compatible endpoints vary: some support json_schema/text only.
|
||||
$payload['response_format'] = ['type' => 'json_schema'];
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::connectTimeout(5)
|
||||
->timeout($timeout)
|
||||
->withOptions(['read_timeout' => $timeout])
|
||||
->post($baseUrl.'/v1/chat/completions', $payload)
|
||||
->throw()
|
||||
->json();
|
||||
} catch (RequestException $e) {
|
||||
$body = (string) ($e->response?->body() ?? '');
|
||||
$isResponseFormatError = str_contains($body, 'response_format.type')
|
||||
|| str_contains($body, 'json_schema');
|
||||
|
||||
if ($expectJson && $isResponseFormatError) {
|
||||
// Fallback retry without response_format for stricter local servers.
|
||||
try {
|
||||
$retryPayload = $payload;
|
||||
unset($retryPayload['response_format']);
|
||||
|
||||
$response = Http::connectTimeout(5)
|
||||
->timeout($timeout)
|
||||
->withOptions(['read_timeout' => $timeout])
|
||||
->post($baseUrl.'/v1/chat/completions', $retryPayload)
|
||||
->throw()
|
||||
->json();
|
||||
|
||||
return (string) ($response['choices'][0]['message']['content'] ?? '');
|
||||
} catch (Throwable $retryException) {
|
||||
throw $this->mapException($retryException, 'generation');
|
||||
}
|
||||
}
|
||||
|
||||
throw $this->mapException($e, 'generation');
|
||||
} catch (Throwable $e) {
|
||||
throw $this->mapException($e, 'generation');
|
||||
}
|
||||
|
||||
return (string) ($response['choices'][0]['message']['content'] ?? '');
|
||||
}
|
||||
|
||||
private function baseUrl(): string
|
||||
{
|
||||
$instance = $this->settings->activeProviderInstance();
|
||||
|
||||
return rtrim((string) ($instance['base_url'] ?? $this->settings->get('llm.providers.lmstudio.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.lmstudio.chat_model', $fallback));
|
||||
$embeddingModel = (string) ($instance['embedding_model'] ?? $this->settings->get('llm.providers.lmstudio.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('lmstudio', $operation, $e->getMessage(), $e, $snippet);
|
||||
}
|
||||
|
||||
return new OllamaUnavailableException('lmstudio', $operation, $e->getMessage(), $e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user