소스 검색

1.对话扣积分规则适配多类型2.新增所有使用对话大模型的方法增加扣分和预检3.积分规则显示、修改和预览适配多类型

lh 3 주 전
부모
커밋
142543b882
3개의 변경된 파일173개의 추가작업 그리고 84개의 파일을 삭제
  1. 10 2
      app/Services/Anime/AnimeService.php
  2. 88 67
      app/Services/DeepSeek/DeepSeekService.php
  3. 75 15
      app/Services/PointsService.php

+ 10 - 2
app/Services/Anime/AnimeService.php

@@ -5576,11 +5576,18 @@ class AnimeService
                 'points' => $points,
             ];
         } else {
-            // AI对话预估:单次10积分;批量生成分集传 anime_id + generate_episode_number 时按实际待创建集数估算
+            // AI对话预估:按 price_type 档位取价(special 默认10 / normal 3);
+            // 批量生成分集传 anime_id + generate_episode_number 时按实际待创建集数估算(该场景实际扣费固定 special 档)
+            $priceType = (string)getProp($data, 'price_type', 'special');
+            if (!in_array($priceType, ['special', 'normal'], true)) {
+                Utils::throwError('1003:price_type参数仅支持special或normal');
+            }
             $count = max(1, (int)getProp($data, 'count', 1));
             $animeId = getProp($data, 'anime_id');
             $generateEpisodes = (int)getProp($data, 'generate_episode_number', 0);
             if ($animeId && $generateEpisodes > 0) {
+                // 批量生成分集按 special 档预估,与实际扣费口径一致
+                $priceType = 'special';
                 $currentMaxEpisode = DB::table('mp_anime_episodes')
                     ->where('anime_id', $animeId)
                     ->where('is_default', 1)
@@ -5598,10 +5605,11 @@ class AnimeService
                     $count = 0;
                 }
             }
-            $points = $this->pointsService->getChatChargePoints((string)$model) * $count;
+            $points = $this->pointsService->getChatChargePoints((string)$model, $priceType) * $count;
             $items[] = [
                 'type' => 'chat',
                 'count' => $count,
+                'price_type' => $priceType,
                 'points' => $points,
             ];
         }

+ 88 - 67
app/Services/DeepSeek/DeepSeekService.php

@@ -148,7 +148,7 @@ class DeepSeekService
             Utils::throwError('20003:仅Gemini模型支持视频输入');
         }
 
-        // Gemini 多模态(图片/视频 -> 文本)按模型预检积分;纯文本不扣费
+        // 计费预检:纯文生文按 normal 档(3);有参考图/视频输入按模型固定价格(图生文)
         $uid = Site::getUid();
         $chargeModality = '';
         $chargePoints = 0;
@@ -156,15 +156,15 @@ class DeepSeekService
             || $this->messagesContainMedia($messages, ['image_url', 'image']);
         $hasVideoMedia = !empty($videos) || !empty($videoUrls)
             || $this->messagesContainMedia($messages, ['video_url', 'video']);
-        if ($isGeminiModel && ($hasImageMedia || $hasVideoMedia)) {
+        if ($hasImageMedia || $hasVideoMedia) {
             $chargeModality = $hasVideoMedia ? 'video' : 'image';
-            $pricingModel = $this->geminiMultimodalPricingModel($model);
-            if ($pricingModel !== '') {
-                $chargePoints = $this->pointsService->getVideoChargePoints(['model' => $pricingModel]);
-                if ($chargePoints > 0) {
-                    $this->pointsService->checkUserPointsEnough($chargePoints, $uid);
-                }
-            }
+            $chargePoints = $this->pointsService->getImageChatChargePoints($model);
+        } else {
+            $chargeModality = 'text';
+            $chargePoints = $this->pointsService->getChatChargePoints('', 'normal');
+        }
+        if ($chargePoints > 0) {
+            $this->pointsService->checkUserPointsEnough($chargePoints, $uid);
         }
         
         // 如果没有提供messages,则根据system_prompt、content和images构建
@@ -335,7 +335,7 @@ class DeepSeekService
             return $this->wrapGenerateTextCharge($streamGenerator, $uid, $model, $chargePoints, $chargeModality);
         }
 
-        // 纯文本(不扣积分):流式生成成功后仅记录 token 使用明细
+        // 免费场景(0 积分):流式生成成功后仅记录 token 使用明细
         return $this->wrapGenerateTextTokenRecord($streamGenerator, $uid, $model);
     }
 
@@ -363,7 +363,7 @@ class DeepSeekService
     }
 
     /**
-     * 流式生成成功后再按 Gemini 多模态规则扣费
+     * 流式生成成功后再按档位/模型规则扣费(文生文 normal 档 / 图生文按模型固定价格)
      *
      * @param \Generator $generator
      * @param int        $uid
@@ -379,7 +379,7 @@ class DeepSeekService
             if (!$charged && isset($chunk['type']) && $chunk['type'] === 'done') {
                 $fullContent = (string)getProp($chunk, 'full_content', getProp($chunk, 'answer', ''));
                 if ($fullContent !== '') {
-                    $this->chargeGeminiMultimodal($uid, $model, $points, $modality, getProp($chunk, 'usage', []));
+                    $this->chargeMultimodal($uid, $model, $points, $modality, getProp($chunk, 'usage', []));
                     $charged = true;
                 }
             }
@@ -388,7 +388,7 @@ class DeepSeekService
     }
 
     /**
-     * Gemini 多模态(图片/视频 -> 文本)成功后扣费并记录 token 明细
+     * AI 对话(含图片/视频 -> 文本)成功后扣费并记录积分与 token 明细
      *
      * @param int    $uid
      * @param string $model
@@ -397,7 +397,7 @@ class DeepSeekService
      * @param mixed  $usage
      * @return void
      */
-    private function chargeGeminiMultimodal(int $uid, string $model, int $points, string $modality, $usage): void
+    private function chargeMultimodal(int $uid, string $model, int $points, string $modality, $usage): void
     {
         $result = $this->pointsService->recordChatCharge(
             $uid,
@@ -412,29 +412,11 @@ class DeepSeekService
             $model
         );
         if (empty($result['charged'])) {
-            throw new \Exception('Gemini多模态计费失败: ' . (string)($result['reason'] ?? 'unknown'));
+            throw new \Exception('AI对话计费失败: ' . (string)($result['reason'] ?? 'unknown'));
         }
     }
 
     /**
-     * Gemini 文本模型 ID 转 mp_video_models 中的按次计费模型 ID
-     *
-     * @param string $model
-     * @return string
-     */
-    private function geminiMultimodalPricingModel(string $model): string
-    {
-        $map = [
-            'gemini-3.1-flash-image-preview-t' => 'gemini-3.1-flash',
-            'gemini-3.1-pro-preview'           => 'gemini-3.1-pro',
-            'gemini-3-flash-preview'           => 'gemini-3.0-flash',
-            'gemini-3-pro-preview'             => 'gemini-3.0-pro',
-            'gemini-3.6-flash'                 => 'gemini-3.6-flash',
-        ];
-        return $map[$model] ?? '';
-    }
-
-    /**
      * 检查 OpenAI 风格消息中是否包含指定媒体类型
      *
      * @param array  $messages
@@ -524,13 +506,13 @@ class DeepSeekService
      * @param array $chargeInfo
      * @return void
      */
-    private function chargeChatSuccess(int $uid, int $tokens, array $chargeInfo): void
+    private function chargeChatSuccess(int $uid, int $tokens, array $chargeInfo, string $chargeType = 'special'): void
     {
         // api_type 记录真实模型名(缺失时回退 deepseek 大类),保证明细与统计口径准确
         $model = (string)getProp($chargeInfo, 'model', '');
         $result = $this->pointsService->recordChatCharge(
             $uid,
-            $this->pointsService->getChatChargePoints($model),
+            $this->pointsService->getChatChargePoints($model, $chargeType),
             $tokens,
             $chargeInfo,
             '',
@@ -3503,6 +3485,10 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
 
     // 与推理模型对话
     public function chatWithReasoner($data) {
+        // 文生文计费预检(normal 档)
+        $uid = Site::getUid();
+        $this->pointsService->checkUserPointsEnough($this->pointsService->getChatChargePoints('', 'normal'), $uid);
+
         $content = getProp($data, 'content');
         $model = getProp($data, 'model', 'r1');
         
@@ -3598,11 +3584,11 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             'words'             => getProp($script_content, 'words'),
         ];
 
-        // 仅记录 token 使用明细(不扣积分)
-        $this->recordTextTokenOnly($this->getCurrentUid(), $this->pointsService->getTokensFromUsage($usage), [
+        // 文生文成功计费(normal 档):同时记录积分与 token 明细
+        $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($usage), [
             'model' => $model,
             'source' => 'chatWithReasoner',
-        ], $model);
+        ], 'normal');
 
         return $result;
     }
@@ -4462,6 +4448,10 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
      * @return \Generator 返回生成器,用于流式输出
      */
     public function generateEpisodes($data) {
+        // 文生文计费预检(special 档,每次调用扣 10)
+        $uid = Site::getUid();
+        $this->pointsService->checkUserPointsEnough($this->pointsService->getChatChargePoints('', 'special'), $uid);
+
         $script_id = getProp($data, 'script_id');
         $group_id = getProp($data, 'group_id');
         // $file = getProp($data, 'file');
@@ -4686,20 +4676,20 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             }else {
                 Utils::throwError('20003:分集剧本解析失败');
             }
+
+            // 文生文成功计费(special 档):与落库同事务
+            $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($usage), [
+                'model' => $model,
+                'script_id' => $script_id,
+                'group_id' => $group_id,
+                'source' => 'generateEpisodes',
+            ]);
         }catch (\Exception $e) {
             DB::rollBack();
             Utils::throwError('20003:'.$e->getMessage());
         }
         DB::commit();
 
-        // 仅记录 token 使用明细(不扣积分)
-        $this->recordTextTokenOnly($this->getCurrentUid(), $this->pointsService->getTokensFromUsage($usage), [
-            'model' => $model,
-            'script_id' => $script_id,
-            'group_id' => $group_id,
-            'source' => 'generateEpisodes',
-        ], $model);
-
         yield [
             'type' => 'done',
             'script' => $script_arr,
@@ -4711,6 +4701,10 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
     }
 
     public function chatWithFileStream($data) {
+        // 剧本解析资产表计费预检(special 档)
+        $uid = Site::getUid();
+        $this->pointsService->checkUserPointsEnough($this->pointsService->getChatChargePoints('', 'special'), $uid);
+
         $script_id = getProp($data, 'script_id');
         $group_id = getProp($data, 'group_id');
         $file = getProp($data, 'file');
@@ -4964,6 +4958,14 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                 Utils::throwError('20003:保存分集失败');
             }
 
+            // 剧本解析资产表成功计费(special 档):与落库同事务
+            $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($usage), [
+                'model' => $model,
+                'script_id' => $script_id,
+                'group_id' => $group_id,
+                'source' => 'chatWithFileStream',
+            ], 'special');
+
 
             // $episodes = [];
             // foreach ($script_arr['episodes'] as $item) {
@@ -4991,14 +4993,6 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
         }
         DB::commit();
 
-        // 仅记录 token 使用明细(不扣积分)
-        $this->recordTextTokenOnly($this->getCurrentUid(), $this->pointsService->getTokensFromUsage($usage), [
-            'model' => $model,
-            'script_id' => $script_id,
-            'group_id' => $group_id,
-            'source' => 'chatWithFileStream',
-        ], $model);
-
         yield [
             'type' => 'done',
             'script' => $script_arr,
@@ -5018,6 +5012,10 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
      * @return array
      */
     public function chatWithFile($data) {
+        // 剧本解析资产表计费预检(special 档)
+        $uid = Site::getUid();
+        $this->pointsService->checkUserPointsEnough($this->pointsService->getChatChargePoints('', 'special'), $uid);
+
         $script_id = getProp($data, 'script_id');
         $file = getProp($data, 'file');
         $content = getProp($data, 'content', '');
@@ -5138,12 +5136,12 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             'updated_at' => date('Y-m-d H:i:s')
         ]);
 
-        // 仅记录 token 使用明细(不扣积分)
-        $this->recordTextTokenOnly($this->getCurrentUid(), $this->pointsService->getTokensFromUsage($usage), [
+        // 剧本解析资产表成功计费(special 档):同时记录积分与 token 明细
+        $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($usage), [
             'model' => $model,
             'script_id' => $script_id,
             'source' => 'chatWithFile',
-        ], $model);
+        ], 'special');
 
         return [
             'script'        => $script_arr,
@@ -12301,11 +12299,14 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             Utils::throwError('20003:片段ID和剧集ID只能选择一个');
         }
 
-        // 积分余额预检(仅剧集ID模式收费)
-        if (!empty($episode_id)) {
+        // 积分余额预检:act_id 单片段模式按 normal 档,episode_id 剧集模式按 special 档
+        $chargeType = $isActIdMode ? 'normal' : 'special';
+        if ($isActIdMode) {
+            $this->pointsService->checkUserPointsEnough($this->pointsService->getChatChargePoints('', $chargeType), $uid);
+        } else {
             $is_generated = DB::table('mp_anime_episodes')->where('id', $episode_id)->value('is_generated');
             if ($is_generated) Utils::throwError('20003:该分集已不支持重新生成片段,请创建副本后重试!');
-            $this->pointsService->checkUserPointsEnough($this->pointsService->getChatChargePoints(), $uid);
+            $this->pointsService->checkUserPointsEnough($this->pointsService->getChatChargePoints('', $chargeType), $uid);
         }
         
         // 如果提供了template_id,获取模板提示词
@@ -12878,6 +12879,14 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
 
                     // 预览确认模式(check_mode=1):不落库,组装 editAct 入参返回,由前端确认后调用 editAct 更新
                     if ($check_mode === 1) {
+                        // 单片段预览生成即按 normal 档扣费
+                        $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($usage), [
+                            'anime_id'   => $target_anime_id,
+                            'episode_id' => $target_episode_id,
+                            'model'      => $model,
+                            'source'     => 'regenerateSegmentScript',
+                        ], 'normal');
+
                         return [
                             'check_mode'   => 1,
                             'act_id'       => $act_id,
@@ -12924,18 +12933,26 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                     }
                     
                     dLog('deepseek')->info('单个片段重新生成成功', ['act_id' => $act_id]);
+
+                    // 单片段模式成功扣费(normal 档)
+                    $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($usage), [
+                        'anime_id'   => $target_anime_id,
+                        'episode_id' => $target_episode_id,
+                        'model'      => $model,
+                        'source'     => 'regenerateSegmentScript',
+                    ], 'normal');
                 }
                 
             } else {
                 // 预览确认模式(check_mode=1):不落库,返回展示参数供前端确认后调用 saveEpisodeActs 保存
                 if ($check_mode === 1) {
-                    // 预览生成即扣除积分并记录 token 明细(与 check_mode=0 保存时扣费一致)
+                    // 剧集模式预览生成即扣除积分并记录 token 明细(special 档
                     $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($usage), [
                         'anime_id'   => $target_anime_id,
                         'episode_id' => $target_episode_id,
                         'model'      => $model,
                         'source'     => 'regenerateSegmentScript',
-                    ]);
+                    ], 'special');
 
                     // 为每个片段生成虚拟 act_id 并写入 redis(1天有效),供后续按虚拟 act_id 恢复片段数据
                     $previewActs = [];
@@ -13011,13 +13028,13 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
                         DB::table('mp_episode_segments')->insert($segments_to_insert);
                     }
 
-                    // 剧集ID模式调用成功,返回结果前扣除积分并记录明细(与保存同事务)
+                    // 剧集ID模式调用成功,返回结果前扣除积分并记录明细(special 档,与保存同事务)
                     $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($usage), [
                         'anime_id' => $target_anime_id,
                         'episode_id' => $target_episode_id,
                         'model' => $model,
                         'source' => 'regenerateSegmentScript',
-                    ]);
+                    ], 'special');
                     
                     DB::commit();
                     
@@ -14879,6 +14896,10 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
     }
 
     public function chatChangeImg($data) {
+        // 文生文计费预检(normal 档)
+        $uid = Site::getUid();
+        $this->pointsService->checkUserPointsEnough($this->pointsService->getChatChargePoints('', 'normal'), $uid);
+
         $segment = getProp($data, 'segment');
         $segment_content = getProp($segment, 'segment_content');
         $prompt = getProp($data, 'prompt');
@@ -14942,11 +14963,11 @@ Q版卡通风格,头大身小,造型圆润可爱,线条简单,色彩明
             Utils::throwError('20003: 接口调用失败!');
         }
 
-        // 仅记录 token 使用明细(不扣积分)
-        $this->recordTextTokenOnly($this->getCurrentUid(), $this->pointsService->getTokensFromUsage($usage), [
+        // 文生文成功计费(normal 档):同时记录积分与 token 明细
+        $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($usage), [
             'model' => $model,
             'source' => 'chatChangeImg',
-        ], $model);
+        ], 'normal');
 
         return [
             'type' => 'done',

+ 75 - 15
app/Services/PointsService.php

@@ -28,35 +28,90 @@ class PointsService
      * AI对话(chatForAce / addChatForAce单剧集 / reGenerateAnimeForAce单剧集 / regenerateSegmentScript剧集模式)单次扣费积分数
      *
      * 该值为文生文(对话)的默认扣费积分数:
-     * 各模型可在 mp_text_models.price_json 中按 model 单独配置({"price": 10}),
-     * 未配置或模型不存在时回退此默认值。
+     * 各模型可在 mp_text_models.price_json 中按 model 配置 special/normal 两档({"special": 10, "normal": 3}),
+     * 未配置或模型不存在时回退此默认值(special 档)
      */
     const CHAT_CHARGE_POINTS = 10;
 
     /**
+     * 文生文(对话)normal 档默认扣费积分数
+     *
+     * 其他文生文接口(chatWithFile / chatWithReasoner / chatChangeImg / generateText 纯文本等)统一按 normal 档计费,
+     * 未配置或模型不存在时回退此默认值。
+     */
+    const NORMAL_CHAT_CHARGE_POINTS = 3;
+
+    /**
      * 获取文生文(AI对话)单次应扣积分数(公共方法)
      *
      * 计费规则从 mp_text_models 表读取(charge_type=per_call,按次计费):
-     * - 传入 model 且该模型配置了 price_json.price 时,返回配置值(允许 0,即免费)
-     * - 模型未配置、模型不存在或未传 model 时,返回默认值 10
+     * - 传入 model 且该模型配置了 price_json.{type} 时,返回对应档位配置值(允许 0,即免费)
+     * - 兼容旧格式 price_json.price
+     * - 模型未配置、模型不存在或未传 model 时,special 档返回 10、normal 档返回 3
      *
      * @param string $model 文本模型 ID(如 deepseek-reasoner / doubao-seed-2-0-mini-260215)
+     * @param string $type  计费档位:special(主对话,默认)或 normal(其他文生文)
      * @return int
      */
-    public function getChatChargePoints(string $model = ''): int
+    public function getChatChargePoints(string $model = '', string $type = 'special'): int
     {
+        $defaultPoints = $type === 'normal' ? self::NORMAL_CHAT_CHARGE_POINTS : self::CHAT_CHARGE_POINTS;
+
         if ($model !== '') {
             $modelRow = DB::table('mp_text_models')->where('model', $model)->first();
             if ($modelRow && ($modelRow->charge_type ?? '') === 'per_call') {
                 $priceJson = $modelRow->price_json;
                 $priceRule = is_string($priceJson) ? json_decode($priceJson, true) : $priceJson;
-                if (is_array($priceRule) && isset($priceRule['price']) && is_numeric($priceRule['price'])) {
-                    return (int)max(0, round((float)$priceRule['price']));
+                if (is_array($priceRule)) {
+                    if (isset($priceRule[$type]) && is_numeric($priceRule[$type])) {
+                        return (int)max(0, round((float)$priceRule[$type]));
+                    }
+                    // 兼容旧格式 {"price": 10}
+                    if (isset($priceRule['price']) && is_numeric($priceRule['price'])) {
+                        return (int)max(0, round((float)$priceRule['price']));
+                    }
                 }
             }
         }
 
-        return self::CHAT_CHARGE_POINTS;
+        return $defaultPoints;
+    }
+
+    /**
+     * 获取图生文(有参考图/视频输入的对话)单次应扣积分数(公共方法)
+     *
+     * 计费规则固定如下(未在映射表中的模型回退 normal 档,即 3):
+     * - Gemini3.1 flash / pro   5 积分/次
+     * - Gemini3.0 flash / pro   3 积分/次
+     * - Gemini3.6 flash        10 积分/次
+     * - GPT-5.4 / GPT-5.6-terra / GPT-5.6-sol / GPT-5.6-luna 3 积分/次
+     * - doubao-seed-2.0 pro / lite / mini 3 积分/次
+     *
+     * @param string $model 文本模型 ID
+     * @return int
+     */
+    public function getImageChatChargePoints(string $model = ''): int
+    {
+        $map = [
+            'gemini-3.1-flash-image-preview-t' => 5,
+            'gemini-3.1-pro-preview'           => 5,
+            'gemini-3-flash-preview'           => 3,
+            'gemini-3-pro-preview'             => 3,
+            'gemini-3.6-flash'                 => 10,
+            'gpt-5.4'                          => 3,
+            'gpt-5.6-terra'                    => 3,
+            'gpt-5.6-sol'                      => 3,
+            'gpt-5.6-luna'                     => 3,
+            'doubao-seed-2-0-pro-260215'       => 3,
+            'doubao-seed-2-0-lite-260215'      => 3,
+            'doubao-seed-2-0-mini-260215'      => 3,
+        ];
+
+        if (isset($map[$model])) {
+            return $map[$model];
+        }
+
+        return $this->getChatChargePoints($model, 'normal');
     }
 
     /**
@@ -288,18 +343,22 @@ class PointsService
         // }
 
         $textModel = DB::table('mp_text_models')->where('is_enabled', 1)->whereNotNull('price_json')->first();
+        $charge_type = 'per_call';
         if ($textModel) {
-            $price = $this->getChatChargePoints($textModel->model);
-            $charge_type = 'per_call';
-        }else {
-            $price = self::CHAT_CHARGE_POINTS;
-            $charge_type = 'per_call';
+            $special = $this->getChatChargePoints($textModel->model, 'special');
+            $normal  = $this->getChatChargePoints($textModel->model, 'normal');
+        } else {
+            $special = self::CHAT_CHARGE_POINTS;
+            $normal  = self::NORMAL_CHAT_CHARGE_POINTS;
         }
 
         return [
             'chat' => [
                 'charge_type' => $charge_type,
-                'price_json' => ['price'=>$price],
+                'price_json' => [
+                    'special' => $special,
+                    'normal'  => $normal,
+                ],
                 // 'models' => $textList,
             ],
             'image_models' => array_map([$this, 'formatModelRule'], $imageModels->all()),
@@ -340,7 +399,8 @@ class PointsService
      * 更新积分规则(公共方法)
      *
      * 支持三个区块,均可选传,未传区块不处理:
-     * - text_rules:文生文(对话)按 model 更新(不传 model 时更新全部模型),charge_type 仅支持 per_call,price_json 为 {"price": 积分}
+     * - text_rules:文生文(对话)按 model 更新(不传 model 时更新全部模型),charge_type 仅支持 per_call,
+     *   price_json 为 {"special": 积分, "normal": 积分}(special=主对话档,normal=其他文生文档)
      * - image_rules:图片按 model 更新,charge_type 仅支持 per_image,price_json 为 {"1k": 积分, "2k": 积分, "4k": 积分}
      * - video_rules:视频按 model 更新,charge_type 支持 per_second / per_call
      *   per_second 的 price_json 为 {"场景": {"分辨率": 单价/秒}};per_call 的 price_json 为 {"price": 积分}