Przeglądaj źródła

优化预估积分接口

lh 1 miesiąc temu
rodzic
commit
3987851a5e

+ 2 - 2
app/Http/Controllers/Anime/AnimeController.php

@@ -2424,9 +2424,9 @@ class AnimeController extends BaseController
      * @param Request $request
      * @return mixed
      */
-    public function previewVideoCharge(Request $request) {
+    public function previewCharge(Request $request) {
         $data = $request->all();
-        $result = $this->AnimeService->previewVideoCharge($data);
+        $result = $this->AnimeService->previewCharge($data);
         return $this->success($result);
     }
 

+ 80 - 131
app/Services/Anime/AnimeService.php

@@ -5076,159 +5076,112 @@ class AnimeService
     }
 
     /**
-     * 预估视频生成所需积分(支持单一/批量
+     * 预估生成所需积分(mode=chat/image/video,支持多数量总额
      *
-     * 传参与对应创建接口一致,另支持 type 参数区分场景:
-     * segment-单分镜转视频、act-单片段转视频、batch_segment-分镜批量、batch_act-片段批量;
-     * type 缺省时按 segment_id / act_id 自动推断,批量场景需显式传入。
+     * chat  : count(默认1,AI对话单次10积分);批量分集可传 anime_id + generate_episode_number 按实际待创建集数估算
+     * image : model 必传;resolution(1k/2k/4k)或 width/height 自动归一化;image_num(默认1)
+     * video : model 必传;video_resolution(默认720p);video_duration(默认5秒);count(默认1);scene(默认video_generation)
      *
      * @param array $data
      * @return array
      */
-    public function previewVideoCharge(array $data): array
+    public function previewCharge(array $data): array
     {
-        $type = (string)getProp($data, 'type', '');
-        if (!$type) {
-            if (getProp($data, 'segment_id')) {
-                $type = 'segment';
-            } elseif (getProp($data, 'act_id')) {
-                $type = 'act';
-            } else {
-                Utils::throwError('1003:请传入type参数(segment/act/batch_segment/batch_act)');
-            }
+        $mode = (string)getProp($data, 'mode', '');
+        if (!in_array($mode, ['chat', 'image', 'video'])) {
+            Utils::throwError('1003:请传入mode参数(chat/image/video)');
         }
 
-        $mode = (string)getProp($data, 'mode', 'video_generation');
+        // 视频计费场景(video_generation/video_to_video),默认视频生成
+        $scene = (string)getProp($data, 'scene', 'video_generation');
         $items = [];
         $model = '';
-        $resolution = '720p';
+        $resolution = '';
 
-        $buildCharge = function ($itemId, $duration, $model, $resolution, $mode) {
-            return $this->pointsService->getVideoChargePoints([
-                'model' => $model,
-                'video_resolution' => $resolution,
-                'video_duration' => $duration,
-                'mode' => $mode,
-            ]);
-        };
-
-        if ($type === 'segment') {
-            // 单分镜转视频
-            $segmentId = getProp($data, 'segment_id');
-            if (!$segmentId) {
-                Utils::throwError('1002:分镜ID不能为空');
-            }
-            $segment = DB::table('mp_episode_segments')->where('segment_id', $segmentId)->first();
-            if (!$segment) {
-                Utils::throwError('20003:分镜不存在');
-            }
-            $episode = DB::table('mp_anime_episodes')->where('id', $segment->episode_id)->first();
-            $model = getProp($data, 'model') ?: getProp($episode, 'video_model', 'doubao-seedance-1-5-pro-251215');
-            if (!DB::table('mp_video_models')->where('model', $model)->where('is_enabled', 1)->exists()) {
-                $model = 'doubao-seedance-1-5-pro-251215';
+        if ($mode === 'video') {
+            // 通用视频预估:单价/秒 × 时长 × 数量(多个视频计算总额)
+            $model = (string)getProp($data, 'model', '');
+            if (!$model) {
+                Utils::throwError('1002:model参数不能为空');
             }
-            $duration = (int)ceil((float)$segment->audio_duration);
+            $resolution = strtolower((string)getProp($data, 'video_resolution', '720p'));
+            $duration = (int)getProp($data, 'video_duration', 5);
             if ($duration <= 0) {
                 $duration = 5;
             }
+            $count = max(1, (int)getProp($data, 'count', 1));
+            $pointsPerVideo = $this->pointsService->getVideoChargePoints([
+                'model' => $model,
+                'video_resolution' => $resolution,
+                'video_duration' => $duration,
+                'mode' => $scene,
+            ]);
+            $points = $pointsPerVideo * $count;
             $items[] = [
-                'id' => $segment->segment_id,
-                'duration' => $duration,
-                'points' => $buildCharge($segment->segment_id, $duration, $model, '720p', $mode),
+                'type' => 'video',
+                'model' => $model,
+                'video_resolution' => $resolution,
+                'video_duration' => $duration,
+                'count' => $count,
+                'points' => $points,
             ];
-        } elseif ($type === 'act') {
-            // 单片段转视频
-            $actId = getProp($data, 'act_id');
-            if (!$actId) {
-                Utils::throwError('1002:片段ID不能为空');
-            }
-            $act = DB::table('mp_episode_segments')->where('id', $actId)->first();
-            if (!$act) {
-                Utils::throwError('20003:片段不存在');
-            }
-            $episode = DB::table('mp_anime_episodes')->where('id', $act->episode_id)->first();
-            $model = getProp($data, 'model') ?: getProp($episode, 'video_model', 'zhizhen-20');
-            if (!DB::table('mp_video_models')->where('model', $model)->where('is_enabled', 1)->exists()) {
-                $model = 'zhizhen-20';
+        } elseif ($mode === 'image') {
+            // 通用图片预估:单张价格 × 张数(resolution 可直传,或传 width/height 自动归一化)
+            $model = (string)getProp($data, 'model', '');
+            if (!$model) {
+                Utils::throwError('1002:model参数不能为空');
             }
-            $resolution = strtolower((string)getProp($data, 'video_resolution', ''));
+            $resolution = strtolower((string)getProp($data, 'resolution', ''));
             if (!$resolution) {
-                $resolution = strtolower((string)getProp($episode, 'video_resolution', '720p'));
-            }
-            $duration = (int)getProp($data, 'video_duration', 0);
-            if ($duration <= 0) {
-                $duration = (int)ceil((float)$act->act_duration);
-            }
-            if ($duration <= 0) {
-                $duration = 5;
+                $width = (int)getProp($data, 'width', 0);
+                $height = (int)getProp($data, 'height', 0);
+                if ($width <= 0 || $height <= 0) {
+                    $width = 1600;
+                    $height = 2848;
+                }
+                $resolution = $this->pointsService->normalizeImageResolutionKey($width, $height);
             }
+            $imageNum = max(1, (int)getProp($data, 'image_num', 1));
+            $points = $this->pointsService->getImageChargePoints([
+                'model' => $model,
+                'resolution' => $resolution,
+            ]) * $imageNum;
             $items[] = [
-                'id' => $act->id,
-                'duration' => $duration,
-                'points' => $buildCharge($act->id, $duration, $model, $resolution, $mode),
+                'type' => 'image',
+                'model' => $model,
+                'resolution' => $resolution,
+                'image_num' => $imageNum,
+                'points' => $points,
             ];
-        } elseif (in_array($type, ['batch_segment', 'batch_act'])) {
-            // 批量场景
+        } else {
+            // AI对话预估:单次10积分;批量生成分集传 anime_id + generate_episode_number 时按实际待创建集数估算
+            $count = max(1, (int)getProp($data, 'count', 1));
             $animeId = getProp($data, 'anime_id');
-            $episodeId = getProp($data, 'episode_id');
-            if (!$animeId || !$episodeId) {
-                Utils::throwError('1002:anime_id和episode_id不能为空');
-            }
-            $episode = DB::table('mp_anime_episodes')->where('id', $episodeId)->where('anime_id', $animeId)->first();
-            if (!$episode) {
-                Utils::throwError('20003:分集不存在');
-            }
-
-            if ($type === 'batch_segment') {
-                $model = getProp($data, 'model') ?: getProp($episode, 'video_model', 'doubao-seedance-1-5-pro-251215');
-                if (!DB::table('mp_video_models')->where('model', $model)->where('is_enabled', 1)->exists()) {
-                    $model = 'doubao-seedance-1-5-pro-251215';
-                }
-                $segments = DB::table('mp_episode_segments')
+            $generateEpisodes = (int)getProp($data, 'generate_episode_number', 0);
+            if ($animeId && $generateEpisodes > 0) {
+                $currentMaxEpisode = DB::table('mp_anime_episodes')
                     ->where('anime_id', $animeId)
-                    ->where('episode_id', $episodeId)
-                    ->whereNotIn('video_task_status', ['已完成', '生成中'])
-                    ->orderBy('segment_number')
-                    ->get();
-                foreach ($segments as $segment) {
-                    if ((int)$segment->current_type === 2) {
-                        continue;
-                    }
-                    $duration = (int)ceil((float)$segment->audio_duration);
-                    if ($duration <= 0) {
-                        $duration = 5;
-                    }
-                    $items[] = [
-                        'id' => $segment->segment_id,
-                        'duration' => $duration,
-                        'points' => $buildCharge($segment->segment_id, $duration, $model, '720p', $mode),
-                    ];
-                }
-            } else {
-                // 片段批量固定智帧20
-                $model = 'zhizhen-20';
-                $acts = DB::table('mp_episode_segments')
+                    ->where('is_default', 1)
+                    ->max('episode_number');
+                $currentMaxEpisode = $currentMaxEpisode ?: 0;
+                $startEpisodeNumber = $currentMaxEpisode + 1;
+                $endEpisodeNumber = $startEpisodeNumber + $generateEpisodes - 1;
+                $existingSkipped = DB::table('mp_batch_episode_generation_details')
                     ->where('anime_id', $animeId)
-                    ->where('episode_id', $episodeId)
-                    ->orderBy('act_number')
-                    ->get();
-                foreach ($acts as $act) {
-                    if ($act->video_task_status === '生成中' || (int)$act->current_type === 2) {
-                        continue;
-                    }
-                    $duration = (int)ceil((float)$act->act_duration);
-                    if ($duration <= 0) {
-                        $duration = 5;
-                    }
-                    $items[] = [
-                        'id' => $act->id,
-                        'duration' => $duration,
-                        'points' => $buildCharge($act->id, $duration, $model, '720p', $mode),
-                    ];
+                    ->whereBetween('episode_number', [$startEpisodeNumber, $endEpisodeNumber])
+                    ->whereIn('status', ['pending', 'processing', 'completed'])
+                    ->count();
+                $count = ($endEpisodeNumber - $startEpisodeNumber + 1) - $existingSkipped;
+                if ($count < 0) {
+                    $count = 0;
                 }
             }
-        } else {
-            Utils::throwError('1003:type参数不正确');
+            $points = PointsService::CHAT_CHARGE_POINTS * $count;
+            $items[] = [
+                'type' => 'chat',
+                'count' => $count,
+                'points' => $points,
+            ];
         }
 
         $totalPoints = 0;
@@ -5237,15 +5190,11 @@ class AnimeService
         }
 
         return [
-            'type' => $type,
-            'model' => $model,
-            'video_resolution' => $resolution,
             'mode' => $mode,
-            'count' => count($items),
             'total_points' => $totalPoints,
             'items' => $items,
             'points_balance' => $this->pointsService->getUserPointsBalance(),
-            'remark' => '积分为预估值,按预估时长计算;实际扣费以视频生成完成后的实际时长为准',
+            'remark' => '积分为预估值,实际扣费以生成完成后的实际消耗为准',
         ];
     }
 

+ 1 - 1
routes/api.php

@@ -214,7 +214,7 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
             Route::get('saveActVideoGenerateParams', [AnimeController::class, 'saveActVideoGenerateParams']);   // 保存生成视频参数
             Route::post('createActVideoTask', [AnimeController::class, 'createActVideoTask']);          // 片段转视频
             Route::post('batchSetActVideos', [AnimeController::class, 'batchSetActVideos']);            // 片段一键转视频
-            Route::post('previewVideoCharge', [AnimeController::class, 'previewVideoCharge']);          // 预估视频生成积分(单一/批量)
+            Route::post('previewCharge', [AnimeController::class, 'previewCharge']);          // 预估视频生成积分(单一/批量)
             
             // 完整视频合成任务
             Route::get('createCompleteVideoTask', [AnimeController::class, 'createCompleteVideoTask']);