+
@{{ title }}
diff --git a/packages/Webkul/Admin/src/Resources/views/leads/index.blade.php b/packages/Webkul/Admin/src/Resources/views/leads/index.blade.php
index d19c1d472..0795dc369 100644
--- a/packages/Webkul/Admin/src/Resources/views/leads/index.blade.php
+++ b/packages/Webkul/Admin/src/Resources/views/leads/index.blade.php
@@ -25,6 +25,11 @@
{!! view_render_event('admin.leads.index.header.right.before') !!}
+
+ @if(core()->getConfigData('general.magic_ai.pdf_generation.enabled'))
+ @include('admin::leads.index.upload')
+ @endif
+
@if ((request()->view_type ?? "kanban") == "table")
diff --git a/packages/Webkul/Admin/src/Resources/views/leads/index/upload.blade.php b/packages/Webkul/Admin/src/Resources/views/leads/index/upload.blade.php
new file mode 100644
index 000000000..81b274ac8
--- /dev/null
+++ b/packages/Webkul/Admin/src/Resources/views/leads/index/upload.blade.php
@@ -0,0 +1,134 @@
+
+
+
+
+@pushOnce('scripts')
+
+
+
+@endPushOnce
diff --git a/packages/Webkul/Admin/src/Routes/Admin/leads-routes.php b/packages/Webkul/Admin/src/Routes/Admin/leads-routes.php
index c94940f4d..43a0be06f 100644
--- a/packages/Webkul/Admin/src/Routes/Admin/leads-routes.php
+++ b/packages/Webkul/Admin/src/Routes/Admin/leads-routes.php
@@ -14,6 +14,8 @@
Route::post('create', 'store')->name('admin.leads.store');
+ Route::post('create-by-ai', 'createByAI')->name('admin.leads.create_by_ai');
+
Route::get('view/{id}', 'view')->name('admin.leads.view');
Route::get('edit/{id}', 'edit')->name('admin.leads.edit');
diff --git a/packages/Webkul/Core/src/Vite.php b/packages/Webkul/Core/src/Vite.php
index 633093869..9df887a2c 100644
--- a/packages/Webkul/Core/src/Vite.php
+++ b/packages/Webkul/Core/src/Vite.php
@@ -30,7 +30,7 @@ public function asset(string $filename, string $namespace = 'admin')
}
/**
- * Set bagisto vite.
+ * Set krayin vite.
*
* @return mixed
*/
diff --git a/packages/Webkul/Lead/src/Helpers/Lead.php b/packages/Webkul/Lead/src/Helpers/Lead.php
new file mode 100644
index 000000000..a2c936a3f
--- /dev/null
+++ b/packages/Webkul/Lead/src/Helpers/Lead.php
@@ -0,0 +1,126 @@
+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,
+ ];
+ }
+}
diff --git a/packages/Webkul/Lead/src/Services/GeminiService.php b/packages/Webkul/Lead/src/Services/GeminiService.php
new file mode 100644
index 000000000..8ba62f4ae
--- /dev/null
+++ b/packages/Webkul/Lead/src/Services/GeminiService.php
@@ -0,0 +1,88 @@
+ 'application/json',
+ ])->post($url, $data);
+
+ if ($response->failed()) {
+ throw new Exception($response->json('error.message'));
+ }
+
+ return $response->json();
+ } catch (Exception $e) {
+ return ['error' => $e->getMessage()];
+ }
+ }
+
+ /**
+ * Prepare request data for AI.
+ */
+ private static function prepareLeadExtractionRequestData($prompt)
+ {
+ return [
+ 'contents' => [
+ [
+ 'parts' => [
+ [
+ 'text' => "You are an AI assistant. Extract data from the provided PDF text.
+
+ Example Output:
+ {
+ \"status\": 1,
+ \"title\": \"Untitled Lead\",
+ \"person\": {
+ \"name\": \"Unknown\",
+ \"emails\": {
+ \"value\": null,
+ \"label\": null
+ },
+ \"contact_numbers\": {
+ \"value\": null,
+ \"label\": null
+ }
+ },
+ \"lead_pipeline_stage_id\": null,
+ \"lead_value\": 0,
+ \"source\": \"AI Extracted\"
+ }
+
+ Note: Only return the output. Do not return or add any comments.
+
+ PDF Content:
+ $prompt",
+ ],
+ ],
+ 'role' => 'user',
+ ],
+ ],
+ 'generationConfig' => [
+ 'temperature' => 0.2,
+ 'topK' => 30,
+ 'topP' => 0.8,
+ 'maxOutputTokens' => 512,
+ ],
+ ];
+ }
+}
diff --git a/packages/Webkul/Lead/src/Services/LeadService.php b/packages/Webkul/Lead/src/Services/LeadService.php
new file mode 100644
index 000000000..5c14072d6
--- /dev/null
+++ b/packages/Webkul/Lead/src/Services/LeadService.php
@@ -0,0 +1,52 @@
+parseFile($pdfPath)->getText()))) {
+ throw new Exception('PDF content is empty or could not be extracted.');
+ }
+
+ return self::processPromptWithAI($pdfText);
+ } catch (Exception $e) {
+ return ['error' => $e->getMessage()];
+ }
+ }
+
+ /**
+ * Send a request to the LLM API.
+ */
+ private static function processPromptWithAI($prompt)
+ {
+ $model = core()->getConfigData('general.magic_ai.settings.model');
+ $apiKey = core()->getConfigData('general.magic_ai.settings.api_key');
+
+ if (! $apiKey || ! $model) {
+ return ['error' => 'Missing API key or model configuration.'];
+ }
+
+ if (str_contains($model, Lead::GEMINI_MODEL)) {
+ return GeminiService::ask($prompt, $model, $apiKey);
+ } else {
+ return OpenAIService::ask($prompt, $model);
+ }
+ }
+}
diff --git a/packages/Webkul/Lead/src/Services/OpenAIService.php b/packages/Webkul/Lead/src/Services/OpenAIService.php
new file mode 100644
index 000000000..9c015e452
--- /dev/null
+++ b/packages/Webkul/Lead/src/Services/OpenAIService.php
@@ -0,0 +1,94 @@
+ 'application/json',
+ 'Authorization' => 'Bearer '.core()->getConfigData('general.magic_ai.settings.api_key'),
+ ])->post($url, $data);
+
+ if ($response->failed()) {
+ throw new Exception($response->json('error.message'));
+ }
+
+ return $response->json();
+ } catch (Exception $e) {
+ return ['error' => $e->getMessage()];
+ }
+ }
+
+ /**
+ * Prepare request data for AI.
+ */
+ private static function prepareLeadExtractionRequestData($model, $prompt)
+ {
+ return [
+ 'model' => $model,
+ 'messages' => [
+ [
+ 'role' => 'system',
+ 'content' => 'You are an AI assistant. You have to extract the data from the PDF file.
+ Example Output:
+ {
+ "status": 1,
+ "title": "Untitled Lead",
+ "person": {
+ "name": "Unknown",
+ "emails": {
+ "value": null,
+ "label": null
+ },
+ "contact_numbers": {
+ "value": null,
+ "label": null
+ }
+ },
+ "lead_pipeline_stage_id": null,
+ "lead_value": 0,
+ "source": "AI Extracted"
+ }
+ Note: Only return the output, Do not return or add any comments.',
+ ],
+ ['role' => 'user', 'content' => "PDF:\n$prompt"],
+ ],
+ ];
+ }
+
+ /**
+ * Get API Url for the model.
+ */
+ private static function getApiUrlForModel($model)
+ {
+ $apiDomain = core()->getConfigData('general.magic_ai.settings.api_domain');
+
+ $apiUrlMap = [
+ 'gpt-4o' => Lead::OPEN_AI_MODEL_URL,
+ 'gpt-4o-mini' => Lead::OPEN_AI_MODEL_URL,
+ 'llama3.2:latest' => "$apiDomain/v1/chat/completions",
+ 'deepseek-r1:8b' => "$apiDomain/v1/chat/completions",
+ ];
+
+ return $apiUrlMap[$model] ?? 'https://api.groq.com/openai/v1/chat/completions';
+ }
+}
diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore
index abdee4c37..0efcfa459 100644
--- a/storage/app/public/.gitignore
+++ b/storage/app/public/.gitignore
@@ -1,3 +1,5 @@
*
!data-transfer
+!lead-samples/
+!lead-samples/*
!.gitignore
diff --git a/storage/app/public/lead-samples/sample.pdf b/storage/app/public/lead-samples/sample.pdf
new file mode 100644
index 000000000..078a5df76
Binary files /dev/null and b/storage/app/public/lead-samples/sample.pdf differ