fjffk简�的咖啡店科开�2方法2
环æ»�皆山也。其西å�—诸峰,林壑尤美,望之蔚然而深秀者,ç�…ç�Šä¹Ÿã€‚山行å…七里,æ¸�闻水声潺潺,而泻出于两峰之间者,酿泉也
???????????????????????????????
??????????????/?????????????????
............/...................
.........................../...
???????????????????????????????
>>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>
ÿØÿà JFIF ÿÛ „ ( %!1!%)+//.383,7(-.+
-%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ ¥2" ÿÄ ÿÄ J ! 1AQ"aq�2‘#BR‚¡�Ñ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ ÿÄ * !1AQa‘"2q3�±ð#b¡ÿÚ ? ¼QxJQa�uò¸Zö Úü8,�Úú
"SSn<rçù–´âE—^ªBÖ9À\†¸Â�ÔÂ�Ã�TÂÃÛ5
ëd´³Ã�#Ã�;Þ38ϔÂ� ¶H£M:wÃŽ3…³…âpÔF&‚FK¸9„â4à GEõªfÂ�ÿ ‘ñ(ßwÂpŽF|È¥ù®häðÃ�Ѷ¹‘[Ã’inÙW¶ùñYËœQ{›K"išÒ[Ú8žë\F¹@-?v"ÔUâ€�,ìöžkÿ {I‡£šÃ�?e
rÂ�ÃV
?>
.........................................
.............................................................................
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
dsdfPK W]Xð»v v ChatbotDomains.phpnu „[µü¤ 'setDomains',
];
public function mount(Chatbot $chatbot): void
{
$chatbot->refresh();
$this->chatbot = $chatbot;
$this->setDomains();
}
public function setDomains(): void
{
$this->domains = $this->chatbot->domains ?? collect();
$this->addDomainButton = $this->chatbot->canNewDomainAdd();
}
public function showModal(): void
{
$this->domain = null;
$this->addDomainDialog = true;
}
public function closeModal(): void
{
$this->domain = null;
$this->addDomainDialog = false;
}
public function rules(): array
{
return [
'domain' => [
'required',
new DomainRule(true),
Rule::unique('domains')->where('chatbot_id', $this->chatbot->id),
],
];
}
public function getValidationAttributes(): array
{
return [
'domain' => __('Domain'),
];
}
public function addDomain(): void
{
$this->validate();
// $domains = $this->chatbot->domains();
if (! $this->chatbot->canNewDomainAdd()) {
return;
}
$this->chatbot->domains()->create([
'domain' => $this->domain,
'app_key' => Str::uuid(),
]);
$this->resetErrorBag();
$this->dispatch('confirming-add-domain');
$this->closeModal();
$this->dispatch('refreshDomains')->self();
}
public function render(): View
{
return view('livewire.chatbot.domains');
}
}
PK W]})Ö‘¹ ¹ ChatbotCode.phpnu „[µü¤ domain = $domain;
}
public function showCodeModal(): void
{
$this->showCodeDialog = true;
}
public function closeCodeModal(): void
{
$this->showCodeDialog = false;
}
private function getScriptCode(): string
{
return '';
}
private function getEmbedCode(): string
{
$appDomain = parse_url(config('app.url'), PHP_URL_HOST);
return sprintf('', $appDomain, $this->domain->app_key);
}
public function render(): View
{
return view('livewire.chatbot.code', [
'scriptCode' => $this->getScriptCode(),
'embedCode' => $this->getEmbedCode(),
'appKey' => $this->domain->app_key,
]);
}
}
PK W]§ü´íJ J ChatbotDomain.phpnu „[µü¤ refresh();
$this->domain = $domain;
$this->editingDomain = $domain->domain;
}
public function rules(): array
{
return [
'editingDomain' => [
'required',
new DomainRule(true),
Rule::unique('domains', 'domain')->ignore($this->domain->id)->where('chatbot_id', $this->domain->chatbot->id),
],
];
}
public function getValidationAttributes(): array
{
return [
'editingDomain' => __('Domain'),
];
}
public function showModal(): void
{
$this->editingDomain = $this->domain->domain;
$this->editDomainDialog = true;
}
public function showDeleteModal(): void
{
$this->editingDomain = $this->domain->domain;
$this->deleteDomainDialog = true;
}
public function closeModal(): void
{
$this->editingDomain = '';
$this->editDomainDialog = false;
$this->deleteDomainDialog = false;
}
public function delete(): void
{
$this->domain?->delete();
$this->refreshList();
}
public function refreshList(): void
{
$this->dispatch('refreshDomains');
}
public function editDomain(): void
{
$this->validate();
$this->domain->update([
'domain' => $this->editingDomain,
]);
$this->resetErrorBag();
$this->dispatch('confirming-edit-domain');
$this->closeModal();
$this->refreshList();
}
public function render(): View
{
return view('livewire.chatbot.domain');
}
}
PK W]ÞqÌÝÐ Ð Offline.phpnu „[µü¤ step = 1;
$this->useOrCreatePlan($plan);
$this->privatePlanData();
}
#[On('updateEntities')]
public function updateEntities($key, $value): void
{
$this->entities[$key] = $value;
}
public function rules(): array
{
return array_merge([
// Step 1
'plan.active' => 'required_if:step,1|boolean',
'plan.name' => 'required_if:step,1|string|max:190',
'plan.description' => 'required_if:step,1|string|max:15000',
'plan.features' => 'required_if:step,1|string|max:15000',
'plan.price' => 'required_if:step,1|numeric|min:0',
'plan.frequency' => 'required_if:step,1|string|max:190',
'plan.is_team_plan' => 'required_if:step,1|nullable|boolean',
'plan.price_tax_included' => 'required_if:step,1|nullable|boolean',
'plan.plan_allow_seat' => 'nullable|numeric|min:0',
'plan.trial_days' => 'required_if:step,1|numeric|min:0',
'plan.affiliate_status' => 'nullable|boolean',
'plan.is_featured' => 'required_if:step,1|nullable|boolean',
'plan.user_api' => 'required_if:step,1|nullable|boolean',
'plan.plan_type' => 'required_if:step,1|in:' . implode(',', AccessType::getValues()),
'plan.default_ai_model' => 'required_if:step,1|string|max:255',
'plan.hidden' => 'boolean',
'plan.max_subscribe' => 'integer|min:-1|nullable',
'plan.multi_model_support' => 'nullable',
'plan.reset_credits_on_renewal' => 'boolean',
'plan.last_date' => 'date|nullable',
'plan.hidden_url' => 'nullable',
],
// Step 2
$this->rulesOfPlanAiTools(),
$this->rulesOfPlanFeature(),
// Step 3
$this->plan->openAiItemsRules('plan.open_ai_items.', 'nullable|boolean'),
// Step 4 rules and validation in child livewire component
);
}
public function nextStep(): void
{
$this->validate();
if (! $this->hasNextStep()) {
$this->submit();
return;
}
$this->toNextStep();
}
private function useOrCreatePlan(?Plan $plan): void
{
$this->plan = ($plan && $plan->exists) ? $plan : Plan::createFreshPlan();
}
private function getModels(): Collection
{
return Entity::query()
->isEnabled()
->whereNotIn('key', [
EntityEnum::WHISPER_1,
EntityEnum::DALL_E_2,
EntityEnum::DALL_E_3,
EntityEnum::TTS_1,
EntityEnum::TTS_1_HD,
])->whereIn('engine', [
EngineEnum::OPEN_AI,
EngineEnum::GEMINI,
EngineEnum::ANTHROPIC,
])->get();
}
public function stepTitle(int $num): string
{
return match ($num) {
1 => 'Plan Details',
2 => 'Features',
3 => 'Templates',
4 => 'Limits',
};
}
public function getPercent(): int
{
return match ($this->currentStep()) {
1 => 25,
2 => 50,
3 => 75,
default => 100,
};
}
public function submit(): RedirectResponse|Redirector
{
if (Helper::appIsDemo()) {
return redirect(route('dashboard.admin.finance.plan.index'))->with([
'message' => 'This feature is disabled in demo mode.',
'type' => 'error',
]);
}
$isSensitiveDataChanged = $this->isSensitiveDataChanged();
$this->changePlanValuesWithSuppliedEntities();
$this->plan->save();
if ($isSensitiveDataChanged) {
PaymentProcessController::saveGatewayProducts($this->plan, $this->gatewaysToCreatePriceIds);
}
app(MenuService::class)->regenerate();
Plan::forgetCache();
return redirect(route('dashboard.admin.finance.plan.index'))->with([
'message' => 'Plan ' . $this->plan->name . ' successfully.',
'type' => 'success',
]);
}
public function rulesOfPlanAiTools(): array
{
$data = $this->planAiToolsMenu();
return collect($data)->mapWithKeys(function ($item) {
return ['plan.plan_ai_tools.' . $item['key'] => 'nullable|sometimes|boolean'];
})->toArray();
}
public function rulesOfPlanFeature(): array
{
$data = $this->planFeatureMenu();
return collect($data)->mapWithKeys(function ($item) {
return ['plan.plan_features.' . $item['key'] => 'nullable|sometimes|boolean'];
})->toArray();
}
public function planAiToolsMenu(): array
{
return once(static fn () => MenuService::planAiToolsMenu());
}
public function planFeatureMenu(): array
{
return once(static fn () => MenuService::planFeatureMenu());
}
public function render(): View
{
return view('livewire.admin.finance.plan.subscription-plan-create', [
'models' => $this->getModels(),
'aiEngines' => EngineEnum::whereHasEnabledModels(),
'planAiToolsMenu' => $this->planAiToolsMenu(),
'planFeatureMenu' => $this->planFeatureMenu(),
]);
}
private function changePlanValuesWithSuppliedEntities(): void
{
foreach ($this->entities as $key => $value) {
$keys = explode('.', $key);
$tmp = $this->plan->ai_models;
$tmp[$keys[1]][$keys[2]][$keys[3]] = $value;
$this->plan->ai_models = $tmp;
}
}
private function isSensitiveDataChanged(): bool
{
// check if price_id did not create for any gateway
$missingPriceGatwayCods = collect($this->plan->gatewayProducts)
->filter(fn ($gateway) => ! $gateway->price_id)
->pluck('gateway_code')
->toArray();
if (count($missingPriceGatwayCods) > 0) {
$this->gatewaysToCreatePriceIds = Gateways::whereIn('code', $missingPriceGatwayCods)->get();
}
return ! $this->plan?->exists || $this->plan->isDirty(['price', 'frequency']) || count($missingPriceGatwayCods) > 0;
}
private function privatePlanData(): void
{
if (is_null($this->plan->hidden_url)) {
$this->plan->hidden_url = Helper::parseUrl(
config('app.url'),
'plan/private',
$this->plan->type,
Str::random(20)
);
}
}
}
PK W]Ä$ âý ý * Admin/Finance/Plan/TokenPackPlanCreate.phpnu „[µü¤ 'boolean',
'plan.name' => 'required_if:step,1|string|max:190',
'plan.description' => 'required_if:step,1|nullable|string|max:15000',
'plan.price' => 'required_if:step,1|numeric|min:0',
'plan.price_tax_included' => 'nullable|boolean',
'plan.features' => 'required_if:step,1|string|max:15000',
'plan.type' => 'nullable',
'plan.plan_ai_tools' => 'nullable',
'plan.plan_features' => 'nullable',
// Step 2 rules and validation in child livewire component
];
}
public function mount($plan = null): void
{
$this->useOrCreateTokenPackPlan($plan);
}
#[On('updateEntities')]
public function updateEntities($key, $value): void
{
$this->entities[$key] = $value;
}
public function nextStep(): void
{
$this->validate();
if (! $this->hasNextStep()) {
$this->submit();
return;
}
$this->toNextStep();
}
public function submit(): RedirectResponse|Redirector
{
if (Helper::appIsDemo()) {
return redirect(route('dashboard.admin.finance.plan.index'))->with([
'message' => 'This feature is disabled in demo mode.',
'type' => 'error',
]);
}
$isSensitiveDataChanged = $this->isSensitiveDataChanged();
$this->changePlanValuesWithSuppliedEntities();
$this->plan->save();
if ($isSensitiveDataChanged) {
PaymentProcessController::saveGatewayProducts($this->plan);
}
Plan::forgetCache();
return redirect(route('dashboard.admin.finance.plan.index'))->with([
'message' => 'Plan successfully updated.',
'type' => 'success',
]);
}
#[NoReturn]
private function useOrCreateTokenPackPlan(?Plan $plan): void
{
$this->plan = ($plan && $plan->exists) ? $plan : Plan::createFreshTokenPackPlan();
}
public function stepTitle(int $num): string
{
return match ($num) {
1 => 'Plan Details',
2 => 'Limits',
};
}
public function getPercent(): int
{
return match ($this->currentStep()) {
1 => 50,
default => 100,
};
}
public function render(): View
{
return view('livewire.admin.finance.plan.token-pack-plan-create', [
'aiEngines' => EngineEnum::whereHasEnabledModels(),
]);
}
private function changePlanValuesWithSuppliedEntities(): void
{
foreach ($this->entities as $key => $value) {
$keys = explode('.', $key);
$tmp = $this->plan->ai_models;
$tmp[$keys[1]][$keys[2]][$keys[3]] = $value;
$this->plan->ai_models = $tmp;
}
}
private function isSensitiveDataChanged(): bool
{
// $this->plan->isDirty([]);
// always update gateway products for token pack
return true;
}
}
PK W]Ø…Ù)EÊ EÊ MediaManagerModal.phpnu „[µü¤ 'openModal',
'loadMore' => 'loadMore',
];
protected function rules()
{
$maxSize = (int) setting('media_max_size', 25) * 1024; // Convert MB to KB for Laravel validation
return [
'uploadingFiles.*' => "file|max:{$maxSize}",
];
}
protected function messages()
{
$maxSize = setting('media_max_size', 25);
return [
'uploadingFiles.*.max' => __("Each file must be smaller than {$maxSize}MB."),
'uploadingFiles.*.file' => __('The uploaded item must be a valid file.'),
];
}
public function mount(): void
{
$this->filters = array_filter([
'Upload Files',
'Images',
'Videos',
'Other Files',
empty(setting('pexels_api_key')) ? null : 'Stock Images',
empty(setting('pexels_api_key')) ? null : 'Stock Videos',
MarketplaceHelper::isRegistered('google-drive') ? 'Google Drive' : null,
]);
$this->sortButtons = [
['label' => __('Date'), 'sort' => 'created_at'],
['label' => __('Title'), 'sort' => 'title'],
['label' => __('Input'), 'sort' => 'input'],
];
}
public function updatedSearchTerm(): void
{
// Reset counters when search term changes
$this->resetLoadingCounters();
// Load initial batch immediately after search
$this->loadInitialBatch();
// Emit event to reinitialize intersection observer
$this->dispatch('searchUpdated');
}
public function updatedActiveFilter(): void
{
// Reset counters when filter changes
$this->resetLoadingCounters();
// Load initial batch for new filter
$this->loadInitialBatch();
// Emit event to reinitialize intersection observer
$this->dispatch('searchUpdated');
}
public function updatedSort(): void
{
// Reset counters when sort changes
$this->resetLoadingCounters();
// Reload data with new sort
$this->loadInitialBatch();
}
public function updatedSortAscDesc(): void
{
// Reset counters when sort direction changes
$this->resetLoadingCounters();
// Reload data with new sort direction
$this->loadInitialBatch();
}
public function updatedUploadingFiles(): void
{
$isAdmin = auth()->user()?->isAdmin() ?? false;
$maxFiles = $isAdmin ? PHP_INT_MAX : (int) setting('media_max_files', 5);
$maxSizeMB = $isAdmin ? PHP_INT_MAX : (int) setting('media_max_size', 25);
$allowedTypes = $isAdmin ? [] : explode(',', setting('media_allowed_types', 'jpg,png,gif,webp,svg,mp4,avi,mov,wmv,flv,webm,mp3,wav,m4a,pdf,doc,docx,xls,xlsx'));
if (! $isAdmin) {
$allowedTypes = array_map('trim', $allowedTypes);
$allowedTypes = array_map('strtolower', $allowedTypes);
}
// Set processing state immediately
$this->isProcessingFiles = true;
$this->processingMessage = $isAdmin ? __('Admin mode: Processing files...') : __('Validating selected files...');
$this->uploadErrors = [];
// Emit event to update UI immediately
$this->dispatch('fileProcessingStarted');
if (empty($this->uploadingFiles)) {
$this->isProcessingFiles = false;
$this->processingMessage = '';
return;
}
try {
// Update processing message for large file validation
if (count($this->uploadingFiles) > 1) {
$this->processingMessage = $isAdmin
? __('Admin mode: Processing ' . count($this->uploadingFiles) . ' files...')
: __('Validating ' . count($this->uploadingFiles) . ' files...');
}
// Validate file count using dynamic setting (skip for admin)
if (! $isAdmin && count($this->uploadingFiles) > $maxFiles) {
$this->uploadErrors[] = __("Maximum {$maxFiles} files allowed per upload.");
$this->uploadingFiles = array_slice($this->uploadingFiles, 0, $maxFiles);
}
$validFiles = [];
$processedCount = 0;
// Validate each file with progress updates (skip validation for admin)
foreach ($this->uploadingFiles as $index => $file) {
$processedCount++;
if (! $file) {
continue;
}
// Update progress for multiple files
if (count($this->uploadingFiles) > 1) {
$this->processingMessage = $isAdmin
? __("Admin mode: Processing file {$processedCount} of " . count($this->uploadingFiles) . '...')
: __("Validating file {$processedCount} of " . count($this->uploadingFiles) . '...');
}
$fileName = $file->getClientOriginalName();
$fileSize = $file->getSize();
// Skip all validation for admin users
if ($isAdmin) {
$validFiles[] = $file;
continue;
}
// Check file size using dynamic setting (only for non-admin)
$maxSizeBytes = $maxSizeMB * 1024 * 1024;
if ($fileSize > $maxSizeBytes) {
$this->uploadErrors[] = __("File '{$fileName}' is larger than {$maxSizeMB}MB.");
continue;
}
// Check file type using dynamic setting (only for non-admin)
$extension = strtolower($file->guessExtension());
if (! in_array($extension, $allowedTypes)) {
$this->uploadErrors[] = __("File '{$fileName}' has an unsupported format. Allowed types: " . implode(', ', $allowedTypes));
continue;
}
// Additional validation for very large files (show progress)
if ($fileSize > 10 * 1024 * 1024) { // Files larger than 10MB
$this->processingMessage = __("Processing large file: {$fileName}...");
// Add a small delay to allow UI to update for very large files
usleep(100000); // 0.1 second
}
$validFiles[] = $file;
}
// Update the files array with only valid files
$this->uploadingFiles = $validFiles;
// Final processing message
if (count($validFiles) > 0) {
$this->processingMessage = $isAdmin
? __('Admin mode: Files ready for upload!')
: __('Files ready for upload!');
}
} catch (Exception $e) {
$this->uploadErrors[] = 'Error processing files: ' . $e->getMessage();
} finally {
// Clear processing state
$this->isProcessingFiles = false;
$this->processingMessage = '';
// Emit completion event
$this->dispatch('fileProcessingCompleted', [
'validFileCount' => count($this->uploadingFiles),
'hasErrors' => ! empty($this->uploadErrors),
]);
// Automatically start upload if there are valid files
if (! empty($this->uploadingFiles)) {
$this->uploadFiles(); // Trigger upload automatically
}
}
}
public function clearProcessingState(): void
{
$this->isProcessingFiles = false;
$this->processingMessage = '';
}
public function uploadFiles(): void
{
$this->validateOnly('uploadingFiles');
if (empty($this->uploadingFiles)) {
$this->uploadErrors[] = __('No files selected for upload.');
return;
}
$this->isUploading = true;
$this->uploadProgress = __('Starting upload...');
$this->uploadedFiles = [];
$this->uploadErrors = [];
try {
foreach ($this->uploadingFiles as $index => $file) {
if (! $file) {
continue;
}
$this->uploadProgress = __('Uploading file ' . ($index + 1) . ' of ' . count($this->uploadingFiles) . '...');
if (! isFileSecure($file)) {
$this->uploadErrors[] = __("File '{$file->getClientOriginalName()}' is not allowed for security reasons.");
continue;
}
$extension = strtolower($file->guessExtension());
$isImage = in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg']);
$isVideo = in_array($extension, ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm']);
$fileType = $isImage ? 'images' : ($isVideo ? 'videos' : 'other');
// Create user-specific folder
$userFolder = 'media/' . $fileType . '/u-' . auth()->id() . '/';
// Ensure directory exists
if (! Storage::disk('public')->exists($userFolder)) {
Storage::disk('public')->makeDirectory($userFolder);
}
// Generate unique filename to prevent conflicts
$originalName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$sanitizedName = Str::slug($originalName);
$uniqueName = $sanitizedName . '_' . time() . '_' . Str::random(5) . '.' . $extension;
// Store the file
$filePath = $file->storeAs($userFolder, $uniqueName, 'public');
if ($filePath) {
$fullPath = Storage::disk('public')->path($filePath);
if (! validateUploadedFile($fullPath, $extension)) {
Storage::disk('public')->delete($filePath);
$this->uploadErrors[] = __("File '{$file->getClientOriginalName()}' failed security validation.");
continue;
}
$this->uploadedFiles[] = [
'name' => $file->getClientOriginalName(),
'path' => $filePath,
'type' => $fileType,
'size' => $file->getSize(),
];
} else {
$this->uploadErrors[] = __("Failed to upload '{$file->getClientOriginalName()}'.");
}
}
$this->uploadProgress = __('Upload completed!');
// Clear the upload files after successful upload
$this->uploadingFiles = [];
// Show success message
if (count($this->uploadedFiles) > 0) {
$this->dispatch('uploadComplete', [
'message' => count($this->uploadedFiles) . ' file(s) uploaded successfully!',
'files' => $this->uploadedFiles,
]);
// FIXED: Switch to appropriate tab based on the first uploaded file type
if (count($this->uploadedFiles) > 0) {
$firstFileType = $this->uploadedFiles[0]['type'];
// Switch to the correct tab based on file type
switch ($firstFileType) {
case 'images':
$this->activeFilter = 'Images';
break;
case 'videos':
$this->activeFilter = 'Videos';
break;
case 'other':
$this->activeFilter = 'Other Files';
break;
default:
// Stay on Upload Files tab if type is unknown
break;
}
$this->resetLoadingCounters();
$this->loadInitialBatch();
}
}
} catch (Exception $e) {
$this->uploadErrors[] = 'Upload failed: ' . $e->getMessage();
} finally {
$this->isUploading = false;
$this->dispatch('clearUploadProgress');
}
}
private function resetLoadingCounters(): void
{
$this->loadedImagesCount = 0;
$this->loadedVideosCount = 0;
$this->loadedOtherFilesCount = 0;
$this->hasMoreImages = true;
$this->hasMoreVideos = true;
$this->hasMoreOtherFiles = true;
}
private function loadInitialBatch(): void
{
if ($this->activeFilter === 'Images') {
$this->loadedImagesCount = $this->loadPerBatch;
} elseif ($this->activeFilter === 'Videos') {
$this->loadedVideosCount = $this->loadPerBatch - 25;
} elseif ($this->activeFilter === 'Other Files') {
$this->loadedOtherFilesCount = $this->loadPerBatch;
}
}
public function loadMore(): void
{
if ($this->isLoading) {
return;
}
$this->isLoading = true;
if ($this->activeFilter === 'Images' && $this->hasMoreImages) {
$this->loadedImagesCount += $this->loadPerBatch;
} elseif ($this->activeFilter === 'Videos' && $this->hasMoreVideos) {
$this->loadedVideosCount += $this->loadPerBatch;
} elseif ($this->activeFilter === 'Other Files' && $this->hasMoreOtherFiles) {
$this->loadedOtherFilesCount += $this->loadPerBatch;
}
$this->isLoading = false;
}
public function openModal(array $allowedTypes = ['all'], bool $isMultiple = false): void
{
$this->allowedTypes = $allowedTypes;
$this->showModal = true;
$this->allowMultipleSelection = $isMultiple;
// Load initial batch when opening modal
$this->loadInitialBatch();
}
public function closeModal(): void
{
$this->showModal = false;
$this->clearFiles();
$this->uploadErrors = [];
$this->uploadProgress = '';
$this->uploadedFiles = [];
$this->isProcessingFiles = false;
$this->processingMessage = '';
}
// Updated getStockVideos method with better video file selection
protected function getStockVideos(bool $getAllForSelection = false): Collection
{
$searchTerm = trim($this->searchTerm);
if (empty($searchTerm)) {
return collect();
}
$perPage = (int) setting('pexels_video_count', 6);
$url = "https://api.pexels.com/videos/search?query={$searchTerm}&per_page={$perPage}";
try {
$response = Http::withHeaders([
'Authorization' => setting('pexels_api_key'),
])->get($url);
if ($response->successful()) {
$data = $response->json();
return collect($data['videos'] ?? [])->map(function ($video) {
// Get video files and sort by quality preference
$videoFiles = collect($video['video_files'] ?? []);
// First try to get a smaller/compressed version for preview
$previewFile = $videoFiles->first(function ($file) {
return in_array($file['quality'], ['sd', 'hd']) &&
isset($file['width']) && $file['width'] <= 640;
});
// If no small preview found, get the smallest available
if (! $previewFile) {
$previewFile = $videoFiles->sortBy('width')->first();
}
// For download, prefer HD quality
$downloadFile = $videoFiles->first(function ($file) {
return $file['quality'] === 'hd';
}) ?? $videoFiles->first(function ($file) {
return $file['quality'] === 'sd';
}) ?? $videoFiles->first();
return (object) [
'id' => 'pexels_video_' . $video['id'],
'title' => $video['tags'] ? ucfirst($video['tags']) : "Video by {$video['user']['name']}",
'url' => $downloadFile['link'] ?? '', // Main video URL for download
'preview_url' => $previewFile['link'] ?? '', // Smaller preview URL for in-browser playback
'thumbnail' => $video['image'] ?? '',
'duration' => $video['duration'] ?? 0,
'source' => 'pexels_video',
'created_at' => now(),
'format_date' => now()->format('M d, Y'),
'width' => $downloadFile['width'] ?? 0,
'height' => $downloadFile['height'] ?? 0,
'file_type' => $downloadFile['file_type'] ?? 'video/mp4',
'quality' => $downloadFile['quality'] ?? 'sd',
// Additional debug info
'preview_width' => $previewFile['width'] ?? 0,
'preview_height' => $previewFile['height'] ?? 0,
];
});
}
} catch (Exception $e) {
Log::error('Pexels Video API Error: ' . $e->getMessage());
}
return collect();
}
public function downloadAndInsertStockVideos(): void
{
if (empty($this->selectedStockVideos)) {
return;
}
$stockVideos = $this->getStockVideos(true);
$selectedStockVideos = $stockVideos->whereIn('id', $this->selectedStockVideos);
if ($selectedStockVideos->isEmpty()) {
return;
}
$downloadedVideos = collect();
$errors = [];
foreach ($selectedStockVideos as $stockVideo) {
try {
// Extract the actual video ID from the prefixed ID
$videoId = str_replace('pexels_video_', '', $stockVideo->id);
// Download the video
$response = Http::timeout(60)->get($stockVideo->url); // Increased timeout for videos
if ($response->successful()) {
// Generate filename
$fileName = 'stock_video_' . $videoId . '_' . time() . '.mp4';
$userFolder = 'media/videos/u-' . auth()->id() . '/';
// Ensure directory exists
if (! Storage::disk('public')->exists($userFolder)) {
Storage::disk('public')->makeDirectory($userFolder);
}
$filePath = $userFolder . $fileName;
Storage::disk('public')->put($filePath, $response->body());
// Create downloaded video object with consistent structure
$downloadedVideo = (object) [
'id' => 'downloaded_video_' . $videoId . '_' . time(),
'title' => $stockVideo->title,
'input' => $stockVideo->title,
'output_url' => url('/uploads/' . $filePath),
'url' => url('/uploads/' . $filePath),
'created_at' => now(),
'extension' => 'mp4',
'type' => 'video',
'source' => 'stock_video_downloaded',
'file_path' => $filePath,
];
$downloadedVideos->push($downloadedVideo);
} else {
$errors[] = 'Failed to download: ' . $stockVideo->title;
}
} catch (Exception $e) {
Log::error('Stock video download failed: ' . $e->getMessage());
$errors[] = 'Download error for: ' . $stockVideo->title;
}
}
// Show errors if any
if (! empty($errors)) {
foreach ($errors as $error) {
$this->dispatch('stockVideoDownloadFailed', [
'message' => $error,
]);
}
}
// If we have successfully downloaded videos, insert them
if ($downloadedVideos->isNotEmpty()) {
// Format the downloaded videos for frontend consumption
$formattedItems = $downloadedVideos->map(function ($item) {
return [
'id' => $item->id,
'url' => $item->output_url,
'title' => $item->title,
'input' => $item->input,
'extension' => $item->extension,
'type' => 'video',
'created_at' => $item->created_at,
];
});
// Dispatch success message
$this->dispatch('stockVideosDownloaded', [
'message' => count($downloadedVideos) . ' stock video(s) downloaded and inserted successfully!',
'files' => $formattedItems->toArray(),
]);
// Dispatch media selected event with downloaded videos
$this->dispatch('mediaSelected', [
'type' => 'video',
'items' => $formattedItems->toArray(),
]);
// Reset selections and close modal
$this->selectedStockVideos = [];
$this->closeModal();
// Switch to Videos tab to show the downloaded videos
$this->activeFilter = 'Videos';
$this->resetLoadingCounters();
$this->loadInitialBatch();
}
}
public function toggleSelect(string $type, string $id): void
{
$selectedProperty = match ($type) {
'image' => 'selectedImages',
'video' => 'selectedVideos',
'other' => 'selectedOtherFiles',
'stockImage' => 'selectedStockImages',
'stockVideo' => 'selectedStockVideos',
default => 'selectedImages'
};
$id = (string) $id;
if (in_array($id, $this->$selectedProperty, true)) {
$this->$selectedProperty = collect($this->$selectedProperty)
->reject(fn ($selectedId) => $selectedId === $id)
->values()
->toArray();
} else {
$this->{$selectedProperty}[] = $id;
}
}
public function changeFilter(string $filter): void
{
if ($this->isLoading) {
return;
}
$this->isLoading = true;
try {
$this->activeFilter = $filter;
$this->selectedImages = [];
$this->selectedVideos = [];
$this->selectedOtherFiles = [];
$this->selectedStockImages = [];
$this->selectedStockVideos = [];
// Clear upload data when switching away from upload tab
if ($filter !== 'Upload Files') {
$this->clearFiles();
$this->uploadErrors = [];
$this->uploadProgress = '';
}
// Reset loading counters
$this->resetLoadingCounters();
// Load initial batch for the new filter
$this->loadInitialBatch();
// Emit event to update UI
$this->dispatch('filterChanged', ['filter' => $filter]);
} finally {
$this->isLoading = false;
}
}
/**
* Download and insert selected stock images
*/
public function downloadAndInsertStockImages(): void
{
if (empty($this->selectedStockImages)) {
return;
}
$stockImages = $this->getStockImages(true);
$selectedStockImages = $stockImages->whereIn('id', $this->selectedStockImages);
if ($selectedStockImages->isEmpty()) {
return;
}
$downloadedImages = collect();
$errors = [];
foreach ($selectedStockImages as $stockImage) {
try {
// Extract the actual image ID from the prefixed ID
$imageId = str_replace('pexels_', '', $stockImage->id);
// Download the image
$response = Http::timeout(30)->get($stockImage->url);
if ($response->successful()) {
// Generate filename
$fileName = 'stock_' . $imageId . '_' . time() . '.jpg';
$userFolder = 'media/images/u-' . auth()->id() . '/';
// Ensure directory exists
if (! Storage::disk('public')->exists($userFolder)) {
Storage::disk('public')->makeDirectory($userFolder);
}
$filePath = $userFolder . $fileName;
Storage::disk('public')->put($filePath, $response->body());
// Create downloaded image object with consistent structure
$downloadedImage = (object) [
'id' => 'downloaded_' . $imageId . '_' . time(),
'title' => $stockImage->title,
'input' => $stockImage->title,
'output_url' => url('/uploads/' . $filePath),
'url' => url('/uploads/' . $filePath),
'created_at' => now(),
'extension' => 'jpg',
'type' => 'image',
'source' => 'stock_downloaded',
'file_path' => $filePath,
];
$downloadedImages->push($downloadedImage);
} else {
$errors[] = 'Failed to download: ' . $stockImage->title;
}
} catch (Exception $e) {
Log::error('Stock image download failed: ' . $e->getMessage());
$errors[] = 'Download error for: ' . $stockImage->title;
}
}
// Show errors if any
if (! empty($errors)) {
foreach ($errors as $error) {
$this->dispatch('stockImageDownloadFailed', [
'message' => $error,
]);
}
}
// If we have successfully downloaded images, insert them
if ($downloadedImages->isNotEmpty()) {
// Format the downloaded images for frontend consumption
$formattedItems = $downloadedImages->map(function ($item) {
return [
'id' => $item->id,
'url' => $item->output_url,
'title' => $item->title,
'input' => $item->input,
'extension' => $item->extension,
'type' => 'image',
'created_at' => $item->created_at,
];
});
// Dispatch success message
$this->dispatch('stockImagesDownloaded', [
'message' => count($downloadedImages) . ' stock image(s) downloaded and inserted successfully!',
'files' => $formattedItems->toArray(),
]);
// Dispatch media selected event with downloaded images
$this->dispatch('mediaSelected', [
'type' => 'image',
'items' => $formattedItems->toArray(),
]);
// Reset selections and close modal
$this->selectedStockImages = [];
$this->closeModal();
// Switch to Images tab to show the downloaded images
$this->activeFilter = 'Images';
$this->resetLoadingCounters();
$this->loadInitialBatch();
}
}
public function changeSort(string $sort): void
{
if ($this->sort === $sort) {
$this->sortAscDesc = $this->sortAscDesc === 'asc' ? 'desc' : 'asc';
} else {
$this->sort = $sort;
$this->sortAscDesc = 'desc';
}
}
protected function getUserUploadedOtherFiles(): Collection
{
if (! auth()->check()) {
return collect();
}
$userUploadedMediaFolder = 'media/other/u-' . auth()->id() . '/';
// Create the folder if it doesn't exist
if (! Storage::disk('public')->exists($userUploadedMediaFolder)) {
Storage::disk('public')->makeDirectory($userUploadedMediaFolder);
}
$files = Storage::disk('public')->files($userUploadedMediaFolder);
return collect($files)
->map(function ($file) {
$filename = basename($file);
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
try {
$lastModified = Storage::disk('public')->lastModified($file);
$createdAt = \Carbon\Carbon::createFromTimestamp($lastModified);
} catch (Exception $e) {
$createdAt = \Carbon\Carbon::now();
}
$fileUrl = url('/uploads/' . $file);
return (object) [
'id' => 'file_' . md5($file),
'title' => pathinfo($filename, PATHINFO_FILENAME),
'filename' => $filename,
'extension' => $extension,
'input' => '',
'output_url' => $fileUrl,
'url' => $fileUrl,
'created_at' => $createdAt,
'format_date' => $createdAt->format('M d, Y'),
'source' => 'uploaded',
'file_path' => $file,
'file_size' => Storage::disk('public')->size($file),
];
})
->values();
}
/**
* Fetch other files with limit and offset for infinite scroll
*/
protected function getOtherFiles(bool $getAllForSelection = false): Collection
{
// Get uploaded other files
$uploadedOtherFiles = $this->getUserUploadedOtherFiles();
// Filter by search term if provided
$searchTerm = trim($this->searchTerm);
if (! empty($searchTerm)) {
$uploadedOtherFiles = $uploadedOtherFiles->filter(function ($item) use ($searchTerm) {
return Str::contains(strtolower($item->title), strtolower($searchTerm)) ||
Str::contains(strtolower($item->filename), strtolower($searchTerm));
});
}
// Apply sorting
$allOtherFiles = $this->sortCollection($uploadedOtherFiles);
// Handle pagination if not getting all for selection
if (! $getAllForSelection) {
$totalCount = $allOtherFiles->count();
$this->hasMoreOtherFiles = $this->loadedOtherFilesCount < $totalCount;
return $allOtherFiles->take($this->loadedOtherFilesCount);
}
return $allOtherFiles;
}
public function clearFiles(): void
{
$this->uploadingFiles = [];
$this->uploadErrors = [];
$this->uploadedFiles = [];
$this->isProcessingFiles = false;
$this->processingMessage = '';
}
public function clearSearch(): void
{
$this->searchTerm = '';
$this->resetLoadingCounters();
$this->loadInitialBatch();
// Emit event to reinitialize intersection observer
$this->dispatch('searchUpdated');
}
public function insertSelected(string $type): void
{
$selectedItems = match ($type) {
'image' => $this->selectedImages,
'video' => $this->selectedVideos,
'other' => $this->selectedOtherFiles,
'stockImage' => $this->selectedStockImages,
'stockVideo' => $this->selectedStockVideos,
default => []
};
if (empty($selectedItems)) {
return;
}
// If single selection is enforced, only pick the first item
if (! $this->allowMultipleSelection) {
$selectedItems = [reset($selectedItems)];
}
// Get the actual media items based on selected IDs
$mediaItems = match ($type) {
'image' => $this->getImages(true),
'video' => $this->getVideos(true),
'other' => $this->getOtherFiles(true),
'stockImage' => $this->getStockImages(true),
'stockVideo' => $this->getStockVideos(true),
default => collect()
};
$selectedMediaItems = $mediaItems->whereIn('id', $selectedItems);
// Format the data for frontend consumption
$formattedItems = $selectedMediaItems->map(function ($item) use ($type) {
return [
'id' => $item->id,
'url' => $item->output_url ?? $item->url,
'title' => $item->title ?? $item->input ?? $item->filename,
'input' => $item->input ?? '',
'extension' => $item->extension ?? '',
'type' => $type,
'created_at' => $item->created_at,
];
});
// Dispatch an event with the selected items
$this->dispatch('mediaSelected', [
'type' => $type,
'items' => $formattedItems->toArray(),
]);
// Reset selections and close modal
match ($type) {
'image' => $this->selectedImages = [],
'video' => $this->selectedVideos = [],
'other' => $this->selectedOtherFiles = [],
'stockImage' => $this->selectedStockImages = [],
'stockVideo' => $this->selectedStockVideos = [],
default => $this->selectedImages = [],
};
$this->closeModal();
}
/**
* Get uploaded files from user's media folder
*/
protected function getUserUploadedFiles(string $type = 'image'): Collection
{
if (! auth()->check()) {
return collect();
}
$userUploadedMediaFolder = 'media/' . ($type === 'image' ? 'images' : 'videos') . '/u-' . auth()->id() . '/';
// Create the folder if it doesn't exist
if (! Storage::disk('public')->exists($userUploadedMediaFolder)) {
Storage::disk('public')->makeDirectory($userUploadedMediaFolder);
}
// Get allowed extensions from settings
$allowedTypesFromSettings = explode(',', setting('media_allowed_types', 'jpg,png,gif,webp,svg,mp4,avi,mov,wmv,flv,webm,mp3,wav,m4a,pdf,doc,docx,xls,xlsx'));
$allowedTypesFromSettings = array_map('trim', $allowedTypesFromSettings);
// Filter by type (image or video)
if ($type === 'image') {
$imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
$allowedExtensions = array_intersect($allowedTypesFromSettings, $imageExtensions);
} else {
$videoExtensions = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm'];
$allowedExtensions = array_intersect($allowedTypesFromSettings, $videoExtensions);
}
$files = Storage::disk('public')->files($userUploadedMediaFolder);
return collect($files)
->filter(function ($file) use ($allowedExtensions) {
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
return in_array($extension, $allowedExtensions);
})
->map(function ($file) {
$filename = basename($file);
try {
$lastModified = Storage::disk('public')->lastModified($file);
$createdAt = \Carbon\Carbon::createFromTimestamp($lastModified);
} catch (Exception $e) {
// Fallback to current time if we can't get the file timestamp
$createdAt = \Carbon\Carbon::now();
}
$fileUrl = url('/uploads/' . $file);
return (object) [
'id' => 'file_' . md5($file),
'title' => pathinfo($filename, PATHINFO_FILENAME),
'filename' => $filename,
'input' => '',
'output_url' => $fileUrl,
'url' => $fileUrl,
'created_at' => $createdAt,
'format_date' => $createdAt->format('M d, Y'),
'source' => 'uploaded',
'file_path' => $file,
'file_size' => Storage::disk('public')->size($file),
];
})
->values();
}
/**
* Fetch images with limit and offset for infinite scroll - Enhanced to include uploaded files
*/
protected function getImages(bool $getAllForSelection = false): Collection
{
// Get database images
$query = UserOpenai::query()
->where('user_id', auth()->id())
->whereNotNull('output')
->where('output', '!=', '')
->whereHas('generator', function ($q) {
$q->where('type', 'image');
})
->with(['generator' => function ($q) {
$q->select('id', 'type', 'title');
}]);
// Apply search filter at database level
$searchTerm = trim($this->searchTerm);
if (! empty($searchTerm)) {
$query->where(function ($q) use ($searchTerm) {
$q->where('title', 'like', "%{$searchTerm}%")
->orWhere('input', 'like', "%{$searchTerm}%");
});
}
$dbImages = $query->get()->map(function ($item) {
$item->source = 'database';
return $item;
});
// Get uploaded files
$uploadedImages = $this->getUserUploadedFiles('image');
// Filter uploaded files by search term if provided
if (! empty($searchTerm)) {
$uploadedImages = $uploadedImages->filter(function ($item) use ($searchTerm) {
return Str::contains(strtolower($item->title), strtolower($searchTerm)) ||
Str::contains(strtolower($item->filename), strtolower($searchTerm));
});
}
// Merge both collections
$allImages = $dbImages->concat($uploadedImages);
// Apply sorting
$allImages = $this->sortCollection($allImages);
// Handle pagination if not getting all for selection
if (! $getAllForSelection) {
$totalCount = $allImages->count();
$this->hasMoreImages = $this->loadedImagesCount < $totalCount;
return $allImages->take($this->loadedImagesCount);
}
return $allImages;
}
/**
* Fetch stock images with limit and offset for infinite scroll
*/
protected function getStockImages(bool $getAllForSelection = false): Collection
{
$searchTerm = trim($this->searchTerm);
if (empty($searchTerm)) {
return collect();
}
$perPage = (int) setting('pexels_image_count', 20);
$url = "https://api.pexels.com/v1/search?query={$searchTerm}&per_page={$perPage}";
try {
$response = Http::withHeaders([
'Authorization' => setting('pexels_api_key'),
])->get($url);
if ($response->successful()) {
$data = $response->json();
return collect($data['photos'] ?? [])->map(function ($photo) {
return (object) [
'id' => 'pexels_' . $photo['id'],
'title' => $photo['alt'] ?: "Photo by {$photo['photographer']}",
'url' => $photo['src']['original'],
'thumbnail' => $photo['src']['medium'],
'source' => 'pexels',
'created_at' => now(),
'format_date' => now()->format('M d, Y'),
];
});
}
} catch (Exception $e) {
Log::error('Pexels API Error: ' . $e->getMessage());
}
return collect();
}
/**
* Fetch videos with limit and offset for infinite scroll - Enhanced to include uploaded files
*/
protected function getVideos(bool $getAllForSelection = false): Collection
{
// Early return if not on videos tab and not getting all for selection
if (! $getAllForSelection && $this->activeFilter !== 'Videos') {
return collect();
}
$searchTerm = trim($this->searchTerm);
$allVideos = collect();
try {
// Get database videos
$query = UserOpenai::query()
->where('user_id', auth()->id())
->where('status', 'COMPLETED')
->whereNotNull('output')
->where('output', '!=', '')
->whereHas('generator', function ($q) {
$q->where('type', 'video');
})
->with(['generator' => function ($q) {
$q->select('id', 'type', 'title');
}]);
// Apply search filter at database level for UserOpenai
if (! empty($searchTerm)) {
$query->where(function ($q) use ($searchTerm) {
$q->where('title', 'like', "%{$searchTerm}%")
->orWhere('input', 'like', "%{$searchTerm}%");
});
}
$dbVideos = $query->get()->map(function ($item) {
$item->source = 'database';
return $item;
});
$allVideos = $allVideos->concat($dbVideos);
// Get UserFall videos if the class exists (with error handling)
if (class_exists('App\Extensions\AiVideoPro\System\Models\UserFall')) {
try {
$userFallQuery = UserFall::query()
->where('user_id', auth()->id())
->where('status', 'complete')
->whereNotNull('video_url')
->where('video_url', '!=', '');
// Apply search filter for UserFall videos
if (! empty($searchTerm)) {
$userFallQuery->where('prompt', 'like', "%{$searchTerm}%");
}
$userFallVideos = $userFallQuery->get()->map(function ($item) {
return (object) [
'id' => 'userfall_' . $item->id,
'title' => $this->generateTitleFromPrompt($item->prompt),
'input' => $item->prompt,
'output' => $item->video_url,
'output_url' => $item->video_url,
'url' => $item->video_url,
'created_at' => $item->created_at,
'format_date' => $item->created_at->format('M d, Y'),
'source' => 'userfall',
'model' => $item->model ?? 'veo2',
'request_id' => $item->request_id,
'is_demo' => $item->is_demo ?? 0,
'generator' => (object) [
'id' => null,
'type' => 'video',
'title' => ucfirst($item->model ?? 'veo2') . ' Video',
],
];
});
$allVideos = $allVideos->concat($userFallVideos);
} catch (Exception $e) {
Log::warning('Error fetching UserFall videos: ' . $e->getMessage());
}
}
// Get ExportedVideos (with error handling)
try {
$exportedVideosQuery = ExportedVideo::query()
->where('user_id', auth()->id())
->where('status', 'completed')
->whereNotNull('video_url')
->where('video_url', '!=', '');
// Apply search filter for ExportedVideos
if (! empty($searchTerm)) {
$exportedVideosQuery->where('title', 'like', "%{$searchTerm}%");
}
$exportedVideos = $exportedVideosQuery->get()->map(function ($item) {
return (object) [
'id' => 'exported_' . $item->id,
'title' => $item->title,
'input' => '',
'output' => $item->video_url,
'output_url' => $item->video_url,
'url' => $item->video_url,
'created_at' => $item->created_at,
'format_date' => $item->created_at->format('M d, Y'),
'source' => 'exported',
'generator' => (object) [
'id' => null,
'type' => 'video',
'title' => __('Exported Video'),
],
];
});
$allVideos = $allVideos->concat($exportedVideos);
} catch (Exception $e) {
Log::warning('Error fetching ExportedVideos: ' . $e->getMessage());
}
// Get uploaded video files
$uploadedVideos = $this->getUserUploadedFiles('video');
// Filter uploaded files by search term if provided
if (! empty($searchTerm)) {
$uploadedVideos = $uploadedVideos->filter(function ($item) use ($searchTerm) {
return Str::contains(strtolower($item->title ?? ''), strtolower($searchTerm)) ||
Str::contains(strtolower($item->filename ?? ''), strtolower($searchTerm));
});
}
$allVideos = $allVideos->concat($uploadedVideos);
// Apply sorting
$allVideos = $this->sortCollection($allVideos);
// Handle pagination if not getting all for selection
if (! $getAllForSelection) {
$totalCount = $allVideos->count();
$this->hasMoreVideos = $this->loadedVideosCount < $totalCount;
return $allVideos->take($this->loadedVideosCount);
}
return $allVideos;
} catch (Exception $e) {
Log::error('Error in getVideos(): ' . $e->getMessage());
return collect();
}
}
/**
* Sort a collection based on current sort settings
*/
protected function sortCollection(Collection $collection): Collection
{
$sortField = $this->sort;
$direction = $this->sortAscDesc === 'desc';
return $collection->sortBy(function ($item) use ($sortField) {
switch ($sortField) {
case 'title':
return $item->title ?? $item->filename ?? '';
case 'input':
return $item->input ?? '';
case 'created_at':
default:
return $item->created_at;
}
}, SORT_REGULAR, $direction)->values();
}
public function render()
{
$images = collect();
$videos = collect();
$otherFiles = collect();
$stockImages = collect();
$stockVideos = collect();
if ($this->activeFilter === 'Images') {
$images = $this->getImages();
} elseif ($this->activeFilter === 'Videos') {
$videos = $this->getVideos();
} elseif ($this->activeFilter === 'Other Files') {
$otherFiles = $this->getOtherFiles();
} elseif ($this->activeFilter === 'Stock Images') {
$stockImages = $this->getStockImages();
} elseif ($this->activeFilter === 'Stock Videos') {
$stockVideos = $this->getStockVideos();
}
return view('livewire.media-manager-modal', [
'images' => $images,
'videos' => $videos,
'otherFiles' => $otherFiles,
'stockImages' => $stockImages,
'stockVideos' => $stockVideos,
]);
}
/**
* Generate a title from a prompt by taking the first few words
*/
private function generateTitleFromPrompt(string $prompt): string
{
$words = explode(' ', trim($prompt));
$titleWords = array_slice($words, 0, 6); // Take first 6 words
$title = implode(' ', $titleWords);
// Add ellipsis if the prompt was longer
if (count($words) > 6) {
$title .= '...';
}
return $title ?: 'Untitled Video';
}
public function isCardDisabled(string $type): bool
{
if (in_array('all', $this->allowedTypes, true)) {
return false;
}
$mapping = [
'image' => ['image'],
'video' => ['video'],
'other' => ['file'],
'file' => ['other'],
];
$allowed = array_merge(...array_values(array_intersect_key($mapping, array_flip($this->allowedTypes))));
return ! in_array($type, $allowed, true);
}
}
PK W]¶p- - Chatbot.phpnu „[µü¤ maxStep;
}
if (method_exists($this, 'maxStep')) {
return $this->maxStep();
}
return 1;
}
public function currentStep(): int
{
return $this->step;
}
public function totalStep(): int
{
return $this->getMaxStep();
}
public function currentStepIs(int $step): bool
{
return $this->step === $step;
}
public function toPreviousStep(): void
{
if (! $this->hasPreviousStep()) {
return;
}
$this->changeStep($this->step - 1);
}
public function toNextStep(): void
{
if (! $this->hasNextStep()) {
return;
}
$this->changeStep($this->step + 1);
}
public function hasNextStep(): bool
{
return $this->step < $this->getMaxStep();
}
public function hasPreviousStep(): bool
{
return $this->step > 1;
}
public function changeStep(int $step): void
{
$this->step = $step;
}
}
PK W]”µˆ¥a a AssignViewCredits.phpnu „[µü¤ entities = $entities;
$this->plan = $plan;
$this->enabledAiEngines = EngineEnum::whereHasEnabledModels();
$this->costs = $this->setAllCost($entities);
$this->calculateTotals();
}
public function calculateTotals(): void
{
$this->totals['costs'] = 0;
$total = 0;
foreach ($this->costs as $groupKey => $groupCosts) {
$groupCost = array_map(function ($cost) {
return $cost['credit'];
}, $groupCosts);
$groupTotal = array_sum($groupCost);
$this->totals['engine'][$groupKey] = [
'enum' => EngineEnum::fromSlug($groupKey),
'total' => $groupTotal,
];
$total += $groupTotal;
$this->totals['costs'] = $total;
}
}
private function setAllCost(array $entities): array
{
foreach ($entities as $groupKey => $groupEntities) {
foreach ($groupEntities as $key => $entity) {
$unitPrice = EntityEnum::fromSlug($key)?->unitPrice();
$unitPrice = is_float($unitPrice) ? $unitPrice : 0.00;
$credit = $entity['credit'] * $unitPrice;
$this->entityUnitPrices[$groupKey][$key] = $unitPrice;
if (isset($entity['isUnlimited']) && $entity['isUnlimited']) {
$credit = 0;
}
$costs[$groupKey][$key]['credit'] = $credit;
$costs[$groupKey][$key]['isUnlimited'] = $entity['isUnlimited'] ?? false;
}
}
return $costs;
}
public function rules(): array
{
return EngineEnum::rules('entities.', ['sometimes|numeric|min:0', 'sometimes|boolean']);
}
public function messages(): array
{
return collect($this->enabledAiEngines)->flatMap(function (EngineEnum $engine) {
return collect($engine->models())->mapWithKeys(function (EntityEnum $model) use ($engine) {
return [
"entities.{$engine->slug()}.{$model->slug()}.credit.numeric" => str_replace(':aiModel', $model->value, 'The :aiModel credit must be a valid number.'),
"entities.{$engine->slug()}.{$model->slug()}.credit.min" => str_replace(':aiModel', $model->value, 'The :aiModel credit must be at least :min.'),
"entities.{$engine->slug()}.{$model->slug()}.isUnlimited.boolean" => str_replace(':aiModel', $model->value, 'The :aiModel boolean value must be true or false.'),
];
});
})->toArray();
}
public function updateEntities($key, $value): void
{
$this->validate();
$this->dispatch('updateEntities', $key, $value);
$keys = explode('.', $key);
$engine = data_get($keys, 1);
$entity = data_get($keys, 2);
$unitPrice = $this->entityUnitPrices[$engine][$entity];
if (is_numeric($value)) {
$this->costs[$engine][$entity]['credit'] = $value * $unitPrice;
}
if (Str::contains($key, 'isUnlimited')) {
if ($value) {
$this->costs[$engine][$entity]['credit'] = 0;
$this->costs[$engine][$entity]['isUnlimited'] = true;
} else {
$this->costs[$engine][$entity]['isUnlimited'] = false;
$this->costs[$engine][$entity]['credit'] = $this->entities[$engine][$entity]['credit'] * $unitPrice;
}
}
$this->calculateTotals();
}
public function render(): View
{
return view('livewire.assign-view-credits');
}
}
PK W]Xð»v v ChatbotDomains.phpnu „[µü¤ PK W]})Ö‘¹ ¹ ¸ ChatbotCode.phpnu „[µü¤ PK W]§ü´íJ J °
ChatbotDomain.phpnu „[µü¤ PK W]ÞqÌÝÐ Ð ; Offline.phpnu „[µü¤ PK W]Pîx˜ - F Admin/Finance/Plan/SubscriptionPlanCreate.phpnu „[µü¤ PK W]Ä$ âý ý * ½6 Admin/Finance/Plan/TokenPackPlanCreate.phpnu „[µü¤ PK W]Ø…Ù)EÊ EÊ F MediaManagerModal.phpnu „[µü¤ PK W]¶p- - ž Chatbot.phpnu „[µü¤ PK W]½-ï®ì ì Concerns/WithSteps.phpnu „[µü¤ PK W]”µˆ¥a a 8 AssignViewCredits.phpnu „[µü¤ PK
_ Þ'