Skip to content

Commit

Permalink
refactor: streamline AI request handling and introduce Lead helper fo…
Browse files Browse the repository at this point in the history
…r data mapping
  • Loading branch information
amit-webkul committed Jan 31, 2025
2 parents 908fe04 + 80c8f42 commit 50be070
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 180 deletions.
108 changes: 3 additions & 105 deletions packages/Webkul/Admin/src/Http/Controllers/Lead/LeadController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Validator;
use Illuminate\View\View;
use Prettus\Repository\Criteria\RequestCriteria;
use Webkul\Admin\DataGrids\Lead\LeadDataGrid;
Expand All @@ -20,6 +19,7 @@
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Contact\Repositories\PersonRepository;
use Webkul\DataGrid\Enums\DateRangeOptionEnum;
use Webkul\Lead\Helpers\Lead;
use Webkul\Lead\Repositories\LeadRepository;
use Webkul\Lead\Repositories\PipelineRepository;
use Webkul\Lead\Repositories\ProductRepository;
Expand All @@ -30,6 +30,7 @@
use Webkul\Tag\Repositories\TagRepository;
use Webkul\User\Repositories\UserRepository;


class LeadController extends Controller
{
/**
Expand Down Expand Up @@ -650,7 +651,7 @@ public function createByAI(LeadForm $request)
], 400);
}

$leadData = $this->mapAIDataToLead($extractedData);
$leadData = Lead::mapAIDataToLead($extractedData);

if (
! empty($leadData['status'])
Expand All @@ -665,109 +666,6 @@ public function createByAI(LeadForm $request)
return self::leadCreate($leadData);
}

/**
* Mapped the receive Extracted AI data.
*/
private function mapAIDataToLead($aiData)
{
$model = core()->getConfigData('general.magic_ai.settings.model');

if (str_contains($model, 'gemini-1.5-flash')) {
$content = $aiData['candidates'][0]['content']['parts'][0]['text'] ?? '';
} else {
$content = $aiData['choices'][0]['message']['content'] ?? '';
}

$content = strip_tags($content);

preg_match('/\{.*\}/s', $content, $matches);

$jsonString = $matches[0] ?? null;

if (! $jsonString) {
return [
'status' => 'error',
'message' => trans('admin::app.leads.file.invalid-response'),
];
}

$finalData = json_decode($jsonString);

if (json_last_error() !== JSON_ERROR_NONE) {
return [
'status' => 'error',
'message' => trans('admin::app.leads.file.invalid-format'),
];
}

try {
$this->validateLeadData($finalData);

$data = [
'status' => 1,
'title' => $finalData->title ?? 'N/A',
'description' => $finalData->description ?? null,
'lead_source_id' => 1,
'lead_type_id' => 1,
'lead_value' => $finalData->lead_value ?? 0,
'person' => [
'name' => $finalData->person->name ?? 'Unknown',
'emails' => [
[
'value' => $finalData->person->emails->value ?? null,
'label' => $finalData->person->emails->label ?? 'work',
],
],
'contact_numbers' => [
[
'value' => $finalData->person->contact_numbers->value ?? null,
'label' => $finalData->person->contact_numbers->label ?? 'work',
],
],
'entity_type' => 'persons',
],
'entity_type' => 'leads',
];

$validatedData = app(LeadForm::class)->validated();

return array_merge($validatedData, $data);
} catch (\Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
];
}
}

/**
* Validate the lead data.
*/
private function validateLeadData($data)
{
$dataArray = json_decode(json_encode($data), true);

$validator = Validator::make($dataArray, [
'title' => 'required|string|max:255',
'lead_value' => 'required|numeric|min:0',
'person.name' => 'required|string|max:255',
'person.emails.value' => 'required|email',
'person.contact_numbers.value' => 'required|string|max:20',
]);

if ($validator->fails()) {
throw new \Illuminate\Validation\ValidationException(
$validator,
response()->json([
'status' => 'error',
'message' => $validator->errors()->getMessages(),
], 400)
);
}

return $data;
}

/**
* Create lead independent entity.
*/
Expand Down
126 changes: 126 additions & 0 deletions packages/Webkul/Lead/src/Helpers/Lead.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

namespace Webkul\Lead\Helpers;

use Illuminate\Support\Facades\Validator;
use Webkul\Admin\Http\Requests\LeadForm;

class Lead
{
/**
* Const Variable of GEMINI_MODEL.
*/
const GEMINI_MODEL = 'gemini-1.5-flash';

/**
* Const Variable of LEAD_ENTITY.
*/
const LEAD_ENTITY = 'leads';

/**
* Const Variable of PERSON_ENTITY.
*/
const PERSON_ENTITY = 'persons';

const OPEN_AI_MODEL_URL = 'https://api.openai.com/v1/chat/completions';

/**
* Mapped the receive Extracted AI data.
*/
public static function mapAIDataToLead($aiData)
{
$isGeminiModel = str_contains(core()->getConfigData('general.magic_ai.settings.model'), self::GEMINI_MODEL);

$content = $isGeminiModel ? $aiData['candidates'][0]['content']['parts'][0]['text'] : $aiData['choices'][0]['message']['content'];

$content = strip_tags($content);

preg_match('/\{.*\}/s', $content, $matches);

if (! $jsonString = $matches[0] ?? null) {
return [
'status' => 'error',
'message' => trans('admin::app.leads.file.invalid-response'),
];
}

$finalData = json_decode($jsonString);

if (json_last_error() !== JSON_ERROR_NONE) {
return [
'status' => 'error',
'message' => trans('admin::app.leads.file.invalid-format'),
];
}

try {
self::validateLeadData($finalData);

$validatedData = app(LeadForm::class)->validated();

return array_merge($validatedData, self::prepareLeadData($finalData));
} catch (\Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
];
}
}

/**
* Validate the lead data.
*/
private static function validateLeadData($data)
{
$dataArray = json_decode(json_encode($data), true);

$validator = Validator::make($dataArray, [
'title' => 'required|string|max:255',
'lead_value' => 'required|numeric|min:0',
'person.name' => 'required|string|max:255',
'person.emails.value' => 'required|email',
'person.contact_numbers.value' => 'required|string|max:20',
]);

if ($validator->fails()) {
throw new \Illuminate\Validation\ValidationException(
$validator,
response()->json([
'status' => 'error',
'message' => $validator->errors()->getMessages(),
], 400)
);
}

return $data;
}

private static function prepareLeadData($finalData)
{
return [
'status' => 1,
'title' => $finalData->title ?? 'N/A',
'description' => $finalData->description ?? null,
'lead_source_id' => 1,
'lead_type_id' => 1,
'lead_value' => $finalData->lead_value ?? 0,
'person' => [
'name' => $finalData->person->name ?? 'Unknown',
'emails' => [
[
'value' => $finalData->person->emails->value ?? null,
'label' => $finalData->person->emails->label ?? 'work',
],
],
'contact_numbers' => [
[
'value' => $finalData->person->contact_numbers->value ?? null,
'label' => $finalData->person->contact_numbers->label ?? 'work',
],
],
'entity_type' => self::PERSON_ENTITY,
],
'entity_type' => self::LEAD_ENTITY,
];
}
}
36 changes: 9 additions & 27 deletions packages/Webkul/Lead/src/Services/GeminiService.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,24 @@ public static function ask($prompt, $model, $apiKey)
{
$url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$apiKey}";

return self::curlRequest($url, self::prepareRequestData($prompt));
return self::sendHttpRequest($url, self::prepareLeadExtractionRequestData($prompt));
}

/**
* Request data to AI using Curl API.
*/
private static function curlRequest($url, array $data)
private static function sendHttpRequest($url, array $data)
{
try {
$ch = curl_init($url);
$response = \Http::withHeaders([
'Content-Type' => 'application/json',
])->post($url, $data);

curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if (curl_errno($ch)) {
throw new Exception('cURL Error: '.curl_error($ch));
}

curl_close($ch);

$decodedResponse = json_decode($response, true);

if ($httpCode !== 200 || isset($decodedResponse['error'])) {
throw new Exception('LLM API Error: '.($decodedResponse['error']['message'] ?? 'Unknown error'));
if ($response->failed()) {
throw new Exception($response->json('error.message'));
}

return $decodedResponse;
return $response->json();
} catch (Exception $e) {
return ['error' => $e->getMessage()];
}
Expand All @@ -57,7 +39,7 @@ private static function curlRequest($url, array $data)
/**
* Prepare request data for AI.
*/
private static function prepareRequestData($prompt)
private static function prepareLeadExtractionRequestData($prompt)
{
return [
'contents' => [
Expand Down
13 changes: 5 additions & 8 deletions packages/Webkul/Lead/src/Services/LeadService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@

use Exception;
use Smalot\PdfParser\Parser;
use Webkul\Lead\Helpers\Lead;

class LeadService
{
/**
* Const variable
*/
const GEMINI_MODEL = 'gemini-1.5-flash';

const OPEN_AI_MODEL_URL = 'https://api.openai.com/v1/chat/completions';

/**
Expand All @@ -22,13 +21,11 @@ public static function extractDataFromPdf($pdfPath)
try {
$parser = new Parser;

$pdfText = trim($parser->parseFile($pdfPath)->getText());

if (empty($pdfText)) {
if (empty($pdfText = trim($parser->parseFile($pdfPath)->getText()))) {
throw new Exception('PDF content is empty or could not be extracted.');
}

return self::sendLLMRequest($pdfText);
return self::processPromptWithAI($pdfText);
} catch (Exception $e) {
return ['error' => $e->getMessage()];
}
Expand All @@ -37,7 +34,7 @@ public static function extractDataFromPdf($pdfPath)
/**
* Send a request to the LLM API.
*/
private static function sendLLMRequest($prompt)
private static function processPromptWithAI($prompt)
{
$model = core()->getConfigData('general.magic_ai.settings.model');
$apiKey = core()->getConfigData('general.magic_ai.settings.api_key');
Expand All @@ -46,7 +43,7 @@ private static function sendLLMRequest($prompt)
return ['error' => 'Missing API key or model configuration.'];
}

if (str_contains($model, self::GEMINI_MODEL)) {
if (str_contains($model, Lead::GEMINI_MODEL)) {
return GeminiService::ask($prompt, $model, $apiKey);
} else {
return OpenAIService::ask($prompt, $model);
Expand Down
Loading

0 comments on commit 50be070

Please sign in to comment.