Files
portal/app/app/Services/Bot/AitunnelChatClient.php
T
Дмитрий e969b7700a chore(chat): убрать настройки Jivo — чат теперь свой; экскурсии выключены
Config: jivo_bot -> bot (убраны неиспользуемые webhook_secret/outbound_url/token
- транспорт Jivo уже удалён), jivosite убран целиком (виджет больше не нужен).
Env: JIVO_BOT_* -> BOT_*; JIVO_WIDGET_ID/VITE_JIVO_WIDGET_ID удалены.
tours_enabled выключен по умолчанию (BOT_TOURS_ENABLED=false) — экскурсии
«Показать на портале» уходили не по теме вопроса (живая проверка 13.07.2026),
чинить релевантность отдельно; сторож-тест не даёт включить незаметно.

Заодно убраны обнаруженные хвосты того же виджета, оставшиеся от прежних
задач: JivoLivenessProbe (класс+тест+регистрация в мониторинге внешних
сервисов), плитка «JivoSite» в админ-дашборде, встроенный скрипт виджета
в welcome.blade.php, осиротевший тест resources/js/.../JivoWidget.vue
(компонент уже был удалён ранее), declaration VITE_JIVO_WIDGET_ID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 08:32:21 +03:00

64 lines
2.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Bot;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* Зарубежные модели (Claude, GPT, Gemini) через российский шлюз AITunnel — тот же канал,
* которым уже пользуется движок автоподбора. Протокол OpenAI-совместимый.
*
* ⚠️ ПДн: текст уезжает за пределы РФ, поэтому телефоны и почта клиента вырезаются
* {@see PersonalDataMask} ДО отправки. Для ответа они боту не нужны.
*/
class AitunnelChatClient implements LlmClient
{
public function __construct(private readonly PersonalDataMask $mask) {}
public function complete(string $systemPrompt, string $userText, array $history = []): ?string
{
$cfg = (array) config('services.aitunnel');
$key = (string) ($cfg['key'] ?? '');
if ($key === '') {
return null;
}
$messages = [['role' => 'system', 'content' => $systemPrompt]];
foreach ($history as $message) {
$messages[] = [
'role' => ((string) $message['role']) === 'assistant' ? 'assistant' : 'user',
'content' => $this->mask->hide((string) $message['text']),
];
}
$messages[] = ['role' => 'user', 'content' => $this->mask->hide($userText)];
try {
$response = Http::timeout((int) config('services.bot.timeout_seconds', 10))
->withToken($key)
->post(rtrim((string) ($cfg['base_url'] ?? ''), '/').'/chat/completions', [
'model' => (string) config('services.bot.aitunnel_model', 'gpt-4o-mini'),
'temperature' => 0.2,
'max_tokens' => 500,
'messages' => $messages,
]);
if (! $response->ok()) {
Log::warning('AITunnel non-OK', ['status' => $response->status()]);
return null;
}
$text = trim((string) $response->json('choices.0.message.content', ''));
return $text === '' ? null : $text;
} catch (\Throwable $e) {
Log::warning('AITunnel failure', ['error' => $e->getMessage()]);
return null;
}
}
}