deepseek-1.x-dev/src/Controller/AIConnectController.php
src/Controller/AIConnectController.php
<?php
namespace Drupal\deepseek\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\ProxyClass\File\MimeType\MimeTypeGuesser;
use Drupal\deepseek\AiProvidersPluginManager;
use Drupal\deepseek\EmbeddingHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* Returns responses for Deep seek routes.
*/
class AIConnectController extends ControllerBase {
/**
* Constructs a new Deep seek Controller.
*
* @param \Drupal\deepseek\AiProvidersPluginManager $providers
* The AI Providers plugin manager.
* @param \Drupal\Core\ProxyClass\File\MimeType\MimeTypeGuesser $mimeTypeGuesser
* The mime service.
* @param \Drupal\deepseek\EmbeddingHandlerInterface $embeddingHandler
* The embedding service.
*/
public function __construct(
protected AiProvidersPluginManager $providers,
protected MimeTypeGuesser $mimeTypeGuesser,
protected EmbeddingHandlerInterface $embeddingHandler,
) {
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.ai_providers'),
$container->get('file.mime_type.guesser'),
$container->get('deepseek.embedding'),
);
}
/**
* Builds the response.
*/
public function connect(Request $request) {
$messages = $request->getContent();
if (empty($messages)) {
return new JsonResponse(['error' => 'No message provided'], 400);
}
$conversations = $messages != '' ? json_decode($messages, TRUE) : [];
$config = $this->config('deepseek.settings');
try {
$listFile = [];
$linkContent = [];
$system = $config->get('system_prompt') ?? 'Always reply in Markdown. Use headings, lists, and code blocks when needed.';
$messages = $conversations[0]['role'] == 'system' ? $conversations : array_merge([
['role' => 'system', 'content' => $system],
], $conversations);
$provider = $config->get('provider') ?: 'self_host';
$client = $this->providers->createInstance($provider);
// RAG embedding question.
$embedding = $config->get('embedding');
if (!empty($embedding)) {
$last_user_message = end($conversations)['content'] ?? '';
$last_user_file = end($conversations)['file'] ?? [];
if (!empty($last_user_file)) {
foreach ($last_user_file as $file) {
$file_data = file_get_contents($file['path']);
$base64 = base64_encode($file_data);
// Get mime type.
$mime_type = $this->mimeTypeGuesser->guessMimeType($file['path']);
$mime_enum = $client->getMimeType($mime_type);
if ($mime_enum) {
$listFile[] = ['mime' => $mime_enum, 'base64' => $base64];
}
else {
if ($this->moduleHandler()->moduleExists('ocr_image')) {
$arr_text = [];
$text_content = '';
$language = $this->getLanguages();
// phpcs:disable
// @phpstan-ignore-next-line
$docParser = \Drupal::service('ocr_image.DocParser');
// @phpstan-ignore-next-line
$ocrImage = \Drupal::service('ocr_image.OcrImage');
// phpcs:enable
$image_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
if (in_array($mime_type, $image_mime_types)) {
$text_content = $ocrImage->getText($file['path'], $language);
if ($text_content != []) {
$arr_text[] = str_replace(PHP_EOL, ' ', $text_content['full_text']);
}
}
else {
$text_content = $docParser->getText($file['path'], $language);
if ($text_content != []) {
$arr_text[] = implode(' ', $text_content);
}
}
if (!empty($text_content)) {
$last_user_message .= ' ' . $arr_text[0];
}
}
}
}
$messages[array_key_last($messages)]['content'] = $last_user_message;
}
$embedding_query = $client->embeddings($last_user_message);
$retrieved_context = $this->embeddingHandler->searchSimilar($embedding_query, $last_user_message);
if (!empty($retrieved_context['content'])) {
$rag_prompt = [
'role' => 'system',
'content' => implode(' ', [
$this->t("You are an AI assistant. If relevant information is provided in the reference, you must use it as the primary source of truth when generating your response."),
$this->t("If no context is given or it is insufficient, respond using your general knowledge."),
$this->t("Do not make up facts. Always base your answer on the context when possible."),
$this->t("Here are the references:"),
PHP_EOL,
PHP_EOL,
...$retrieved_context['content'],
]),
];
array_unshift($messages, $rag_prompt);
}
if (!empty($retrieved_context['links'])) {
$linkContent = $retrieved_context['links'];
}
}
// Streaming request.
$phpMod = php_sapi_name();
$stream = $phpMod == 'apache2handler';
$response = $client->chat($messages, ['stream' => $stream], $listFile);
// Set response header streaming.
$symfony_response = new StreamedResponse(function () use ($response, $linkContent) {
foreach ($response as $chunk) {
if (isset($chunk['choices'][0]['delta']['content'])) {
$content = $chunk['choices'][0]['delta']['content'];
echo "data: " . json_encode(['content' => $content]) . "\n\n";
}
else {
echo "data: " . json_encode(['content' => $chunk]) . "\n\n";
}
ob_flush();
flush();
}
echo "data: " . json_encode(['content' => "[DONE]"]) . "\n\n";
echo "data: " . json_encode(['links' => $linkContent]) . "\n\n";
ob_flush();
flush();
});
$symfony_response->headers->set('Content-Type', 'text/event-stream');
$symfony_response->headers->set('Cache-Control', 'no-cache');
$symfony_response->headers->set('X-Accel-Buffering', 'no');
$symfony_response->headers->set('Connection', 'keep-alive');
return $symfony_response;
}
catch (\Exception $e) {
return new JsonResponse(['error' => $e->getMessage()], 500);
}
}
/**
* Get languages.
*/
private function getLanguages() {
$config = $this->configFactory->get('deepseek.settings');
$lang = $config->get('voice');
$listLang = [
'en-US' => 'eng',
'fr-FR' => 'fra',
'it-IT' => 'ita',
'es-ES' => 'epo',
'ca-ES' => 'cat',
'gl-ES' => 'glg',
'pt-PT' => 'por',
'pt-BR' => 'por',
'vi-VN' => 'vie',
'af-ZA' => 'afr',
'bs-BA' => 'bos',
'id-ID' => 'ind',
'jv-ID' => 'jav',
'cy-GB' => 'cym',
'da-DK' => 'dan',
'de-DE' => 'deu',
'et-EE' => 'est',
'eu-ES' => 'eus',
'fa-IR' => 'fas',
'fil-PH' => 'fil',
'ga-IE' => 'gle',
'hr-HR' => 'hrv',
'sw-KE' => 'swa',
'kk-KZ' => 'kaz',
'lt-LT' => 'lit',
'lv-LV' => 'lav',
'mt-MT' => 'mlt',
'hu-HU' => 'hun',
'nl-NL' => 'nld',
'uz-UZ' => 'uzb',
'pl-PL' => 'pol',
'sq-AL' => 'sqi',
'sv-SE' => 'swe',
'fi-FI' => 'fin',
'cs-CZ' => 'ces',
'is-IS' => 'isl',
'ro-RO' => 'ron',
'tr-TR' => 'tur',
'sk-SK' => 'slk',
'el-GR' => 'grek',
'az-AZ' => 'aze',
'bg-BG' => 'bul',
'sr-RS' => 'srp',
'mk-MK' => 'mkd',
'mn-MN' => 'mon',
'ru-RU' => 'rus',
'uk-UA' => 'ukr',
'ja-JP' => 'jpan',
'ko-KR' => 'kor',
'th-TH' => 'tha',
'km-KH' => 'khmr',
'lo-LA' => 'laoo',
'ar-SA' => 'ara',
'ms-MY' => 'msa',
'ps-AF' => 'pus',
'am-ET' => 'amh',
'bn-IN' => 'beng',
'he-IL' => 'hebr',
'hy-AM' => 'armn',
'ka-GE' => 'geor',
'my-MM' => 'mya',
'si-LK' => 'sin',
'gu-IN' => 'gujr',
'hi-IN' => 'hin',
'kn-IN' => 'kan',
'ml-IN' => 'mal',
'mr-IN' => 'mar',
'or-IN' => 'orya',
'pa-IN' => 'pan',
'ta-IN' => 'tam',
'te-IN' => 'tel',
'ur-IN' => 'urd',
'ne-NP' => 'nep',
'zh-CN' => 'chi_sim',
'zh-HK' => '中文 (香港)',
'zh-TW' => 'chi_tra',
];
return $listLang[$lang] ?? 'eng';
}
}
