e969b7700a
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>
64 lines
2.4 KiB
PHP
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;
|
|
}
|
|
}
|
|
}
|