alfa-antiqye

This commit is contained in:
Husanjonazamov
2026-03-14 16:28:50 +05:00
commit 912e46b1de
11530 changed files with 1726493 additions and 0 deletions

3
.dcdignore Executable file
View File

@@ -0,0 +1,3 @@
# Note:-Write exact file or filepath
# Ignore node_modules directory
node_modules

15
.editorconfig Executable file
View File

@@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2

40
.env.example Executable file
View File

@@ -0,0 +1,40 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:1vaU3dfc+sWjx8TuDXzginRsEa2dp2SBL+Ujs6QCb5c=
APP_DEBUG=true
APP_MODE=live
APP_URL=http://localhost
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=6valley
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
AWS_ENDPOINT=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
NEXMO_KEY=
NEXMO_SECRET=
PURCHASE_CODE=
BUYER_USERNAME=
SOFTWARE_ID=MzE0NDg1OTc=
OPENAI_API_KEY=
OPENAI_ORGANIZATION=

5
.gitattributes vendored Executable file
View File

@@ -0,0 +1,5 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore

29
.gitignore vendored Executable file
View File

@@ -0,0 +1,29 @@
/node_modules
/public/hot
/public/storage
/public/themes
#/storage/*.key
/storage/app/*
/storage/framework/cache/*
/storage/framework/sessions/*
/storage/framework/views/*
/storage/framework/testing/*
/storage/logs/*
/vendor
.vscode
.idea
.idea/*
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
composer-setup.php
.DS_Store
*.map
style.css.map
style-extended.css.map
temp-data-folder/

30
.htaccess Executable file
View File

@@ -0,0 +1,30 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
# cors
# Header set Access-Control-Allow-Origin "*"
</IfModule>
#<IfModule mod_headers.c>
# Header set Access-Control-Allow-Origin "*"
#</IfModule>
# Hide a specific file
<Files .env>
Order allow,deny
Deny from all
</Files>

1
.php-cs-fixer.cache Normal file
View File

@@ -0,0 +1 @@
{"php":"8.3.6","version":"3.85.1","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_parentheses":true,"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true},"hashes":{"app\/Providers\/.null-ls_520872_AppServiceProvider.php":"a008b4ac16e84be1996eac068b4823b9","public\/.null-ls_423893_index.php":"621e7438258b65808822ea660bc931a2","public\/.null-ls_858247_index.php":"621e7438258b65808822ea660bc931a2","public\/.null-ls_100078_index.php":"d529c4b5567abe4fccf799c9fd7f7ed3","public\/.null-ls_394857_index.php":"2a77c0c77d66cba704bf8dff7c61dfb4","app\/Providers\/.null-ls_400742_AppServiceProvider.php":"871c39678636d28a3b988396e0587872","app\/Providers\/.null-ls_668694_AppServiceProvider.php":"a008b4ac16e84be1996eac068b4823b9","app\/Providers\/.null-ls_654994_AppServiceProvider.php":"9988dc184a49af880522c1c0dc9655e7","app\/Providers\/.null-ls_180764_AppServiceProvider.php":"a008b4ac16e84be1996eac068b4823b9","app\/Http\/Controllers\/Web\/.null-ls_966053_HomeController.php":"5564c402e67adccc30541afc6a537e1a","app\/Http\/Controllers\/Web\/.null-ls_476215_HomeController.php":"bae6316becf119d57bc54a77926134ca","app\/Http\/Controllers\/Web\/.null-ls_736819_HomeController.php":"bae6316becf119d57bc54a77926134ca","app\/Http\/Controllers\/Web\/.null-ls_917072_HomeController.php":"bae6316becf119d57bc54a77926134ca"}}

BIN
.rnd Executable file

Binary file not shown.

13
.styleci.yml Executable file
View File

@@ -0,0 +1,13 @@
php:
preset: laravel
disabled:
- unused_use
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View File

@@ -0,0 +1,115 @@
<?php
namespace Modules\AI\AIProviders;
use Illuminate\Support\Facades\Cache;
use Modules\AI\app\Exceptions\AIProviderException;
use Modules\AI\app\Exceptions\ImageValidationException;
use Modules\AI\app\Exceptions\UsageLimitException;
use Modules\AI\app\Exceptions\ValidationException;
use Modules\AI\app\Models\AISetting;
use Modules\AI\app\Services\AIResponseValidatorService;
use Modules\AI\app\Services\AIUsageManagerService;
use Modules\AI\app\Traits\AIModuleManager;
use Modules\AI\app\Utils\CurrentAuthUser;
class AIProviderManager
{
use AIModuleManager;
protected array $providers;
public function __construct(array $providers = [])
{
$this->providers = $providers;
}
/**
* @throws AIProviderException
*/
public function getAvailableProviderObject()
{
$activeAiProvider = $this->getActiveAIProvider();
foreach ($this->providers as $provider) {
if ($activeAiProvider->ai_name === $provider->getName()) {
$provider->setApiKey($activeAiProvider->api_key);
$provider->setOrganization($activeAiProvider->organization_id);
return $provider;
}
}
throw new AIProviderException('No matching AI provider found.');
}
/**
* @throws AIProviderException
*/
public function getActiveAIProvider(): AISetting
{
$provider = $this->getActiveAIProviderConfig();
if (!$provider) {
throw new AIProviderException('No active AI provider available at this moment.');
}
return $provider;
}
/**
* @throws ImageValidationException
* @throws AIProviderException
* @throws ValidationException
* @throws UsageLimitException
*/
public function generate(string $prompt, ?string $imageUrl = null, array $options = []): string
{
$providerObject = $this->getAvailableProviderObject();
$activeProvider = $this->getActiveAIProvider();
$aiUsage = new AIUsageManagerService();
$aiValidator = new AIResponseValidatorService();
$isAdmin = CurrentAuthUser::isAdmin();
$appMode = env('APP_MODE');
$section = $options['section'] ?? '';
if ($appMode === 'demo') {
$ip = request()->header('x-forwarded-for');
$cacheKey = 'demo_ip_usage_' . $ip;
$count = Cache::get($cacheKey, 0);
if ($count >= 10) {
throw new ValidationException("Demo limit reached: You can only generate 10 times.");
}
Cache::forever($cacheKey, $count + 1);
}
$aiSettingLog = $aiUsage->getOrCreateLog($activeProvider);
if (!$isAdmin) {
$aiUsage->checkUsageLimits($aiSettingLog, $activeProvider, $imageUrl, $section);
}
$response = $providerObject->generate($prompt, $imageUrl);
if (!$isAdmin) {
$aiUsage->incrementUsage($aiSettingLog, $imageUrl, $section);
}
$validatorMap = [
'product_name' => 'validateProductTitle',
'product_description' => 'validateProductDescription',
'generate_product_title_suggestion' => 'validateProductKeyword',
'general_setup' => 'validateProductGeneralSetup',
'pricing_and_others' => 'validateProductPricingAndOthers',
'variation_setup' => 'validateProductVariationSetup',
'seo_section' => 'validateProductSeoContent',
'generate_title_from_image' => 'validateImageResponse',
'blog_title' => 'validateBlogTitle',
'blog_description' => 'validateBlogDescription',
'blog_seo_section' => 'validateBlogSeoContent',
'blog_title_suggestion' => 'validateBlogKeyword',
'generate_blog_title_from_image' => 'validateBlogImageResponse',
];
if ($section && isset($validatorMap[$section])) {
$aiValidator->{$validatorMap[$section]}($response, $options['context'] ?? null);
}
return $response;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Modules\AI\AIProviders;
class ClaudeProvider
{
public function getName(): string
{
return 'Claude';
}
public function generate(string $prompt, ?string $imageUrl = null, array $options = []): string
{
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Modules\AI\AIProviders;
use Modules\AI\app\Contracts\AIProviderInterface;
use OpenAI;
class OpenAIProvider implements AIProviderInterface
{
protected string $apiKey;
protected ?string $organization;
public function getName(): string
{
return 'OpenAI';
}
public function setApiKey($apikey): void
{
$this->apiKey = $apikey;
}
public function setOrganization($organization): void
{
$this->organization = $organization;
}
public function generate(string $prompt, ?string $imageUrl = null, array $options = []): string
{
$client = OpenAI::client($this->apiKey, $this->organization);
$content = [['type' => 'text', 'text' => $prompt]];
if (!empty($imageUrl)) {
$content[] = [
'type' => 'image_url',
'image_url' => ['url' => $imageUrl],
];
}
$response = $client->chat()->create([
'model' => 'gpt-4o',
'messages' => [
[
'role' => 'user',
'content' => $content,
],
],
'temperature' => 0.3,
]);
return $response->choices[0]->message->content;
}
}

10
Modules/AI/Addon/info.php Executable file
View File

@@ -0,0 +1,10 @@
<?php
return [
'software_id' => '12345678',
'name' => 'AI',
'module_name' => 'AI',
'is_published' => 1,
'purchase_code' => '123456',
'username' => 'user',
];

View File

@@ -0,0 +1,10 @@
<?php
namespace Modules\AI\app\Contracts;
interface AIProviderInterface
{
public function generate(string $prompt, ?string $imageUrl = null, array $options = []): string;
public function getName(): string;
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Modules\AI\app\Contracts;
interface PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string;
public function getType(): string;
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Modules\AI\app\Exceptions;
class AIProviderException extends ApiException
{
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\AI\app\Exceptions;
use Exception;
class ApiException extends Exception
{
protected int $status;
public function __construct(string $message = "", int $status = 403)
{
parent::__construct($message, $status);
$this->status = $status;
}
public function getStatusCode(): int
{
return $this->status;
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Modules\AI\app\Exceptions;
class ImageValidationException extends ApiException
{
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Modules\AI\app\Exceptions;
class UsageLimitException extends ApiException
{
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Modules\AI\app\Exceptions;
class ValidationException extends ApiException
{
}

View File

View File

@@ -0,0 +1,40 @@
<?php
namespace Modules\AI\app\Http\Controllers;
use App\Http\Controllers\Controller;
class AIController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('ai::index');
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('ai::create');
}
/**
* Show the specified resource.
*/
public function show($id)
{
return view('ai::show', ['id' => $id]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit($id)
{
return view('ai::edit', ['id' => $id]);
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace Modules\AI\app\Http\Controllers\API\V3;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\AI\app\Exceptions\ApiException;
use Modules\AI\app\Http\Requests\ApiRequests\GeneralSetupRequest;
use Modules\AI\app\Http\Requests\ApiRequests\GenerateProductTitleSuggestionRequest;
use Modules\AI\app\Http\Requests\ApiRequests\GenerateTitleFromImageRequest;
use Modules\AI\app\Http\Requests\ApiRequests\ProductDescriptionAutoFillRequest;
use Modules\AI\app\Http\Requests\ApiRequests\ProductPricingRequest;
use Modules\AI\app\Http\Requests\ApiRequests\ProductSeoSectionAutoFillRequest;
use Modules\AI\app\Http\Requests\ApiRequests\ProductTitleAutoFillRequest;
use Modules\AI\app\Http\Requests\ApiRequests\ProductVariationSetupAutoFillRequest;
use Modules\AI\app\Response\ProductResponse;
use Modules\AI\app\Services\AIContentGeneratorService;
use Modules\AI\app\Services\AIUsageManagerService;
class AIProductController extends Controller
{
protected AIContentGeneratorService $aiContentGeneratorService;
protected ProductResponse $productResponse;
protected AIUsageManagerService $aIUsageManagerService;
public function __construct(AIContentGeneratorService $AIContentGeneratorService, ProductResponse $productResponse, AIUsageManagerService $aiUsageManagerService)
{
parent::__construct();
$this->aiContentGeneratorService = $AIContentGeneratorService;
$this->productResponse = $productResponse;
$this->aIUsageManagerService = $aiUsageManagerService;
}
public function titleAutoFill(ProductTitleAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "product_name", context: $request['name'], langCode: $request['langCode'] ?? 'en');
return $this->successResponse(data: $result, message: 'Title generated successfully', status: 200);
} catch (ApiException $e) {
return $this->errorResponse(message: $e->getMessage(), status: $e->getStatusCode());
}catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function descriptionAutoFill(ProductDescriptionAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "product_description", context: $request['name'], langCode: $request['langCode']);
return $this->successResponse(data: $result, status: 200);
} catch (ApiException $e) {
return $this->errorResponse(message: $e->getMessage(), status: $e->getStatusCode());
}catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generalSetupAutoFill(GeneralSetupRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "general_setup", context: $request['name'], description: $request['description']);
$data = $this->productResponse->productGeneralSetupAutoFillFormat(result: $result);
return $this->successResponse(data: $data, status: 200);
} catch (ApiException $e) {
return $this->errorResponse(message: $e->getMessage(), status: $e->getStatusCode());
}catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function pricingAndOthersAutoFill(ProductPricingRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "pricing_and_others", context: $request['name'], description: $request['description']);
$data = $this->productResponse->productPriceAndOthersAutoFill(result: $result);
return $this->successResponse(data: $data, status: 200);
}catch (ApiException $e) {
return $this->errorResponse(message: $e->getMessage(), status: $e->getStatusCode());
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function productVariationSetupAutoFill(ProductVariationSetupAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "variation_setup", context: $request['name'], description: $request['description']);
$response = $this->productResponse->variationSetupAutoFill(result: $result);
return $this->successResponse(data: $response['data'], status: 200);
} catch (ApiException $e) {
return $this->errorResponse(message: $e->getMessage(), status: $e->getStatusCode());
}catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function productSeoSectionAutoFill(ProductSeoSectionAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "seo_section", context: $request['name'], description: $request['description']);
$response = $this->productResponse->productSeoAutoFill(result: $result);
return $this->successResponse(data: $response, status: 200);
}catch (ApiException $e) {
return $this->errorResponse(message: $e->getMessage(), status: $e->getStatusCode());
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generateProductTitleSuggestion(GenerateProductTitleSuggestionRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "generate_product_title_suggestion", context: $request['keywords'], description: $request['description']);
$response = $this->productResponse->generateTitleSuggestions(result: $result);
return $this->successResponse(data: $response, status: 200);
} catch (ApiException $e) {
return $this->errorResponse(message: $e->getMessage(), status: $e->getStatusCode());
}catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generateTitleFromImages(GenerateTitleFromImageRequest $request): JsonResponse
{
try {
$imageFile = $request->file('image');
$imagePath = $this->aiContentGeneratorService->getAnalyizeImagePath($imageFile);
$result = $this->aiContentGeneratorService->generateContent(contentType: "generate_title_from_image", imageUrl: $imagePath['imageFullPath']);
$this->aiContentGeneratorService->deleteAiImage($imagePath['imageName'], 'product');
return $this->successResponse(data: $result, status: 200);
}catch (ApiException $e) {
return $this->errorResponse(message: $e->getMessage(), status: $e->getStatusCode());
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generateLimitCheck(){
try{
$result = $this->aIUsageManagerService->getGenerateRemainingCount();
return $this->successResponse(data: $result);
}catch (ApiException $e) {
return $this->errorResponse(message: $e->getMessage(), status: $e->getStatusCode());
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace Modules\AI\app\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\AI\app\Http\Requests\GeneralSetupRequest;
use Modules\AI\app\Http\Requests\GenerateProductTitleSuggestionRequest;
use Modules\AI\app\Http\Requests\GenerateTitleFromImageRequest;
use Modules\AI\app\Http\Requests\ProductDescriptionAutoFillRequest;
use Modules\AI\app\Http\Requests\ProductPricingRequest;
use Modules\AI\app\Http\Requests\ProductSeoSectionAutoFillRequest;
use Modules\AI\app\Http\Requests\ProductTitleAutoFillRequest;
use Modules\AI\app\Http\Requests\ProductVariationSetupAutoFillRequest;
use Modules\AI\app\Response\ProductResponse;
use Modules\AI\app\Services\AIContentGeneratorService;
class AIProductController extends Controller
{
protected AIContentGeneratorService $aiContentGeneratorService;
protected ProductResponse $productResponse;
public function __construct(AIContentGeneratorService $AIContentGeneratorService, ProductResponse $productResponse)
{
parent::__construct();
$this->aiContentGeneratorService = $AIContentGeneratorService;
$this->productResponse = $productResponse;
}
public function titleAutoFill(ProductTitleAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "product_name", context: $request['name'], langCode: $request['langCode']);
return $this->successResponse(data: $result, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function descriptionAutoFill(ProductDescriptionAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "product_description", context: $request['name'], langCode: $request['langCode']);
return $this->successResponse(data: $result, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generalSetupAutoFill(GeneralSetupRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "general_setup", context: $request['name'], description: $request['description']);
$data = $this->productResponse->productGeneralSetupAutoFillFormat(result: $result);
return $this->successResponse(data: $data, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function pricingAndOthersAutoFill(ProductPricingRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "pricing_and_others", context: $request['name'], description: $request['description']);
$data = $this->productResponse->productPriceAndOthersAutoFill(result: $result);
return $this->successResponse(data: $data, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function productVariationSetupAutoFill(ProductVariationSetupAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "variation_setup", context: $request['name'], description: $request['description']);
$response = $this->productResponse->variationSetupAutoFill(result: $result);
return $this->successResponse(data: $response['data'], status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function productSeoSectionAutoFill(ProductSeoSectionAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "seo_section", context: $request['name'], description: $request['description']);
$response = $this->productResponse->productSeoAutoFill(result: $result);
return $this->successResponse(data: $response, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generateProductTitleSuggestion(GenerateProductTitleSuggestionRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "generate_product_title_suggestion", context: $request['keywords'], description: $request['description']);
$response = $this->productResponse->generateTitleSuggestions(result: $result);
return $this->successResponse(data: $response, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generateTitleFromImages(GenerateTitleFromImageRequest $request): JsonResponse
{
try {
$imageFile = $request->file('image');
$imagePath = $this->aiContentGeneratorService->getAnalyizeImagePath($imageFile);
$result = $this->aiContentGeneratorService->generateContent(contentType: "generate_title_from_image", imageUrl: $imagePath['imageFullPath']);
$this->aiContentGeneratorService->deleteAiImage($imagePath['imageName'],'product');
return $this->successResponse(data: $result, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Modules\AI\app\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Devrabiul\ToastMagic\Facades\ToastMagic;
use Exception;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Cache;
use Modules\AI\app\Http\Requests\AISettingRequest;
use Modules\AI\app\Http\Requests\AIVendorUsagesLimitRequest;
use Modules\AI\app\Models\AISetting;
class AISettingController extends Controller
{
public function index()
{
$AiSetting = AISetting::first();
return view('ai::admin-views.ai-setting.index', compact('AiSetting'));
}
public function getVendorUsagesLimitView()
{
$AiSetting = AISetting::first();
return view('ai::admin-views.ai-setting.vendors-usage-limits', compact('AiSetting'));
}
public function store(AISettingRequest $request): RedirectResponse
{
Cache::forget('active_ai_provider');
self::addFirstAISetting();
try {
$AiSetting = AISetting::first();
$AiSetting->update([
'api_key' => $request['api_key'],
'organization_id' => $request['organization_id'],
'status' => !empty($request['api_key']) && !empty($request['organization_id']) && $request['status'] == 1 ? 1 : 0,
]);
ToastMagic::success(translate('AI_configuration_saved_successfully'));
} catch (Exception $exception) {
ToastMagic::error(translate('Failed_to_save_AI_configuration'));
}
return redirect()->back();
}
public function updateVendorUsagesLimit(AIVendorUsagesLimitRequest $request): RedirectResponse
{
Cache::forget('active_ai_provider');
self::addFirstAISetting();
try {
$AiSetting = AISetting::first();
$AiSetting->update([
'image_upload_limit' => $request['image_upload_limit'] ?? 0,
'generate_limit' => $request['generate_limit'] ?? 0
]);
ToastMagic::success(translate('AI_configuration_saved_successfully'));
} catch (Exception $exception) {
ToastMagic::error(translate('Failed_to_save_AI_configuration'));
}
return redirect()->back();
}
public function addFirstAISetting(): void
{
Cache::forget('active_ai_provider');
if (!AISetting::first()) {
AISetting::create([
'ai_name' => 'OpenAI',
'api_key' => '',
'organization_id' => '',
'image_upload_limit' => 0,
'generate_limit' => 0,
'status' => 0,
]);
}
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Modules\AI\app\Http\Controllers\Admin\Blog;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\AI\app\Http\Requests\Blog\BlogDescriptionRequest;
use Modules\AI\app\Http\Requests\Blog\BlogSeoSectionRequest;
use Modules\AI\app\Http\Requests\Blog\BlogTitleRequest;
use Modules\AI\app\Http\Requests\Blog\BlogTitleSuggestionRequest;
use Modules\AI\app\Http\Requests\Blog\GenerateBlogTitleFromImageRequest;
use Modules\AI\app\Http\Requests\GenerateTitleFromImageRequest;
use Modules\AI\app\Http\Requests\ProductSeoSectionAutoFillRequest;
use Modules\AI\app\Services\AIContentGeneratorService;
class AIBlogController extends Controller
{
protected AIContentGeneratorService $aiContentGeneratorService;
public function __construct(AIContentGeneratorService $AIContentGeneratorService)
{
parent::__construct();
$this->aiContentGeneratorService = $AIContentGeneratorService;
}
public function titleAutoFill(BlogTitleRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "blog_title", context: $request['title'], langCode: $request['langCode']);
return $this->successResponse(data: json_decode($result,true), status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function descriptionAutoFill(BlogDescriptionRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "blog_description", context: $request['title'], langCode: $request['langCode']);
return $this->successResponse(data: $result, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function seoSectionAutoFill(BlogSeoSectionRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "blog_seo_section", context: $request['title'], description: $request['description']);
return $this->successResponse(data: json_decode($result,true), status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generateBlogTitleSuggestion(BlogTitleSuggestionRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "blog_title_suggestion", context: $request['keywords'], description: $request['description']);
$response = json_decode($result,true);
return $this->successResponse(data: $response, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generateBlogTitleFromImages(GenerateBlogTitleFromImageRequest $request): JsonResponse
{
try {
$imageFile = $request->file('image');
$imagePath = $this->aiContentGeneratorService->getBlogImagePath($imageFile);
$result = $this->aiContentGeneratorService->generateContent(contentType: "generate_blog_title_from_image", description: $request['description'], imageUrl: $imagePath['imageFullPath']);
$this->aiContentGeneratorService->deleteAiImage($imagePath['imageName'],'blog');
return $this->successResponse(data: $result, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace Modules\AI\app\Http\Controllers\Vendor;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\AI\app\Http\Requests\GeneralSetupRequest;
use Modules\AI\app\Http\Requests\GenerateProductTitleSuggestionRequest;
use Modules\AI\app\Http\Requests\GenerateTitleFromImageRequest;
use Modules\AI\app\Http\Requests\ProductDescriptionAutoFillRequest;
use Modules\AI\app\Http\Requests\ProductPricingRequest;
use Modules\AI\app\Http\Requests\ProductSeoSectionAutoFillRequest;
use Modules\AI\app\Http\Requests\ProductTitleAutoFillRequest;
use Modules\AI\app\Http\Requests\ProductVariationSetupAutoFillRequest;
use Modules\AI\app\Response\ProductResponse;
use Modules\AI\app\Services\AIContentGeneratorService;
use Modules\AI\app\Services\AIUsageManagerService;
class AIProductController extends Controller
{
protected AIContentGeneratorService $aiContentGeneratorService;
protected ProductResponse $productResponse;
protected AIUsageManagerService $AIUsageManagerService;
public function __construct(AIContentGeneratorService $AIContentGeneratorService, ProductResponse $productResponse, AIUsageManagerService $AIUsageManagerService)
{
parent::__construct();
$this->aiContentGeneratorService = $AIContentGeneratorService;
$this->productResponse = $productResponse;
$this->AIUsageManagerService = $AIUsageManagerService;
}
public function titleAutoFill(ProductTitleAutoFillRequest $request): JsonResponse
{
try {
$content = $this->aiContentGeneratorService->generateContent(contentType: "product_name", context: $request['name'], langCode: $request['langCode']);
$remainingCount = $this->AIUsageManagerService->getGenerateRemainingCount();
$result = ['data' => $content, 'remaining_count' => $remainingCount];
return $this->successResponse(data: $result, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function descriptionAutoFill(ProductDescriptionAutoFillRequest $request): JsonResponse
{
try {
$content = $this->aiContentGeneratorService->generateContent(contentType: "product_description", context: $request['name'], langCode: $request['langCode']);
$remainingCount = $this->AIUsageManagerService->getGenerateRemainingCount();
$result = ['data' => $content, 'remaining_count' => $remainingCount,];
return $this->successResponse(data: $result, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generalSetupAutoFill(GeneralSetupRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "general_setup", context: $request['name'], description: $request['description']);
$data = $this->productResponse->productGeneralSetupAutoFillFormat(result: $result);
$remainingCount = $this->AIUsageManagerService->getGenerateRemainingCount();
$data = ['data' => $data, 'remaining_count' => $remainingCount];
return $this->successResponse(data: $data, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function pricingAndOthersAutoFill(ProductPricingRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "pricing_and_others", context: $request['name'], description: $request['description']);
$data = $this->productResponse->productPriceAndOthersAutoFill(result: $result);
$remainingCount = $this->AIUsageManagerService->getGenerateRemainingCount();
$data = ['data' => $data, 'remaining_count' => $remainingCount];
return $this->successResponse(data: $data, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function productVariationSetupAutoFill(ProductVariationSetupAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "variation_setup", context: $request['name'], description: $request['description']);
$response = $this->productResponse->variationSetupAutoFill(result: $result);
$remainingCount = $this->AIUsageManagerService->getGenerateRemainingCount();
$data = ['data' => $response['data'], 'remaining_count' => $remainingCount];
return $this->successResponse(data: $data, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function productSeoSectionAutoFill(ProductSeoSectionAutoFillRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "seo_section", context: $request['name'], description: $request['description']);
$response = $this->productResponse->productSeoAutoFill(result: $result);
$remainingCount = $this->AIUsageManagerService->getGenerateRemainingCount();
$data = ['data' => $response, 'remaining_count' => $remainingCount];
return $this->successResponse(data: $data, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generateProductTitleSuggestion(GenerateProductTitleSuggestionRequest $request): JsonResponse
{
try {
$result = $this->aiContentGeneratorService->generateContent(contentType: "generate_product_title_suggestion", context: $request['keywords'], description: $request['description']);
$response = $this->productResponse->generateTitleSuggestions(result: $result);
$remainingCount = $this->AIUsageManagerService->getGenerateRemainingCount();
$data = ['data' => $response, 'remaining_count' => $remainingCount];
return $this->successResponse(data: $data, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
public function generateTitleFromImages(GenerateTitleFromImageRequest $request): JsonResponse
{
try {
$imageFile = $request->file('image');
$imagePath = $this->aiContentGeneratorService->getAnalyizeImagePath($imageFile);
$result = $this->aiContentGeneratorService->generateContent(contentType: "generate_title_from_image", imageUrl: $imagePath['imageFullPath']);
$this->aiContentGeneratorService->deleteAiImage($imagePath['imageName'],'product');
$remainingCount = $this->AIUsageManagerService->getGenerateRemainingCount();
$data = ['data' => $result, 'remaining_count' => $remainingCount];
return $this->successResponse(data: $data, status: 200);
} catch (Exception $e) {
$status = $e->getCode() > 0 ? $e->getCode() : 500;
return $this->errorResponse(message: $e->getMessage(), status: $status);
}
}
}

View File

View File

View File

@@ -0,0 +1,36 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class AISettingRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'api_key' => ['nullable', 'required_if:status,1', 'string'],
'organization_id' => ['nullable', 'required_if:status,1', 'string'],
];
}
public function messages(): array
{
return [
'api_key.required_if' => translate('The API Key is required when status is enabled.'),
'organization_id.required_if' => translate('The Organization ID is required when status is enabled.'),
];
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class AIVendorUsagesLimitRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'image_upload_limit' => ['nullable', 'integer', 'min:0'],
'generate_limit' => ['nullable', 'integer', 'min:0'],
];
}
public function messages(): array
{
return [
'image_upload_limit.integer' => translate('The image upload limit must be a valid number.'),
'image_upload_limit.min' => translate('The image upload limit must be at least 0.'),
'generate_limit.integer' => translate('The generate limit must be a valid number.'),
'generate_limit.min' => translate('The generate limit must be at least 0.'),
];
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Modules\AI\app\Http\Requests\ApiRequests;
use App\Traits\ResponseHandler;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class GeneralSetupRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'required|string',
];
}
public function messages(): array
{
return [
'name.required' => translate('product_name_is_required_to_generate_general_setup'),
'description.required' => translate('product_description_is_required_to_generate_general_setup'),
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)], 403));
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Modules\AI\app\Http\Requests\ApiRequests;
use App\Traits\ResponseHandler;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class GenerateProductTitleSuggestionRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'keywords' => 'required|string|max:255',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)], 403));
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Modules\AI\app\Http\Requests\ApiRequests;
use App\Traits\ResponseHandler;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class GenerateTitleFromImageRequest extends FormRequest
{
use ResponseHandler;
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'image' => getRulesStringForImageValidation(
rules: ['required', 'image'],
skipMimes: ['.svg','.webp'],
maxSize: getFileUploadMaxSize(unit: 'kb'),
isDisallowed: true
),
];
}
public function messages(): array
{
return [
'image.required' => translate('Image is required for analysis.'),
'image.image' => translate('The uploaded file must be an image.'),
'image.mimes' => translate('The image must be a file of type:') . getFileUploadFormats(skip: ['.svg', '.webp'], asMessage: true),
'image.max' => translate('Image size must not exceed '). getFileUploadMaxSize() . ' MB.',
];
}
/**
* Determine if the user is authorized to make this request.
*/
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)], 403));
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Modules\AI\app\Http\Requests\ApiRequests;
use App\Traits\ResponseHandler;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class ProductDescriptionAutoFillRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'langCode' => 'required|string|max:20',
];
}
public function messages(): array
{
return ['name.required' => translate('product_name_is_required_to_generate_description')];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)], 403));
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Modules\AI\app\Http\Requests\ApiRequests;
use App\Traits\ResponseHandler;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class ProductPricingRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string',
];
}
public function messages(): array
{
return ['name.required' => translate('product_name_and_description_are_required_to_generate_pricing')];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)], 403));
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Modules\AI\app\Http\Requests\ApiRequests;
use App\Traits\ResponseHandler;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class ProductSeoSectionAutoFillRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string',
];
}
public function messages(): array
{
return [
'name.required' => translate('product_name_is_required_to_generate_seo_information'),
'description.required' => translate('product_description_is_required_to_generate_seo_information'),
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)], 403));
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Modules\AI\app\Http\Requests\ApiRequests;
use App\Traits\ResponseHandler;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class ProductTitleAutoFillRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'langCode' => 'required|string|max:20',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function messages(): array
{
return [
'name.required' => translate('product_name_is_required_to_generate_product_name'),
];
}
public function authorize(): bool
{
return true;
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)], 403));
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Modules\AI\app\Http\Requests\ApiRequests;
use App\Traits\ResponseHandler;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class ProductVariationSetupAutoFillRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'required',
];
}
public function messages(): array
{
return [
'name.required' => translate('product_name_is_required_to_generate_product_variation'),
'description.required' => translate('product_description_is_required_to_generate_product_variation'),
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)], 403));
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Modules\AI\app\Http\Requests\Blog;
use App\Traits\ResponseHandler;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class BlogDescriptionRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'langCode' => 'nullable|string|max:20',
];
}
public function messages(): array{
return ['title.required' => translate('blog_title_is_required_to_generate_description')];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)]));
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Modules\AI\app\Http\Requests\Blog;
use App\Traits\ResponseHandler;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class BlogSeoSectionRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'description' => 'nullable|string',
];
}
public function messages(): array{
return [
'name.required' => translate('blog_title_is_required_to_generate_seo_information'),
'description.required' => translate('blog_description_is_required_to_generate_seo_information'),
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)]));
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Modules\AI\app\Http\Requests\Blog;
use App\Traits\ResponseHandler;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class BlogTitleRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'langCode' => 'nullable|string|max:20',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function messages(): array{
return [
'title.required' => translate('blog_title_is_required_to_generate_blog_title'),
];
}
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)]));
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\AI\app\Http\Requests\Blog;
use App\Traits\ResponseHandler;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class BlogTitleSuggestionRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'keywords' => 'required|string|max:255',
];
}
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)]));
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Modules\AI\app\Http\Requests\Blog;
use App\Traits\ResponseHandler;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class GenerateBlogTitleFromImageRequest extends FormRequest
{
use ResponseHandler;
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'description' => 'nullable|string',
'image' => 'required|image|'. getFileUploadFormats(skip: '.svg', asRule: 'true'). '|max:'.getFileUploadMaxSize(unit: 'kb'),
];
}
public function messages(): array{
return [
'description.string' => 'Description must be a string.',
'image.image' => translate('The uploaded file must be an image.'),
'image.mimes' => translate('Only'.getFileUploadFormats(skip: '.svg', asMessage: 'true'). 'are_allowed'),
'image.max' => translate('Image size must not exceed 1MB.'),
];
}
/**
* Determine if the user is authorized to make this request.
*/
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $this->errorProcessor($validator)]));
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class GeneralSetupRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'required|string',
];
}
public function messages(): array{
return [
'name.required' => translate('product_name_is_required_to_generate_general_setup'),
'description.required' => translate('product_description_is_required_to_generate_general_setup'),
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class GenerateProductTitleSuggestionRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'keywords' => 'required|string|max:255',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class GenerateTitleFromImageRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'image' => 'required|image|mimes:jpeg,png,jpg,gif|max:1024',
];
}
public function messages(): array{
return [
'image.required' => translate('Image is required for analysis.'),
'image.image' => translate('The uploaded file must be an image.'),
'image.mimes' => translate('Only JPEG, PNG, JPG, and GIF images are allowed.'),
'image.max' => translate('Image size must not exceed 1MB.'),
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ProductDescriptionAutoFillRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'langCode' => 'nullable|string|max:20',
];
}
public function messages(): array{
return ['name.required' => translate('product_name_is_required_to_generate_description')];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ProductPricingRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string',
];
}
public function messages(): array{
return ['name.required' => translate('product_name_and_description_are_required_to_generate_pricing')];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ProductSeoSectionAutoFillRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'nullable|string',
];
}
public function messages(): array{
return [
'name.required' => translate('product_name_is_required_to_generate_seo_information'),
'description.required' => translate('product_description_is_required_to_generate_seo_information'),
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ProductTitleAutoFillRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'langCode' => 'nullable|string|max:20',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function messages(): array{
return [
'name.required' => translate('product_name_is_required_to_generate_product_name'),
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\AI\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ProductVariationSetupAutoFillRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'description' => 'required',
];
}
public function messages(): array{
return [
'name.required' => translate('product_name_is_required_to_generate_product_variation'),
'description.required' => translate('product_description_is_required_to_generate_product_variation'),
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
}

0
Modules/AI/app/Models/.gitkeep Executable file
View File

View File

@@ -0,0 +1,50 @@
<?php
namespace Modules\AI\app\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* Class AISetting
*
* @property int $id
* @property string $ai_name
* @property string|null $base_url
* @property string|null $api_key
* @property string|null $organization_id
* @property int|null $generate_limit
* @property int|null $image_upload_limit
* @property array|string|null $settings
* @property int $status
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*/
class AISetting extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'ai_settings';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'ai_name',
'base_url',
'api_key',
'organization_id',
'generate_limit',
'image_upload_limit',
'settings',
'status',
];
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Modules\AI\app\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* Class AISettingLog
*
* @property int $id
* @property int $seller_id
* @property int $total_generated_count
* @property int $total_image_generated_count
* @property int $limit_at_time
* @property string $action
* @property array|null $section_usage
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*/
class AISettingLog extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'ai_setting_logs';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'seller_id',
'total_generated_count',
'total_image_generated_count',
'limit_at_time',
'action',
'section_usage',
];
/**
* The attributes that should be cast to native types.
*
* @var array<string, string>
*/
protected $casts = [
'section_usage' => 'array',
];
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Modules\AI\app\PromptTemplates\Blog;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class BlogSeoSectionTemplate implements PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$blogInfo = $description
? "Blog Title: \"{$context}\". Description: \"" . addslashes($description) . "\"."
: "Blog Title: \"{$context}\".";
return <<<PROMPT
You are an expert SEO content writer and technical SEO specialist.
Given the following blog content:
{$blogInfo}
Generate ONLY a JSON object with the following SEO meta fields:
{
"meta_title": "", // Concise SEO title (max 100 chars)
"meta_description": "", // Compelling meta description (max 160 chars)
"meta_index": "index", // Either "index" or "noindex"
"meta_no_follow": 0, // 0 or 1 (boolean)
"meta_no_image_index": 0, // 0 or 1
"meta_no_archive": 0, // 0 or 1
"meta_no_snippet": 0, // 0 or 1
"meta_max_snippet": 0, // 0 or 1
"meta_max_snippet_value": -1, // Number, -1 means no limit
"meta_max_video_preview": 0, // 0 or 1
"meta_max_video_preview_value": -1,// Number, -1 means no limit
"meta_max_image_preview": 0, // 0 or 1
"meta_max_image_preview_value": "large" // One of "large", "medium", or "small"
}
Instructions:
- Optimize meta_title and meta_description for blog content.
- Keep character limits.
- Return ONLY the pure JSON text.
- Do NOT include markdown, code fences, or triple backticks or ```html ``` or ```json ```.
- If the input text is meaningless or empty, respond only with "INVALID_INPUT"
- Do not return generic explanations, fallback messages, or apologies.
PROMPT;
}
public function getType(): string
{
return "blog_seo_section";
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Modules\AI\app\PromptTemplates\Blog;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class DescriptionTemplate implements PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$langCode = $langCode ? strtoupper($langCode) : 'EN';
$title = $context && trim($context) !== '' ? $context : 'a blog post title';
return <<<PROMPT
You are an expert SEO content strategist and professional copywriter.
Your task: Using the blog title "{$title}", generate a **detailed, SEO-optimized HTML blog introduction section** that could appear at the top of the article.
CRITICAL LANGUAGE RULES:
- The output must be 100% in language code "{$langCode}" — this is mandatory.
- If the original title is not in language code "{$langCode}", fully translate it into language code "{$langCode}" while keeping the meaning.
- Do not mix languages; use only language code "{$langCode}" characters and words.
- Adapt tone and examples to be natural for {$langCode} readers.
CONTENT REQUIREMENTS:
- Include an <h1> main title and at least one <h2> subheading.
- Write a minimum of 250400 words of SEO-focused, engaging, and informative content.
- Begin with a strong introduction paragraph summarizing the importance of the topic.
- Add 23 follow-up paragraphs expanding on key ideas or benefits.
- Highlight important SEO keywords or phrases using <b> tags.
- Include an ordered or unordered list (<ol>/<ul>) with <li><span> elements summarizing main takeaways, strategies, or benefits.
- The tone should be authoritative, helpful, and motivating.
FORMATTING RULES:
- You MUST output raw HTML only.
- NEVER include markdown syntax, backticks, or ```html fences.
- The response must begin directly with an <h1> tag.
- Avoid empty <p> tags or blank lines.
- Return ONLY the HTML content — no comments, code blocks, or explanations.
IMPORTANT:
- If the input title is meaningless or empty, respond only with "INVALID_INPUT".
- Otherwise, generate the HTML directly.
PROMPT;
}
public function getType(): string
{
return "blog_description";
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Modules\AI\app\PromptTemplates\Blog;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class GenerateBlogTitleFromImageTemplate implements PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$langCode ??= 'en';
$langCode = strtoupper($langCode);
$descriptionInstruction = !empty($description)
? "Additionally, consider the user's description: \"{$description}\" and incorporate it only if it adds clarity or relevance."
: "Ignore user description if irrelevant or missing.";
return <<<PROMPT
You are an advanced SEO content strategist, copywriter, and image recognition analyst.
Analyze the uploaded blog image provided by the user.
{$descriptionInstruction}
Your task:
Generate a clean, concise, and professional blog title closely related to the product or topic shown in the image — adjusted to match any meaningful user-provided context.
CRITICAL INSTRUCTION:
- The output must be 100% in {$langCode}.
- Do not include subjective phrases like “high quality”, “best”, or overly emotional language.
- Keep it short (3570 characters), simple, and optimized for online listings.
- Only return the title (plain text, no quotes).
IMPORTANT:
- If the image is irrelevant, unidentifiable, or meaningless → return only: INVALID_INPUT
- Do NOT apologize or explain anything in the response.
PROMPT;
}
public function getType(): string
{
return 'generate_blog_title_from_image';
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Modules\AI\app\PromptTemplates\Blog;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class TitleSuggestionTemplate implements PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$langCode = strtoupper($langCode);
$keywordsText = $context;
if (is_array($context)) {
$keywordsText = implode(' ', $context);
}
return <<<PROMPT
You are an expert SEO content strategist and professional copywriter.
Using the keywords "{$keywordsText}", generate 4 professional, clean, and concise blog titles for online stores.
CRITICAL INSTRUCTIONS:
- The output must be 100% in {$langCode}.
- Titles must use the keywords naturally.
- Keep them short (3570 characters), clear, and ready for listings.
- Return exactly 4 titles in **plain JSON** format as shown below (do not include ```json``` or any extra markdown):
{
"titles": [
"Title 1",
"Title 2",
"Title 3",
"Title 4"
]
}
Do not include any extra explanation, only return the JSON.
IMPORTANT:
- If the keywords are not relevant or is meaningless, respond with only the word "INVALID_INPUT".
- Do not return generic explanations, fallback messages, or apologies.
PROMPT;
}
public function getType(): string
{
return 'blog_title_suggestion';
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Modules\AI\app\PromptTemplates\Blog;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class TitleTemplate implements PromptTemplateInterface
{
public function build(mixed $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$langCode = strtoupper($langCode);
$topic = $context ?? 'a blog post';
return <<<PROMPT
You are an expert SEO content strategist and professional copywriter.
Rewrite the blog title "{$context}", as a clean, concise, creative, engaging, and SEO-optimized blog post title.
REQUIREMENTS:
- The output must be 100% in language code "{$langCode}" — this is mandatory.
- If the original title is not in language code "{$langCode}", fully translate it into language code "{$langCode}" while keeping the meaning.
- Do not mix languages; use only language code "{$langCode}" characters and words.
- The title must be directly relevant to the given topic.
- It should be clear, compelling, and between **5070 characters**.
- Focus on readability, emotional appeal, and search intent.
- Return the result in **plain JSON format** as shown below — no markdown, code blocks, or explanations.
Example format:
{
"title": Your SEO-Optimized Blog Title Here
}
IMPORTANT:
- If the input topic is unclear, meaningless, or unsuitable for a professional blog title, respond **only** with: "INVALID_INPUT".
- Do not include any additional commentary, reasoning, or filler text.
PROMPT;
}
public function getType(): string
{
return "blog_title";
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Modules\AI\app\PromptTemplates;
class CategoryTemplate
{
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Modules\AI\app\PromptTemplates;
use Modules\AI\app\Contracts\PromptTemplateInterface;
use Modules\AI\app\Services\ProductResourceService;
class GeneralSetupTemplates implements PromptTemplateInterface
{
protected ProductResourceService $productResource;
public function __construct()
{
$this->productResource = new ProductResourceService();
}
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$resource = $this->productResource->productGeneralSetupData();
$categories = $resource['categories'];
$subCategories = $resource['sub_categories'];
$subSubCategories = $resource['sub_sub_categories'];
$brands = $resource['brands'];
$units = $resource['units'];
$productTypes = $resource['product_types'];
$deliveryType = implode("', '",$resource['delivery_types']);
$categories = implode("', '", array_keys($categories));
$subCategories = implode("', '", array_keys($subCategories));
$subSubCategories = implode("', '", array_keys($subSubCategories));
$brands = implode("', '", array_keys($brands));
$units = implode("', '", $units);
$productTypes = implode("', '", $productTypes);
return <<<PROMPT
Analyze the product with these details:
- Name: '{$context}'
- Description: '{$description}'
Generate ONLY valid JSON with these exact fields:
{
"category_name": "Category name",
"sub_category_name": "Sub-category name",
"sub_sub_category_name": "Sub-sub-category name",
"brand_name": "Brand name",
"unit_name": "Unit name",
"product_type": "Product type",
"delivery_type" "Delivery Type"
"search_tags": ["tag1", "tag2"]
}
=== INSTRUCTIONS ===
1. SELECT the best matching category, sub-category, brand, unit, and product type from the provided options.
2. IF multiple options are possible, choose the most specific.
3. Extract 3-5 relevant search tags from the name and description.
4. sub_sub_category_name is optional; include if applicable.
5. DO NOT include comments, explanations, or any text outside the JSON.
6. JSON must be valid for json_decode in PHP.
=== AVAILABLE OPTIONS ===
[MAIN CATEGORIES] '{$categories}'
[SUB CATEGORIES] '{$subCategories}'
[SUB-SUB CATEGORIES] '{$subSubCategories}'
[BRANDS] '{$brands}'
[DELIVERY_TYPE] '{$deliveryType}'
[UNITS] '{$units}'
[PRODUCT TYPES] '{$productTypes}'
=== OUTPUT FORMAT RULE ===
- Return ONLY the raw JSON object — no code blocks, no markdown, no explanation, no labels, no timestamps, no extra text,(do not include ```json```).
- The response must start with "{" and end with "}".
- Only respond with "INVALID_INPUT" if the name or description is completely irrelevant, nonsensical, or empty.
- Do not return generic explanations, fallback messages, or apologies.
PROMPT;
}
public function getType(): string
{
return 'general_setup';
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Modules\AI\app\PromptTemplates;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class GenerateProductTitleSuggestionTemplate implements PromptTemplateInterface
{
public function build(mixed $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$langCode = strtoupper($langCode);
$keywordsText = $context;
if (is_array($context)) {
$keywordsText = implode(' ', $context);
}
return <<<PROMPT
You are an advanced e-commerce product analyst.
Using the keywords "{$keywordsText}", generate 4 professional, clean, and concise product titles for online stores.
CRITICAL INSTRUCTIONS:
- The output must be 100% in {$langCode}.
- Titles must use the keywords naturally.
- Keep them short (3570 characters), clear, and ready for listings.
- Return exactly 4 titles in **plain JSON** format as shown below (do not include ```json``` or any extra markdown):
{
"titles": [
"Title 1",
"Title 2",
"Title 3",
"Title 4"
]
}
Do not include any extra explanation, only return the JSON.
IMPORTANT:
- If the keywords are not relevant to e-commerce products or is meaningless, respond with only the word "INVALID_INPUT".
- Do not return generic explanations, fallback messages, or apologies.
PROMPT;
}
public function getType(): string
{
return "generate_product_title_suggestion";
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Modules\AI\app\PromptTemplates;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class GenerateTitleFromImageTemplate implements PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$langCode ??= 'en';
$langCode = strtoupper($langCode);
return <<<PROMPT
You are an advanced e-commerce product analyst with strong skills in image recognition.
Analyze the uploaded product image provided by the user.
Your task is to generate a clean, concise, and professional product title for online stores.
CRITICAL INSTRUCTION:
- The output must be 100% in {$langCode} — this is mandatory.
- Identify the main product in the image and name it clearly.
- Do not add extra descriptions like "high quality" or "best".
- Keep it short (3570 characters), plain, and ready for listings.
- Return only the translated product title as plain text in {$langCode}.
IMPORTANT:
- If the image is not relevant to e-commerce products (e.g., food items, vegetables, random objects, or meaningless images), respond with only the word "INVALID_INPUT".
- Do not return generic explanations, fallback messages, or apologies.
PROMPT;
}
public function getType(): string
{
return 'generate_title_from_image';
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Modules\AI\app\PromptTemplates;
class MetaTemplate
{
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Modules\AI\app\PromptTemplates;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class PricingTemplate implements PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$currency = getCurrencySymbol();
$productInfo = $description
? "Product name: \"{$context}\". Description: \"" . addslashes($description) . "\"."
: "Product name: \"{$context}\".";
return <<<PROMPT
You are an expert pricing analyst.
Given the following product information:
{$productInfo}
Using the currency symbol "{$currency}", provide ONLY a JSON object with pricing details below.
Set realistic values based on the product info and currency.
The JSON must contain exactly these fields:
{
"unit_price": 100.00,
"minimum_order_quantity": 1,
"current_stock": 50,
"discount_type": "flat", // or "percent"
"discount_amount": 0.00,
"shipping_cost": 0.00,
"is_shipping_cost_multil": 0 // 0 or 1
}
IMPORTANT:
- Return ONLY the pure JSON text with no markdown, no code fences, no extra text or explanation.
- If the product name or description is not relevant to e-commerce products or is meaningless, respond with only the word "INVALID_INPUT".
- Do not return generic explanations, fallback messages, or apologies.
PROMPT;
}
public function getType(): string
{
return 'pricing_and_others';
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Modules\AI\app\PromptTemplates;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class ProductDescriptionTemplate implements PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$langCode = strtoupper($langCode);
return <<<PROMPT
You are a creative and professional e-commerce copywriter.
Generate a detailed, engaging, and persuasive product description for the product named "{$context}".
CRITICAL LANGUAGE RULES:
- The entire description must be written 100% in {$langCode} — this is mandatory.
- If the product name is in another language, translate and localize it naturally into {$langCode}.
- Do not mix languages; use only {$langCode} characters and words.
- Adapt the tone, phrasing, and examples to be natural for {$langCode} readers.
Content & Structure:
- Include a section with key features as separate paragraphs. - Each paragraph should start with a <b>bold feature title</b> followed by a colon and the description.
- Start with a short introductory paragraph describing the product and key features, its main benefit, and who it is for.
- Follow with a "Specifications:" section in bullet points.
- Each bullet point should include one key specification or feature with its value or description.
- Keep text clear, concise, and marketing-friendly.
- End with a closing sentence highlighting why the product is essential or beneficial.
- Use clear, compelling, and marketing-friendly language.
Formatting:
- Output valid HTML using only <p>, <b>, <h1>, <h2>, and <ol>/<li><span> tags for bullet points.
- Do NOT include any markdown syntax, code fences, or triple backticks (``` or ```html```) — remove them completely.
- Avoid multiple consecutive <p> tags or empty lines that cause large gaps.
- Return only the HTML content without any commentary or explanation.
IMPORTANT:
- Only process inputs that are actual e-commerce products (electronics, clothing, home goods, gadgets, accessories, etc.).
- If the input is food, vegetables, fruits, or anything unrelated to e-commerce products, respond with only "INVALID_INPUT".
- If the original input is not meaningful or cannot be converted into a professional product description, respond with only the word "INVALID_INPUT" instead of a full sentence.
- Do not return generic explanations or fallback messages.
PROMPT;
}
public function getType(): string
{
return 'product_description';
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Modules\AI\app\PromptTemplates;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class ProductNameTemplate implements PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$langCode = strtoupper($langCode);
return <<<PROMPT
You are a professional e-commerce copywriter.
Rewrite the product name "{$context}" as a clean, concise, and professional product title for online stores.
CRITICAL INSTRUCTION:
- The output must be 100% in language code "{$langCode}" — this is mandatory.
- If the original name is not in language code "{$langCode}", fully translate it into language code "{$langCode}" while keeping the meaning.
- Do not mix languages; use only language code "{$langCode}" characters and words.
- Keep it short (3570 characters), plain, and ready for listings.
- No extra words, slogans, or punctuation.
- Return only the translated title as plain text in language code "{$langCode}".
IMPORTANT:
- Only process inputs that are actual e-commerce products (electronics, clothing, home goods, gadgets, accessories, etc.).
- If the input is food, vegetables, fruits, or anything unrelated to e-commerce products, respond with only "INVALID_INPUT".
- If the original input is not meaningful or cannot be converted into a professional product title, respond with only "INVALID_INPUT".
- Do not return generic explanations, fallback messages, or translations for unrelated items.
PROMPT;
}
public function getType(): string
{
return 'product_name';
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Modules\AI\app\PromptTemplates;
use Modules\AI\app\Contracts\PromptTemplateInterface;
use Modules\AI\app\Services\ProductResourceService;
class ProductVariationSetup implements PromptTemplateInterface
{
protected ProductResourceService $productResource;
public function __construct()
{
$this->productResource = new ProductResourceService();
}
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$langCode = strtoupper($langCode);
$resource = $this->productResource->getVariationData();
$selectedValues = [];
foreach ($resource['attributes'] as $attrName => $attrId) {
$selectedValues[] = [
'id' => (string)$attrId,
'name' => $attrName,
'variation' => ''
];
}
$myColors = [];
foreach ($resource['color'] as $color) {
$myColors[] = [
'color' => $color['code'],
'text' => $color['name'],
'name' => $color['name']
];
}
$attributesList = [];
foreach ($selectedValues as $attr) {
$attributesList[] = "{$attr['name']} (ID:{$attr['id']})";
}
$attributesString = implode(', ', $attributesList);
$colorOptions = [];
foreach ($myColors as $color) {
$colorOptions[] = "{$color['name']} ({$color['color']})";
}
$colorsString = implode(', ', $colorOptions);
return <<<PROMPT
You are an expert e-commerce product specialist with deep knowledge of product variations and attributes.
Given the following product:
- Name: {$context}
- Description: {$description}
Available configuration options from the system:
- Attributes: {$attributesString}
- Colors: {$colorsString}
Generate ONLY a JSON object with the following structure for product variation setup:
{
"colors_active": 0,
"colors": [],
"choice_attributes": [],
"genereate_variation": [
{
"option": "",
"sku": "",
"price": 0,
"stock": 0
}
]
}
Rules:
1. Use the provided attribute options when generating "choice_attributes".
2. Select relevant options from the given attributes dynamically, based on product name and description.
3. Do NOT invent options not present in the provided attributes.
4. Determine product category (clothing, electronics, digital, etc.) from name/description.
5. For clothing/fashion:
- Set colors_active: 1
- Select 35 relevant colors from the provided colors, including both code and name
- Enable size attribute with ["S","M","L","XL"]
6. Inside "genereate_variation", create objects for each unique combination of selected attributes and colors.
Each object must include:
- "option": attribute values (e.g.White-S])
- "sku": unique identifier (e.g., "SKU-RED-M")
- "price": numeric value for the variation
- "stock": integer stock quantity
7. For electronics:
- Only enable colors if explicitly mentioned
- Focus on technical or type attributes
8. For other products:
- Only enable variations if clearly relevant
9. **Output Format Rule:** Return ONLY the raw JSON object — no code blocks, no markdown, no explanation, no labels, no timestamps, no extra text. The response must start with "{" and end with "}".
IMPORTANT:
- If the Name or description is not relevant to e-commerce products or is meaningless, respond with only the word "INVALID_INPUT".
- Do not return generic explanations, fallback messages, or apologies.
PROMPT;
}
public function getType(): string
{
return 'variation_setup';
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Modules\AI\app\PromptTemplates;
use Modules\AI\app\Contracts\PromptTemplateInterface;
class SeoSectionTemplate implements PromptTemplateInterface
{
public function build(?string $context = null, ?string $langCode = null, ?string $description = null, ?array $options = null): string
{
$productInfo = $description
? "Product name: \"{$context}\". Description: \"" . addslashes($description) . "\"."
: "Product name: \"{$context}\".";
return <<<PROMPT
You are an expert SEO content writer and technical SEO specialist.
Given the following product information:
{$productInfo}
Generate ONLY a JSON object with the following SEO meta fields:
{
"meta_title": "", // Concise SEO title (max 100 chars)
"meta_description": "", // Compelling meta description (max 160 chars)
"meta_index": "index", // Either "index" or "noindex"
"meta_no_follow": 0, // 0 or 1 (boolean)
"meta_no_image_index": 0, // 0 or 1
"meta_no_archive": 0, // 0 or 1
"meta_no_snippet": 0, // 0 or 1
"meta_max_snippet": 0, // 0 or 1
"meta_max_snippet_value": -1, // Number, -1 means no limit
"meta_max_video_preview": 0, // 0 or 1
"meta_max_video_preview_value": -1,// Number, -1 means no limit
"meta_max_image_preview": 0, // 0 or 1
"meta_max_image_preview_value": "large" // One of "large", "medium", or "small"
}
Instructions:
- Use natural, clear language optimized for search engines.
- Choose values for index/noindex and booleans based on product info.
- Keep character limits for title and description.
- Return ONLY the pure JSON text without markdown, code fences, or explanations.
IMPORTANT:
- If the Name or description is not relevant to e-commerce products or is meaningless, respond with only the word "INVALID_INPUT".
- Do not return generic explanations, fallback messages, or apologies.
PROMPT;
}
public function getType(): string
{
return "seo_section";
}
}

View File

View File

@@ -0,0 +1,114 @@
<?php
namespace Modules\AI\app\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AIServiceProvider extends ServiceProvider
{
protected string $moduleName = 'AI';
protected string $moduleNameLower = 'ai';
/**
* Boot the application events.
*/
public function boot(): void
{
$this->registerCommands();
$this->registerCommandSchedules();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->moduleName, 'database/migrations'));
}
/**
* Register the service provider.
*/
public function register(): void
{
$this->app->register(RouteServiceProvider::class);
}
/**
* Register commands in the format of Command::class
*/
protected function registerCommands(): void
{
// $this->commands([]);
}
/**
* Register command Schedules.
*/
protected function registerCommandSchedules(): void
{
// $this->app->booted(function () {
// $schedule = $this->app->make(Schedule::class);
// $schedule->command('inspire')->hourly();
// });
}
/**
* Register translations.
*/
public function registerTranslations(): void
{
$langPath = resource_path('lang/modules/'.$this->moduleNameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
$this->loadJsonTranslationsFrom($langPath);
} else {
$this->loadTranslationsFrom(module_path($this->moduleName, 'lang'), $this->moduleNameLower);
$this->loadJsonTranslationsFrom(module_path($this->moduleName, 'lang'));
}
}
/**
* Register config.
*/
protected function registerConfig(): void
{
$this->publishes([module_path($this->moduleName, 'config/config.php') => config_path($this->moduleNameLower.'.php')], 'config');
$this->mergeConfigFrom(module_path($this->moduleName, 'config/config.php'), $this->moduleNameLower);
}
/**
* Register views.
*/
public function registerViews(): void
{
$viewPath = resource_path('views/modules/'.$this->moduleNameLower);
$sourcePath = module_path($this->moduleName, 'resources/views');
$this->publishes([$sourcePath => $viewPath], ['views', $this->moduleNameLower.'-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);
$componentNamespace = str_replace('/', '\\', config('modules.namespace').'\\'.$this->moduleName.'\\'.config('modules.paths.generator.component-class.path'));
Blade::componentNamespace($componentNamespace, $this->moduleNameLower);
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [];
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (config('view.paths') as $path) {
if (is_dir($path.'/modules/'.$this->moduleNameLower)) {
$paths[] = $path.'/modules/'.$this->moduleNameLower;
}
}
return $paths;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Modules\AI\app\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* The module namespace to assume when generating URLs to actions.
*/
protected string $moduleNamespace = 'Modules\AI\app\Http\Controllers';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*/
public function boot(): void
{
parent::boot();
}
/**
* Define the routes for the application.
*/
public function map(): void
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*/
protected function mapWebRoutes(): void
{
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('AI', '/routes/web.php'));
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('AI', '/routes/admin/routes.php'));
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('AI', '/routes/vendor/routes.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*/
protected function mapApiRoutes(): void
{
Route::prefix('api')
->middleware('api')
->namespace($this->moduleNamespace)
->group(module_path('AI', '/routes/api.php'));
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Modules\AI\app\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class AIContentResource extends JsonResource
{
/**
* Transform the resource into an array.
*/
public function toArray($request): array
{
return parent::toArray($request);
}
}

View File

@@ -0,0 +1,242 @@
<?php
namespace Modules\AI\app\Response;
use http\Exception\RuntimeException;
use Illuminate\Http\JsonResponse;
use InvalidArgumentException;
use Modules\AI\app\Exceptions\ValidationException;
use Modules\AI\app\Services\ProductResourceService;
use Modules\TaxModule\app\Traits\VatTaxManagement;
class ProductResponse
{
use VatTaxManagement;
protected ProductResourceService $productResource;
public function __construct()
{
$this->productResource = new ProductResourceService();
}
public function productGeneralSetupAutoFillFormat(string $result): array
{
$resource = $this->productResource->productGeneralSetupData();
$data = json_decode($result, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new InvalidArgumentException('Invalid JSON: ' . json_last_error_msg());
}
if (empty($data['category_name']) || !is_string($data['category_name'])) {
throw new InvalidArgumentException('The "category_name" field is required and must be a non-empty string.');
}
if (empty($data['unit_name']) || !is_string($data['unit_name'])) {
throw new InvalidArgumentException('The "unit_name" field is required and must be a non-empty string.');
}
if (empty($data['unit_name']) || !is_string($data['product_type'])) {
throw new InvalidArgumentException('The "product_type" field is required and must be a non-empty string.');
}
$processedData = $this->productGeneralSetConvertNamesToIds($data, $resource);
if (!$processedData['success']) {
return $processedData;
}
$data = $processedData['data'];
$fields = [
'sub_category_name',
'sub_sub_category_name',
'brand_name',
'unit_name',
'product_type',
'search_tags'
];
foreach ($fields as $field) {
if (!array_key_exists($field, $data)) {
$data[$field] = null;
}
}
return $data;
}
public function productPriceAndOthersAutoFill($result): array|JsonResponse
{
$taxData = $this->getTaxSystemType();
$productWiseTax = $taxData['productWiseTax'] && !$taxData['is_included'];
$taxVats = $taxData['taxVats'];
$data = json_decode($result, true);
if ($productWiseTax) {
$taxVats = $taxData['taxVats']->map(function ($v) {
return [
'id' => $v['id'],
'name' => $v['name'],
'tax_rate' => $v['tax_rate'],
];
})->values()->toArray();
}
$data['vatTax'] = $taxVats;
if (json_last_error() !== JSON_ERROR_NONE) {
throw new InvalidArgumentException('Invalid JSON: ' . json_last_error_msg());
}
$fields = [
'unit_price',
'minimum_order_quantity',
'current_stock',
'discount_type',
'discount_amount',
'shipping_cost',
];
$errors = [];
foreach ($fields as $field) {
if (!array_key_exists($field, $data) || $data[$field] === null || $data[$field] === '') {
$errors[$field] = "$field is required.";
}
}
if (!empty($errors)) {
return response()->json(
$this->formatAIGenerationValidationErrors($errors),
422
);
}
$data['unit_price'] = round($data['unit_price']);
return $data;
}
public function productSeoAutoFill($result): array
{
$data = json_decode($result, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new InvalidArgumentException('Invalid JSON: ' . json_last_error_msg());
}
$fields = [
'meta_title',
'meta_description',
'meta_index',
'meta_no_follow',
'meta_no_image_index',
'meta_no_archive',
'meta_no_snippet',
'meta_max_snippet',
'meta_max_snippet_value',
'meta_max_video_preview',
'meta_max_video_preview_value',
'meta_max_image_preview',
'meta_max_image_preview_value',
];
$errors = [];
foreach ($fields as $field) {
if (!array_key_exists($field, $data) || $data[$field] === null || $data[$field] === '') {
$errors[$field] = "$field is required.";
}
}
if (!empty($errors)) {
throw new RuntimeException($this->formatAIGenerationValidationErrors($errors));
}
return $data;
}
private function formatAIGenerationValidationErrors(array $errors): string
{
$messages = [];
foreach ($errors as $message) {
$messages[] = $message;
}
return 'AI couldnt generate product ' . implode(' ', $messages);
}
/**
* @throws ValidationException
*/
public function variationSetupAutoFill(string $result): array
{
$data = json_decode($result, true);
$errors = [];
if (empty($data['choice_attributes']) || !is_array($data['choice_attributes'])) {
$errors['choice_attributes'] = 'choice attributes .Please provide a more specific product name and a clear description';
}
if (isset($data['colors_active']) && $data['colors_active'] == 1) {
if (empty($data['colors']) || !is_array($data['colors'])) {
$errors['colors'] = 'Color variation. Please provide a more specific product name and a clear description';
}
}
if (isset($data['genereate_variation']) && is_array($data['genereate_variation'])) {
foreach ($data['genereate_variation'] as &$variation) {
$variation['price'] = isset($variation['price']) ? round($variation['price']) : 0;
}
}
$response = [
'data' => $data,
];
if (!empty($errors)) {
throw new ValidationException($this->formatAIGenerationValidationErrors($errors));
}
$response['status'] = 'success';
return $response;
}
public function generateTitleSuggestions(string $result)
{
return json_decode($result, true);
}
public function productGeneralSetConvertNamesToIds(array $data, array $resources): array
{
if (isset($data['category_name'])) {
$categoryName = strtolower(trim($data['category_name']));
if (isset($resources['categories'][$categoryName])) {
$data['category_id'] = $resources['categories'][$categoryName];
} else {
$errors[] = "Invalid category name: {$data['category_name']}";
}
}
if (isset($data['sub_category_name'])) {
$subCategoryName = strtolower(trim($data['sub_category_name']));
if (isset($resources['sub_categories'][$subCategoryName])) {
$data['sub_category_id'] = $resources['sub_categories'][$subCategoryName];
}
}
if (isset($data['sub_sub_category_name'])) {
$subSubCategoryName = strtolower(trim($data['sub_sub_category_name']));
if (isset($resources['sub_sub_categories'][$subSubCategoryName])) {
$data['sub_sub_category_id'] = $resources['sub_sub_categories'][$subSubCategoryName];
}
}
if (isset($data['brand_name'])) {
$brandName = strtolower(trim($data['brand_name']));
if (isset($resources['brands'][$brandName])) {
$data['brand_id'] = $resources['brands'][$brandName];
}
}
if (!empty($errors)) {
throw new \RuntimeException($this->formatAIGenerationValidationErrors($errors));
}
return [
'success' => true,
'data' => $data
];
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Modules\AI\app\Services;
use App\Traits\FileManagerTrait;
use Modules\AI\AIProviders\AIProviderManager;
use Modules\AI\AIProviders\ClaudeProvider;
use Modules\AI\AIProviders\OpenAIProvider;
use Modules\AI\app\Exceptions\AIProviderException;
use Modules\AI\app\Exceptions\ImageValidationException;
use Modules\AI\app\Exceptions\UsageLimitException;
use Modules\AI\app\Exceptions\ValidationException;
use Modules\AI\app\PromptTemplates\Blog\BlogSeoSectionTemplate;
use Modules\AI\app\PromptTemplates\Blog\DescriptionTemplate;
use Modules\AI\app\PromptTemplates\Blog\GenerateBlogTitleFromImageTemplate;
use Modules\AI\app\PromptTemplates\Blog\TitleSuggestionTemplate;
use Modules\AI\app\PromptTemplates\Blog\TitleTemplate;
use Modules\AI\app\PromptTemplates\GeneralSetupTemplates;
use Modules\AI\app\PromptTemplates\GenerateProductTitleSuggestionTemplate;
use Modules\AI\app\PromptTemplates\GenerateTitleFromImageTemplate;
use Modules\AI\app\PromptTemplates\PricingTemplate;
use Modules\AI\app\PromptTemplates\ProductDescriptionTemplate;
use Modules\AI\app\PromptTemplates\ProductNameTemplate;
use Modules\AI\app\PromptTemplates\ProductVariationSetup;
use Modules\AI\app\PromptTemplates\SeoSectionTemplate;
class AIContentGeneratorService
{
use FileManagerTrait;
protected array $templates = [];
protected array $providers;
public function __construct()
{
$this->loadTemplates();
$this->providers = [new OpenAIProvider(), new ClaudeProvider()];
}
protected function loadTemplates(): void
{
$templateClasses = [
'product_name' => ProductNameTemplate::class,
'product_description' => ProductDescriptionTemplate::class,
'general_setup' => GeneralSetupTemplates::class,
'pricing_and_others' => PricingTemplate::class,
'variation_setup' => ProductVariationSetup::class,
'seo_section' => SeoSectionTemplate::class,
'generate_product_title_suggestion' => GenerateProductTitleSuggestionTemplate::class,
'generate_title_from_image' => GenerateTitleFromImageTemplate::class,
'blog_title' => TitleTemplate::class,
'blog_description' => DescriptionTemplate::class,
'blog_title_suggestion' => TitleSuggestionTemplate::class,
'blog_seo_section' => BlogSeoSectionTemplate::class,
'generate_blog_title_from_image' => GenerateBlogTitleFromImageTemplate::class
];
foreach ($templateClasses as $type => $class) {
if (class_exists($class)) {
$this->templates[$type] = new $class();
}
}
}
/**
* @throws ImageValidationException
* @throws AIProviderException
* @throws ValidationException
* @throws UsageLimitException
*/
public function generateContent(string $contentType, mixed $context = null, string $langCode = 'en', ?string $description = null, ?string $imageUrl = null): string
{
$template = $this->templates[$contentType];
$prompt = $template->build(context: $context, langCode: $langCode, description: $description, options:['image' => $imageUrl]);
return (new AIProviderManager($this->providers))->generate(prompt: $prompt, imageUrl: $imageUrl, options: ['section' => $contentType, 'context' => $context, 'description' => $description]);
}
public function getAnalyizeImagePath($image): array
{
return $this->getAiImagePath($image, 'product');
}
public function getBlogImagePath($image): array
{
return $this->getAiImagePath($image, 'blog');
}
public function deleteAiImage($imageName, $type): void
{
$dir = "{$type}/ai_{$type}_image/". $imageName;
$this->delete($dir);
}
public function getAiImagePath($image, string $type): array
{
$dir = "{$type}/ai_{$type}_image/";
$extension = $image->getClientOriginalExtension();
$imageName = $this->fileUpload(dir: $dir, format: $extension, file: $image);
return $this->getAiImageFullPath($imageName, $type);
}
public function getAiImageFullPath(string $imageName, string $type): array
{
if (in_array(request()->ip(), ['127.0.0.1', '::1'])) {
return [
'imageName' => $imageName,
'imageFullPath' => "https://www.notebookcheck.net/fileadmin/_processed_/5/e/csm_IMG_7625_d5ec5f95a9.jpg",
];
}
return [
'imageName' => $imageName,
'imageFullPath' => dynamicStorage(path: "storage/app/public/{$type}/ai_{$type}_image/{$imageName}")
];
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace Modules\AI\app\Services;
use Illuminate\Support\Facades\Log;
use Modules\AI\app\Exceptions\ImageValidationException;
use Modules\AI\app\Exceptions\ValidationException;
class AIResponseValidatorService
{
/**
* @throws ValidationException
*/
public function validateProductTitle(string $response, ?string $context = null): void
{
if ($this->isInvalidProductTitle($response, $context)) {
throw new ValidationException('The provided input is not valid for generating a product title. Please provide a meaningful product name.');
}
}
/**
* @throws ValidationException
*/
public function validateProductDescription(string $response, ?string $context = null): void
{
if ($this->isInvalidProductTitle($response, $context)) {
throw new ValidationException('The provided input is not valid for generating a product description. Please provide a meaningful name or description.');
}
}
/**
* @throws ValidationException
*/
public function validateProductGeneralSetup(string $response, ?string $context = null): void
{
if ($this->isInvalidProductTitle($response, $context)) {
throw new ValidationException('The provided input is not valid for general setup. Please provide meaningful data.');
}
}
/**
* @throws ValidationException
*/
public function validateProductPricingAndOthers(string $response, ?string $context = null): void
{
if ($this->isInvalidProductTitle($response, $context)) {
throw new ValidationException('The provided input is not valid for generating a product description. Please provide a meaningful name or description.');
}
}
/**
* @throws ValidationException
*/
public function validateProductVariationSetup(string $response, ?string $context = null): void
{
if ($this->isInvalidProductTitle($response, $context)) {
throw new ValidationException('The provided input is not valid for generating a product description. Please provide a meaningful name or description.');
}
}
/**
* @throws ValidationException
*/
public function validateProductSeoContent(string $response, ?string $context = null): void
{
if ($this->isInvalidProductTitle($response, $context)) {
throw new ValidationException('The provided input is not valid for generating a product seo information. Please provide a meaningful name or description.');
}
}
/**
* @throws ValidationException
*/
public function validateProductKeyword(string $response, ?string $context = null): void
{
if ($this->isInvalidProductKeyword($response, $context)) {
throw new ValidationException('The provided input is not valid for generating a product title. Please provide a meaningful keyword.');
}
}
/**
* @throws ImageValidationException
*/
public function validateImageResponse(string $response): void
{
if ($this->isInvalidImageResponse($response)) {
throw new ImageValidationException('The uploaded image is not valid for generating product content. Please provide a meaningful image.');
}
}
/**
* @throws ValidationException
*/
public function validateBlogTitle(string $response, ?string $context = null): void
{
if ($this->isInvalidProductTitle($response, $context)) {
throw new ValidationException('The provided input is not valid for generating a blog title. Please provide a meaningful blog title.');
}
}
/**
* @throws ValidationException
*/
public function validateBlogDescription(string $response, ?string $context = null): void{
if ($this->isInvalidProductDescription($response, $context)) {
throw new ValidationException('The provided input is not valid for generating a blog description. Please provide a meaningful blog title or description.');
}
}
/**
* @throws ValidationException
*/
public function validateBlogSeoContent(string $response, ?string $context = null): void
{
if ($this->isInvalidProductTitle($response, $context)) {
throw new ValidationException('The provided input is not valid for generating a blog seo information. Please provide a meaningful title or description.');
}
}
/**
* @throws ValidationException
*/
public function validateBlogKeyword(string $response, ?string $context = null): void
{
if ($this->isInvalidProductTitle($response, $context)) {
throw new ValidationException('The provided input is not valid for generating blog titles. Please provide meaningful keywords');
}
}
public function validateBlogImageResponse(string $response): void
{
if ($this->isInvalidImageResponse($response)) {
throw new ImageValidationException('The uploaded image is not valid for generating blog content. Please provide a meaningful image.');
}
}
private function isInvalidProductTitle(string $response, ?string $context = null): bool
{
return $this->phraseCheck($response, $context);
}
private function isInvalidProductDescription(string $response, ?string $context = null): bool
{
return $this->phraseCheck($response, $context);
}
private function isInvalidProductKeyword(string $response, ?string $context = null): bool
{
return $this->phraseCheck($response, $context);
}
private function isInvalidImageResponse(string $response): bool
{
return $this->phraseCheck($response, null);
}
public function phraseCheck(string $response, ?string $context): bool
{
$invalidPhrases = [
'INVALID_INPUT',
];
foreach ($invalidPhrases as $phrase) {
if (stripos($response, $phrase) !== false) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Modules\AI\app\Services;
use Modules\AI\app\Exceptions\UsageLimitException;
use Modules\AI\app\Models\AISetting;
use Modules\AI\app\Models\AISettingLog;
use Modules\AI\app\Utils\CurrentAuthUser;
class AIUsageManagerService
{
/**
* @throws UsageLimitException
*/
public function checkUsageLimits(AISettingLog $log, AISetting $provider, ?string $imageUrl, ?string $section = null): void
{
$providerGenerateLimit = env('APP_MODE') == 'demo' ? 10 : $provider->generate_limit;
$generateLimit = env('APP_MODE') == 'demo' ? 10 : $provider?->image_upload_limit;
$remainingImgAction = $generateLimit <= 0 ? 0 : ($log?->total_image_generated_count < $generateLimit ? ($generateLimit - $log->total_image_generated_count) : 0);
if (!empty($imageUrl)) {
if ($remainingImgAction <= 0) {
throw new UsageLimitException('Image upload limit reached for this seller.');
}
} else {
if ($section !== 'generate_title_from_image' &&
$log->total_generated_count >= $providerGenerateLimit) {
throw new UsageLimitException('Text generation limit reached for this seller.');
}
}
}
public function incrementUsage(AISettingLog $log, ?string $imageUrl, ?string $section = ''): void
{
if (!empty($imageUrl)) {
$log->total_image_generated_count += 1;
}
$log->total_generated_count += 1;
$usage = $log->section_usage ?? [];
$usage[$section] = ($usage[$section] ?? 0) + 1;
$log->section_usage = $usage;
$log->save();
}
public function getOrCreateLog(AISetting $activeProvider): AISettingLog
{
$sellerId = CurrentAuthUser::id();
$aiSettingLog = AISettingLog::where('seller_id', $sellerId)->first();
if (!$aiSettingLog) {
$aiSettingLog = new AISettingLog();
$aiSettingLog->seller_id = $sellerId;
$aiSettingLog->total_generated_count = 0;
$aiSettingLog->total_image_generated_count = 0;
$aiSettingLog->limit_at_time = $activeProvider->generate_limit;
}
return $aiSettingLog;
}
public function getGenerateRemainingCount(): int
{
$sellerId = CurrentAuthUser::vendor();
$aiSettingLog = AISettingLog::where('seller_id', $sellerId->id)->first();
$generateLimit = env('APP_MODE') == 'demo' ? 10 : (AISetting::first()?->generate_limit ?? 0);
if (!$aiSettingLog) {
return $generateLimit;
}
return $generateLimit <= 0 ? 0 : ($aiSettingLog && ($aiSettingLog?->total_generated_count < $generateLimit) ? ($generateLimit - $aiSettingLog->total_generated_count) : 0);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Modules\AI\app\Services;
use App\Models\Attribute;
use App\Models\Author;
use App\Models\Brand;
use App\Models\Category;
use App\Models\Color;
use App\Models\PublishingHouse;
class ProductResourceService
{
protected Category $category;
protected Brand $brand;
protected Attribute $attribute;
protected Color $color;
protected Author $author;
protected PublishingHouse $publishingHouse;
private array $productType = ["physical", "digital"];
private array $deliveryTypes = ["ready_product", "ready_after_sell"];
public function __construct()
{
$this->category = new Category();
$this->brand = new Brand();
$this->attribute = new Attribute();
$this->color = new Color();
$this->author = new Author();
$this->publishingHouse = new PublishingHouse();
}
private function getCategoryEntitiyData($position = 0)
{
return $this->category
->where(['position' => $position])
->get(['id', 'name'])
->mapWithKeys(fn($item) => [strtolower($item->name) => $item->id])
->toArray();
}
private function getBrandData()
{
return $this->brand->active()
->get(['id', 'name'])
->mapWithKeys(fn($item) => [strtolower($item->name) => $item->id])
->toArray();
}
public function productGeneralSetupData(): array
{
return [
'categories' => $this->getCategoryEntitiyData(0),
'sub_categories' => $this->getCategoryEntitiyData(1),
'sub_sub_categories' => $this->getCategoryEntitiyData(2),
'brands' => $this->getBrandData(),
'units' => $this->units(),
'product_types' => $this->productType,
'delivery_types' => $this->deliveryTypes,
];
}
public function getVariationData(): array
{
return [
'attributes' => $this->attribute
->get(['id', 'name'])
->mapWithKeys(fn($item) => [strtolower($item->name) => $item->id])
->toArray(),
'color' => $this->color
->get(['id', 'name', 'code'])
->mapWithKeys(fn($item) => [
strtolower($item->name) => [
'id' => $item->id,
'name' => $item->name,
'code' => $item->code
]
])
->toArray(),
];
}
public function units(): array
{
return ['kg', 'pc', 'gms', 'ltrs'];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Modules\AI\app\Traits;
use Illuminate\Support\Facades\Cache;
use Modules\AI\app\Models\AISetting;
trait AIModuleManager
{
public function getActiveAIProviderConfig()
{
return Cache::remember('active_ai_provider', 60, function () {
return AISetting::where('status', 1)
->whereNotNull('api_key')
->where('api_key', '!=', '')
->first();
});
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Modules\AI\app\Utils;
use Illuminate\Support\Facades\Auth;
class CurrentAuthUser
{
public static function id(): ?int
{
if (request()->has('seller') && request()->seller) {
return request()->seller->id;
}
if (Auth::guard('seller')->check()) {
return Auth::guard('seller')->id();
}
if (Auth::guard('admin')->check()) {
return Auth::guard('admin')->id();
}
return null;
}
public static function model(): ?object
{
if (request()->has('seller') && request()->seller) {
return request()->seller;
}
if (Auth::guard('seller')->check()) {
return Auth::guard('seller')->user();
}
if (Auth::check()) {
return Auth::guard('admin')->user();
}
return null;
}
public static function isAdmin(): bool
{
return Auth::guard('admin')->check();
}
public static function vendor(): object
{
if (request()->has('seller') && request()->seller) {
return request()->seller;
}
if (Auth::guard('seller')->check()) {
return Auth::guard('seller')->user();
}
return (object)[];
}
}

31
Modules/AI/composer.json Executable file
View File

@@ -0,0 +1,31 @@
{
"name": "nwidart/ai",
"description": "",
"authors": [
{
"name": "Nicolas Widart",
"email": "n.widart@gmail.com"
}
],
"extra": {
"laravel": {
"providers": [],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\AI\\": "",
"Modules\\AI\\App\\": "app/",
"Modules\\AI\\Database\\Factories\\": "database/factories/",
"Modules\\AI\\Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Modules\\AI\\Tests\\": "tests/"
}
}
}

0
Modules/AI/config/.gitkeep Executable file
View File

17
Modules/AI/config/config.php Executable file
View File

@@ -0,0 +1,17 @@
<?php
return [
/**
* The display name of the module.
*/
'name' => 'AI',
/**
* The path to the module's thumbnail image.
*
* Uses the `getModuleDynamicAsset` helper to generate
* the correct public URL for the asset.
*/
'thumbnail' => getModuleDynamicAsset(path: 'public/Modules/AI/module-assets/thumbnail.png'),
];

View File

View File

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('ai_settings', function (Blueprint $table) {
$table->id();
$table->string('ai_name');
$table->string('base_url')->nullable();
$table->text('api_key');
$table->string('organization_id')->nullable();
$table->integer('generate_limit')->default(0);
$table->integer('image_upload_limit')->default(0);
$table->json('settings')->nullable();
$table->tinyInteger('status')->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ai_settings');
}
};

View File

@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('ai_setting_logs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('seller_id')->index();
$table->integer('total_generated_count')->default(0);
$table->integer('total_image_generated_count')->default(0);
$table->integer('limit_at_time')->nullable();
$table->json('section_usage')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ai_setting_logs');
}
};

View File

View File

@@ -0,0 +1,16 @@
<?php
namespace Modules\AI\database\seeders;
use Illuminate\Database\Seeder;
class AIDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
}
}

View File

@@ -0,0 +1,5 @@
<?php
return [
'version' => '1.0',
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

11
Modules/AI/module.json Executable file
View File

@@ -0,0 +1,11 @@
{
"name": "AI",
"alias": "ai",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\AI\\app\\Providers\\AIServiceProvider"
],
"files": []
}

15
Modules/AI/package.json Executable file
View File

@@ -0,0 +1,15 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5",
"sass": "^1.69.5",
"postcss": "^8.3.7",
"vite": "^4.0.0"
}
}

View File

View File

@@ -0,0 +1,28 @@
<div class="position-relative nav--tab-wrapper mb-4">
<ul class="nav nav-pills nav--tab" id="pills-tab" role="tablist">
<li class="nav-item">
<a class="nav-link {{ Request::is('admin/third-party/ai-setting') ?'active':'' }}"
href="{{ route('admin.third-party.ai-setting.index') }}">
{{ translate('AI_Configuration') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ Request::is('admin/third-party/ai-setting/vendors-usage-limits') ?'active':'' }}"
href="{{ route('admin.third-party.ai-setting.vendors-usage-limits') }}">
{{ translate('Setup_AI_Usage_Limit_For_Vendors') }}
</a>
</li>
</ul>
<div class="nav--tab__prev">
<button type="button" class="btn btn-circle border-0 bg-white text-primary">
<i class="fi fi-sr-angle-left"></i>
</button>
</div>
<div class="nav--tab__next">
<button type="button" class="btn btn-circle border-0 bg-white text-primary">
<i class="fi fi-sr-angle-right"></i>
</button>
</div>
</div>

View File

@@ -0,0 +1,134 @@
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasSetupGuide" aria-labelledby="offcanvasSetupGuideLabel"
data-status="{{ request('offcanvasShow') && request('offcanvasShow') == 'offcanvasSetupGuide' ? 'show' : '' }}">
<div class="offcanvas-header bg-body">
<div>
<h3 class="mb-1">{{ translate('AI_Configuration_Guideline') }}</h3>
</div>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div class="p-12 p-sm-20 bg-section rounded mb-3 mb-sm-20">
<div class="d-flex gap-3 align-items-center justify-content-between overflow-hidden">
<button class="btn-collapse d-flex gap-3 align-items-center bg-transparent border-0 p-0 collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#collapsePurpose" aria-expanded="true">
<div class="btn-collapse-icon border bg-light icon-btn rounded-circle text-dark collapsed">
<i class="fi fi-sr-angle-right"></i>
</div>
<span class="fw-bold text-start">{{ translate('Purpose') }} </span>
</button>
</div>
<div class="collapse mt-3 show" id="collapsePurpose">
<div class="card card-body">
<p class="fs-12">
{{ translate('To_configure_your_preferred_AI_provider_(e.g.,_OpenAI)_by_entering_the_necessary_credentials_and_managing_usage_limits_for_AI_based_features_like_content_generation_or_image_processing') }}
</p>
</div>
</div>
</div>
<div class="p-12 p-sm-20 bg-section rounded mb-3 mb-sm-20">
<div class="d-flex gap-3 align-items-center justify-content-between overflow-hidden">
<button class="btn-collapse d-flex gap-3 align-items-center bg-transparent border-0 p-0 collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#collapseAiFeatureToggle" aria-expanded="true">
<div class="btn-collapse-icon border bg-light icon-btn rounded-circle text-dark collapsed">
<i class="fi fi-sr-angle-right"></i>
</div>
<span class="fw-bold text-start">{{ translate('AI_Feature_Toggle') }} </span>
</button>
</div>
<div class="collapse mt-3" id="collapseAiFeatureToggle">
<div class="card card-body">
<p class="fs-12">
{{ translate('Use_this_switch_to_turn_AI_features_on_or_off_for_your_platform.') }}
</p>
<ul class="fs-12">
<li>
{{ translate('When_ON') }}: {{ translate('AI_tools_like_content_and_image_generation_will_work.') }}
</li>
<li>
{{ translate('When_OFF') }}: {{ translate('all_AI_features_will_stop_working_until_you_turn_it_back_on.') }}
</li>
</ul>
</div>
</div>
</div>
<div class="p-12 p-sm-20 bg-section rounded mb-3 mb-sm-20">
<div class="d-flex gap-3 align-items-center justify-content-between overflow-hidden">
<button class="btn-collapse d-flex gap-3 align-items-center bg-transparent border-0 p-0 collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#collapseAiFeatureEnableOpenAlConfigurationToggle" aria-expanded="true">
<div class="btn-collapse-icon border bg-light icon-btn rounded-circle text-dark collapsed">
<i class="fi fi-sr-angle-right"></i>
</div>
<span class="fw-bold text-start">{{ translate('Enable OpenAl Configuration') }} </span>
</button>
</div>
<div class="collapse mt-3" id="collapseAiFeatureEnableOpenAlConfigurationToggle">
<div class="card card-body">
<ul class="fs-12">
<li>
{{ translate('Go to the OpenAl API platform and') }}
<a target="_blank" href="{{ 'https://platform.openai.com/docs/overview' }}">{{ translate('Sign up') }}</a>
<span class="px-1">{{ translate('or') }}</span>
<a target="_blank" href="{{ 'https://platform.openai.com/docs/overview' }}">{{ translate('Log in.') }}</a>
</li>
<li>
{{ translate('Create a new API key and use it in the OpenAI API key section.') }}
</li>
<li>
{{ translate('Get your OpenAI Organization ID and enter it here for access and billing.') }}
</li>
</ul>
</div>
</div>
</div>
<div class="p-12 p-sm-20 bg-section rounded mb-3 mb-sm-20">
<div class="d-flex gap-3 align-items-center justify-content-between overflow-hidden">
<button class="btn-collapse d-flex gap-3 align-items-center bg-transparent border-0 p-0 collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#collapseVendorLimitsOnUsingAIToggle" aria-expanded="true">
<div class="btn-collapse-icon border bg-light icon-btn rounded-circle text-dark collapsed">
<i class="fi fi-sr-angle-right"></i>
</div>
<span class="fw-bold text-start">{{ translate('Vendor Limits On Using AI') }} </span>
</button>
</div>
<div class="collapse mt-3" id="collapseVendorLimitsOnUsingAIToggle">
<div class="card card-body">
<div class="mb-4">
<h6 class="fw-semibold fs-14">{{ translate('Limit_For_Section_Wise_Data_Generation') }}</h6>
<p class="fs-12">{{ translate('Set how many times AI can generate data for each element of the vendor panel or app') }}</p>
</div>
<div>
<h6 class="fw-semibold fs-14">{{ translate('Limit_For_Image_Based_Data_Generation') }}</h6>
<p class="fs-12">{{ translate('Set how many times AI can generate data from an image upload in vendor panel or vendor app') }}</p>
</div>
</div>
</div>
</div>
<div class="p-12 p-sm-20 bg-section rounded mb-3 mb-sm-20">
<div class="d-flex gap-3 align-items-center justify-content-between overflow-hidden">
<button class="btn-collapse d-flex gap-3 align-items-center bg-transparent border-0 p-0 collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#collapseTip" aria-expanded="true">
<div class="btn-collapse-icon border bg-light icon-btn rounded-circle text-dark collapsed">
<i class="fi fi-sr-angle-right"></i>
</div>
<span class="fw-bold text-start">{{ translate('Tip') }} </span>
</button>
</div>
<div class="collapse mt-3" id="collapseTip">
<div class="card card-body">
<p class="fs-12">
{{ translate('you_need_to_enter_the_correct_api_details_and_limits_so_the_AI_tools_(like_text_or_image_generation)_can_work_without_errors.') }}
</p>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,113 @@
@extends('layouts.admin.app')
@section('title', translate('AI_Configuration'))
@section('content')
<div class="content container-fluid">
<div class="mb-3 mb-sm-20">
<h2 class="h1 mb-0 text-capitalize d-flex align-items-center gap-2">
{{ translate('AI_Setup') }}
</h2>
</div>
@include('ai::admin-views.ai-setting._ai-setup-menu')
<div class="card card-body">
<form action="{{ route('admin.third-party.ai-setting.store') }}" method="post" class="form-advance-validation form-advance-inputs-validation form-advance-file-validation non-ajax-form-validate" novalidate>
@csrf
<div class="view-details-container">
<div>
<h3 class="mb-1">
{{ translate('AI_Configuration') }}
</h3>
<p class="mb-0 fs-12">
{{ translate('Fill_up_the_necessary_info_to_activate_AI_feature') }}
</p>
</div>
<div class="mt-3">
<div
class="d-flex justify-content-between align-items-center gap-3 border rounded px-20 py-2 user-select-none">
<span class="fw-semibold text-dark">{{ translate('AI_Status') }}</span>
<label class="switcher" for="ai-status-id">
<input class="switcher_input custom-modal-plugin" type="checkbox" value="1"
{{ $AiSetting && $AiSetting->status== 1 ? 'checked':''}}
name="status" id="ai-status-id"
data-modal-type="input-change"
data-on-image="{{ dynamicAsset(path: 'public/assets/new/back-end/img/modal/maintenance_mode-on.png') }}"
data-off-image="{{ dynamicAsset(path: 'public/assets/new/back-end/img/modal/maintenance_mode-off.png') }}"
data-on-title="{{ translate('Do_you_want_to_activate_AI_feature').'?'}}"
data-off-title="{{ translate('Do_you_want_to_deactivate_AI_feature').'?'}}"
data-on-message="<p>{{ translate('Enabling this will activate AI features in admin, vendor panel, and vendor app') }}</p>"
data-off-message="<p>{{ translate('Disabling this will hide AI features from admin, vendor panel, and vendor app') }}</p>"
data-on-button-text="{{ translate('Activate') }}"
data-off-button-text="{{ translate('Deactivate') }}">
<span class="switcher_control"></span>
</label>
</div>
</div>
<div class="mt-3 mt-sm-4">
<div class="p-12 p-sm-20 bg-section rounded">
<div class="row g-3">
<div class="col-lg-6">
<div class="form-group mb-3">
<label class="form-label" for="">
{{ translate('OpenAI_API_Key') }}
<span class="text-danger">*</span>
<span class="tooltip-icon" data-bs-toggle="tooltip" data-bs-placement="top"
aria-label="{{ translate('Sign in to OpenAI, create an API key, and use it here.') }}"
data-bs-title="{{ translate('Sign in to OpenAI, create an API key, and use it here.') }}">
<i class="fi fi-sr-info"></i>
</span>
</label>
<input type="text" id="api_key" name="api_key" class="form-control"
value="{{ showDemoModeInputValue(value: $AiSetting->api_key ?? '') }}"
data-required-msg="{{translate('api_key_field_is_required')}}"
placeholder="{{ translate('Type_API_Key') }}" required>
</div>
</div>
<div class="col-lg-6">
<div class="form-group mb-0">
<label class="form-label" for="">{{ translate('OpenAI_Organization_ID') }}
<span class="text-danger">*</span>
<span class="tooltip-icon" data-bs-toggle="tooltip" data-bs-placement="top"
aria-label="{{ translate('Get your OpenAI Organization ID and enter it here for access and billing') }}"
data-bs-title="{{ translate('Get your OpenAI Organization ID and enter it here for access and billing.') }}">
<i class="fi fi-sr-info"></i>
</span>
</label>
<input type="text" id="organization_id" name="organization_id"
class="form-control"
data-required-msg="{{translate('organization_id_field_is_required')}}"
value="{{ showDemoModeInputValue(value: $AiSetting->organization_id ?? '') }}"
placeholder="{{ translate('Type_Organization_Id') }}" required>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-end flex-wrap gap-3 mt-4">
<button type="reset" class="btn btn-secondary w-120 px-4">
{{ translate('reset') }}
</button>
<button type="{{ getDemoModeFormButton(type: 'button') }}"
class="btn btn-primary w-120 px-4 {{ getDemoModeFormButton(type: 'class') }}">
{{ translate('save') }}
</button>
</div>
</div>
</div>
</form>
</div>
</div>
<div
class="d-flex gap-2 bg-white p-2 position-fixed inset-inline-end-0 pointer shadow view-guideline-btn flex-column align-items-center"
data-bs-toggle="offcanvas" data-bs-target="#offcanvasSetupGuide">
<span class="bg-primary py-1 px-2 text-white rounded fs-12"><i class="fi fi-rr-redo"></i></span>
<span class="view-guideline-btn-text text-dark fw-medium pb-2 text-nowrap">
{{ translate('View_Guideline') }}
</span>
</div>
@include('ai::admin-views.ai-setting._ai-setup-view-guideline')
@endsection

View File

@@ -0,0 +1,90 @@
@extends('layouts.admin.app')
@section('title', translate('Setup_AI_Usage_Limit_For_Vendors'))
@section('content')
<div class="content container-fluid">
<div class="mb-3 mb-sm-20">
<h2 class="h1 mb-0 text-capitalize d-flex align-items-center gap-2">
{{ translate('AI_Setup') }}
</h2>
</div>
@include('ai::admin-views.ai-setting._ai-setup-menu')
<div class="card card-body">
<form action="{{ route('admin.third-party.ai-setting.vendors-usage-limits-update') }}" method="post" class="form-advance-validation form-advance-inputs-validation form-advance-file-validation non-ajax-form-validate" novalidate>
@csrf
<div class="view-details-container">
<div>
<h3 class="mb-1">
{{ translate('Vendor_Limits_On_Using_AI') }}
</h3>
<p class="mb-0 fs-12">
{{ translate('Set_how_many_times_AI_can_generate_data_from_the_vendor_panel_or_app') }}
</p>
</div>
<div class="mt-3 mt-sm-4">
<div class="p-12 p-sm-20 bg-section rounded">
<div class="row g-3">
<div class="col-lg-6">
<div class="form-group mb-3">
<label class="form-label" for="">
{{ translate('Limit_For_Section_Wise_Data_Generation') }}
<span class="tooltip-icon" data-bs-toggle="tooltip" data-bs-placement="top"
aria-label="{{ translate('set_the_maximum_number_of_AI_generated_content_requests_allowed_for_vendors.') }}"
data-bs-title="{{ translate('set_the_maximum_number_of_AI_generated_content_requests_allowed_for_vendors.') }}">
<i class="fi fi-sr-info"></i>
</span>
</label>
<input type="number" id="generate_limit" name="generate_limit"
class="form-control no-negative-symbol" min="0"
value="{{ showDemoModeInputValue(value: $AiSetting->generate_limit ?? 0) }}"
placeholder="{{ translate('Type_Data_Generate_Limit') }}">
</div>
</div>
<div class="col-lg-6">
<div class="form-group mb-3">
<label class="form-label" for="">
{{ translate('Limit_For_Image_Based_Data_Generation') }}
<span class="tooltip-icon" data-bs-toggle="tooltip" data-bs-placement="top"
aria-label="{{ translate('define_how_many_images_can_be_uploaded_using_the_AI_system_for_vendors.') }}"
data-bs-title="{{ translate('define_how_many_images_can_be_uploaded_using_the_AI_system_for_vendors.') }}">
<i class="fi fi-sr-info"></i>
</span>
</label>
<input type="number" id="image_upload_limit" name="image_upload_limit"
class="form-control no-negative-symbol" min="0"
value="{{ showDemoModeInputValue(value: $AiSetting->image_upload_limit ?? 0) }}"
placeholder="{{ translate('Type_Image_Upload_Limit') }}">
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-end flex-wrap gap-3 mt-4">
<button type="reset" class="btn btn-secondary w-120 px-4">
{{ translate('reset') }}
</button>
<button type="{{ getDemoModeFormButton(type: 'button') }}"
class="btn btn-primary w-120 px-4 {{ getDemoModeFormButton(type: 'class') }}">
{{ translate('save') }}
</button>
</div>
</div>
</div>
</form>
</div>
</div>
<div
class="d-flex gap-2 bg-white p-2 position-fixed inset-inline-end-0 pointer shadow view-guideline-btn flex-column align-items-center"
data-bs-toggle="offcanvas" data-bs-target="#offcanvasSetupGuide">
<span class="bg-primary py-1 px-2 text-white rounded fs-12"><i class="fi fi-rr-redo"></i></span>
<span class="view-guideline-btn-text text-dark fw-medium pb-2 text-nowrap">
{{ translate('View_Guideline') }}
</span>
</div>
@include('ai::admin-views.ai-setting._ai-setup-view-guideline')
@endsection

View File

@@ -0,0 +1,7 @@
@extends('ai::layouts.master')
@section('content')
<h1>Hello World</h1>
<p>Module: {!! config('ai.name') !!}</p>
@endsection

Some files were not shown because too many files have changed in this diff Show More