alfa-antiqye
This commit is contained in:
3
.dcdignore
Executable file
3
.dcdignore
Executable file
@@ -0,0 +1,3 @@
|
||||
# Note:-Write exact file or filepath
|
||||
# Ignore node_modules directory
|
||||
node_modules
|
||||
15
.editorconfig
Executable file
15
.editorconfig
Executable 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
40
.env.example
Executable 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
5
.gitattributes
vendored
Executable 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
29
.gitignore
vendored
Executable 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
30
.htaccess
Executable 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
1
.php-cs-fixer.cache
Normal 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"}}
|
||||
13
.styleci.yml
Executable file
13
.styleci.yml
Executable 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
|
||||
115
Modules/AI/AIProviders/AIProviderManager.php
Executable file
115
Modules/AI/AIProviders/AIProviderManager.php
Executable 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;
|
||||
}
|
||||
|
||||
}
|
||||
16
Modules/AI/AIProviders/ClaudeProvider.php
Executable file
16
Modules/AI/AIProviders/ClaudeProvider.php
Executable 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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
50
Modules/AI/AIProviders/OpenAIProvider.php
Executable file
50
Modules/AI/AIProviders/OpenAIProvider.php
Executable 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
10
Modules/AI/Addon/info.php
Executable file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'software_id' => '12345678',
|
||||
'name' => 'AI',
|
||||
'module_name' => 'AI',
|
||||
'is_published' => 1,
|
||||
'purchase_code' => '123456',
|
||||
'username' => 'user',
|
||||
];
|
||||
10
Modules/AI/app/Contracts/AIProviderInterface.php
Executable file
10
Modules/AI/app/Contracts/AIProviderInterface.php
Executable 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;
|
||||
}
|
||||
10
Modules/AI/app/Contracts/PromptTemplateInterface.php
Executable file
10
Modules/AI/app/Contracts/PromptTemplateInterface.php
Executable 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;
|
||||
}
|
||||
8
Modules/AI/app/Exceptions/AIProviderException.php
Executable file
8
Modules/AI/app/Exceptions/AIProviderException.php
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AI\app\Exceptions;
|
||||
|
||||
class AIProviderException extends ApiException
|
||||
{
|
||||
|
||||
}
|
||||
21
Modules/AI/app/Exceptions/ApiException.php
Executable file
21
Modules/AI/app/Exceptions/ApiException.php
Executable 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;
|
||||
}
|
||||
}
|
||||
8
Modules/AI/app/Exceptions/ImageValidationException.php
Executable file
8
Modules/AI/app/Exceptions/ImageValidationException.php
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AI\app\Exceptions;
|
||||
|
||||
class ImageValidationException extends ApiException
|
||||
{
|
||||
|
||||
}
|
||||
8
Modules/AI/app/Exceptions/UsageLimitException.php
Executable file
8
Modules/AI/app/Exceptions/UsageLimitException.php
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AI\app\Exceptions;
|
||||
|
||||
class UsageLimitException extends ApiException
|
||||
{
|
||||
|
||||
}
|
||||
8
Modules/AI/app/Exceptions/ValidationException.php
Executable file
8
Modules/AI/app/Exceptions/ValidationException.php
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AI\app\Exceptions;
|
||||
|
||||
class ValidationException extends ApiException
|
||||
{
|
||||
|
||||
}
|
||||
0
Modules/AI/app/Http/Controllers/.gitkeep
Executable file
0
Modules/AI/app/Http/Controllers/.gitkeep
Executable file
40
Modules/AI/app/Http/Controllers/AIController.php
Executable file
40
Modules/AI/app/Http/Controllers/AIController.php
Executable 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]);
|
||||
}
|
||||
}
|
||||
160
Modules/AI/app/Http/Controllers/API/V3/AIProductController.php
Executable file
160
Modules/AI/app/Http/Controllers/API/V3/AIProductController.php
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
Modules/AI/app/Http/Controllers/Admin/AIProductController.php
Executable file
129
Modules/AI/app/Http/Controllers/Admin/AIProductController.php
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
Modules/AI/app/Http/Controllers/Admin/AISettingController.php
Executable file
84
Modules/AI/app/Http/Controllers/Admin/AISettingController.php
Executable 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
Modules/AI/app/Http/Controllers/Admin/Blog/AIBlogController.php
Executable file
83
Modules/AI/app/Http/Controllers/Admin/Blog/AIBlogController.php
Executable 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
149
Modules/AI/app/Http/Controllers/Vendor/AIProductController.php
vendored
Executable file
149
Modules/AI/app/Http/Controllers/Vendor/AIProductController.php
vendored
Executable 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
0
Modules/AI/app/Http/Middleware/.gitkeep
Executable file
0
Modules/AI/app/Http/Middleware/.gitkeep
Executable file
0
Modules/AI/app/Http/Requests/.gitkeep
Executable file
0
Modules/AI/app/Http/Requests/.gitkeep
Executable file
36
Modules/AI/app/Http/Requests/AISettingRequest.php
Executable file
36
Modules/AI/app/Http/Requests/AISettingRequest.php
Executable 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.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
39
Modules/AI/app/Http/Requests/AIVendorUsagesLimitRequest.php
Executable file
39
Modules/AI/app/Http/Requests/AIVendorUsagesLimitRequest.php
Executable 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.'),
|
||||
];
|
||||
}
|
||||
}
|
||||
45
Modules/AI/app/Http/Requests/ApiRequests/GeneralSetupRequest.php
Executable file
45
Modules/AI/app/Http/Requests/ApiRequests/GeneralSetupRequest.php
Executable 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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
53
Modules/AI/app/Http/Requests/ApiRequests/GenerateTitleFromImageRequest.php
Executable file
53
Modules/AI/app/Http/Requests/ApiRequests/GenerateTitleFromImageRequest.php
Executable 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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
42
Modules/AI/app/Http/Requests/ApiRequests/ProductPricingRequest.php
Executable file
42
Modules/AI/app/Http/Requests/ApiRequests/ProductPricingRequest.php
Executable 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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
46
Modules/AI/app/Http/Requests/ApiRequests/ProductTitleAutoFillRequest.php
Executable file
46
Modules/AI/app/Http/Requests/ApiRequests/ProductTitleAutoFillRequest.php
Executable 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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
36
Modules/AI/app/Http/Requests/Blog/BlogDescriptionRequest.php
Executable file
36
Modules/AI/app/Http/Requests/Blog/BlogDescriptionRequest.php
Executable 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)]));
|
||||
}
|
||||
}
|
||||
41
Modules/AI/app/Http/Requests/Blog/BlogSeoSectionRequest.php
Executable file
41
Modules/AI/app/Http/Requests/Blog/BlogSeoSectionRequest.php
Executable 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)]));
|
||||
}
|
||||
}
|
||||
43
Modules/AI/app/Http/Requests/Blog/BlogTitleRequest.php
Executable file
43
Modules/AI/app/Http/Requests/Blog/BlogTitleRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
33
Modules/AI/app/Http/Requests/Blog/BlogTitleSuggestionRequest.php
Executable file
33
Modules/AI/app/Http/Requests/Blog/BlogTitleSuggestionRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
43
Modules/AI/app/Http/Requests/Blog/GenerateBlogTitleFromImageRequest.php
Executable file
43
Modules/AI/app/Http/Requests/Blog/GenerateBlogTitleFromImageRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
33
Modules/AI/app/Http/Requests/GeneralSetupRequest.php
Executable file
33
Modules/AI/app/Http/Requests/GeneralSetupRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
27
Modules/AI/app/Http/Requests/GenerateProductTitleSuggestionRequest.php
Executable file
27
Modules/AI/app/Http/Requests/GenerateProductTitleSuggestionRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
35
Modules/AI/app/Http/Requests/GenerateTitleFromImageRequest.php
Executable file
35
Modules/AI/app/Http/Requests/GenerateTitleFromImageRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
30
Modules/AI/app/Http/Requests/ProductDescriptionAutoFillRequest.php
Executable file
30
Modules/AI/app/Http/Requests/ProductDescriptionAutoFillRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
29
Modules/AI/app/Http/Requests/ProductPricingRequest.php
Executable file
29
Modules/AI/app/Http/Requests/ProductPricingRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
32
Modules/AI/app/Http/Requests/ProductSeoSectionAutoFillRequest.php
Executable file
32
Modules/AI/app/Http/Requests/ProductSeoSectionAutoFillRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
33
Modules/AI/app/Http/Requests/ProductTitleAutoFillRequest.php
Executable file
33
Modules/AI/app/Http/Requests/ProductTitleAutoFillRequest.php
Executable 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;
|
||||
}
|
||||
}
|
||||
33
Modules/AI/app/Http/Requests/ProductVariationSetupAutoFillRequest.php
Executable file
33
Modules/AI/app/Http/Requests/ProductVariationSetupAutoFillRequest.php
Executable 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
0
Modules/AI/app/Models/.gitkeep
Executable file
50
Modules/AI/app/Models/AISetting.php
Executable file
50
Modules/AI/app/Models/AISetting.php
Executable 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',
|
||||
];
|
||||
}
|
||||
55
Modules/AI/app/Models/AISettingLog.php
Executable file
55
Modules/AI/app/Models/AISettingLog.php
Executable 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',
|
||||
];
|
||||
}
|
||||
57
Modules/AI/app/PromptTemplates/Blog/BlogSeoSectionTemplate.php
Executable file
57
Modules/AI/app/PromptTemplates/Blog/BlogSeoSectionTemplate.php
Executable 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";
|
||||
}
|
||||
}
|
||||
52
Modules/AI/app/PromptTemplates/Blog/DescriptionTemplate.php
Executable file
52
Modules/AI/app/PromptTemplates/Blog/DescriptionTemplate.php
Executable 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 250–400 words of SEO-focused, engaging, and informative content.
|
||||
- Begin with a strong introduction paragraph summarizing the importance of the topic.
|
||||
- Add 2–3 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";
|
||||
}
|
||||
}
|
||||
40
Modules/AI/app/PromptTemplates/Blog/GenerateBlogTitleFromImageTemplate.php
Executable file
40
Modules/AI/app/PromptTemplates/Blog/GenerateBlogTitleFromImageTemplate.php
Executable 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 (35–70 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';
|
||||
}
|
||||
}
|
||||
49
Modules/AI/app/PromptTemplates/Blog/TitleSuggestionTemplate.php
Executable file
49
Modules/AI/app/PromptTemplates/Blog/TitleSuggestionTemplate.php
Executable 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 (35–70 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';
|
||||
}
|
||||
}
|
||||
44
Modules/AI/app/PromptTemplates/Blog/TitleTemplate.php
Executable file
44
Modules/AI/app/PromptTemplates/Blog/TitleTemplate.php
Executable 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 **50–70 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";
|
||||
}
|
||||
}
|
||||
8
Modules/AI/app/PromptTemplates/CategoryTemplate.php
Executable file
8
Modules/AI/app/PromptTemplates/CategoryTemplate.php
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AI\app\PromptTemplates;
|
||||
|
||||
class CategoryTemplate
|
||||
{
|
||||
|
||||
}
|
||||
83
Modules/AI/app/PromptTemplates/GeneralSetupTemplates.php
Executable file
83
Modules/AI/app/PromptTemplates/GeneralSetupTemplates.php
Executable 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';
|
||||
}
|
||||
|
||||
}
|
||||
49
Modules/AI/app/PromptTemplates/GenerateProductTitleSuggestionTemplate.php
Executable file
49
Modules/AI/app/PromptTemplates/GenerateProductTitleSuggestionTemplate.php
Executable 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 (35–70 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";
|
||||
}
|
||||
|
||||
}
|
||||
39
Modules/AI/app/PromptTemplates/GenerateTitleFromImageTemplate.php
Executable file
39
Modules/AI/app/PromptTemplates/GenerateTitleFromImageTemplate.php
Executable 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 (35–70 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';
|
||||
}
|
||||
}
|
||||
8
Modules/AI/app/PromptTemplates/MetaTemplate.php
Executable file
8
Modules/AI/app/PromptTemplates/MetaTemplate.php
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AI\app\PromptTemplates;
|
||||
|
||||
class MetaTemplate
|
||||
{
|
||||
|
||||
}
|
||||
49
Modules/AI/app/PromptTemplates/PricingTemplate.php
Executable file
49
Modules/AI/app/PromptTemplates/PricingTemplate.php
Executable 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';
|
||||
}
|
||||
}
|
||||
50
Modules/AI/app/PromptTemplates/ProductDescriptionTemplate.php
Executable file
50
Modules/AI/app/PromptTemplates/ProductDescriptionTemplate.php
Executable 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';
|
||||
}
|
||||
}
|
||||
40
Modules/AI/app/PromptTemplates/ProductNameTemplate.php
Executable file
40
Modules/AI/app/PromptTemplates/ProductNameTemplate.php
Executable 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 (35–70 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';
|
||||
}
|
||||
}
|
||||
108
Modules/AI/app/PromptTemplates/ProductVariationSetup.php
Executable file
108
Modules/AI/app/PromptTemplates/ProductVariationSetup.php
Executable 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 3–5 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';
|
||||
}
|
||||
|
||||
}
|
||||
61
Modules/AI/app/PromptTemplates/SeoSectionTemplate.php
Executable file
61
Modules/AI/app/PromptTemplates/SeoSectionTemplate.php
Executable 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";
|
||||
}
|
||||
}
|
||||
0
Modules/AI/app/Providers/.gitkeep
Executable file
0
Modules/AI/app/Providers/.gitkeep
Executable file
114
Modules/AI/app/Providers/AIServiceProvider.php
Executable file
114
Modules/AI/app/Providers/AIServiceProvider.php
Executable 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;
|
||||
}
|
||||
}
|
||||
67
Modules/AI/app/Providers/RouteServiceProvider.php
Executable file
67
Modules/AI/app/Providers/RouteServiceProvider.php
Executable 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'));
|
||||
}
|
||||
}
|
||||
16
Modules/AI/app/Resources/AIContentResource.php
Executable file
16
Modules/AI/app/Resources/AIContentResource.php
Executable 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);
|
||||
}
|
||||
}
|
||||
242
Modules/AI/app/Response/ProductResponse.php
Executable file
242
Modules/AI/app/Response/ProductResponse.php
Executable 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 couldn’t 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
|
||||
];
|
||||
}
|
||||
}
|
||||
115
Modules/AI/app/Services/AIContentGeneratorService.php
Executable file
115
Modules/AI/app/Services/AIContentGeneratorService.php
Executable 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}")
|
||||
];
|
||||
}
|
||||
}
|
||||
172
Modules/AI/app/Services/AIResponseValidatorService.php
Executable file
172
Modules/AI/app/Services/AIResponseValidatorService.php
Executable 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;
|
||||
}
|
||||
}
|
||||
71
Modules/AI/app/Services/AIUsageManagerService.php
Executable file
71
Modules/AI/app/Services/AIUsageManagerService.php
Executable 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);
|
||||
}
|
||||
|
||||
}
|
||||
89
Modules/AI/app/Services/ProductResourceService.php
Executable file
89
Modules/AI/app/Services/ProductResourceService.php
Executable 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'];
|
||||
}
|
||||
}
|
||||
19
Modules/AI/app/Traits/AIModuleManager.php
Executable file
19
Modules/AI/app/Traits/AIModuleManager.php
Executable 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
54
Modules/AI/app/Utils/CurrentAuthUser.php
Executable file
54
Modules/AI/app/Utils/CurrentAuthUser.php
Executable 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
31
Modules/AI/composer.json
Executable 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
0
Modules/AI/config/.gitkeep
Executable file
17
Modules/AI/config/config.php
Executable file
17
Modules/AI/config/config.php
Executable 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'),
|
||||
];
|
||||
0
Modules/AI/database/factories/.gitkeep
Executable file
0
Modules/AI/database/factories/.gitkeep
Executable file
0
Modules/AI/database/migrations/.gitkeep
Executable file
0
Modules/AI/database/migrations/.gitkeep
Executable 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');
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
0
Modules/AI/database/seeders/.gitkeep
Executable file
0
Modules/AI/database/seeders/.gitkeep
Executable file
16
Modules/AI/database/seeders/AIDatabaseSeeder.php
Executable file
16
Modules/AI/database/seeders/AIDatabaseSeeder.php
Executable 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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
5
Modules/AI/module-assets/asset-version.php
Executable file
5
Modules/AI/module-assets/asset-version.php
Executable file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'version' => '1.0',
|
||||
];
|
||||
BIN
Modules/AI/module-assets/thumbnail.png
Executable file
BIN
Modules/AI/module-assets/thumbnail.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 428 KiB |
11
Modules/AI/module.json
Executable file
11
Modules/AI/module.json
Executable 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
15
Modules/AI/package.json
Executable 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"
|
||||
}
|
||||
}
|
||||
0
Modules/AI/resources/views/.gitkeep
Executable file
0
Modules/AI/resources/views/.gitkeep
Executable file
28
Modules/AI/resources/views/admin-views/ai-setting/_ai-setup-menu.blade.php
Executable file
28
Modules/AI/resources/views/admin-views/ai-setting/_ai-setup-menu.blade.php
Executable 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>
|
||||
@@ -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>
|
||||
113
Modules/AI/resources/views/admin-views/ai-setting/index.blade.php
Executable file
113
Modules/AI/resources/views/admin-views/ai-setting/index.blade.php
Executable 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
|
||||
@@ -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
|
||||
7
Modules/AI/resources/views/index.blade.php
Executable file
7
Modules/AI/resources/views/index.blade.php
Executable 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
Reference in New Issue
Block a user